/*
 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 java.io.IOException;
import java.io.InputStream;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Set;
import com.ochafik.util.string.StringUtils;

import org.rococoa.cocoa.foundation.NSObject;
//import org.rococoa.cocoa.foundation.NSString;
import org.rococoa.Rococoa;
import org.rococoa.ObjCObject;

import static com.ochafik.lang.jnaerator.parser.ElementsHelper.*;

import com.ochafik.io.ReadText;
import com.ochafik.lang.jnaerator.parser.Arg;
import com.ochafik.lang.jnaerator.parser.Declaration;
import com.ochafik.lang.jnaerator.parser.DeclarationsHolder;
import com.ochafik.lang.jnaerator.parser.Declarator;
import com.ochafik.lang.jnaerator.parser.Enum;
import com.ochafik.lang.jnaerator.parser.Expression;
import com.ochafik.lang.jnaerator.parser.Function;
import com.ochafik.lang.jnaerator.parser.Identifier;
import com.ochafik.lang.jnaerator.parser.Modifier;
import com.ochafik.lang.jnaerator.parser.ModifierType;
import com.ochafik.lang.jnaerator.parser.Statement;
import com.ochafik.lang.jnaerator.parser.StoredDeclarations;
import com.ochafik.lang.jnaerator.parser.Struct;
import com.ochafik.lang.jnaerator.parser.TaggedTypeRefDeclaration;
import com.ochafik.lang.jnaerator.parser.TypeRef;
import com.ochafik.lang.jnaerator.parser.VariablesDeclaration;
import com.ochafik.lang.jnaerator.parser.Expression.AssignmentOp;
import com.ochafik.lang.jnaerator.parser.Expression.AssignmentOperator;
import com.ochafik.lang.jnaerator.parser.Expression.BinaryOperator;
import com.ochafik.lang.jnaerator.parser.Expression.Constant;
import com.ochafik.lang.jnaerator.parser.Expression.MemberRefStyle;
import com.ochafik.lang.jnaerator.parser.Function.SignatureType;
import com.ochafik.lang.jnaerator.parser.Statement.Block;
import com.ochafik.lang.jnaerator.parser.StoredDeclarations.TypeDef;
import com.ochafik.lang.jnaerator.parser.Struct.Type;
import com.ochafik.lang.jnaerator.parser.TypeRef.FunctionSignature;
import com.ochafik.lang.jnaerator.parser.TypeRef.SimpleTypeRef;
import com.ochafik.lang.jnaerator.parser.TypeRef.TaggedTypeRef;
import java.util.*;
import org.rococoa.ObjCClass;
import org.rococoa.cocoa.foundation.NSClass;

/*
 include com/ochafik/lang/jnaerator/ObjectiveCStaticForwardsExcludeList.data
 include com/ochafik/lang/jnaerator/ObjectiveCProtocolsForcedInheritanceList.data
 */
public class ObjectiveCGenerator {

    String classClassName = "_class_",
            classInterfaceNameInCategoriesAndProtocols = "_static_",
            classInstanceName = "_NSCLASS_",
            classInstanceGetterName = "getNSClass";
    boolean AUTO_RELEASE_IN_FACTORIES = false;
    static Map<String, Set<String>> protocolsForcedInheritance;

    public static Set<String> getForcedProtocolParents(String protocolName) {
        if (protocolsForcedInheritance == null) {
            protocolsForcedInheritance = new LinkedHashMap<String, Set<String>>();
            try {
                InputStream in = ObjectiveCGenerator.class.getClassLoader().getResourceAsStream("com/ochafik/lang/jnaerator/ObjectiveCProtocolsForcedInheritanceList.data");
                List<String> lines = ReadText.readLines(in);
                for (String line : lines) {
                    line = line.trim();
                    if (line.startsWith("//") || line.startsWith("#") || line.length() == 0) {
                        continue;
                    }
                    String[] tks = line.split(":");
                    protocolsForcedInheritance.put(tks[0], new TreeSet<String>(Arrays.asList(tks[1].split(","))));
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        Set<String> ret = protocolsForcedInheritance.get(protocolName);
        return ret == null ? Collections.EMPTY_SET : ret;

    }
    static Map<String, Set<String>> methodsExcludedFromStaticForwarding;

    public static boolean isMethodExcludedFromStaticForwarding(Function m) {
        if (methodsExcludedFromStaticForwarding == null) {
            methodsExcludedFromStaticForwarding = new LinkedHashMap<String, Set<String>>();
            try {
                InputStream in = ObjectiveCGenerator.class.getClassLoader().getResourceAsStream("com/ochafik/lang/jnaerator/ObjectiveCStaticForwardsExcludeList.data");
//				InputStream in = new FileInputStream("/Users/ochafik/Prog/Java/sources/com/ochafik/lang/jnaerator/ObjectiveCStaticForwardsExcludeList.data");
                List<String> lines = ReadText.readLines(in);
                for (String line : lines) {
                    line = line.trim();
                    if (line.startsWith("//") || line.startsWith("#") || line.length() == 0) {
                        continue;
                    }
                    String[] tks = line.split("\\|");
                    String className = tks[0].trim(), methodSignature = tks[1].trim();
                    Set<String> set = methodsExcludedFromStaticForwarding.get(className);
                    if (set == null) {
                        methodsExcludedFromStaticForwarding.put(className, set = new HashSet<String>());
                    }
                    set.add(methodSignature);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        if (!(m.getParentElement() instanceof Struct)) {
            return false;
        }

        Struct s = (Struct) m.getParentElement();
        Identifier n = s.getTag();
        if (n != null && n.equals("NSObject"))// || n.equals("NSClass"))
        {
            return true;
        }

        String sig = m.computeSignature(SignatureType.JavaStyle);
        if (DeclarationsConverter.getMethodsAndTheirSignatures(NSObject.class).getSecond().contains(sig)) {
            return true;
        }

        String cn = s.getTag() == null ? "" : s.getTag().toString();
        Set<String> set = methodsExcludedFromStaticForwarding.get(cn);
        if (set != null && set.contains(sig)) {
            return true;
        }

        set = methodsExcludedFromStaticForwarding.get("");
        if (set != null && set.contains(sig)) {
            return true;
        }

        return false;
    }

    public ObjectiveCGenerator(Result result) {
        this.result = result;
    }
    final Result result;

    public Struct getStruct(Identifier className, Struct.Type type) {
        return Result.getMap(result.classes, type).get(className);
    }

    public Identifier getPackageName(Struct struct) {
        if (struct == null) {
            return null;
        }

        String library = result.getLibrary(struct);
        Identifier javaPackage = result.getLibraryPackage(library);

        //if (struct.getType() == Struct.Type.ObjCClass) {
        //	String name = String.valueOf(struct.getTag());
        //if (name.equals("NSObject"))
        //	javaPackage = ident(NSObject.class.getPackage().getName().split("\\."));
        //else if (name.equals("NSClass"))
        //	javaPackage = ident(NSClass.class.getPackage().getName().split("\\."));
        //else if (name.equals("NSString"))
        //	javaPackage = ident(NSString.class.getPackage().getName().split("\\."));
        //}

        if (struct.getType() == Type.ObjCProtocol) {
            javaPackage = ident(javaPackage, "protocols");
        } else if (struct.getCategoryName() != null) {
            javaPackage = ident(javaPackage, "categories");
        }
        return javaPackage;
    }

    public Identifier getFullClassName(Struct struct) {
        if (struct == null) {
            return null;
        }
        Identifier javaPackage = getPackageName(struct);
        Identifier tag = struct.getTag();
        String categ = struct.getCategoryName();
        
        Identifier fullName = ident(javaPackage, categ == null ? tag.clone() : ident(categ));
        return fullName;
    }

    public void generateObjectiveCClasses() throws IOException {
        for (Struct in : Result.getMap(result.classes, Type.ObjCClass).values()) {
            outputObjectiveCClass(in);
        }
        for (Struct protocol : Result.getMap(result.classes, Type.ObjCProtocol).values()) {
            for (String parent : getForcedProtocolParents(String.valueOf(protocol.getTag()))) {
                protocol.addParent(ident(parent));
            }
            outputObjectiveCClass(protocol);
        }
    }

    public void outputObjectiveCClass(Struct in) throws IOException {
        Identifier fullClassName = getFullClassName(in);
        Signatures signatures = new Signatures();

        Struct s = generateObjectiveCClass(in, signatures);
        result.notifyBeforeWritingClass(fullClassName, s, signatures, result.getLibrary(in));

        PrintWriter out = result.classOutputter.getClassSourceWriter(fullClassName.toString());
        result.printJavaClass(getPackageName(in), s, out);
        out.close();
    }
    static Identifier NSObjectIdent = ident(NSObject.class),
            ObjCObjectIdent = ident(ObjCObject.class),
            ObjCClassIdent = ident(ObjCClass.class);
    //NSClassIdent = ident(NSClass.class);

    public Struct generateObjectiveCClass(Struct in, Signatures signatures) throws IOException {
        boolean isProtocol = in.getType() == Type.ObjCProtocol, isCategory = in.getCategoryName() != null;

        Struct instanceStruct = new Struct().addModifiers(ModifierType.Public);

        instanceStruct.setCommentBefore(in.getCommentBefore());
        instanceStruct.addToCommentBefore(in.getCommentAfter());
        instanceStruct.setTag(isCategory ? ident(in.getCategoryName()) : in.getTag().clone());
        if (isProtocol || isCategory) {
            instanceStruct.setType(Type.JavaInterface);
        } else {
            instanceStruct.addModifiers(ModifierType.Abstract).setType(Type.JavaClass);
        }

        Struct classStruct = new Struct();
        classStruct.setTag(ident(classClassName));
        classStruct.setType(Struct.Type.JavaClass);
        classStruct.addModifiers(ModifierType.Public, ModifierType.Static, ModifierType.Abstract);

        List<SimpleTypeRef> interfacesForInstance = new ArrayList<SimpleTypeRef>();

        List<SimpleTypeRef> parentsForInstance = new ArrayList<SimpleTypeRef>(in.getParents());

        //for (Identifier p : parentsForInstance)
        //	parentsForClass
        boolean isNSObject = in.getTag().equals(NSObject.class.getSimpleName());
        /*if (parentsForInstance.isEmpty()) {
         if (isProtocol || isCategory)
         parentsForInstance.add(ObjCObjectIdent);
         else
         if (!isNSObject)
         parentsForInstance.add(NSObjectIdent);
         }*/
        //interfacesForClass.add(ObjCClassIdent);

        if (!(isCategory || isProtocol)) {
            for (Struct catIn : Result.getMap(result.objCCategoriesByTargetType, in.getTag()).values()) {
                Identifier catId = getFullClassName(catIn);

                Identifier sim = catId.resolveLastSimpleIdentifier();
                String categName = catIn.getTag() + "_" + sim;
                if (add(instanceStruct, createCastMethod(ident(categName), catId, false), signatures)) {
                    classStruct.addDeclaration(createCastMethod(ident(categName), ident(catId, classInterfaceNameInCategoriesAndProtocols), true));
                }

                //interfacesForInstance.add(catId);
                //interfacesForClass.add(ident(catId, classInterfaceNameInCategoriesAndProtocols));
                outputObjectiveCClass(catIn);
            }
        }

        for (SimpleTypeRef sp : parentsForInstance) {
            Identifier p = sp.getName();
            String ps = p.toString();
            boolean basic = ps.toString().equals(ObjCObject.class.getName()) || ps.equals(NSObject.class.getName());
            Identifier id = basic ? p : result.typeConverter.findObjCClassIdent(p);
            //Identifier id = result.typeConverter.findObjCClassIdent(p);
            if (id != null || (!p.isPlain() && (id = p) != null)) {
                if (ps.toString().equals("NSObject")) {
                    instanceStruct.addProtocol(ident(ObjCObject.class));
                } else {
                    instanceStruct.addParent(id.clone());
                }
                if (!basic) {
                    classStruct.addParent(ident(id, classClassName));
                }
            }
        }
        /*for (Identifier p : interfacesForClass) {
         boolean basic = p == ObjCClassIdent || p == NSObjectIdent;
         Identifier id = basic ? p : result.typeConverter.findObjCClassIdent(p);
         if (id != null)
         classStruct.addProtocol(p);
         }*/
        boolean isInterface = isProtocol || isCategory;

        if (instanceStruct.getParents().isEmpty()) {
            if (isInterface) {
                instanceStruct.addParent(ident(ObjCObject.class));
            } else if (isNSObject) {
                instanceStruct.addProtocol(ident(ObjCObject.class));
            } else {
                instanceStruct.addParent(ident(NSObject.class));
            }
        }
        if (classStruct.getParents().isEmpty()) {
            if (isNSObject) {
                classStruct.addParent(ident(NSClass.class));
            } else {
                classStruct.addParent(ident(ident(NSObject.class), classClassName));
            }
            //classStruct.addProtocol(ident(ObjCClass.class));
        }

        for (SimpleTypeRef p : in.getProtocols()) {
            Identifier id = getFullClassName(getStruct(p.getName(), Type.ObjCProtocol));
            if (id != null) {
                interfacesForInstance.add(typeRef(id));
            }
        }
        for (SimpleTypeRef id : interfacesForInstance) {
            if (isProtocol || isCategory) {
                instanceStruct.addParent(id);
            } else {
                instanceStruct.addProtocol(id);
            }
        }

//		CompoundCollection<Declaration> declarations = new CompoundCollection<Declaration>();
//		declarations.addComponent(in.getDeclarations());
//		for (Struct catIn : Result.getMap(result.objCCategoriesByTargetType, in.getTag()).values()) {
//			for (Declaration d : catIn.getDeclarations())
//				d.addToCommentBefore("From category " + catIn.getCategoryName());
//			declarations.addComponent(catIn.getDeclarations());
//			
//			if (catIn.getCommentBefore() != null)
//				instanceStruct.addToCommentBefore("<p>Category " + catIn.getTag()+ " : " + catIn.getCommentBefore() +"</p>");
//		}


        StoredDeclarations classHolder = new VariablesDeclaration();
        if (!(isProtocol || isCategory)) {
            classHolder.addModifiers(ModifierType.Private, ModifierType.Static);
        }

        classHolder.setValueType(typeRef(classClassName));

        Expression.FunctionCall call = methodCall(expr(typeRef(Rococoa.class)), MemberRefStyle.Dot, "createClass");
        call.addArgument(expr(in.getTag().toString()));
        call.addArgument(memberRef(expr(typeRef(classClassName)), MemberRefStyle.Dot, "class"));

        Function classGetter;
        if (isProtocol || isCategory) {
            classGetter = null;
            classHolder.addDeclarator(new Declarator.DirectDeclarator(classInstanceName, call));
        } else {
            classHolder.addDeclarator(new Declarator.DirectDeclarator(classInstanceName));

            classGetter = new Function(Function.Type.JavaMethod, ident(classInstanceGetterName), typeRef(classClassName));
            classGetter.addModifiers(ModifierType.Public, ModifierType.Static);
            classGetter.setBody(new Block(
                    new Statement.If(
                    expr(
                    varRef(classInstanceName),
                    BinaryOperator.IsEqual,
                    Constant.newNull()),
                    new Statement.ExpressionStatement(
                    new AssignmentOp(
                    varRef(classInstanceName),
                    AssignmentOperator.Equal,
                    call)),
                    null),
                    new Statement.Return(varRef(classInstanceName))));
        }

        Struct classInterfaceStruct = null, structThatReceivesStaticMethods;
        if (isProtocol || isCategory) {
            structThatReceivesStaticMethods = classInterfaceStruct = new Struct();
            classInterfaceStruct.setType(Struct.Type.JavaInterface);
            classInterfaceStruct.setTag(ident(classInterfaceNameInCategoriesAndProtocols));
            classInterfaceStruct.addParent(ident(ObjCClass.class));
            classStruct.addProtocol(ident(classInterfaceNameInCategoriesAndProtocols));
        } else {
            structThatReceivesStaticMethods = classStruct;
        }

        if (!(isProtocol || isCategory)) {
            addAllocIfMissing(in, "alloc");
            addAllocIfMissing(in, "new_");
        }

        Identifier fullClassName = getFullClassName(in);
        outputMembers(signatures, in, instanceStruct, structThatReceivesStaticMethods, in.getDeclarations(), isProtocol || isCategory);
        
        /*
         if (!isProtocol && !isCategory) {
         // Output static proxies for static category methods
         for (Struct catIn : Result.getMap(result.objCCategoriesByTargetType, in.getTag()).values()) {
         for (Declaration d : catIn.getDeclarations()) {
         if (!(d instanceof Function))
         continue;
         Function f = (Function)d;
         if (!f.hasModifier(ModifierType.Static))
         continue;

         List<Declaration> decls = new ArrayList<Declaration>();
         result.declarationsConverter.convertFunction(f, null, false, new DeclarationsHolder.ListWrapper(decls), fullClassName);

         if (f.getModifiers().contains(ModifierType.Static)) {
         for (Declaration decl : decls) {
         if (!(decl instanceof Function))
         continue;

         Function pf = (Function)decl;
         if (!signatures.methodsSignatures.add(pf.computeSignature(false)))
         continue;

         //if (!add(classStruct, decl, signatures, objSigs, clasSigs))
         //	continue;
         instanceStruct.addDeclaration(createProxyCopy(pf, (Function)decl));
         }
         }
         }
         }
         }*/

        instanceStruct.addDeclaration(decl(classInterfaceStruct));
        if (!isCategory) {// && !structThatReceivesStaticMethods.getDeclarations().isEmpty()) {
            instanceStruct.addDeclaration(new TaggedTypeRefDeclaration(classStruct));
            instanceStruct.addDeclaration(classGetter);
            instanceStruct.addDeclaration(classHolder);
        }
        return instanceStruct;
    }

    Function createCastMethod(Identifier name, Identifier classId, boolean isStatic) {
        Function m = new Function();
        m.setType(Function.Type.JavaMethod);
        m.addModifiers(ModifierType.Public);
        m.setName(ident("as" + (isStatic ? "Static_" : "_") + name));
        m.setValueType(typeRef(classId.clone()));
        m.setBody(block(
                new Statement.Return(
                methodCall(
                expr(typeRef(Rococoa.class)),
                MemberRefStyle.Dot,
                "cast",
                varRef("this"),
                result.typeConverter.typeLiteral(typeRef(classId.clone()))))));
        return m;
    }

    private void addAllocIfMissing(Struct in, String allocName) {
        Identifier n = in.getTag();
        if (n.equals("NSObject") || n.equals("NSClass")) {
            return;
        }

        boolean hasAlloc = false;
        for (Declaration d : in.getDeclarations()) {
            if (d instanceof Function) {
                Function f = (Function) d;
                if (f.getArgs().isEmpty() && allocName.equals(f.getName())) {
                    hasAlloc = true;
                    break;
                }
            }
        }
        if (!hasAlloc) {
            in.addDeclaration(new Function(Function.Type.ObjCMethod, ident(allocName), typeRef(in.getTag())).addModifiers(ModifierType.Static));
        }

    }

    private void outputMembers(Signatures signatures, Struct in, Struct instanceStruct,
            Struct classStruct, List<Declaration> declarations, boolean isProtocol) throws IOException {

        Identifier fullClassName = getFullClassName(in);
        instanceStruct.setResolvedJavaIdentifier(fullClassName);

        Set<String> objSigs = DeclarationsConverter.getMethodsAndTheirSignatures(NSObject.class).getSecond(),
                clasSigs = new HashSet<String>();//DeclarationsConverter.getMethodsAndTheirSignatures(NSClass.class).getSecond();

        int[] iChild = new int[1];
        for (Declaration d : declarations) {
            if (d instanceof Function) {
                Function f = (Function) d;
                List<Declaration> decls = new ArrayList<Declaration>();
                DeclarationsHolder out = new DeclarationsHolder.ListWrapper(decls);
                result.declarationsConverter.convertFunction(f, null/*signatures*/, false, out, out, fullClassName, -1);

                if (f.hasModifier(ModifierType.Static)) {
                    for (Declaration decl : decls) {
                        if (!add(classStruct, decl, signatures, objSigs, clasSigs)) {
                            continue;
                        }

                        if (!isProtocol && decl instanceof Function) {
                            instanceStruct.addDeclaration(createProxyCopy(f, (Function) decl));
                            signatures.addMethod((Function) decl);
                        }

                        if (classStruct.getType() == Type.JavaClass) {
                            decl.addModifiers(ModifierType.Public, ModifierType.Abstract);
                            decl.reorganizeModifiers();
                        }
                    }
                } else {
                    for (Declaration decl : decls) {
                        if (!add(instanceStruct, decl, signatures, objSigs)) {
                            continue;
                        }

                        if (!isProtocol && decl instanceof Function) {
//							
                            Function addedF = createCreateCopyFromInit((Function) decl, instanceStruct);
                            signatures.addMethod((Function) decl);
                            instanceStruct.addDeclaration(addedF);

                            if (instanceStruct.getType() == Type.JavaClass) {
                                decl.addModifiers(ModifierType.Public, ModifierType.Abstract);
                                decl.reorganizeModifiers();
                            }
                        }
                    }
                }
//			} else if (d instanceof VariablesDeclaration) {
//				result.declarationsConverter.convertVariablesDeclaration((VariablesDeclaration)d, instanceStruct, iChild, fullClassName);
            } else if (d instanceof TaggedTypeRefDeclaration) {
                TaggedTypeRef tr = ((TaggedTypeRefDeclaration) d).getTaggedTypeRef();
                if (tr instanceof Struct) {
                    result.declarationsConverter.outputConvertedStruct((Struct) tr, signatures, instanceStruct, null, false);
                } else if (tr instanceof Enum) {
                    result.declarationsConverter.convertEnum((Enum) tr, signatures, instanceStruct, fullClassName);
                }
            } else if (d instanceof TypeDef) {
                TypeDef td = (TypeDef) d;
                TypeRef tr = td.getValueType();
                if (tr instanceof Struct) {
                    result.declarationsConverter.outputConvertedStruct((Struct) tr, signatures, instanceStruct, null, false);
                } else if (tr instanceof FunctionSignature) {
                    result.declarationsConverter.convertCallback((FunctionSignature) tr, signatures, instanceStruct, fullClassName);
                }
            }
            iChild[0]++;
        }
    }

    private boolean add(Struct classStruct, Declaration decl, Signatures signatures, Set<?>... additionalMethodSignatures) {
        if (decl instanceof Function) {
            String sig = ((Function) decl).computeSignature(SignatureType.JavaStyle);
            for (Set<?> sigs : additionalMethodSignatures) {
                if (sigs.contains(sig)) {
                    return false;
                }
            }
            if (signatures.addMethod(sig)) {
                classStruct.addDeclaration(decl);
                return true;
            } else {
                return false;
            }
        }
        classStruct.addDeclaration(decl);
        return true;
    }

    /**
     * Create a createXXXWithYYY factory
     *
     * @param meth
     * @param instanceStruct
     * @return
     */
    private Function createCreateCopyFromInit(Function meth, TaggedTypeRef instanceStruct) {
        String name = meth.getName().toString();
        if (!name.matches("^init([A-Z].*|)$")) {
            return null;
        }
        //if (name.startsWith("init") && (name.equals("init") || !Character.isUpperCase(name.charAt("init".length())))) 

        Function createCopy = meth.clone();
        createCopy.setCommentBefore("Factory method");
        createCopy.addToCommentBefore("@see #" + meth.computeSignature(SignatureType.JavaStyle));
        createCopy.setName(ident("create" + name.substring("init".length())));
        createCopy.addModifiers(ModifierType.Public, ModifierType.Static);
        createCopy.reorganizeModifiers();

        Expression[] args = new Expression[meth.getArgs().size()];
        int i = 0;
        for (Arg arg : meth.getArgs()) {
            args[i++] = varRef(arg.getName());
        }

        Expression val = methodCall(
                methodCall(
                methodCall(null, null, classInstanceGetterName),
                Expression.MemberRefStyle.Dot,
                "alloc"),
                Expression.MemberRefStyle.Dot,
                meth.getName().toString(),
                args);

        if (AUTO_RELEASE_IN_FACTORIES) {
            val = methodCall(val, MemberRefStyle.Dot, "autorelease");
            val =
                    methodCall(expr(typeRef(Rococoa.class)), MemberRefStyle.Dot, "cast",
                    val,
                    memberRef(expr(typeRef(instanceStruct.getTag())), MemberRefStyle.Dot, "class"));
        }
        createCopy.setBody(new Block(new Statement.Return(val)));
        return createCopy;
    }

    private Function createProxyCopy(Function originalMethod, Function meth) {
        if (isMethodExcludedFromStaticForwarding(originalMethod)) {
            return null;
        }

        Function proxyCopy = meth.clone();
        proxyCopy.addModifiers(ModifierType.Public, ModifierType.Static);
        proxyCopy.reorganizeModifiers();

        Expression[] args = new Expression[meth.getArgs().size()];
        int i = 0;
        for (Arg arg : meth.getArgs()) {
            args[i++] = varRef(arg.getName());
        }

        Expression val = methodCall(methodCall(null, null, classInstanceGetterName), Expression.MemberRefStyle.Dot, meth.getName().toString(), args);
        proxyCopy.setBody(new Block(
                meth.getValueType() == null || "void".equals(meth.getValueType().toString()) ? stat(val) : new Statement.Return(val)));
        return proxyCopy;
    }
//	protected static _class_ _CLASS_ = org.rococoa.Rococoa.createClass("NSURL", _class_.class);
//	public abstract class _class_ extends 
//		org.rococoa.NSClass {
//		//org.rococoa.NSObject._class_ {
//	}
}
