/*
 * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.apple.internal.jobjc.generator.model;

import java.util.Arrays;

import org.w3c.dom.Node;

import com.apple.internal.jobjc.generator.model.types.Type;
import com.apple.internal.jobjc.generator.model.types.JType.JPrimitive;
import com.apple.internal.jobjc.generator.model.types.NType.NPrimitive;
import com.apple.internal.jobjc.generator.utils.Fp;
import com.apple.jobjc.JObjCRuntime;

public class NativeEnum extends ElementWType<Framework> {
    public final String value, value64, le_value, be_value;
    public boolean ignore;
    public String suggestion;
    public NativeEnum(final Node node, final Framework parent) {
        super(node, typeForEnum(getAttr(node, "name"),
                getAttr(node, "value"), getAttr(node, "value64"),
                getAttr(node, "le_value"), getAttr(node, "be_value"),
                getAttr(node, "ignore")), parent);
        this.value = getAttr(node, "value");
        this.value64 = getAttr(node, "value64");
        this.le_value = getAttr(node, "le_value");
        this.be_value = getAttr(node, "be_value");
        String ignoreS = getAttr(node, "ignore");
        this.ignore = ignoreS == null ? false : Boolean.parseBoolean(ignoreS);
        this.suggestion = getAttr(node, "suggestion");
        assert valueToString() != null;
    }

    private static Type typeForEnum(String name, String value32, String value64, String le_value, String be_value, String ignore){
        if("true".equals(ignore)) return Type.getType(null, NPrimitive.inst('i'), null);

        NumTest[] tests = new NumTest[]{new IntTest(), new LongTest(), new FloatTest(), new DoubleTest()};
        for(NumTest t : tests)
            if(t.confirm(value32, value64, le_value, be_value))
                return t.getType();

        throw new NumberFormatException(String.format("Failed to parse type for enum: %1$s = 32: %2$s / 64: %3$s / le: %4$s / be: %5$s\n",
                name, value32, value64, le_value, be_value));
    }

    public String valueToString(){
        if(ignore == true) return "0";
        JPrimitive jprim = (JPrimitive) type.getJType();
        if(le_value == null && be_value == null){
            if(value == null && value64 != null)
                return value64 + jprim.getLiteralSuffix();
            else if(value != null && value64 == null)
                return value + jprim.getLiteralSuffix();
            else
                return String.format("(%1$s.IS64 ? %2$s%4$s : %3$s%4$s)", JObjCRuntime.class.getName(),
                        value64, value, jprim.getLiteralSuffix());
        }
        else if(value == null && value64 == null){
            return String.format("(%1$s.IS_BIG_ENDIAN ? %2$s%4$s : %3$s%4$s)",
                    JObjCRuntime.class.getName(), be_value, le_value, jprim.getLiteralSuffix());
        }

        throw new RuntimeException("Unable to produce a value for enum " + name);
    }

    // Used to find the best type to use for the enum.

    static abstract class NumTest{
        public boolean confirm(String... values){
            return Fp.all(new Fp.Map1<String,Boolean>(){
                public Boolean apply(String a) {
                    try{ return a == null || confirm(a); }
                    catch(Exception x){ return false; }
                }},
                Arrays.asList(values));
        }

        public abstract boolean confirm(String v);
        public abstract Type getType();
    }

    static class IntTest extends NumTest{
        @Override public boolean confirm(String v) {
            Integer.parseInt(v);
            return true;
        }

        @Override public Type getType() { return Type.getType(null, NPrimitive.inst('i'), null); }
    }

    static class LongTest extends NumTest{
        @Override public boolean confirm(String v) {
            Long.parseLong(v);
            return true;
        }

        @Override public Type getType() { return Type.getType(null, NPrimitive.inst('l'), null); }
    }

    static class FloatTest extends NumTest{
        @Override public boolean confirm(String v) {
            return Float.parseFloat(v) == Double.parseDouble(v);
        }

        @Override public Type getType() { return Type.getType(null, NPrimitive.inst('f'), null); }
    }

    static class DoubleTest extends NumTest{
        @Override public boolean confirm(String v) {
            double d = Double.parseDouble(v);
            return !Double.isInfinite(d) && !Double.isNaN(d);
        }

        @Override public Type getType() { return Type.getType(null, NPrimitive.inst('d'), null); }
    }
}