/*
Copyright (c) 2009-2011 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 static com.ochafik.lang.jnaerator.parser.ElementsHelper.*;

import java.lang.reflect.Method;
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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;

import org.rococoa.ObjCClass;
import org.rococoa.ObjCObject;
import org.rococoa.cocoa.foundation.NSObject;

import org.bridj.util.DefaultParameterizedType;
import com.ochafik.lang.SyntaxUtils;
import com.ochafik.lang.jnaerator.JNAeratorConfig.GenFeatures;
import com.ochafik.lang.jnaerator.parser.Enum;
import com.ochafik.lang.jnaerator.parser.*;
import com.ochafik.lang.jnaerator.parser.Declarator.ArrayDeclarator;
import com.ochafik.lang.jnaerator.parser.Declarator.MutableByDeclarator;
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;

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 HashMap<JavaPrim, Class<? extends ByReference>>();
    public Map<JavaPrim, Class<? extends Global>> primToGlobal = new HashMap<JavaPrim, Class<? extends Global>>();
    public Map<JavaPrim, Class<? extends Buffer>> primToBuffer = new HashMap<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),
        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 HashMap<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();
        }
    }

    public Expression typeLiteral(TypeRef c) {
        if (c != null && c.toString().equals("?"))
            return Constant.newNull();
        return memberRef(expr(c), MemberRefStyle.Dot, "class");
    }

    protected abstract JavaPrim getCppBoolMappingType();
    
    public void initTypes() {

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

        result.prim("UTF32Char", JavaPrim.Char);
        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("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("__darwin_size_t", 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);

        JavaPrim sizePrim = result.config.sizeAsLong ? longPrim : JavaPrim.NativeSize;
        result.prim("size_t", sizePrim);
        result.prim("ptrdiff_t", sizePrim);
        
        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, 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, NativeSizeByReference.class);
        primToByReference.put(JavaPrim.NSInteger, NativeSizeByReference.class);
        primToByReference.put(JavaPrim.NSUInteger, 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);
    }
    
    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;
    }
    boolean isResoluble(TypeRef tr, Identifier libraryClassName) {
        return isResoluble(tr, libraryClassName, new HashSet<Identifier>());
    }
    boolean isResoluble(TypeRef tr, Identifier libraryClassName, Set<Identifier> typeDefsEncountered) {
        if (tr instanceof Primitive ||
            tr instanceof FunctionSignature ||
            tr instanceof TaggedTypeRef) 
        {
            return true;
        } else if (tr instanceof TargettedTypeRef) {
            return isResoluble(((TargettedTypeRef)tr).getTarget(), libraryClassName, typeDefsEncountered);
        } else if (tr instanceof SimpleTypeRef) {
            Identifier name = ((SimpleTypeRef)tr).getName();
            TypeRef tdt = typeDefsEncountered.add(name) ? result.getTypeDef(name) : null;
            if (tdt != null) {
                return isResoluble(tdt, libraryClassName, typeDefsEncountered);
            } else {
                TypeRef ft = findTypeRef(name, libraryClassName);
                return ft != null;
            }
        }
        return false;
    }
    public TypeRef resolveTypeDef(TypeRef valueType, final Identifier libraryClassName, final boolean convertToJavaRef, final boolean convertEnumToJavaRef) {
        return resolveTypeDef(valueType, libraryClassName, convertToJavaRef, convertEnumToJavaRef, new HashSet<Identifier>());
    }
    public TypeRef resolveTypeDef(TypeRef valueType, final Identifier libraryClassName, final boolean convertToJavaRef, final boolean convertEnumToJavaRef, final Set<Identifier> typeDefsEncountered) {
        if (valueType == null) {
            return null;
        }

//		if (valueType.toString().equals("CGFunctionEvaluateCallback"))
//			valueType = valueType;

        if (valueType instanceof TaggedTypeRef && convertToJavaRef) {
            TaggedTypeRef ttr = (TaggedTypeRef) valueType;
            if (ttr.getTag() != null) {

                TypeRef ref = ttr instanceof Struct ? findStructRef(ttr.getTag(), libraryClassName)
                        : ttr instanceof Enum && convertEnumToJavaRef ? findEnum(ttr.getTag(), libraryClassName) : null;
                if (ref == null && convertEnumToJavaRef) {
                    return ref;
                }
            }
        }
        final TypeRef valueTypeCl = valueType.clone();
        Arg holder = new Arg();
        holder.setValueType(valueTypeCl);
        holder.setParentElement(valueType.getParentElement());
        holder.accept(new Scanner() {

            java.util.Stack<String> names = new java.util.Stack<String>();
            int depth = 0;

            @Override
            public void visitSimpleTypeRef(SimpleTypeRef simpleTypeRef) {
                depth++;

                try {
                    Identifier name = ((SimpleTypeRef) simpleTypeRef).getName();
                    if (name == null) {
                        return;
                    }

                    String nameStr = name.toString();
                    if (nameStr == null) {
                        return;
                    }

                    if (JavaPrim.getJavaPrim(nameStr) != null) {
                        return;
                    }

                    if (names.contains(nameStr)) {
                        return;
                    }
                    names.push(nameStr);

                    try {
                        if (result.resolvePrimitive(nameStr) != null) {
                            return;
                        }

                        super.visitSimpleTypeRef(simpleTypeRef);
                        if (simpleTypeRef.isMarkedAsResolved()) {
                            return;
                        }

                        //					Identifier oc = findObjCClassIdent(name);
                        //					if (oc != null) {
                        //						name.replaceBy(oc);
                        //					}

                        if (convertToJavaRef) {
                            TypeRef t = findTypeRef(name, libraryClassName);
                            if (t != null) {
                                if (!convertToJavaRef || (t instanceof Enum) && !convertEnumToJavaRef) {
                                    return;
                                }
                                simpleTypeRef.replaceBy(t);
                            }
                        }

                        Define define = result.defines.get(name);
                        Expression expression = define == null ? null : define.getValue();
                        if (expression != null) {
                            if (!convertToJavaRef) {
                                return;
                            }
                            Identifier fieldName = null;
                            if (expression instanceof Expression.VariableRef) {
                                fieldName = ((Expression.VariableRef) expression).getName();
                            } else if (expression instanceof MemberRef) {
                                fieldName = ((MemberRef) expression).getName();
                            }

                            if (fieldName != null && !fieldName.equals(name)) {
                                simpleTypeRef.replaceBy(resolveTypeDef(new TypeRef.SimpleTypeRef(fieldName), libraryClassName, true /*convertToJavaRef*/, convertEnumToJavaRef, typeDefsEncountered));
                                return;
                            }
                        }
                        
                        TypeRef tr = typeDefsEncountered.add(name) ? result.getTypeDef(name) : null;
                        if (tr != null) {
                            if (!isResoluble(tr, libraryClassName)) {
                                if (convertToJavaRef)
                                    simpleTypeRef.replaceBy(typeRef(result.getFakePointer(libraryClassName, name)));
                                else
                                    simpleTypeRef.replaceBy(tr.clone());
                                return;
                            }
                                
                            if (tr instanceof Enum && !convertEnumToJavaRef) {
                                simpleTypeRef.replaceBy(typeRef(int.class));
                                return;
                            }
                            if (tr instanceof TaggedTypeRef) {
                                Identifier name2 = result.declarationsConverter.getActualTaggedTypeName((TaggedTypeRef) tr);
                                if (name2 != null) {
                                    name = name2;
                                }
                            }
                            if (convertToJavaRef) {
                                if (tr instanceof TaggedTypeRef) {
                                    TaggedTypeRef s = (TaggedTypeRef) tr;
                                    if (s.isForwardDeclaration()) {
                                        return;
                                    }

//									if (tr instanceof Enum) {
//										tr = typeRef(s.getTag().clone());
//									} else {
                                    Identifier ident = getTaggedTypeIdentifierInJava(s);
                                    if (ident != null) {
                                        tr = typeRef(ident);//findRef(name, s, libraryClassName));
                                    }//									}
                                } else if (tr instanceof FunctionSignature) {
                                    tr = findCallbackRef((FunctionSignature) tr, libraryClassName);
                                }
                            }
                            String strs = simpleTypeRef.toString();
                            String trs = tr == null ? null : tr.toString();
                            if (trs != null && !strs.equals(trs)) {
                                TypeRef clo = tr.clone();
                                simpleTypeRef.replaceBy(clo);
                                if (depth < 30) {
                                    clo.accept(this);
                                } else {
                                    System.err.println("Infinite loop in type conversion ? " + tr);
                                }
                            }
                            return;
                        }
                    } finally {
                        names.pop();
                    }
                } finally {
                    depth--;
                }
            }

            
        });
        TypeRef tr = holder.getValueType();
//		tr.setParentElement(valueType.getParentElement());
        return tr == null ? null : tr == valueTypeCl ? valueType : tr.clone();
    }

    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, Identifier libraryClassName) {
        if (!(valueType instanceof Primitive) && !(valueType instanceof JavaPrimitive)) {
            valueType = resolveTypeDef(valueType, libraryClassName, true, true);
        }
        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 && 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;
//	}

    public 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();

        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(JavaPrim p) {
        if (p == null) {
            return null;
        }

        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, 
        Primitive, 
        Struct, 
        NativeLong,
        NativeSize,
        ComplexDouble,
        Void,
        Callback,
        Default
    }
    
    static Map<String, Pair<Integer, Class<?>>> buffersAndArityByType = new HashMap<String, Pair<Integer, Class<?>>>();
    static Map<String, Pair<Integer, Class<?>>> arraysAndArityByType = new HashMap<String, Pair<Integer, Class<?>>>();
    static Map<String, String> pointerFieldGetterNameRadixByType = new HashMap<String, String>();
    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);
                    pointerFieldGetterNameRadixByType.put(type, radix);
                }
            }
        }
    }


    Pattern wstringPat = Pattern.compile("(__)?const wchar_t\\*"),
            stringPat = Pattern.compile("(__)?const char\\*"),
            wstringPtrPtrPat = Pattern.compile("(__)?const wchar_t\\*\\*"),
            stringPtrPtrPat = Pattern.compile("(__)?const 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 HashMap<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);
    
    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 ident = ident(result.getLibraryClassFullName(library), result.declarationsConverter.getActualTaggedTypeName(e), ident(enumItem.getName()));
        return expr(typeRef(ident).setMarkedAsResolved(true));
    }
    /// @see http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html
    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",
            "wait" // not allowed for function names
            ));
    //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);
    }

    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, null);
        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) throws UnsupportedConversionException {
        Pair<Expression, TypeRef> res = null;
        if (x instanceof Expression.AssignmentOp) {
            Pair<Expression, TypeRef> convTarget = convertExpressionToJava(((Expression.AssignmentOp) x).getTarget(), libraryClassName, promoteNativeLongToLong),
                    convValue = convertExpressionToJava(((Expression.AssignmentOp) x).getValue(), libraryClassName, promoteNativeLongToLong);

            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),
                    conv2 = convertExpressionToJava(bop.getSecondOperand(), libraryClassName, promoteNativeLongToLong);

            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, null), 
                                p2 = "long".equals(t2.toString()) ? JavaPrim.Long : getPrimitive(t2, null);
                        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(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);

            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) {
            res = convertVariableRefToJava(((Expression.VariableRef) x).getName(), libraryClassName, promoteNativeLongToLong);
        }
        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;
    }
    

    private Pair<Expression, TypeRef> convertVariableRefToJava(Identifier name, Identifier libraryClassName, boolean promoteNativeLongToLong) {
        
        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);
                }
            } else {
                String sname = name.toString();
                if (sname.equals("True") || sname.equals("true")) {
                    return typed(expr(Expression.Constant.Type.Bool, true), primRef(JavaPrim.Boolean));
                } else if (sname.equals("False") || sname.equals("false")) {
                    return typed(expr(Expression.Constant.Type.Bool, false), primRef(JavaPrim.Boolean));
                } else {
                    Enum.EnumItem enumItem = result.enumItems.get(name);
                    if (enumItem != null) {
                        return typed(getEnumItemValue(enumItem), 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 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);
    }
}
