/*
 * Copyright 2002-2005 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */


package com.sun.tools.javah;

import java.io.File;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Hashtable;
import com.sun.javadoc.*;

 /*
  * @author  Sucheta Dambalkar(Revised)
  */
public class LLNI extends Gen {

    protected final char  pathChar = File.separatorChar;
    protected final char  innerDelim = '$';     /* For inner classes */
    protected Hashtable   doneHandleTypes;
    MemberDoc []fields;
    MemberDoc [] methods;
    private boolean       doubleAlign;
    private int           padFieldNum = 0;


    LLNI(boolean doubleAlign, RootDoc root) {
        super(root);
        this.doubleAlign = doubleAlign;
    }


    protected String getIncludes() {
        return "";
    }

    protected void write(OutputStream o, ClassDoc clazz)
        throws ClassNotFoundException {
        String cname     = mangleClassName(clazz.qualifiedName());
        PrintWriter pw   = wrapWriter(o);
        fields = clazz.fields();
        methods = clazz.methods();
        generateDeclsForClass(pw, clazz, cname);
    }

    protected void generateDeclsForClass(PrintWriter pw,
                                         ClassDoc clazz, String cname)
        throws ClassNotFoundException {
        doneHandleTypes  = new Hashtable();
        /* The following handle types are predefined in "typedefs.h". Suppress
           inclusion in the output by generating them "into the blue" here. */
        genHandleType(null, "java.lang.Class");
        genHandleType(null, "java.lang.ClassLoader");
        genHandleType(null, "java.lang.Object");
        genHandleType(null, "java.lang.String");
        genHandleType(null, "java.lang.Thread");
        genHandleType(null, "java.lang.ThreadGroup");
        genHandleType(null, "java.lang.Throwable");

        pw.println("/* LLNI Header for class " + clazz.qualifiedName() + " */" + lineSep);
        pw.println("#ifndef _Included_" + cname);
        pw.println("#define _Included_" + cname);
        pw.println("#include \"typedefs.h\"");
        pw.println("#include \"llni.h\"");
        pw.println("#include \"jni.h\"" + lineSep);

        forwardDecls(pw, clazz);
        structSectionForClass(pw, clazz, cname);
        methodSectionForClass(pw, clazz, cname);
        pw.println("#endif");
    }

    protected void genHandleType(PrintWriter pw, String clazzname) {
        String cname = mangleClassName(clazzname);
        if (!doneHandleTypes.containsKey(cname)) {
            doneHandleTypes.put(cname, cname);
            if (pw != null) {
                pw.println("#ifndef DEFINED_" + cname);
                pw.println("    #define DEFINED_" + cname);
                pw.println("    GEN_HANDLE_TYPES(" + cname + ");");
                pw.println("#endif" + lineSep);
            }
        }
    }

    protected String mangleClassName(String s) {
        return s.replace('.', '_')
            .replace(pathChar, '_')
            .replace(innerDelim, '_');
    }

    protected void forwardDecls(PrintWriter pw, ClassDoc clazz)
        throws ClassNotFoundException {
        ClassDoc clazzfield = null;

        if (clazz.qualifiedName().equals("java.lang.Object"))
            return;
        genHandleType(pw, clazz.qualifiedName());
        ClassDoc superClass = clazz.superclass();

        if(superClass != null){
            String superClassName = superClass.qualifiedName();
            forwardDecls(pw, superClass);
        }

        for (int i = 0; i < fields.length; i++) {
            FieldDoc field = (FieldDoc)fields[i];

            if (!field.isStatic()) {
                Type t = field.type();
                String tname = t.qualifiedTypeName();
                TypeSignature newTypeSig = new TypeSignature(root);
                String sig = newTypeSig.getTypeSignature(tname);

                if (sig.charAt(0) != '[')
                    forwardDeclsFromSig(pw, sig);
            }
        }

        for (int i = 0; i < methods.length; i++) {
            MethodDoc method = (MethodDoc)methods[i];

            if (method.isNative()) {
                Type retType = method.returnType();
                String typesig = method.signature();
                TypeSignature newTypeSig = new TypeSignature(root);
                String sig = newTypeSig.getTypeSignature(typesig, retType);

                if (sig.charAt(0) != '[')
                    forwardDeclsFromSig(pw, sig);

            }
        }
    }

    protected void forwardDeclsFromSig(PrintWriter pw, String sig) {
        int    len = sig.length();
        int    i   = sig.charAt(0) == '(' ? 1 : 0;

        /* Skip the initial "(". */
        while (i < len) {
            if (sig.charAt(i) == 'L') {
                int j = i + 1;
                while (sig.charAt(j) != ';') j++;
                genHandleType(pw, sig.substring(i + 1, j));
                i = j + 1;
            } else {
                i++;
            }
        }
    }

    protected void structSectionForClass(PrintWriter pw,
                                         ClassDoc jclazz, String cname)
        throws ClassNotFoundException {

        String jname = jclazz.qualifiedName();

        if (cname.equals("java_lang_Object")) {
            pw.println("/* struct java_lang_Object is defined in typedefs.h. */");
            pw.println();
            return;
        }
        pw.println("#if !defined(__i386)");
        pw.println("#pragma pack(4)");
        pw.println("#endif");
        pw.println();
        pw.println("struct " + cname + " {");
        pw.println("    ObjHeader h;");
        pw.print(fieldDefs(jclazz, cname));

        if (jname.equals("java.lang.Class"))
            pw.println("    Class *LLNI_mask(cClass);" +
                       "  /* Fake field; don't access (see oobj.h) */");
        pw.println("};" + lineSep + lineSep + "#pragma pack()");
        pw.println();
        return;
    }

    private static class FieldDefsRes {
        public String className;        /* Name of the current class. */
        public FieldDefsRes parent;
        public String s;
        public int byteSize;
        public boolean bottomMost;
        public boolean printedOne = false;

        FieldDefsRes(ClassDoc clazz, FieldDefsRes parent, boolean bottomMost) {
            this.className = clazz.qualifiedName();
            this.parent = parent;
            this.bottomMost = bottomMost;
            int byteSize = 0;
            if (parent == null) this.s = "";
            else this.s = parent.s;
        }
    }

    /* Returns "true" iff added a field. */
    private boolean doField(FieldDefsRes res, FieldDoc field,
                            String cname, boolean padWord)
        throws ClassNotFoundException {

        String fieldDef = addStructMember(field, cname, padWord);
        if (fieldDef != null) {
            if (!res.printedOne) { /* add separator */
                if (res.bottomMost) {
                    if (res.s.length() != 0)
                        res.s = res.s + "    /* local members: */" + lineSep;
                } else {
                    res.s = res.s + "    /* inherited members from " +
                        res.className + ": */" + lineSep;
                }
                res.printedOne = true;
            }
            res.s = res.s + fieldDef;
            return true;
        }

        // Otherwise.
        return false;
    }

    private int doTwoWordFields(FieldDefsRes res, ClassDoc clazz,
                                int offset, String cname, boolean padWord)
        throws ClassNotFoundException {
        boolean first = true;
        FieldDoc[] fields = clazz.fields();

        for (int i = 0; i <fields.length; i++) {
            FieldDoc field = fields[i];
            String tc =field.type().typeName();
            boolean twoWords = (tc.equals("long") || tc.equals("double"));
            if (twoWords && doField(res, field, cname, first && padWord)) {
                offset += 8; first = false;
            }
        }
        return offset;
    }

    protected String fieldDefs(ClassDoc clazz, String cname)
        throws ClassNotFoundException {
        FieldDefsRes res = fieldDefs(clazz, cname, true);
        return res.s;
    }

    protected FieldDefsRes fieldDefs(ClassDoc clazz, String cname,
                                     boolean bottomMost)
        throws ClassNotFoundException {
        FieldDefsRes res;
        int offset;
        boolean didTwoWordFields = false;
        ClassDoc superclazz = clazz.superclass();

        if (superclazz != null) {
            String supername = superclazz.qualifiedName();
            res = new FieldDefsRes(clazz,
                                   fieldDefs(superclazz, cname, false),
                                   bottomMost);
            offset = res.parent.byteSize;
        } else {
            res = new FieldDefsRes(clazz, null, bottomMost);
            offset = 0;
        }

        FieldDoc[] fields = clazz.fields();

        for (int i = 0; i < fields.length; i++) {
            FieldDoc field = fields[i];

            if (doubleAlign && !didTwoWordFields && (offset % 8) == 0) {
                offset = doTwoWordFields(res, clazz, offset, cname, false);
                didTwoWordFields = true;
            }

            String tc = field.type().typeName();
            boolean twoWords = (tc.equals("long") ||tc.equals("double"));

            if (!doubleAlign || !twoWords) {
                if (doField(res, field, cname, false)) offset += 4;
            }

        }

        if (doubleAlign && !didTwoWordFields) {
            if ((offset % 8) != 0) offset += 4;
            offset = doTwoWordFields(res, clazz, offset, cname, true);
        }

        res.byteSize = offset;
        return res;
    }

    /* OVERRIDE: This method handles instance fields */
    protected String addStructMember(FieldDoc member, String cname,
                                     boolean padWord)
        throws ClassNotFoundException {
        String res = null;

        if (member.isStatic()) {
            res = addStaticStructMember(member, cname);
            //   if (res == null) /* JNI didn't handle it, print comment. */
            //  res = "    /* Inaccessible static: " + member + " */" + lineSep;
        } else {
            if (padWord) res = "    java_int padWord" + padFieldNum++ + ";" + lineSep;
            res = "    " + llniType(member.type(), false, false) + " " + llniFieldName(member);
            if (isLongOrDouble(member.type())) res = res + "[2]";
            res = res + ";" + lineSep;
        }
        return res;
    }

    static private final boolean isWindows =
        System.getProperty("os.name").startsWith("Windows");

    /*
     * This method only handles static final fields.
     */
    protected String addStaticStructMember(FieldDoc field, String cname)
        throws ClassNotFoundException {
        String res = null;
        Object exp = null;

        if (!field.isStatic())
            return res;
        if (!field.isFinal())
            return res;

        exp = field.constantValue();

        if (exp != null) {
            /* Constant. */

            String     cn     = cname + "_" + field.name();
            String     suffix = null;
            long           val = 0;
            /* Can only handle int, long, float, and double fields. */
            if (exp instanceof Integer) {
                suffix = "L";
                val = ((Integer)exp).intValue();
            }
            if (exp instanceof Long) {
                // Visual C++ supports the i64 suffix, not LL
                suffix = isWindows ? "i64" : "LL";
                val = ((Long)exp).longValue();
            }
            if (exp instanceof Float)  suffix = "f";
            if (exp instanceof Double) suffix = "";
            if (suffix != null) {
                // Some compilers will generate a spurious warning
                // for the integer constants for Integer.MIN_VALUE
                // and Long.MIN_VALUE so we handle them specially.
                if ((suffix.equals("L") && (val == Integer.MIN_VALUE)) ||
                    (suffix.equals("LL") && (val == Long.MIN_VALUE))) {
                    res = "    #undef  " + cn + lineSep
                        + "    #define " + cn
                        + " (" + (val + 1) + suffix + "-1)" + lineSep;
                } else {
                    res = "    #undef  " + cn + lineSep
                        + "    #define " + cn + " "+ exp.toString() + suffix + lineSep;
                }
            }
        }
        return res;
    }

    protected void methodSectionForClass(PrintWriter pw,
                                         ClassDoc clazz, String cname)
        throws ClassNotFoundException {
        String methods = methodDecls(clazz, cname);

        if (methods.length() != 0) {
            pw.println("/* Native method declarations: */" + lineSep);
            pw.println("#ifdef __cplusplus");
            pw.println("extern \"C\" {");
            pw.println("#endif" + lineSep);
            pw.println(methods);
            pw.println("#ifdef __cplusplus");
            pw.println("}");
            pw.println("#endif");
        }
    }

    protected String methodDecls(ClassDoc clazz, String cname)
        throws ClassNotFoundException {

        String res = "";
        for (int i = 0; i < methods.length; i++) {
            MethodDoc method = (MethodDoc)methods[i];
            if (method.isNative())
                res = res + methodDecl(method, clazz, cname);
        }
        return res;
    }

    protected String methodDecl(MethodDoc method,
                                ClassDoc clazz, String cname)
        throws ClassNotFoundException {
        String res = null;

        Type retType = method.returnType();
        String typesig = method.signature();
        TypeSignature newTypeSig = new TypeSignature(root);
        String sig = newTypeSig.getTypeSignature(typesig, retType);
        boolean longName = needLongName(method, clazz);

        if (sig.charAt(0) != '(')
            Util.error("invalid.method.signature", sig);


        res = "JNIEXPORT " + jniType(retType) + " JNICALL" + lineSep + jniMethodName(method, cname, longName)
            + "(JNIEnv *, " + cRcvrDecl(method, cname);
        Parameter[] params = method.parameters();
        Type argTypes[] = new Type[params.length];
        for(int p = 0; p <  params.length; p++){
            argTypes[p] =  params[p].type();
        }

        /* It would have been nice to include the argument names in the
           declaration, but there seems to be a bug in the "BinaryField"
           class, causing the getArguments() method to return "null" for
           most (non-constructor) methods. */
        for (int i = 0; i < argTypes.length; i++)
            res = res + ", " + jniType(argTypes[i]);
        res = res + ");" + lineSep;
        return res;
    }

    protected final boolean needLongName(MethodDoc method,
                                         ClassDoc clazz)
        throws ClassNotFoundException {
        String methodName = method.name();
        for (int i = 0; i < methods.length; i++) {
            MethodDoc memberMethod = (MethodDoc) methods[i];
            if ((memberMethod != method) &&
                memberMethod.isNative() && (methodName == memberMethod.name()))
                return true;
        }
        return false;
    }

    protected final String jniMethodName(MethodDoc method, String cname,
                                         boolean longName) {
        String res = "Java_" + cname + "_" + method.name();

        if (longName) {
            Type mType =  method.returnType();
            Parameter[] params = method.parameters();
            Type argTypes[] = new Type[params.length];
            for(int p = 0; p <  params.length; p++){
                argTypes[p] =  params[p].type();
            }

            res = res + "__";
            for (int i = 0; i < argTypes.length; i++){
                Type t = argTypes[i];
                String tname = t.typeName();
                TypeSignature newTypeSig = new TypeSignature(root);
                String sig = newTypeSig.getTypeSignature(tname);
                res = res + nameToIdentifier(sig);
            }
        }
        return res;
    }

    protected final String jniType(Type t) {
        String elmT =t.typeName();
        if (t.dimension().indexOf("[]") != -1) {
            if(elmT.equals("boolean"))return "jbooleanArray";
            else if(elmT.equals("byte"))return "jbyteArray";
            else if(elmT.equals("char"))return "jcharArray";
            else if(elmT.equals("short"))return "jshortArray";
            else if(elmT.equals("int"))return "jintArray";
            else if(elmT.equals("long"))return "jlongArray";
            else if(elmT.equals("float"))return "jfloatArray";
            else if(elmT.equals("double"))return "jdoubleArray";
            else if((t.dimension().indexOf("[][]") != -1) || (t.asClassDoc() != null))  return "jobjectArray";
        } else {
            if(elmT.equals("void"))return "void";
            else if(elmT.equals("boolean"))return "jboolean";
            else if(elmT.equals("byte"))return "jbyte";
            else if(elmT.equals("char"))return "jchar";
            else if(elmT.equals("short"))return "jshort";
            else if(elmT.equals("int"))return "jint";
            else if(elmT.equals("long"))return "jlong";
            else if(elmT.equals("float"))return "jfloat";
            else if(elmT.equals("double"))return "jdouble";
            else if (t.asClassDoc() != null) {
                if (elmT.equals("String"))
                    return "jstring";
                else if (t.asClassDoc().subclassOf(root.classNamed("java.lang.Class")))
                    return "jclass";
                else
                    return "jobject";
            }
        }
        Util.bug("jni.unknown.type");
        return null; /* dead code. */
    }

    protected String llniType(Type t, boolean handleize, boolean longDoubleOK) {
        String res = null;
        String elmt = t.typeName();
        if (t.dimension().indexOf("[]") != -1) {
            if((t.dimension().indexOf("[][]") != -1)
               || (t.asClassDoc() != null)) res = "IArrayOfRef";
            else if(elmt.equals("boolean")) res =  "IArrayOfBoolean";
            else if(elmt.equals("byte")) res =  "IArrayOfByte";
            else if(elmt.equals("char")) res =  "IArrayOfChar";
            else if(elmt.equals("int")) res =  "IArrayOfInt";
            else if(elmt.equals("long")) res =  "IArrayOfLong";
            else if(elmt.equals("float")) res =  "IArrayOfFloat";
            else if(elmt.equals("double")) res =  "IArrayOfDouble";
            if (!handleize) res = "DEREFERENCED_" + res;
        } else {
            if(elmt.equals("void")) res =  "void";
            else if( (elmt.equals("boolean")) || (elmt.equals("byte"))
                     ||(elmt.equals("char")) || (elmt.equals("short"))
                     || (elmt.equals("int")))   res = "java_int";
            else   if(elmt.equals("long")) res = longDoubleOK
                                               ? "java_long" : "val32 /* java_long */";
            else   if(elmt.equals("float")) res =  "java_float";
            else   if(elmt.equals("double")) res =  res = longDoubleOK
                                                 ? "java_double" : "val32 /* java_double */";
            else if(t.asClassDoc() != null) {
                res = "I" +  mangleClassName(t.asClassDoc().qualifiedName());
                if (!handleize) res = "DEREFERENCED_" + res;
            }
        }
        return res;
    }

    protected final String cRcvrDecl(MemberDoc field, String cname) {
        return (field.isStatic() ? "jclass" : "jobject");
    }

    protected String maskName(String s) {
        return "LLNI_mask(" + s + ")";
    }

    protected String llniFieldName(MemberDoc field) {
        return maskName(field.name());
    }

    protected final boolean isLongOrDouble(Type t) {
        String tc = t.typeName();
        return (tc.equals("long") || tc.equals("double"));
    }

    /* Do unicode to ansi C identifier conversion.
       %%% This may not be right, but should be called more often. */
    protected final String nameToIdentifier(String name) {
        int len = name.length();
        StringBuffer buf = new StringBuffer(len);
        for (int i = 0; i < len; i++) {
            char c = name.charAt(i);
            if (isASCIILetterOrDigit(c))
                buf.append(c);
            else if (c == '/')
                buf.append('_');
            else if (c == '.')
                buf.append('_');
            else if (c == '_')
                buf.append("_1");
            else if (c == ';')
                buf.append("_2");
            else if (c == '[')
                buf.append("_3");
            else
                buf.append("_0" + ((int)c));
        }
        return new String(buf);
    }

    protected final boolean isASCIILetterOrDigit(char c) {
        if (((c >= 'A') && (c <= 'Z')) ||
            ((c >= 'a') && (c <= 'z')) ||
            ((c >= '0') && (c <= '9')))
            return true;
        else
            return false;
    }
}
