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

import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;

import com.apple.internal.jobjc.generator.FunctionGenerator;
import com.apple.internal.jobjc.generator.Utils;
import com.apple.internal.jobjc.generator.model.Category;
import com.apple.internal.jobjc.generator.model.Clazz;
import com.apple.internal.jobjc.generator.model.Constant;
import com.apple.internal.jobjc.generator.model.Framework;
import com.apple.internal.jobjc.generator.model.Function;
import com.apple.internal.jobjc.generator.model.NativeEnum;
import com.apple.internal.jobjc.generator.model.StringConstant;
import com.apple.internal.jobjc.generator.model.Struct;
import com.apple.internal.jobjc.generator.model.types.JType;
import com.apple.internal.jobjc.generator.model.types.JType.JStruct;
import com.apple.internal.jobjc.generator.utils.Fp;
import com.apple.internal.jobjc.generator.utils.JavaLang;
import com.apple.internal.jobjc.generator.utils.Fp.Map1;
import com.apple.internal.jobjc.generator.utils.JavaLang.JLCall;
import com.apple.internal.jobjc.generator.utils.JavaLang.JLField;
import com.apple.internal.jobjc.generator.utils.JavaLang.JLMethod;
import com.apple.internal.jobjc.generator.utils.JavaLang.JLReturn;
import com.apple.jobjc.JObjCRuntime;
import com.apple.jobjc.MacOSXFramework;
import com.apple.jobjc.Invoke.FunCall;

public class FrameworkClassFile extends GeneratedClassFile {
    final Framework framework;

    public FrameworkClassFile(final Framework framework) {
        super(framework.pkg, framework.name + "Framework", MacOSXFramework.class.getName());
        this.framework = framework;
    }

    @Override public void writeBeginning(final PrintStream out) {
        List<String> binPaths = Fp.map(new Map1<File,String>(){
            public String apply(File a) { return "\"" + a.getAbsolutePath() + "\""; }},
            framework.binaries);
        out.println(new Utils.Substituter(
                "#public " + className + "(" + JObjCRuntime.class.getName() + " runtime) {~" +
                "##super(runtime, new String[]{" + Fp.join(", ", binPaths) + "});~" +
                "#}~"
        ));
    }

    @Override public void writeBody(final PrintStream out) {
        for(final Struct struct : new ArrayList<Struct>(framework.structs)){
            out.println("\tpublic " + struct.name + " make" + struct.name + "(){");
            out.println("\t\treturn new " + struct.name + "(getRuntime());");
            out.println("\t}");
            out.println("\tpublic " + struct.name + " make" + struct.name + "(com.apple.jobjc.NativeBuffer base){");
            out.println("\t\treturn new " + struct.name + "(getRuntime(), base);");
            out.println("\t}");
        }

        for(final NativeEnum nenum : framework.enums){
            if(nenum.ignore){
                out.println("\t/**");
                out.println("\t * @deprecated Suggestion: " + nenum.suggestion);
                out.println("\t */");
                out.println("\t@Deprecated");
            }
            out.println(String.format("\tpublic final %3$s %1$s(){ return %2$s; }",
                    nenum.name, nenum.valueToString(), nenum.type.getJType().getJavaReturnTypeName()));
        }

        for(final Constant konst : framework.constants){
            String cacheName = "_" + konst.name;
            final JType jtype = konst.type.getJType();
            final String cast = jtype.getReturnTypeCast() == null ? "" : "(" + jtype.getReturnTypeCast() + ")";
            out.println();

            out.print(new JLField("private", jtype.getJavaTypeName(), cacheName, jtype.getDefaultReturnValue()));

            JLMethod reader = new JLMethod("public final", jtype.getJavaReturnTypeName(), konst.name);
            reader.body.add("if(" + cacheName + " != " + jtype.getDefaultReturnValue() + ") return " + cast + cacheName + ";");

            String contextName = jtype instanceof JStruct ? "returnValue" : "nativeBuffer";

            if(jtype instanceof JStruct)
                reader.body.add(((JStruct)jtype).createReturnValue());
            else
                reader.body.add(jtype.createDeclareBuffer(contextName));

            reader.body.add("getConstant(\"" + konst.name + "\", " + contextName + ", " + jtype.getCoderDescriptor().getCoderInstanceName() + ".sizeof());");

            reader.body.add(jtype.createPop(contextName));
            reader.body.add(cacheName + " = returnValue;");
            reader.body.add(jtype.createReturn());

            out.print(reader);
        }

        for(final StringConstant konst : framework.stringConstants){
            if(Fp.any(new Map1<Constant,Boolean>(){ public Boolean apply(Constant a) {
                return a.name.equals(konst.name);
                }}, new ArrayList<Constant>(framework.constants))){
                System.out.println("Warning: [" + framework.name + "] String constant " + konst.name + " is already defined in constants. Skipping.");
            }
            else{
                out.println("\tpublic final String " + konst.name + "(){ return \"" + escapeQuotes(konst.value) + "\"; }");
            }
        }

        /**
         * Order classes to get stable output
         */
        TreeSet<Clazz> sortedClasses = new TreeSet<Clazz>(framework.classes);
        for (final Clazz clazz : sortedClasses) {
            final String classClassName = clazz.name + "Class";
            out.println(JavaLang.makeSingleton("_" + classClassName, clazz.name, classClassName, "getRuntime()"));
        }

        for (final Category cat : framework.categories) {
            final String classClassName = cat.category.name + "Class";
            out.println(JavaLang.makeSingleton("_" + classClassName, cat.category.name, classClassName, "getRuntime()"));

            JLMethod jlm = new JLMethod("public", cat.category.name, cat.category.name, "final " + cat.category.superClass.getFullPath() + " obj");
            jlm.body.add(new JLReturn(new JLCall("new " + cat.category.name, "obj", "getRuntime()")));
            out.println(jlm);
        }

        for (final Function fxn : framework.functions){
            FunctionGenerator.writeOutFunction(out, FunCall.class, fxn, null);
        }
    }

    private String escapeQuotes(String s){
        return s.replace("\"", "\\\"");
    }
}
