/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program 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. 
 * 
 * This program 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 at /legal/license.txt). 
 * 
 * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package runtime;

import components.*;
import vm.*;
import jcc.Util;
import jcc.Const;
import jcc.EVMConst;
import util.*;
import java.util.*;
import java.io.OutputStream;

/*
 * Generate natives table for VM.
 */

public class CLDC_HI_NativesWriter 
    implements CoreImageWriter, Const, EVMConst
{
    /*
     * Natives in the VM can be invoked in two ways -- as a "native" or as an 
     * "entry". Most methods are of the "native" type, unless specified in the
     * following table.
     *
     * Note [1] that a method can be both "native" and "entry". Example:
     *          java.lang.System.arraycopy().
     * Note [2] a "entry" method doesn't necessarily have the ACC_NATIVE
     *          flag -- the VM may try to execute a simple version of the
     *          method in "entry" code. If that fails, it will revert back
     *          to the Java implementation. Example: java.lang.String.compareTo
     */
    static String SYSTEM       = "java/lang/System";
    static String STRING       = "java/lang/String";
    static String MATH         = "java/lang/Math";
    static String VECTOR       = "java/util/Vector";
    static String STRINGBUFFER = "java/lang/StringBuffer";
    static String INTEGER      = "java/lang/Integer";
    static String JVM          = "com/sun/cldchi/jvm/JVM";

    static String[][] use_entries = {
        // class     method  signature(null=any)  entry_name
        {SYSTEM, "arraycopy",   null,   "native_system_arraycopy_entry",
                                        "also-native"},
        {STRING, "charAt",      null,   "native_string_charAt_entry"},
        {STRING, "<init>",     "(Ljava/lang/StringBuffer;)V",   
                                        "native_string_init_entry"},
        {STRING, "equals",      null,   "native_string_equals_entry"},
        {STRING, "indexOf",    "(Ljava/lang/String;I)I", 
                                        "native_string_indexof_string_entry"},
        {STRING, "indexOf",    "(Ljava/lang/String;)I",  
                                        "native_string_indexof0_string_entry"},
        {STRING, "indexOf",    "(II)I", "native_string_indexof_entry"},
        {STRING, "indexOf",    "(I)I",  "native_string_indexof0_entry"},
        {STRING, "startsWith", "(Ljava/lang/String;I)Z", 
                                        "native_string_startsWith_entry"},
        {STRING, "startsWith", "(Ljava/lang/String;)Z",
                                        "native_string_startsWith0_entry"},
        {STRING, "endsWith",    null,   "native_string_endsWith_entry"},
        {STRING, "substring", "(I)Ljava/lang/String;", 
                                        "native_string_substringI_entry"},
        {STRING, "substring", "(II)Ljava/lang/String;", 
                                        "native_string_substringII_entry"},
        {STRING, "valueOf",   "(I)Ljava/lang/String;",
                                        "native_integer_toString_entry"},
        {VECTOR, "elementAt",  "(I)Ljava/lang/Object;",   
                                        "native_vector_elementAt_entry"},
        {VECTOR, "addElement", "(Ljava/lang/Object;)V",   
                                        "native_vector_addElement_entry"},
        {STRINGBUFFER, "append",  "(C)Ljava/lang/StringBuffer;",   
                                        "native_stringbuffer_append_entry"},
        {INTEGER, "toString", "(I)Ljava/lang/String;",
                                        "native_integer_toString_entry"},
        {MATH,   "sin",        "(D)D",  "native_math_sin_entry"},
        {MATH,   "cos",        "(D)D",  "native_math_cos_entry"},
        {MATH,   "tan",        "(D)D",  "native_math_tan_entry"},
        {MATH,   "sqrt",       "(D)D",  "native_math_sqrt_entry"},
        {MATH,   "ceil",       "(D)D",  "native_math_ceil_entry"},
        {MATH,   "floor",      "(D)D",  "native_math_floor_entry"},
        {JVM, "unchecked_byte_arraycopy", null,  
                               "native_jvm_unchecked_byte_arraycopy_entry"},
        {JVM, "unchecked_char_arraycopy", null,  
                               "native_jvm_unchecked_char_arraycopy_entry"},
        {JVM, "unchecked_int_arraycopy",  null,  
                               "native_jvm_unchecked_int_arraycopy_entry"},
        {JVM, "unchecked_long_arraycopy",  null,  
                               "native_jvm_unchecked_long_arraycopy_entry"},
        {JVM, "unchecked_obj_arraycopy",  null,  
                               "native_jvm_unchecked_obj_arraycopy_entry"}
    };

    /* INSTANCE DATA */
    protected  String    outputFileName;
    protected  Exception failureMode = null; // only interesting on failure

    CCodeWriter out;

    protected     vm.VMClassFactory     classMaker = new vm.EVMClassFactory();

    boolean formatError;

    public CLDC_HI_NativesWriter( ){ 
    }

    public void init( boolean classDebug, ClassnameFilterList nativeTypes, 
                      boolean verbose, int maxSegmentSize ){
    }

    public boolean setAttribute( String attribute ){
        return false; 
    }


    public boolean open( String filename ){
        if ( out != null ) { 
            close();
        }
        outputFileName = filename;
        if ( filename == null){
            out = new CCodeWriter(System.out);
        } else {
            try {
                OutputStream os = new java.io.FileOutputStream( filename );
                out = new CCodeWriter(os);
            } catch ( java.io.IOException e ){
                failureMode = e;
                return false;
            }
        }
        return true;
    }


    public void close(){
        if (out != null) { 
            out.close();
            outputFileName = null;
            out = null;
        }
    }


    public boolean writeClasses( ConstantPool consts ) {
        return writeClasses(consts, null);
    }

    public void printError( java.io.PrintStream o ){
        if ( failureMode != null ){
            failureMode.printStackTrace( o );
        } else {
            if ( out != null && out.checkError() )
                o.println(outputFileName+": Output write error");
        }
    }

    public boolean writeClasses( ConstantPool consts, 
                                 ConstantPool sharedconsts ){
        ClassClass classes[] = ClassClass.getClassVector( classMaker );
        ClassClass.setTypes();
        
        // write out some constant pool stuff here,
        writeProlog();
        
        try {
        writeAllNativeTables(classes);
        } catch (RuntimeException e) { 
            out.flush();
            System.out.println(e);
            e.printStackTrace(System.out);
            formatError = true;
        }
    writeEpilog();
        return (!formatError) && (! out.checkError());
    }
    
    int checkEntry(EVMClass cc, MethodInfo mi) {
        for (int i=0; i<use_entries.length; i++) {
            if (cc.ci.className.equals(use_entries[i][0]) &&
                mi.name.string.equals(use_entries[i][1])) {
                if (use_entries[i][2] == null) {
                    return i;
                }
                if (mi.type.string.equals(use_entries[i][2])) {
                    return i;
                }
            }
        }
        return -1;
    }

    boolean isNativeFunc(EVMClass cc, MethodInfo mi) {
        int entry;
        entry = checkEntry(cc, mi);
        if (entry < 0 || use_entries[entry].length > 4) {
            return true;
        } else {
            return false;
        }
    }

    boolean isEntryFunc(EVMClass cc, MethodInfo mi) {
        int entry;
        entry = checkEntry(cc, mi);
        if (entry >= 0) {
            return true;
        } else {
            return false;
        }
    }

    boolean isNativeOrEntryFunc(EVMClass cc, MethodInfo mi) {
        if ((mi.access & Const.ACC_NATIVE) != 0) {
            return true;
        } else {
            return isEntryFunc(cc, mi);
        }
    }

    void sortClasses(ClassClass classes[]) {
        ArrayList list = new ArrayList();
        for (int i=0; i<classes.length; i++) {
            list.add(classes[i]);
        }
        Collections.sort(list, new Comparator() {
            public int compare(Object o1, Object o2) {
                ClassClass c1 = (ClassClass)o1;
                ClassClass c2 = (ClassClass)o2;

                return c1.ci.className.compareTo(c2.ci.className);
            }           
        });
        for (int i=0; i<list.size(); i++) {
            classes[i] = (ClassClass)list.get(i);
        }
    }

    void sortMethods(EVMMethodInfo methods[]) {
        ArrayList list = new ArrayList();
        for (int i=0; i<methods.length; i++) {
            list.add(methods[i]);
        }
        Collections.sort(list, new Comparator() {
            public int compare(Object o1, Object o2) {
                MethodInfo m1 = ((EVMMethodInfo)o1).method;
                MethodInfo m2 = ((EVMMethodInfo)o2).method;

                int n = m1.name.string.compareTo(m2.name.string);
                if (n != 0) {
                    return n;
                } else {
                    return m1.type.string.compareTo(m2.type.string);
                }
            }           
        });
        for (int i=0; i<list.size(); i++) {
            methods[i] = (EVMMethodInfo)list.get(i);
        }
    }

    protected void writeAllNativeTables(ClassClass classes[]) { 
        sortClasses(classes);
        Vector nativeClasses = new Vector();

        // (1) Write all the function declarations
        for (int i = 0; i < classes.length; i++){
        boolean classHasNatives = false;
            EVMClass cc = (EVMClass)classes[i];
            if (cc.isPrimitiveClass() || cc.isArrayClass()) { 
                continue;
            }
            EVMMethodInfo  m[] = cc.methods;
            sortMethods(m);
            int nmethod = (m == null) ? 0 : m.length;
            for (int j = 0; j < nmethod; j++) {
                EVMMethodInfo meth = m[j];
                MethodInfo mi = meth.method;
                if (!isNativeOrEntryFunc(cc, mi)) { 
                    continue;
                }
                if (!classHasNatives) { 
                    classHasNatives = true;
                    nativeClasses.addElement(cc);
                }
                int entry;
                entry = checkEntry(cc, mi);
                if (entry < 0 || use_entries[entry].length > 4) {
                    // This is a "native"
                    out.println("extern \"C\" " + mi.getJNIReturnType() + " " +
                                mi.getNativeName(true) + "();");
                }
                if (entry >= 0) {
                    // This is an "entry" (could also be a "native")
                    out.println("extern \"C\" void " + 
                                use_entries[entry][3] + "();");
                }
            }
        }
        out.println();
        out.println();

        // (2) Write all the "_natives[]" and "_entries[]" tables for
        //     individual classes

        for (Enumeration e = nativeClasses.elements(); e.hasMoreElements();){
            EVMClass cc = (EVMClass)e.nextElement();
            EVMMethodInfo  m[] = cc.methods;
            int nmethod = (m == null) ? 0 : m.length;
            int num_native_methods = 0;
            int num_entry_methods = 0;

            for (int j = 0; j < nmethod; j++){
                EVMMethodInfo meth = m[j];
                MethodInfo mi = meth.method;
                if (!isNativeOrEntryFunc(cc, mi)) { 
                    continue;
                }
                if (isNativeFunc(cc, mi)) {
                    num_native_methods ++;
                }
                if (isEntryFunc(cc, mi)) {
                    num_entry_methods ++;
                }
            }

            // Write the "_natives[]" struct
            if (num_native_methods > 0) {
                out.println("static const JvmNativeFunction " + 
                       cc.getNativeName() + "_natives[] = " + "{");
                for (int j = 0; j < nmethod; j++) {
                    EVMMethodInfo meth = m[j];
                    MethodInfo mi = meth.method;
                    if (!isNativeOrEntryFunc(cc, mi)) { 
                        continue;
                    }
                    if (isNativeFunc(cc, mi)) {
                        out.print(pad("  JVM_NATIVE(\"" + mi.name.string + "\",", 30));
                        out.print(pad("\"" + mi.type.string + "\", ", 25));
                        out.println(mi.getNativeName(true) + "),");
                    }
                }
                out.println("  {(char*)0, (char*)0, (void*)0}");
                out.println("};");
                out.println();
            }

            // Write the "_entries[]" struct
            if (num_entry_methods > 0) {
                out.println("static const JvmNativeFunction " + 
                       cc.getNativeName() + "_entries[] = " + "{");
                for (int j = 0; j < nmethod; j++) {
                    EVMMethodInfo meth = m[j];
                    MethodInfo mi = meth.method;
                    if (!isNativeOrEntryFunc(cc, mi)) { 
                        continue;
                    }
                    int entry;
                    if ((entry = checkEntry(cc, mi)) >= 0) {
                        out.print("  JVM_ENTRY(\"" + mi.name.string + "\"," 
                                  + spaces(20 - mi.name.string.length()));
                        out.print("\"" + mi.type.string + "\", ");
                        out.println(use_entries[entry][3] + "),");
                    }
                }
                out.println("  {(char*)0, (char*)0, (void*)0}");
                out.println("};");
                out.println();
            }
        }

        // (3) Write the top-level table

        out.println("const JvmNativesTable jvm_natives_table[] = {");

        for (Enumeration e = nativeClasses.elements(); e.hasMoreElements();){
            EVMClass cc = (EVMClass)e.nextElement();
            String className = cc.ci.className;
            out.println("  JVM_TABLE(\"" + className + "\",");
            EVMMethodInfo  m[] = cc.methods;
            int nmethod = (m == null) ? 0 : m.length;

            int num_native_methods = 0;
            int num_entry_methods = 0;

            for (int j = 0; j < nmethod; j++){
                EVMMethodInfo meth = m[j];
                MethodInfo mi = meth.method;
                if (!isNativeOrEntryFunc(cc, mi)) { 
                    continue;
                }
                if (isNativeFunc(cc, mi)) {
                    num_native_methods ++;
                }
                if (isEntryFunc(cc, mi)) {
                    num_entry_methods ++;
                }
            }
            
            if (num_native_methods > 0) {
                out.println(spaces(40) + cc.getNativeName() + "_natives,");
            } else {
                out.println(spaces(40) + "(JvmNativeFunction*)0,");
            }
            if (num_entry_methods > 0) {
                out.println(spaces(40) + cc.getNativeName() + "_entries),");
            } else {
                out.println(spaces(40) + "(JvmNativeFunction*)0),");
            }
        }
        out.println("  JVM_TABLE((char*)0, (JvmNativeFunction*)0, " +
                    "(JvmNativeFunction*)0)");
        out.println("};");


        // (4) Write the jvm_native_execution_top-level table

        out.println("const JvmExecutionEntry jvm_api_entries[] = {");
        for (Enumeration e = nativeClasses.elements(); e.hasMoreElements();){
            EVMClass cc = (EVMClass)e.nextElement();
            EVMMethodInfo  m[] = cc.methods;
            int nmethod = (m == null) ? 0 : m.length;

            for (int j = 0; j < nmethod; j++) {
                EVMMethodInfo meth = m[j];
                MethodInfo mi = meth.method;
                int index;
                if ((index = checkEntry(cc, mi)) >= 0) {
                    String entryName = use_entries[index][3];
                    out.println("{(unsigned char*)&" + entryName + ",");
                    out.println("(char*)\"" + entryName + "\"},");
                }
            }
        }
        out.println("{(unsigned char*)0, (char*)0}};");
    }

    String pad(String str, int width) {
        int len = str.length();
        if (len < width) {
            return str + spaces(width - len);
        } else {
            return str;
        }
    }

    protected void writeProlog(){
        out.println("/* This is a generated file.  Do not modify.");
        out.println(" * Generated on " + new java.util.Date());
        out.println(" */");
        out.println();
        out.println("/*");
        out.println(" *");
        out.println(" * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.");
        out.println(" * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER");
        out.println(" * ");
        out.println(" * This program is free software; you can redistribute it and/or");
        out.println(" * modify it under the terms of the GNU General Public License version");
        out.println(" * 2 only, as published by the Free Software Foundation. ");
        out.println(" * ");
        out.println(" * This program is distributed in the hope that it will be useful, but");
        out.println(" * WITHOUT ANY WARRANTY; without even the implied warranty of");
        out.println(" * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU");
        out.println(" * General Public License version 2 for more details (a copy is");
        out.println(" * included at /legal/license.txt). ");
        out.println(" * ");
        out.println(" * You should have received a copy of the GNU General Public License");
        out.println(" * version 2 along with this work; if not, write to the Free Software");
        out.println(" * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA");
        out.println(" * 02110-1301 USA ");
        out.println(" * ");
        out.println(" * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa");
        out.println(" * Clara, CA 95054 or visit www.sun.com if you need additional");
        out.println(" * information or have any questions. ");
        out.println(" */");
        out.println();
        out.println("#include \"jvmconfig.h\"");
        out.println("#if !defined(ROMIZING) || !defined(PRODUCT)");
        out.println("#include \"NativesTable.hpp\"");
	out.println("#include \"kni.h\"");	
    out.println();
    out.println();
    }
    
    protected void writeEpilog() {
    out.println();
    out.println("#endif");
    }

    public void printSpaceStats(java.io.PrintStream stream) {}

    private String spaces(int length) { 
    if (length <= 1) { 
        return " ";
    } if (length <= 10) { 
        return "          ".substring(0, length);
    } else { 
        return spaces(10) + spaces(length - 10);
    }
    }

    private boolean isOverloadedNative(MethodInfo mi) { 
        ClassInfo ci = mi.parent;
    int nmethods = ci.methods.length;
    for (int j = 0; j < nmethods; j ++ ){
        MethodInfo m = ci.methods[j];
        if (    (m != mi) 
                 && ((m.access&Const.ACC_NATIVE) != 0 )
                 && (m.name.equals(mi.name))) { 
                return true;
            }
    } 
        return false;
    }

}
