/*
 Copyright (c) 2009-2013 Olivier Chafik, All Rights Reserved

 This file is part of JNAerator (http://jnaerator.googlecode.com/).

 JNAerator is free software: you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 JNAerator 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 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with JNAerator.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.ochafik.lang.jnaerator;

import com.ochafik.lang.SyntaxUtils;
import com.ochafik.lang.jnaerator.JNAeratorConfig.GenFeatures;
import static com.ochafik.lang.jnaerator.TypeConversion.ConvType;
import static com.ochafik.lang.jnaerator.TypeConversion.JavaPrim;
import com.ochafik.lang.jnaerator.parser.*;
import com.ochafik.lang.jnaerator.parser.Declarator.ArrayDeclarator;
import com.ochafik.lang.jnaerator.parser.Declarator.MutableByDeclarator;
import static com.ochafik.lang.jnaerator.parser.ElementsHelper.*;
import com.ochafik.lang.jnaerator.parser.Enum;
import com.ochafik.lang.jnaerator.parser.Enum.EnumItem;
import com.ochafik.lang.jnaerator.parser.Expression.Constant;
import com.ochafik.lang.jnaerator.parser.Expression.MemberRef;
import com.ochafik.lang.jnaerator.parser.Expression.MemberRefStyle;
import com.ochafik.lang.jnaerator.parser.Expression.TypeRefExpression;
import com.ochafik.lang.jnaerator.parser.Identifier.SimpleIdentifier;
import com.ochafik.lang.jnaerator.parser.StoredDeclarations.TypeDef;
import com.ochafik.lang.jnaerator.parser.TypeRef.ArrayRef;
import com.ochafik.lang.jnaerator.parser.TypeRef.FunctionSignature;
import com.ochafik.lang.jnaerator.parser.TypeRef.Pointer;
import com.ochafik.lang.jnaerator.parser.TypeRef.Primitive;
import com.ochafik.lang.jnaerator.parser.TypeRef.SimpleTypeRef;
import com.ochafik.lang.jnaerator.parser.TypeRef.TaggedTypeRef;
import com.ochafik.lang.jnaerator.parser.TypeRef.TargettedTypeRef;
import com.ochafik.lang.jnaerator.runtime.CGFloatByReference;
import com.ochafik.lang.jnaerator.runtime.CharByReference;
import com.ochafik.lang.jnaerator.runtime.NativeSize;
import com.ochafik.lang.jnaerator.runtime.NativeSizeByReference;
import com.ochafik.lang.jnaerator.runtime.globals.Global;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalByte;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalCGFloat;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalChar;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalDouble;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalFloat;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalInt;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalLong;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalNativeLong;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalNativeSize;
import com.ochafik.lang.jnaerator.runtime.globals.GlobalShort;
import com.ochafik.util.listenable.Pair;
import com.ochafik.util.string.StringUtils;
import com.sun.jna.Native;
import com.sun.jna.WString;
import com.sun.jna.ptr.ByReference;
import com.sun.jna.ptr.ByteByReference;
import com.sun.jna.ptr.DoubleByReference;
import com.sun.jna.ptr.FloatByReference;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.LongByReference;
import com.sun.jna.ptr.NativeLongByReference;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.ptr.ShortByReference;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import org.bridj.util.DefaultParameterizedType;
import org.rococoa.ObjCClass;
import org.rococoa.ObjCObject;
import org.rococoa.cocoa.foundation.NSObject;

public abstract class TypeConversion implements ObjCppParser.ObjCParserHelper {

    Result result;
    public boolean allowUnknownPointers = true, allowFakePointers = false;

    public TypeConversion(Result result) {
        super();
        this.result = result;
        initTypes();
    }

    //public Set<Identifier> fakePointersSink;
    public enum TypeConversionMode {

        PrimitiveOrBufferParameter,
        NativeParameter,
        NativeParameterWithStructsPtrPtrs,
        FieldType,
        ReturnType,
        ExpressionType,
        StaticallySizedArrayField,
        PrimitiveReturnType,
        PointedValue
    }
    public Map<JavaPrim, Class<? extends ByReference>> primToByReference = new LinkedHashMap<JavaPrim, Class<? extends ByReference>>();
    public Map<JavaPrim, Class<? extends Global>> primToGlobal = new LinkedHashMap<JavaPrim, Class<? extends Global>>();
    public Map<JavaPrim, Class<? extends Buffer>> primToBuffer = new LinkedHashMap<JavaPrim, Class<? extends Buffer>>();
    public final Set<String> byReferenceClassesNames = new HashSet<String>();

    public boolean isObjCppPrimitive(String s) {
        return result.isObjCppPrimitive(s);
    }

    public enum JavaPrim {

        Void(java.lang.Void.TYPE, null, ESize.Zero),
        Char(Character.TYPE, Character.class, ESize.CharSize),
        Long(java.lang.Long.TYPE, java.lang.Long.class, ESize.Eight),
        Int(Integer.TYPE, Integer.class, ESize.Four),
        Short(java.lang.Short.TYPE, java.lang.Short.class, ESize.Two),
        Byte(java.lang.Byte.TYPE, java.lang.Byte.class, ESize.One),
        Boolean(java.lang.Boolean.TYPE, java.lang.Boolean.class, ESize.One),
        Float(java.lang.Float.TYPE, java.lang.Float.class, ESize.Four),
        Double(java.lang.Double.TYPE, java.lang.Double.class, ESize.Eight),
        ComplexDouble(null, null, ESize.Sixteen),
        NativeLong(com.sun.jna.NativeLong.class, com.sun.jna.NativeLong.class, ESize.StaticSizeField),
        NativeSize(NativeSize.class, NativeSize.class, ESize.StaticSizeField),
        NativeTime(null, null, ESize.StaticSizeField),
        NSInteger(org.rococoa.cocoa.foundation.NSInteger.class, org.rococoa.cocoa.foundation.NSInteger.class, ESize.StaticSizeField),
        NSUInteger(org.rococoa.cocoa.foundation.NSUInteger.class, org.rococoa.cocoa.foundation.NSUInteger.class, ESize.StaticSizeField),
        CGFloat(org.rococoa.cocoa.CGFloat.class, org.rococoa.cocoa.CGFloat.class, ESize.StaticSizeField);
        public final Class<?> type, wrapperType;
        public final String simpleName, name;
        public final boolean isPrimitive;

        public enum ESize {

            One(expr(1)),
            Two(expr(2)),
            Four(expr(4)),
            Eight(expr(8)),
            Sixteen(expr(16)),
            StaticSizeField(null) {
                @Override
                public Expression sizeof(JavaPrim p) {
                    return staticField(p.type, "SIZE");
                }
            },
            CharSize(null) {
                @Override
                public Expression sizeof(JavaPrim p) {
                    return staticField(Native.class, "WCHAR_SIZE");
                }
            },
            Zero(expr(0));
            private final Expression sizeOfExpression;

            ESize(Expression sizeOfExpression) {
                this.sizeOfExpression = sizeOfExpression;
            }

            public Expression sizeof(JavaPrim p) {
                return sizeOfExpression.clone();
            }
        }
        public final ESize size;
        private static Map<String, JavaPrim> nameToPrim;

        public static JavaPrim getJavaPrim(String name) {
            if (nameToPrim == null) {
                nameToPrim = new LinkedHashMap<String, JavaPrim>();
                for (JavaPrim p : values()) {
                    nameToPrim.put(p.simpleName, p);
                }
            }
            return nameToPrim.get(name);
        }

        JavaPrim(Class<?> type, Class<?> wrapperType, ESize size) {
            this.type = type;
            this.wrapperType = wrapperType;
            this.size = size;
            this.name = type == null ? null : type.getName();
            this.isPrimitive = type == null || type.isPrimitive();
            this.simpleName = type == null ? null : type.getSimpleName();
        }
    }

    protected TypeRef functionPointerTypeRef(TypeRef.FunctionSignature fs) {
        return fs;
    }
    
    protected TypeRef pointerTypeRef(TypeRef targetTypeRef) {
        return typeRef(result.config.runtime.pointerClass);
    }
    
    public Expression typeLiteral(TypeRef c) {
        if (c == null) {
            return null;
        }
        if (c.toString().equals("?")) {
            return new Expression.Cast(typeRef(Type.class), Constant.newNull());
        }
        return memberRef(expr(c), MemberRefStyle.Dot, "class");
    }

    protected abstract JavaPrim getCppBoolMappingType();

    public void initTypes() {

        result.prim("void", JavaPrim.Void);
        result.prim("VOID", JavaPrim.Void);

        result.prim("UTF32Char", JavaPrim.Int);
        result.prim("unichar", JavaPrim.Char);

        result.prim("int64_t", JavaPrim.Long);
        result.prim("uint64_t", JavaPrim.Long);
        result.prim("u_int64_t", JavaPrim.Long);
        result.prim("long long", JavaPrim.Long);
        result.prim("long long int", JavaPrim.Long);
        result.prim("long int", JavaPrim.Int);
        result.prim("LONGLONG", JavaPrim.Long);
        result.prim("ULONGLONG", JavaPrim.Long);
        result.prim("INT", JavaPrim.Int);
        result.prim("UINT", JavaPrim.Int);
        result.prim("SHORT", JavaPrim.Short);
        result.prim("USHORT", JavaPrim.Short);
        result.prim("CHAR", JavaPrim.Byte);
        result.prim("byte", JavaPrim.Byte);
        result.prim("BYTE", JavaPrim.Byte);
        result.prim("UBYTE", JavaPrim.Byte);
        result.prim("DOUBLE", JavaPrim.Double);
        result.prim("FLOAT", JavaPrim.Float);
        result.prim("WORD", JavaPrim.Short);
        result.prim("DWORD", JavaPrim.Int);
        result.prim("DWORD64", JavaPrim.Long);
        result.prim("LONG64", JavaPrim.Long);
        result.prim("UInt64", JavaPrim.Long);
        result.prim("SInt64", JavaPrim.Long);
        result.prim("__int64", JavaPrim.Long);
        result.prim("__int64_t", JavaPrim.Long);

        result.prim("int32_t", JavaPrim.Int);
        result.prim("uint32_t", JavaPrim.Int);
        result.prim("__int32_t", JavaPrim.Int);
        result.prim("__uint32_t", JavaPrim.Int);
        result.prim("u_int32_t", JavaPrim.Int);
        result.prim("uint32", JavaPrim.Int);
        result.prim("int32", JavaPrim.Int);
        result.prim("int", JavaPrim.Int);
        //prim("NSUInteger", JavaPrim.NativeSize);
        //prim("NSInteger", JavaPrim.NativeSize);
        result.prim("SInt32", JavaPrim.Int);
        result.prim("UInt32", JavaPrim.Int);
        result.prim("GLint", JavaPrim.Int);
        result.prim("GLuint", JavaPrim.Int);
        result.prim("GLenum", JavaPrim.Int);
        result.prim("GLsizei", JavaPrim.Int);

        result.prim("__int32", JavaPrim.Int);

        result.prim("NSInteger", JavaPrim.NSInteger);
        result.prim("NSUInteger", JavaPrim.NSUInteger);
        result.prim("CGFloat", JavaPrim.CGFloat);

        JavaPrim longPrim = result.config.gccLong ? JavaPrim.NativeSize : JavaPrim.NativeLong;
        result.prim("long", longPrim);
        result.prim("LONG", longPrim);
        result.prim("ULONG", longPrim);

        result.prim("time_t", JavaPrim.NativeTime);

        JavaPrim sizePrim = result.config.sizeAsLong ? longPrim : JavaPrim.NativeSize;
        result.prim("size_t", sizePrim);
        result.prim("ptrdiff_t", sizePrim);
        result.prim("__darwin_size_t", JavaPrim.NativeSize);

        result.prim("complex double", JavaPrim.ComplexDouble);

        result.prim("int16_t", JavaPrim.Short);
        result.prim("uint16_t", JavaPrim.Short);
        result.prim("__int16_t", JavaPrim.Short);
        result.prim("__uint16_t", JavaPrim.Short);
        result.prim("u_int16_t", JavaPrim.Short);
        result.prim("uint16", JavaPrim.Short);
        result.prim("int16", JavaPrim.Short);
        result.prim("SInt16", JavaPrim.Short);
        result.prim("UInt16", JavaPrim.Short);
        result.prim("short", JavaPrim.Short);
        result.prim("WCHAR", JavaPrim.Short);
        result.prim("wchar_t", result.config.wcharAsShort ? JavaPrim.Short : JavaPrim.Char);

        result.prim("__int16", JavaPrim.Short);


        result.prim("int8_t", JavaPrim.Byte);
        result.prim("uint8_t", JavaPrim.Byte);
        result.prim("u_int8_t", JavaPrim.Byte);
        result.prim("__uint8_t", JavaPrim.Byte);
        result.prim("__int8_t", JavaPrim.Byte);
        result.prim("SInt8", JavaPrim.Byte);
        result.prim("UInt8", JavaPrim.Byte);
        result.prim("char", JavaPrim.Byte);
        result.prim("unsigned char", JavaPrim.Byte);
        result.prim("__unsigned char", JavaPrim.Byte);
        result.prim("signed char", JavaPrim.Byte);
        result.prim("__signed char", JavaPrim.Byte);
        result.prim("SignedByte", JavaPrim.Byte);

        result.prim("__int8", JavaPrim.Byte);

        result.prim("float", JavaPrim.Float);
        result.prim("NSFloat", JavaPrim.Float);
        result.prim("CGFloat", JavaPrim.Float);

        result.prim("double_t", JavaPrim.Double);
        result.prim("double", JavaPrim.Double);
        result.prim("NSDouble", JavaPrim.Double);
        result.prim("CGDouble", JavaPrim.Double);

        JavaPrim cppBoolType = getCppBoolMappingType();
        result.prim("bool", cppBoolType);
        result.prim("Boolean", cppBoolType);
        result.prim("boolean_t", cppBoolType);



        primToByReference.put(JavaPrim.Int, IntByReference.class);
        primToByReference.put(JavaPrim.Char, (Class) CharByReference.class);
        primToByReference.put(JavaPrim.Short, ShortByReference.class);
        primToByReference.put(JavaPrim.Byte, ByteByReference.class);
        primToByReference.put(JavaPrim.Long, LongByReference.class);
        primToByReference.put(JavaPrim.Float, FloatByReference.class);
        primToByReference.put(JavaPrim.Double, DoubleByReference.class);
        primToByReference.put(JavaPrim.NativeLong, NativeLongByReference.class);
        primToByReference.put(JavaPrim.NativeSize, (Class) NativeSizeByReference.class);
        primToByReference.put(JavaPrim.NSInteger, (Class) NativeSizeByReference.class);
        primToByReference.put(JavaPrim.NSUInteger, (Class) NativeSizeByReference.class);
        primToByReference.put(JavaPrim.CGFloat, CGFloatByReference.class);

        //primsByReference.put(JavaPrim.Void, PointerByReference.class);
        for (Class<?> c : primToByReference.values()) {
            byReferenceClassesNames.add(c.getName());
        }
//		byReferenceClassesNames.add(PointerByReference.class.getName());

        primToGlobal.put(JavaPrim.Int, GlobalInt.class);
        primToGlobal.put(JavaPrim.Char, GlobalChar.class);
        primToGlobal.put(JavaPrim.Short, GlobalShort.class);
        primToGlobal.put(JavaPrim.Byte, GlobalByte.class);
        primToGlobal.put(JavaPrim.Long, GlobalLong.class);
        primToGlobal.put(JavaPrim.Float, GlobalFloat.class);
        primToGlobal.put(JavaPrim.Double, GlobalDouble.class);
        primToGlobal.put(JavaPrim.NativeLong, GlobalNativeLong.class);
        primToGlobal.put(JavaPrim.NativeSize, GlobalNativeSize.class);
        primToGlobal.put(JavaPrim.NSInteger, GlobalNativeSize.class);
        primToGlobal.put(JavaPrim.NSUInteger, GlobalNativeSize.class);
        primToGlobal.put(JavaPrim.CGFloat, GlobalCGFloat.class);

        primToBuffer.put(JavaPrim.Int, IntBuffer.class);
        primToBuffer.put(JavaPrim.Char, CharBuffer.class);
        primToBuffer.put(JavaPrim.Short, ShortBuffer.class);
        primToBuffer.put(JavaPrim.Byte, ByteBuffer.class);
        primToBuffer.put(JavaPrim.Long, LongBuffer.class);
        primToBuffer.put(JavaPrim.Float, FloatBuffer.class);
        primToBuffer.put(JavaPrim.Double, DoubleBuffer.class);
        //primToBuffer.put(JavaPrim.NativeLong, NativeLongByReference.class);

        TypeRef pInt = new TypeRef.Pointer(new Primitive("int"), Declarator.PointerStyle.Pointer);
        result.addManualTypeDef("intptr_t", pInt);
        result.addManualTypeDef("uintptr_t", pInt);

        // TODO: Add a windows failsafe mode that defines all the typedefs needed:
        // http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx
//        TypeRef pVoid = new TypeRef.Pointer(new Primitive("void"), Declarator.PointerStyle.Pointer);
//        result.addManualTypeDef("PVOID", pVoid);
//        result.addManualTypeDef("LPVOID", pVoid);
//        result.addManualTypeDef("LPCVOID", pVoid);
    }

    protected TypeRef findTypeRef(Identifier name, Identifier libraryClassName) {
        TypeRef tr;
        tr = findStructRef(name, libraryClassName);
        if (tr != null) {
            return tr;
        }

        tr = findEnum(name, libraryClassName);
        if (tr != null) {
            return tr;
        }

        tr = findCallbackRef(name, libraryClassName);
        if (tr != null) {
            return tr;
        }

        tr = findObjCClass(name);
        if (tr != null) {
            return tr;
        }

        tr = result.manualTypeDefs.get(name);
        if (tr != null) {
            return tr;
        }

        return null;
    }

    

    public TypeRef normalizeTypeRef(TypeRef tr) {
        return normalizeTypeRef(tr, new HashSet<Identifier>());
    }
    public TypeRef normalizeTypeRef(TypeRef tr, HashSet<Identifier> resolvedNames) {
        if (tr instanceof TypeRef.SimpleTypeRef) {
            Identifier name = ((TypeRef.SimpleTypeRef)tr).getName();
//            if (isObjCppPrimitive(name.toString())) {
//                return tr;
//            }
            TypeRef td = resolvedNames.add(name) ? result.getTypeDef(name) : null;
            if (td != null)
                return normalizeTypeRef(td, resolvedNames);
            
            Struct s = result.resolveStruct(name);
            if (s != null)
                return s;
            Enum e = result.resolveEnum(name);
            if (e != null)
                return e;
            TypeRef.FunctionSignature c = result.resolveCallback(name);
            if (c != null)
                return functionPointerTypeRef(c);
        }
        if (tr instanceof TypeRef.TargettedTypeRef) {
            TypeRef.TargettedTypeRef ttr = (TypeRef.TargettedTypeRef) tr;
            TypeRef target = ttr.getTarget();
            TypeRef ntarget = normalizeTypeRef(target, resolvedNames);
            if (ntarget != target) {
                ttr = (TypeRef.TargettedTypeRef) ttr.clone();
                ttr.setTarget(target);
            }
            return ttr;
        }
        if (tr instanceof TaggedTypeRef) {
            TaggedTypeRef ttr = (TaggedTypeRef) tr;
            if (ttr.isForwardDeclaration()) {
                Identifier name = ttr.getTag();
                if (ttr instanceof Struct) {
                    ttr = result.resolveStruct(name);
                } else if (ttr instanceof Enum) {
                    ttr = result.resolveEnum(name);
                }
                if (ttr != null)
                    return ttr;
            }
        }
        return tr;
    }
    

    public static class JavaPrimitive extends Primitive {

        JavaPrim javaPrim;

        public JavaPrimitive() {
        }

        public JavaPrimitive(JavaPrim javaPrim) {
            super();
            setName(ident(javaPrim == JavaPrim.Void ? Void.TYPE : javaPrim.type));
            this.javaPrim = javaPrim;
        }

        public JavaPrim getJavaPrim() {
            return javaPrim;
        }

        public void setJavaPrim(JavaPrim javaPrim) {
            this.javaPrim = javaPrim;
        }
    }

    public JavaPrim getPrimitive(TypeRef valueType) {
        if (!(valueType instanceof Primitive) && !(valueType instanceof JavaPrimitive)) {
            valueType = normalizeTypeRef(valueType);
        }
        if (valueType == null) {
            return null;
        }
        Identifier name = null;
        List<Modifier> mods = valueType.getModifiers();
        int longCount = ModifierType.Long.countIn(mods);
        if (valueType instanceof JavaPrimitive) {
            return ((JavaPrimitive) valueType).getJavaPrim();
        }
        if (valueType instanceof Primitive) {
            name = ((Primitive) valueType).getName();
            if (name == null) {
                if (longCount == 1) {
                    name = ident("long");
                } else if (longCount > 1) {
                    name = ident("long long");
                } else if (valueType.hasModifier(ModifierType.Short)) {
                    name = ident("short");
                } else {
                    name = ident("int");
                }
            }
        } else if (valueType instanceof SimpleTypeRef) {
            name = ((SimpleTypeRef) valueType).getName();
        }


        if (name == null) {
            return null;
        }

        JavaPrim p = JavaPrim.getJavaPrim(name.toString());
        if (p != null && !p.isPrimitive) {
            return p;
        }

        boolean isLong = false;
        String str;
//        if ((isLong = valueType.hasModifier(ModifierType.Long)) || valueType.hasModifier(ModifierType.Short)) {
//        //if ((isLong = valueType.hasModifier(ModifierType.Long)) || valueType.hasModifier(ModifierType.Short)) {
//            str = (isLong ? "long " : "short ") + name;
//        } else {
        str = name.toString();
//        }

        JavaPrim type = result.resolvePrimitive(str);
        if ((type == JavaPrim.Int || type == JavaPrim.NativeLong) && longCount >= 1) {
            return JavaPrim.Long;
        }

        if (type == JavaPrim.Double && valueType.hasModifier(ModifierType._Complex)) {
            return JavaPrim.ComplexDouble;
        }
        return type;
    }

    public SimpleTypeRef findStructRef(Identifier name, Identifier libraryClassName) {
        return findStructRef(result.resolveStruct(name), libraryClassName);
    }

    public SimpleTypeRef findStructRef(Struct s, Identifier name, Identifier libraryClassName) {
        if (s == null || s.isForwardDeclaration()) {
            TypeRef td = result.getTypeDef(name);
            if (!(td instanceof Struct)) {
                return null;
            }
            s = (Struct) td;
        }
        if (s == null && result.config.runtime == JNAeratorConfig.Runtime.BridJ) {
            String ns = name.toString();
            Class<?> cl = null;
            if (ns.equals("IUnknown")) {
                cl = org.bridj.cpp.com.IUnknown.class;
            } else if (ns.equals("GUID")) {
                cl = org.bridj.cpp.com.GUID.class;
            } else if (ns.equals("RECT")) {
                cl = org.bridj.cpp.com.RECT.class;
            }

            if (cl != null) {
                return typeRef(ident(cl));
            }
        }
        return typeRef(getTaggedTypeIdentifierInJava(s));
        /*
         name = result.declarationsConverter.getActualTaggedTypeName((TaggedTypeRef) pair.getFirst().getValueType());

         return findRef(name, s, libraryClassName, !result.config.putTopStructsInSeparateFiles);
         } else {
         return result.getTaggedTypeIdentifierInJava(s);
         //name = result.declarationsConverter.getActualTaggedTypeName(s);
         }*/
    }

    public SimpleTypeRef findStructRef(Struct s, Identifier libraryClassName) {
        if (s == null) {
            return null;
        }
        switch (s.getType()) {
            case ObjCClass:
            case ObjCProtocol:
                return typeRef(result.objectiveCGenerator.getFullClassName(s));
            default:
                return findStructRef(s, result.declarationsConverter.getActualTaggedTypeName(s), libraryClassName);
        }
    }
//	public String find(String name, Element e, String callerLibraryClass) {
//		if (e == null)
//			return null;
//		String library = result.getLibrary(e);
//		if (library == null)
//			return null;
//		SimpleIdentifier libClass = result.getLibraryClassFullName(library);
//		return SyntaxUtils.equal(libClass, callerLibraryClass) ? name : libClass + "." + name;
//	}
  
    protected Identifier packageMember(Identifier libraryPackage, Identifier name) {
        return ident(libraryPackage, name);
    }
    protected Identifier libMember(Identifier libClass, Identifier libraryClassName, Identifier member) {
        //return ident(SyntaxUtils.equal(libClass, libraryClassName) ? null : libClass, member);
        return ident(libClass, member);
        //return member; //TODODODODODODODODOoOOOOO
    }

    public Identifier findRef(Identifier name, Element e, Identifier libraryClassName, boolean inLibClass) {
        if (e == null || !name.isPlain()) {
            return null;
        }
        String library = result.getLibrary(e);
        if (library == null) {
            return null;
        }

//		e = e.getParentElement();
        Struct parentStruct = e instanceof Struct ? (Struct) e : e.findParentOfType(Struct.class);
        if (!inLibClass && parentStruct != null) {
            if (parentStruct == e) {
                return ident(result.getLibraryPackage(library), name);
            }

            return ident(getTaggedTypeIdentifierInJava(parentStruct), name);
        }
        return libMember(result.getLibraryClassFullName(library), libraryClassName, name);
    }

    public SimpleTypeRef findEnum(Identifier name, Identifier libraryClassName) {
        return findEnum(result.resolveEnum(name), libraryClassName);
    }

    public SimpleTypeRef findEnum(Enum s, Identifier libraryClassName) {
        if (s == null) {
            return null;
        }

        if (result.config.runtime == JNAeratorConfig.Runtime.BridJ) {
            return typeRef(getTaggedTypeIdentifierInJava(s));
        }

        Identifier name = result.declarationsConverter.getActualTaggedTypeName(s);

        String library = result.getLibrary(s);
        if (library == null) {
            return null;
        }
        Identifier libClass = result.getLibraryClassFullName(library);
        SimpleTypeRef tr = new SimpleTypeRef("int");
        if (result.config.features.contains(JNAeratorConfig.GenFeatures.EnumTypeLocationComments)) {
            tr.setCommentBefore("@see " + (SyntaxUtils.equal(libClass, libraryClassName) ? name : libClass + "#" + name));
        }
        return tr;
    }

    public static Expression javaStaticFieldRef(Identifier javaClass, Identifier fieldName) {
        return memberRef(
                expr(typeRef(javaClass)),
                MemberRefStyle.Dot,
                fieldName);
    }

    public Expression findDefine(Identifier name) {
        Define s = result.defines.get(name);
        String library = s == null ? null : result.getLibrary(s);
        return library == null ? null : javaStaticFieldRef(result.getLibraryClassFullName(library), name);
    }

    public Identifier inferCallBackName(FunctionSignature functionSignature, boolean prependNamespaces, boolean qualify, Identifier libraryClassName) {
        List<String> nameElements = new ArrayList<String>();
        Identifier name = functionSignature.getFunction().getName();
        if (name != null) {
            name = name.clone();
        }

        Identifier parentIdent = null;

        Element parent = functionSignature.getParentElement();
        if (parent instanceof TypeRef.Pointer)
            parent = parent.getParentElement();

        boolean firstParent = true;
        while (parent != null) {
            if (parent instanceof Struct) {
                SimpleTypeRef parentRef = findStructRef((Struct) parent, null);
                if (parentRef != null) {
                    parentIdent = parentRef.getName();
                    break;
                }
            }
            if (firstParent) {
                if (name == null && parent instanceof TypeDef) {
                    Declarator simpleSto = null;
                    for (Declarator sto : ((TypeDef) parent).getDeclarators()) {
                        String stoName = sto.resolveName();
                        if (stoName == null) {
                            continue;
                        }

                        if (!(sto instanceof ArrayDeclarator)) {
                            boolean weirdName = stoName.startsWith("_") || stoName.endsWith("_");
                            if (simpleSto == null || (simpleSto.resolveName().startsWith("_") || simpleSto.resolveName().endsWith("_")) && !weirdName) {
                                simpleSto = sto;
                            }

                            if (!weirdName) {
                                break;
                            }
                        }
                        if (stoName != null) {
                            name = new SimpleIdentifier(stoName);
                        }
                    }
                } else if (name == null && parent instanceof Arg) {
                    Arg arg = (Arg) parent;
                    Function f = SyntaxUtils.as(arg.getParentElement(), Function.class);
                    if (f != null) {
                        name = new SimpleIdentifier(f.getName() + "_" + arg.getName());
                        break;
                    }
                } else if (firstParent) {
//					if (//parent instanceof VariablesDeclaration || 
//							parent instanceof FunctionPointerDeclaration
//							//|| parent instanceof TypeDef
//					) {
//						nameElements.add("Callback");
//					}
                }
            }
            parent = parent.getParentElement();
            firstParent = false;
        }

        if (qualify && parentIdent == null) {
            //if (libraryClassName != null)
            //	parentIdent = libraryClassName;
            //else {
            String library = result.getLibrary(functionSignature);
            if (library != null) {
                parentIdent = result.getLibraryClassFullName(library);
            }
            //}
        }

        if (prependNamespaces) {
            if (name == null) {
                name = new SimpleIdentifier("callback");
            }

            nameElements.add(name.toString());
            return ident(qualify ? parentIdent : null, StringUtils.implode(nameElements, "_"));
        } else {
            return ident(qualify ? parentIdent : null, name);
        }
    }

    public TypeRef findCallbackRef(Identifier name, Identifier libraryClassName) {
        FunctionSignature s = result.resolveCallback(name);
        if (s == null) {
            return null;
        }

//		Struct parentStruct = s.findParentOfType(Struct.class);
//		if (parentStruct != null && (parentStruct.getType() == Struct.Type.ObjCClass || parentStruct.getType() == Struct.Type.ObjCProtocol)) {
//			//Identifier structName = result.declarationsConverter.getActualTaggedTypeName(parentStruct);
//			return //result.result.getObjCClass(parentStruct.getName()).
//				typeRef(//libMember(structName, libraryClassName, 
//						inferCallBackName(s, true, true)//)
//						);
//		}
        return findCallbackRef(s, libraryClassName);
    }

    public TypeRef findCallbackRef(FunctionSignature s, Identifier callerLibraryClass) {
        String library = s == null ? null : result.getLibrary(s);
        if (library == null) {
            return null;
        }

//		Struct parentStruct = s.findParentOfType(Struct.class);
//		if (parentStruct != null && (parentStruct.getType() == Struct.Type.ObjCClass || parentStruct.getType() == Struct.Type.ObjCProtocol)) {
//			Identifier structName = result.declarationsConverter.getActualTaggedTypeName(parentStruct);
//			return
//				typeRef(ident(structName, inferCallBackName(s, true, true)));
//		}
        Identifier identifier = s.getResolvedJavaIdentifier();
        if (identifier == null) {
            throw new UnsupportedConversionException(s, null);
        }
        return typeRef(identifier);
//		return typeRef(libMember(result.getLibraryClassFullName(library), callerLibraryClass, inferCallBackName(s, true, true)));
        //typeRef(ident(result.getLibraryClassFullName(library), inferCallBackName(s, true)));
    }

    static TypeRef primRef(Element element, JavaPrim p) {
        if (p == null) {
            return null;
        }
        if (p.type == null) {
            throw new UnsupportedConversionException(element, "Primitive without known type for this runtime: " + p);
        }

        return new JavaPrimitive(p);
//		return new SimpleTypeRef(toString(p));
    }

    boolean isResolved(SimpleTypeRef tr) {
        return tr != null && (tr.isMarkedAsResolved() || isResolved(tr.getName()));
    }

    boolean isResolved(Identifier i) {
        if (i == null || i.isPlain()) {
            return false;
        }
        return (i instanceof Identifier.QualifiedIdentifier)
                && Identifier.QualificationSeparator.Dot.equals(((Identifier.QualifiedIdentifier) i).getSeparator());
    }

    public enum ConvType {

        Enum,
        Pointer,
        FunctionSignature,
        Primitive,
        Struct,
        NativeLong,
        NativeSize,
        NativeTime,
        ComplexDouble,
        Void,
        Callback,
        Default
    }
    static Map<String, Pair<Integer, Class<?>>> buffersAndArityByType = new LinkedHashMap<String, Pair<Integer, Class<?>>>();
    static Map<String, Pair<Integer, Class<?>>> arraysAndArityByType = new LinkedHashMap<String, Pair<Integer, Class<?>>>();
    static Set<String> objectMethodNames = new HashSet<String>();

    static {
        for (Method method : Object.class.getDeclaredMethods()) {
            objectMethodNames.add(method.getName());
        }

        Object[] data = new Object[]{
            "char", Byte.TYPE, byte[].class, ByteBuffer.class, "Char",
            "long", Long.TYPE, long[].class, LongBuffer.class, "Long",
            "int", Integer.TYPE, int[].class, IntBuffer.class, "Int",
            "short", Short.TYPE, short[].class, ShortBuffer.class, "Short",
            "wchar_t", Character.TYPE, char[].class, CharBuffer.class, "WChar",
            "double", Double.TYPE, double[].class, DoubleBuffer.class, "Double",
            "float", Float.TYPE, float[].class, FloatBuffer.class, "Float",
            "bool", Boolean.TYPE, boolean[].class, null, "Bool"
        };
        for (int arity : new int[]{1, 2, 4, 8, 16}) {
            String suffix = arity == 1 ? "" : arity + "";
            for (int i = 0; i < data.length; i += 5) {
                String rawType = (String) data[i];
                Class<?> scalClass = (Class<?>) data[i + 1];
                Class<?> arrClass = (Class<?>) data[i + 2];
                Class<?> buffClass = (Class<?>) data[i + 3];
                String radix = (String) data[i + 4];

                Pair<Integer, Class<?>> buffPair = new Pair<Integer, Class<?>>(arity, arity == 1 ? scalClass : buffClass),
                        arrPair = new Pair<Integer, Class<?>>(arity, arity == 1 ? scalClass : arrClass);

                for (String type : new String[]{rawType + suffix, "u" + rawType + suffix}) {
                    buffersAndArityByType.put(type, buffPair);
                    arraysAndArityByType.put(type, arrPair);
                }
            }
        }
    }
    Pattern wstringPat = Pattern.compile("((__)?const ){1,2}wchar_t\\*"),
            stringPat = Pattern.compile("((__)?const ){1,2}char\\*"),
            wstringPtrPtrPat = Pattern.compile("((__)?const ){1,2}wchar_t\\*\\*"),
            stringPtrPtrPat = Pattern.compile("((__)?const ){1,2}char\\*\\*");

    protected boolean isString(String typeRefAsString, boolean wide) {
        if (wide) {
            return wstringPat.matcher(typeRefAsString).matches()
                    || result.config.charPtrAsString && typeRefAsString.equals("wchar_t*");
        } else {
            return stringPat.matcher(typeRefAsString).matches()
                    || result.config.charPtrAsString && typeRefAsString.equals("char*");
        }
    }

    protected boolean isStringPtrPtr(String typeRefAsString, boolean wide) {
        if (wide) {
            return wstringPtrPtrPat.matcher(typeRefAsString).matches()
                    || result.config.charPtrAsString && typeRefAsString.equals("wchar_t**");
        } else {
            return stringPtrPtrPat.matcher(typeRefAsString).matches()
                    || result.config.charPtrAsString && typeRefAsString.equals("char**");
        }
    }
    static Map<String, Class<?>> predefObjCClasses = new LinkedHashMap<String, Class<?>>();

    static {
        predefObjCClasses.put("id", ObjCObject.class);//org.rococoa.ID.class);
        predefObjCClasses.put("SEL", org.rococoa.Selector.class);
        predefObjCClasses.put("IMP", com.sun.jna.Pointer.class);
        predefObjCClasses.put("Class", ObjCClass.class);
        predefObjCClasses.put("Protocol", ObjCClass.class);
        predefObjCClasses.put("NSObject", NSObject.class);
        //predefObjCClasses.put("NSClass", NSClass.class);
    }

    public Identifier findObjCClassIdent(Identifier name) {
        if (name instanceof SimpleIdentifier) {
            SimpleIdentifier sname = (SimpleIdentifier) name;
            String n = sname.getName();
            if (n.equals("id")
                    && sname.getTemplateArguments().size() == 1/* &&
                     conversionMode != TypeConversionMode.NativeParameter &&
                     conversionMode != TypeConversionMode.NativeParameterWithStructsPtrPtrs*/) {
                Expression x = sname.getTemplateArguments().get(0);
                TypeRefExpression trx = x instanceof TypeRefExpression ? (TypeRefExpression) x : null;
                SimpleTypeRef str = trx.getType() instanceof SimpleTypeRef ? (SimpleTypeRef) trx.getType() : null;
                if (str != null) {
                    name = str.getName();
                }
            }
        }

        Class<?> class1 = predefObjCClasses.get(name.toString());
        if (class1 != null) {
            return ident(class1);
        }

        Struct s = result.resolveObjCClass(name);
        if (s != null) {
            return result.objectiveCGenerator.getFullClassName(s);
        }
        return null;
    }

    public TypeRef findObjCClass(Identifier name) {
        return typeRef(findObjCClassIdent(name));
    }

    protected TypeRef arrayRef(TypeRef tr) {
        ArrayRef arrayRef;
        if (tr instanceof ArrayRef) {
            arrayRef = (ArrayRef) tr;
            arrayRef.addDimension(new Expression.EmptyArraySize());
        } else {
            arrayRef = new ArrayRef(tr);
        }
        return arrayRef;
    }
    Set<String> unknownTypes = new HashSet<String>();

    public static <A, B> Pair<A, B> pair(A a, B b) {
        return new Pair<A, B>(a, b);
    }

    public static Pair<Expression, TypeRef> typed(Expression a, TypeRef b) {
        return new Pair<Expression, TypeRef>(a, b);
    }

    public boolean isString(Expression val) {
        return val instanceof Constant && ((Constant) val).getType() == Constant.Type.String; // TODO use typer + type annotations !
    }

    public Constant.Type getConstantType(Expression expr) {
        if (!(expr instanceof Constant)) {
            return null;
        }
        return ((Constant) expr).getType();
    }

    public abstract Expression getEnumItemValue(EnumItem enumItem, boolean forceConstant);

    public TypeRef convertToJavaType(Constant.Type type) {
        switch (type) {
            case Bool:
                return typeRef(Boolean.TYPE);
            case IntegerString:
            case UInt:
            case Int:
                return typeRef(Integer.TYPE);
            case LongString:
            case ULong:
            case Long:
                return typeRef(Long.TYPE);
            case Short:
                return typeRef(Short.TYPE);
            case Byte:
                return typeRef(Byte.TYPE);
            case Float:
                return typeRef(Float.TYPE);
            case Double:
                return typeRef(Double.TYPE);
            case String:
                return typeRef(String.class);
            default:
                return null;
        }
    }

    protected Expression sizeof(JavaPrim prim) {
        return prim.size.sizeof(prim);
    }

    protected Expression findEnumItem(EnumItem enumItem) {
        String library = result.getLibrary(enumItem);
        if (library == null) {
            return null;
        }

        Element parent = enumItem.getParentElement();
        if (parent == null || !(parent instanceof Enum)) {
            return null;
        }

        Enum e = (Enum) parent;
        Identifier enumItemName = ident(enumItem.getName());
        enumItemName.resolveLastSimpleIdentifier().setJavaStaticImportable(true);
        Identifier ident = ident(result.getLibraryClassFullName(library), result.declarationsConverter.getActualTaggedTypeName(e), enumItemName);
        return expr(typeRef(ident).setMarkedAsResolved(true));
    }
    /// @see http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html
    public static Set<String> JAVA_OBJECT_METHODS = new HashSet<String>(Arrays.asList(
            "notify", "notifyAll", "equals", "finalize", "getClass", "hashCode", "clone", "toString", "wait" // not allowed for function names
            ));
    public static Set<String> JAVA_KEYWORDS = new HashSet<String>(Arrays.asList(
            "null",
            "true",
            "false",
            "abstract",
            "continue",
            "for",
            "new",
            "switch",
            "assert",
            "default",
            "goto",
            "package",
            "synchronized",
            "boolean",
            "do",
            "if",
            "private",
            "this",
            "break",
            "double",
            "implements",
            "protected",
            "throw",
            "byte",
            "else",
            "import",
            "public",
            "throws",
            "case",
            "enum",
            "instanceof",
            "return",
            "transient",
            "catch",
            "extends",
            "int",
            "short",
            "try",
            "char",
            "final",
            "interface",
            "static",
            "void",
            "class",
            "finally",
            "long",
            "strictfp",
            "volatile",
            "const",
            "float",
            "native",
            "super",
            "while"));
    //static String keywords = " true false double float wait new null boolean return class public protected private ";

    public Identifier getValidJavaArgumentName(Identifier name) {
        return getValidJavaIdentifier(name);
    }

    public Identifier getValidJavaMethodName(Identifier name) {
        String nameStr = name.toString();
        String newName = null;
        if (nameStr.matches("operator[^\\w]+")) {
            String op = nameStr.substring("operator".length());
            //int nArgs = method.getArgs().size();
            String suffix = null;
            java.lang.Enum<?> e = Expression.getAnyOperator(op);
            if (e == null) {
                if (op.equals("()")) {
                    suffix = "parenthesis";
                } else if (op.equals("[]")) {
                    suffix = "brackets";
                } else if (op.equals("->")) {
                    suffix = "arrow";
                }
            } else {
                suffix = e.name();
            }

            if (suffix != null) {
                newName = "operator" + StringUtils.capitalize(suffix);
            }
        } else if (objectMethodNames.contains(nameStr)) {
            newName = name + "$";
        }/* else if (nameStr.startsWith("~")) {
         newName = getValidJavaIdentifierString(ident(nameStr.substring(1))) + "Destructor";
         }*/

        if (newName == null) {
            newName = getValidJavaIdentifierString(name);
        }
//        else if (result.config.beautifyNames) {
//            newName = beautify(newName);
//        }

        return ident(newName);
    }

    String beautify(String name, boolean isType) {
        String newName = StringUtils.underscoredToCamel(name);
        if (!isType) {
            newName = StringUtils.uncapitalize(newName);
        }

        if (name.endsWith("_")) {
            newName += "$";
        }
        return newName;
    }

    public boolean isJavaKeyword(String name) {
        return JAVA_KEYWORDS.contains(name)
                || JAVA_OBJECT_METHODS.contains(name); // not really keywords, but roughly same restrictions apply.
    }

    public Identifier getValidJavaIdentifier(Identifier name) {
        return ident(getValidJavaIdentifierString(name));
    }

    public String getValidJavaIdentifierString(Identifier name) {
        if (name == null) {
            return null;
        }

        if (isJavaKeyword(name.toString())) {
            return name + "$";
        } else {
            String newName = name.toString().replace('-', '_').replaceAll("[^\\w]", "\\$");
//            if (result.config.beautifyNames) {
//                newName = beautify(newName);
//            }
            return newName;
        }
    }

    public static String toPrimString(JavaPrim prim) {
        return prim.name;
    }

    public Expression getJavaClassLitteralExpression(TypeRef tr) {
        JavaPrim prim = result.typeConverter.getPrimitive(tr);
        return prim != null ? classLiteral(prim.type) : typeLiteral(tr.clone());
    }

    public Expression getJavaClassLitteralExpression() {
        throw new UnsupportedOperationException(getClass().getName() + "." + toString() + " not handled !");
    }

    public Pair<Expression, TypeRef> convertExpressionToJava(Expression x, Identifier libraryClassName, boolean promoteNativeLongToLong, boolean forceConstants, Map<String, Pair<Expression, TypeRef>> mappings) throws UnsupportedConversionException {
        Pair<Expression, TypeRef> res = null;
        if (x instanceof Expression.AssignmentOp) {
            Pair<Expression, TypeRef> convTarget = convertExpressionToJava(((Expression.AssignmentOp) x).getTarget(), libraryClassName, promoteNativeLongToLong, forceConstants, mappings),
                    convValue = convertExpressionToJava(((Expression.AssignmentOp) x).getValue(), libraryClassName, promoteNativeLongToLong, forceConstants, mappings);

            res = typed(expr(convTarget.getFirst(), Expression.AssignmentOperator.Equal, convValue.getFirst()), convTarget.getSecond());
        } else if (x instanceof Expression.BinaryOp) {
            Expression.BinaryOp bop = (Expression.BinaryOp) x;
            Pair<Expression, TypeRef> conv1 = convertExpressionToJava(bop.getFirstOperand(), libraryClassName, promoteNativeLongToLong, forceConstants, mappings),
                    conv2 = convertExpressionToJava(bop.getSecondOperand(), libraryClassName, promoteNativeLongToLong, forceConstants, mappings);

            if (conv1 != null && conv2 != null) {
                TypeRef t1 = conv1.getSecond(), t2 = conv2.getSecond();
                Expression x1 = conv1.getFirst(), x2 = conv2.getFirst();

                String s1 = String.valueOf(t1), s2 = String.valueOf(t2);
                TypeRef tr = null;
                if (bop.getOperator().givesBool) {
                    tr = typeRef(boolean.class);
                } else {
                    if (s1.equals(s2)) {
                        tr = t1;
                    } else {
                        // t1 & t2 are already java primitives...
                        JavaPrim p1 = "long".equals(t1.toString()) ? JavaPrim.Long : getPrimitive(t1),
                                p2 = "long".equals(t2.toString()) ? JavaPrim.Long : getPrimitive(t2);
                        if (p1 != null && p2 != null) {
                            switch (bop.getOperator()) {
                                case LeftShift:
                                case RightShift:
                                case SignedRightShift:
                                    tr = t1;
                                    break;
                                default:
                                    for (JavaPrim p : new JavaPrim[]{
                                        JavaPrim.Double, JavaPrim.Float,
                                        JavaPrim.Long, JavaPrim.NativeSize, JavaPrim.NativeLong, JavaPrim.Int,
                                        JavaPrim.Short, JavaPrim.Byte
                                    }) {
                                        if (p1 == p || p2 == p) {
                                            if (promoteNativeLongToLong && (p == JavaPrim.NativeLong || p == JavaPrim.NativeSize)) {
                                                p = JavaPrim.Long;
                                            }
                                            tr = primRef(x, p);
                                            break;
                                        }
                                    }
                            }

                        }
                    }
                }
                res = typed(expr(x1, ((Expression.BinaryOp) x).getOperator(), x2), tr);
            }
        } else if (x instanceof Expression.UnaryOp) {
            Expression.UnaryOperator op = ((Expression.UnaryOp) x).getOperator();
            if (op == Expression.UnaryOperator.Not) {
                throw new UnsupportedConversionException(x, null); // TODO handle this properly ?
            }
            Pair<Expression, TypeRef> conv = convertExpressionToJava(((Expression.UnaryOp) x).getOperand(), libraryClassName, promoteNativeLongToLong, forceConstants, mappings);

            res = typed(expr(op, conv.getFirst()), conv.getSecond());
        } else if (x instanceof Expression.Constant) {
            Class<?> c = null;
            Expression.Constant jc = ((Expression.Constant) x).asJava();
            switch (jc.getType()) {
                case Byte:
                    c = Byte.TYPE;
                    break;
                case Char:
                    c = Character.TYPE;
                    break;
                case Double:
                    c = Double.TYPE;
                    break;
                case Float:
                    c = Float.TYPE;
                    break;
                case Int:
                case UInt:
                case IntegerString:
                    c = Integer.TYPE;
                    break;
                case ULong:
                case Long:
                case LongString:
                    c = Long.TYPE;
                    break;
                case Short:
                    c = Short.TYPE;
                    break;
                case String:
                    c = String.class;
                    break;
            }
            if (c != null) {
                res = typed(((Expression.Constant) x).asJava(), typeRef(c));
            }

        } else if (x instanceof Expression.TypeRefExpression) {
            // The parser might recognize enum items incorrectly as a TypeRefExpression...
            Expression.TypeRefExpression tre = (Expression.TypeRefExpression) x;
            TypeRef tr = tre.getType();
            if (tr instanceof TypeRef.SimpleTypeRef) {
                TypeRef.SimpleTypeRef str = (TypeRef.SimpleTypeRef) tr;
                Identifier name = str.getName();
                if (name != null) {
                    if (result.enumItemsFullName.contains(name)) {
                        res = typed(tre, typeRef(Integer.TYPE));
                    }
//                    res = convertVariableRefToJava(name, libraryClassName, promoteNativeLongToLong);
                }
            }
            if (res == null) {
                if (tr.isMarkedAsResolved()) {
                    res = typed(tre, tr);
                }
//                } else {
//                    TypeRef conv = convertTypeToJNA(tr, TypeConversionMode.ExpressionType, libraryClassName);
//                    res = typed(new Expression.TypeRefExpression(conv), conv);
//                }
            }
        } else if (x instanceof Expression.VariableRef) {
            Identifier name = ((Expression.VariableRef) x).getName();
            Pair<Expression, TypeRef> mapping = mappings == null ? null : mappings.get(name.toString());
            if (mapping != null) {
                res = mapping;
            } else {
                res = convertVariableRefToJava(name, libraryClassName, promoteNativeLongToLong, forceConstants);
            }
        }
        if (res == null) {
//			return convertExpressionToJava(x);
            throw new UnsupportedConversionException(x, null);
        }
        if (res.getFirst() == null) {
            return null;
        }
        res.getFirst().setParenthesis(x.getParenthesis());
        return (Pair<Expression, TypeRef>) res;
    }

    static class EnumItemResult {

        public Enum.EnumItem originalItem;
        public Expression convertedValue, unconvertedValue, constantValue;
        public String comments;
        public String exceptionMessage;
        public Declaration errorElement;
    }

    protected Map<String, EnumItemResult> getEnumValuesAndCommentsByName(Enum e, Identifier libraryClassName) {
        Map<String, EnumItemResult> ret = new LinkedHashMap<String, EnumItemResult>();
        Integer lastAdditiveValue = null;
        Expression lastRefValue = null;
        boolean failedOnceForThisEnum = false;
        Map<String, Pair<Expression, TypeRef>> mappings = new LinkedHashMap<String, Pair<Expression, TypeRef>>();
        for (com.ochafik.lang.jnaerator.parser.Enum.EnumItem item : e.getItems()) {
            EnumItemResult res = new EnumItemResult();
            res.originalItem = item;
            try {
                if (item.getArguments().isEmpty()) {
                    // no explicit value
                    if (lastRefValue == null) {
                        if (lastAdditiveValue != null) {
                            lastAdditiveValue++;
                            res.unconvertedValue = expr(lastAdditiveValue);
                        } else {
                            if (item == e.getItems().get(0)) {
                                lastAdditiveValue = 0;
                                res.unconvertedValue = expr(lastAdditiveValue);
                            } else {
                                res.unconvertedValue = null;
                            }
                        }
                    } else {
                        // has a last reference value
                        if (lastAdditiveValue != null) {
                            lastAdditiveValue++;
                        } else {
                            lastAdditiveValue = 1;
                        }

                        res.unconvertedValue = expr(
                                lastRefValue.clone(),
                                Expression.BinaryOperator.Plus,
                                expr(lastAdditiveValue));
                    }
                } else {
                    // has an explicit value
                    failedOnceForThisEnum = false;// reset skipping
                    lastAdditiveValue = null;
                    lastRefValue = item.getArguments().get(0);
                    res.unconvertedValue = lastRefValue;
                    if (lastRefValue instanceof Expression.Constant) {
                        try {
                            lastAdditiveValue = ((Expression.Constant) lastRefValue).asInteger();
                            lastRefValue = null;
                        } catch (Exception ex) {
                        }
                    }
                }
                res.convertedValue = result.typeConverter.convertExpressionToJava(res.unconvertedValue, libraryClassName, true, false, mappings).getFirst();
                res.constantValue = result.typeConverter.convertExpressionToJava(res.unconvertedValue, libraryClassName, true, true, mappings).getFirst();
                mappings.put(item.getName(), typed(res.constantValue, typeRef(int.class)));
            } catch (Exception ex) {
                failedOnceForThisEnum = true;
                res.exceptionMessage = ex.toString();
            }
            failedOnceForThisEnum = failedOnceForThisEnum || res.errorElement != null;
            if (failedOnceForThisEnum) {
                res.errorElement = result.declarationsConverter.skipDeclaration(item);
            }

            ret.put(item.getName(), res);
        }
        return ret;
    }

    private Pair<Expression, TypeRef> convertVariableRefToJava(Identifier name, Identifier libraryClassName, boolean promoteNativeLongToLong, boolean forceConstants) {

        if (name != null) {
            Define define = result.defines.get(name);
            if (define != null && define.getValue() != null) {
                if (name.toString().equals(define.getValue().toString())) {
                    return null; // avoid some nasty loops
                } else {
                    Expression defineValue = define.getValue();
                    if (defineValue instanceof Expression.Constant) {
                        Expression.Constant constant = (Expression.Constant) defineValue;
                        return typed(findDefine(name), convertToJavaType(constant.getType()));
                    }

                    return convertExpressionToJava(defineValue, libraryClassName, promoteNativeLongToLong, forceConstants, null);
                }
            } else {
                String sname = name.toString();
                if (sname.equals("True") || sname.equals("true")) {
                    return typed(expr(Expression.Constant.Type.Bool, true), primRef(define, JavaPrim.Boolean));
                } else if (sname.equals("False") || sname.equals("false")) {
                    return typed(expr(Expression.Constant.Type.Bool, false), primRef(define, JavaPrim.Boolean));
                } else {
                    Enum.EnumItem enumItem = result.enumItems.get(name);
                    if (enumItem != null) {
                        return typed(getEnumItemValue(enumItem, forceConstants), typeRef(Integer.TYPE));
                    } else {
                        VariablesDeclaration constant = result.globalVariablesByName.get(name);
                        if (constant != null) {
                            return typed(varRef(findRef(name, constant, libraryClassName, true)), null);
                        } else {
                            return typed(new Expression.VariableRef(name), null);
                        }
                    }
                }
            }
        }
        return null;
    }

    public Identifier getTaggedTypeIdentifierInJava(TaggedTypeRef s) {
        return result.resolveFullTaggedTypeRef(s).getResolvedJavaIdentifier();
    }

    public Identifier computeTaggedTypeIdentifierInJava(TaggedTypeRef s) {
        Identifier name = result.declarationsConverter.getActualTaggedTypeName(s);
        if (name == null) {
            return null;
        }

        String library = result.getLibrary(s);
        if (library == null) {
            return null;
        }

        name = name.clone();
        Struct parentStruct = s.findParentOfType(Struct.class);
        //Struct parentStruct = s.findParentOfType(Struct.class);
        if (parentStruct != null && parentStruct != s) {
            return ident(getTaggedTypeIdentifierInJava(parentStruct), name);
        } else if ((s instanceof Struct) && (result.config.putTopStructsInSeparateFiles)) {// || result.config.runtime == JNAeratorConfig.Runtime.BridJ)) {
            return packageMember(result.getLibraryPackage(library), name);
            //return ident(result.getLibraryPackage(library), name);
        } else {
            return libMember(result.getLibraryClassFullName(library), null, name);
        }
    }

    public Identifier computeCallbackIdentifierInJava(FunctionSignature fs) {
        Identifier name = inferCallBackName(fs, false, false, null);
        if (name == null) {
            return null;
        }

        String library = result.getLibrary(fs);
        if (library == null) {
            return null;
        }

        name = name.clone();
        Struct parentStruct = fs.findParentOfType(Struct.class);
        //Struct parentStruct = s.findParentOfType(Struct.class);
        if (parentStruct != null) {
            return ident(getTaggedTypeIdentifierInJava(parentStruct), name);
        } else {
            return libMember(result.getLibraryClassFullName(library), null, name);
        }
    }
}
