// copyright=> http://www.jxml.com/mdsax/License.txt

package com.jxml.quick.util.qjml2java;

import java.io.PrintWriter;
import java.util.ArrayList;

/**
 * This class represents a field or a property in a Java class.
 */
public class ClassField {
    public String name;
    public String classpath;
    public boolean property;
    public boolean newClass = false; // True if class is generated
    public boolean hasAClass; // False if type is int, float, double, boolean
    public String comment = null;
    public String tagName = null; // for XPath
    public boolean attribute = false; // for XPath
    public boolean optional = false;
    public String defaultValue = null;
    public String initializer = null;
    
    // for properties:
    public boolean abst = false;
    public boolean read = true;
    public boolean write = true;
    
   // for a list:
    public boolean typedList = false;
    public String childClass = null;
    public boolean childNewClass = false;
    public String childName = null; // useful when childNewClass = false
    public ArrayList children = null; // of Element
    public ArrayList xchildren = null; // of Element
    
    public ClassField() {
    }
    
    public ClassField(String name, String classpath, boolean property,
                      String initializer, boolean abst, boolean read,
                      boolean write) {
        if (name == null)
            System.err.println("Warning: new ClassField with null name.");
        this.name = name;
        if (classpath == null)
            System.err.println("Warning: no classpath for " + name);
        this.classpath = classpath;
        this.property = property;
        this.hasAClass = (Element.typeToJava(classpath) == null);
        this.initializer = initializer;
        this.abst = abst;
        this.read = read;
        this.write = write;
        //if (isList(classpath) && children == null)
        //    System.err.println("Warning: list with no child for " + name);
    }
    
    public ClassField(String name, String classpath, boolean property,
                      boolean newClass, boolean hasAClass, String comment,
                      String tagName, boolean attribute, boolean optional,
                      String defaultValue, boolean typedList,
                      String childClass, boolean childNewClass,
                      String childName, ArrayList children,
                      ArrayList xchildren) {
        if (name == null)
            System.err.println("Warning: new ClassField with null name. tagName is " + tagName);
        this.name = name;
        if (classpath == null)
            System.err.println("Warning: no classpath for " + name);
        this.classpath = classpath;
        this.property = property;
        this.newClass = newClass;
        this.hasAClass = hasAClass;
        this.comment = comment;
        this.tagName = tagName;
        this.attribute = attribute;
        this.optional = optional;
        this.defaultValue = defaultValue;
        this.typedList = typedList;
        this.childClass = childClass;
        this.childNewClass = childNewClass;
        this.childName = childName;
        this.children = children;
        this.xchildren = xchildren;
        //if (isList(classpath) && children == null)
        //    System.err.println("Warning: list with no child for " + name);
    }
    
    /* adds the field to the Vector, unless there is a conflict... */
    public static void addNewField(ArrayList v, ClassField f) {
        ClassField f2 = null;
        boolean conflict = false;
        for (int i=0; i<v.size() && !conflict; i++) {
            f2 = (ClassField)v.get(i);
            if (f.name.equals(f2.name)) {
                conflict = true;
                break;
            }
        }
        if (!conflict)
            v.add(f);
        else {
            //System.err.println("conflict with " + f.name);
            //System.err.println(f.classpath + " " + f2.classpath);
            if (!f.classpath.equals(f2.classpath))
                f2.classpath = "java.lang.Object";
        }
    }
    
    public void printComment(PrintWriter pw, String comment) {
        final int MAXLINE = 80;
        
        String nl = System.getProperty("line.separator");
        if (comment.indexOf(nl) == -1 && comment.length() < MAXLINE - 10)
            pw.println("    /** " + comment + " */");
        else {
            pw.println("    /**");
            String line;
            int i = 0;
            int j = 0, j2;
            while (j != comment.length()) {
                j = comment.substring(i).indexOf(nl);
                if (j == -1)
                    j = comment.length();
                else
                    j += i;

                if (j - i > MAXLINE - 7) {
                    j2 = comment.substring(i, i + MAXLINE - 7).lastIndexOf(' ');
                    if (j2 != -1)
                        j = j2 + i;
                }
                line = comment.substring(i, j);
                pw.println("     * " + line);
                i = j + 1;
            }
            pw.println("     */");
        }
    }
    
    public void printDeclaration(PrintWriter pw) {
        final int MAXLINE = 80;
        
        String classname = Element.getClassName(classpath);
        String access;
        if (property)
            access = "protected";
        else
            access = "public";
        if (initializer == null) {
            if (classname.equals("ArrayList"))
                initializer = "new ArrayList()";
            else if (defaultValue != null) {
                if (classname.equals("String") || classname.equals("Object"))
                    initializer = "\"" + defaultValue + "\"";
                else if (classname.equals("char"))
                    initializer = "'" + defaultValue + "'";
                else
                    initializer = defaultValue;
            }
        }
        String init;
        if (initializer != null)
            init = " = " + initializer;
        else
            init = "";
        if (comment != null)
            printComment(pw, comment);
        
        pw.println("    " + access + " " + classname + " " + name + init + ";");
        pw.println();
    }
    
    /* name used in the get and set methods for typed lists */
    public static String getGetName(String childClass, String childName,
        boolean childNewClass, String fieldname) {
        String childclassname = Element.getClassName(childClass);
        if (childNewClass)
            return(childclassname);
        else if (childName != null)
            //return(upFirst(childName));
            return(upFirst(childName.toLowerCase())); // bec. my tags are uppercase
        else if (fieldname != null && fieldname.endsWith("s"))
            return(upFirst(fieldname.substring(0, fieldname.length()-1)));
        else
            return(upFirst(fieldname + "Item"));
    }
    
    public String getGetName() {
        return(getGetName(childClass, childName, childNewClass, name));
    }
    
    public static String getGetName(Element el) {
        return(getGetName(el.targetClass, el.tagName, el.newClass, null));
    }
    
    public void printMethod(PrintWriter pw, boolean intf) {
        if (property) {
            String classname = Element.getClassName(classpath);
            if (comment != null)
                printComment(pw, comment);
            if (read) {
                if (intf || abst) {
                    pw.println("    public " + classname + " get" +
                        upFirst(name) + "();");
                    pw.println();
                } else {
                    pw.println("    public " + classname + " get" +
                        upFirst(name) + "() {");
                    pw.println("        return(" + name + ");");
                    pw.println("    }");
                    pw.println();
                }
            }
            if (comment != null)
                printComment(pw, comment);
            if (write) {
                if (intf || abst) {
                    pw.println("    public void set" + upFirst(name) +
                        "(" + classname + " " + name + ");");
                    pw.println();
                } else {
                    pw.println("    public void set" + upFirst(name) +
                        "(" + classname + " " + name + ") {");
                    if (newClass && hasAClass && Element.setParents) {
                        pw.println("        if (" + name + " != null)");
                        pw.println("            " + name + ".setXParent(this);");
                    }
                    pw.println("        this." + name + " = " + name + ";");
                    pw.println("    }");
                    pw.println();
                }
            }
        }
    }
    
    public void printMoreMethods(PrintWriter pw, boolean intf) {
        if (typedList) {
            String fieldtype = Element.getClassName(childClass);
            String getname = getGetName();
            String elem = loFirst(getname);
            if (read) {
                if (intf || abst) {
                    pw.println("    public "+fieldtype+" get" + getname +
                        "(int i);");
                    pw.println();
                } else {
                    pw.println("    public "+fieldtype+" get" +
                        getname + "(int i) {");
                    pw.println("        return(("+fieldtype+")"+name+".get(i));");
                    pw.println("    }");
                    pw.println();
                }
            }
            if (write) {
                if (intf || abst) {
                    pw.println("    public void set" + getname +
                        "(int i, "+fieldtype+" "+elem+");");
                    pw.println();
                } else {
                    pw.println("    public void set" + getname +
                        "(int i, "+fieldtype+" "+elem+") {");
                    if (childNewClass && Element.setParents) {
                        pw.println("        if (" + elem + " != null)");
                        pw.println("            " + elem + ".setXParent(this);");
                    }
                    pw.println("        "+name+".set(i, "+elem+");");
                    pw.println("    }");
                    pw.println();
                }
            }
            if (read) {
                if (intf || abst) {
                    pw.println("    public int " + elem + "Count();");
                    pw.println();
                } else {
                    pw.println("    public int " + elem + "Count() {");
                    pw.println("        return("+name+".size());");
                    pw.println("    }");
                    pw.println();
                }
            }
            if (write) {
                if (intf || abst) {
                    pw.println("    public void add" + getname +
                        "("+fieldtype+" "+elem+");");
                    pw.println();
                } else {
                    pw.println("    public void add" + getname +
                        "("+fieldtype+" "+elem+") {");
                    if (childNewClass && Element.setParents) {
                        pw.println("        if (" + elem + " != null)");
                        pw.println("            " + elem + ".setXParent(this);");
                    }
                    pw.println("        "+name+".add("+elem+");");
                    pw.println("    }");
                    pw.println();
                }
            }
        } else if (write && isList(classpath) && children != null) {
            for (int i=0; i<children.size(); i++) {
                Element el = (Element)children.get(i);
                String fieldtype = Element.getClassName(el.targetClass);
                String getname = getGetName(el);
                String elem = loFirst(getname);
                
                if (intf || abst) {
                    pw.println("    public void add" + getname +
                        "("+fieldtype+" "+elem+");");
                    pw.println();
                } else {
                    pw.println("    public void add" + getname +
                        "("+fieldtype+" "+elem+") {");
                    if (el.newClass && Element.setParents) {
                        pw.println("        if (" + elem + " != null)");
                        pw.println("            " + elem + ".setXParent(this);");
                    }
                    pw.println("        "+name+".add("+elem+");");
                    pw.println("    }");
                    pw.println();
                }
            }
        }
    }
    
    public void printClone(PrintWriter pw, String newobjname) {
        if (isList(classpath)) {
            String newlistname = "new" + name;
            if (property)
                pw.println("        " +listName(classpath)+ " " +newlistname+
                    " = " + newobjname + ".get" + upFirst(name) + "();");
            else
                pw.println("        " +listName(classpath)+ " " + newlistname +
                    " = " + listInit(classpath) + ";");
            if (typedList && childNewClass) {
                pw.println("        for (int i=0; i<" + name +
                    ".size(); i++)");
                pw.println("            " +newlistname+ ".add(get" +
                    getGetName() + "(i).clone());");
            } else if (typedList) {
                pw.println("        for (int i=0; i<" + name +
                    ".size(); i++)");
                pw.println("            " +newlistname+ ".add(get" +
                    getGetName() + "(i));");
            } else {
            // in this case, we don't even know if the children
            // have a clone method !
                pw.println("        for (int i=0; i<" + name +
                    ".size(); i++) {");
                pw.println("            if (" + name + ".get(i) instanceof QGBaseIntf)");
                pw.println("                " +newlistname+ ".add(((QGBaseIntf)" + name + ".get(i)).clone());");
                pw.println("            else");
                pw.println("                " +newlistname+ ".add(" + name + ".get(i));");
                pw.println("        }");
            }
            if (!property)
                pw.println("        " + newobjname + "." + name +
                    " = " +newlistname+ ";");
        } else {
            String fn2;
            String ind = "        ";
            if (newClass || classpath.equals("java.lang.Object")) {
                pw.println("        if (" + name + " != null) {");
                ind += "    ";
            }
            if (classpath.equals("java.lang.Object")) {
                fn2 = "new" + name;
                pw.println(ind + "Object " + fn2 + ";");
                if (!newClass || !hasAClass)
                    pw.println(ind + fn2 + " = " + name + ";");
                else {
                    pw.println(ind + "if (" + name + " instanceof QGBaseIntf)");
                    pw.println(ind + "    " + fn2 + " = ((QGBaseIntf)" + name + ").clone();");
                    pw.println(ind + "else");
                    pw.println(ind + "    " + fn2 + " = " + name + ";");
                }
            } else {
                if (!newClass)
                    fn2 = name;
                else
                    fn2 = "(" + Element.getClassName(classpath) + ")" +
                        name + ".clone()";
            }
            if (property)
                pw.println(ind + newobjname + ".set" +
                    upFirst(name) + "(" + fn2 + ");");
            else
                pw.println(ind + newobjname + "." + name +
                    " = " + fn2 + ";");
            if (newClass || classpath.equals("java.lang.Object")) {
                ind = "        ";
                pw.println(ind + "} else");
                fn2 = "null";
                if (property)
                    pw.println(ind + "    " + newobjname + ".set" +
                        upFirst(name) + "(null);");
                else
                    pw.println(ind + "    " + newobjname + "." + name +
                        " = null;");
            }
        }
    }
    
    public void printPrint(PrintWriter pw) {
        if (isList(classpath)) {
            if (typedList && childNewClass) {
                pw.println("        for (int i=0; i<" + name +
                    ".size(); i++)");
                pw.println("            get" + getGetName() +
                    "(i).print(pw, indent + \"    \");");
            } else if (typedList) {
                pw.println("        for (int i=0; i<" + name +
                    ".size(); i++)");
                pw.println("            pw.println(indent + \"" + name +
                    "[\"+i+\"] = \" + get" + getGetName() + "(i));");
            } else {
            // in this case, we don't even know if the children
            // have a print method !
                pw.println("        for (int i=0; i<" + name +
                    ".size(); i++) {");
                pw.println("            if (" + name + ".get(i) instanceof QGBaseIntf)");
                pw.println("                ((QGBaseIntf)" + name + ".get(i)).print(pw, indent + \"    \");");
                pw.println("            else");
                pw.println("                pw.println(indent + \"" + name +
                    "[\"+i+\"] = \" + " + name + ".get(i));");
                pw.println("        }");
            }
        } else {
            String fn2;
            String ind = "        ";
            if (hasAClass) {
                pw.println("        if (" + name + " != null) {");
                ind += "    ";
            }
            if (classpath.equals("java.lang.Object")) {
                if (!newClass || !hasAClass)
                    pw.println(ind + "pw.println(indent + \"" + name + " = \" + " + name + ");");
                else {
                    pw.println(ind + "if (" + name + " instanceof QGBaseIntf)");
                    pw.println(ind + "    ((QGBaseIntf)" + name + ").print(pw, indent + \"    \");");
                    pw.println(ind + "else");
                    pw.println(ind + "    pw.println(indent + \"" + name +
                    " = \" + " + name + ");");
                }
            } else {
                if (newClass && hasAClass)
                    pw.println(ind + name + ".print(pw, indent + \"    \");");
                else
                    pw.println(ind + "pw.println(indent + \"" + name + " = \" + " + name + ");");
            }
            if (hasAClass) {
                ind = "        ";
                pw.println(ind + "} else");
                pw.println(ind + "    pw.println(indent + \"" + name + " = null\");");
            }
        }
    }
    
    protected String childTag(int i) {
        return(((Element)xchildren.get(i)).tagName);
    }
    
    protected String childClass(int i) {
        return(((Element)xchildren.get(i)).targetClass);
    }
    
    public void printgetXChildren(PrintWriter pw) {
        if (typedList && xchildren != null && xchildren.size() == 1) {
            pw.println("        if (childTagName.equals(\"" + childTag(0) + "\"))");
            pw.println("            r.addAll(" + name + ");");
        } else if (isList(classpath) && xchildren != null) {
            pw.println("        for (int i=0; i<" + name +
                ".size(); i++) {");
            for (int i=0; i<xchildren.size(); i++) {
                pw.println("            if (" + name + ".get(i) instanceof " +
                    Element.getClassName(childClass(i)) +
                    " && childTagName.equals(\"" + childTag(i) + "\"))");
                pw.println("                r.add(" + name + ".get(i));");
            }
            pw.println("        }");
        } else if (tagName != null) {
            pw.println("        if (childTagName.equals(\"" + tagName + "\"))");
            if (newClass || classpath.endsWith("String") ||
                classpath.endsWith("Object") || hasAClass) {
                pw.println("            if (" + name + " != null)");
                pw.println("                r.add(" + name + ");");
            } else
                pw.println("            r.add(new " +
                    classpath.substring(classpath.lastIndexOf('.')+1) +
                    "(" + name + "));");
        } else if (xchildren != null) {
            pw.print("        if (");
            for (int i=0; i<xchildren.size(); i++) {
                if (i != 0) {
                    pw.println();
                    pw.print("            || ");
                }
                pw.print("childTagName.equals(\"" + childTag(i) + "\")");
            }
            pw.println(")");
            pw.println("            if (" + name + " != null)");
            pw.println("                r.add(" + name + ");");
        }
    }
    
    public void printgetXAttribute(PrintWriter pw) {
        if (typedList) {
            pw.println("        if (attName.equals(\"" + childName + "\"))");
            pw.println("            return(" + name + ");");
        } else if (tagName != null) {
            pw.println("        if (attName.equals(\"" + tagName + "\"))");
            if (newClass || classpath.endsWith("String") ||
                classpath.endsWith("Object") || hasAClass)
                pw.println("            return(" + name + ");");
            else
                pw.println("            return(new " +
                    classpath.substring(classpath.lastIndexOf('.')+1) +
                    "(" + name + "));");
        }
    }
    
    public void printgetAllXChildren(PrintWriter pw) {
        if (isList(classpath))
            pw.println("        r.addAll(" + name + ");");
        else {
            if (newClass || classpath.endsWith("String") ||
                classpath.endsWith("Object") || hasAClass) {
                pw.println("        if (" + name + " != null)");
                pw.println("            r.add(" + name + ");");
            } else
                pw.println("        r.add(new " +
                    classpath.substring(classpath.lastIndexOf('.')+1) +
                    "(" + name + "));");
        }
    }
    
    private static String upFirst(String s) {
        return(s.substring(0,1).toUpperCase() + s.substring(1));
    }
    
    private static String loFirst(String s) {
        return(s.substring(0,1).toLowerCase() + s.substring(1));
    }
    
    private static boolean isList(String classpath) {
        if (classpath == null)
            return(false);
        if (classpath.equals("java.util.ArrayList"))
            return(true);
        if (classpath.equals("java.util.List"))
            return(true);
        if (classpath.equals("java.util.Vector"))
            return(true);
        return(false);
    }
    
    /** default initializer for a list */
    private static String listInit(String classpath) {
        if (classpath == null)
            return(null);
        if (classpath.equals("java.util.ArrayList"))
            return("new ArrayList()");
        if (classpath.equals("java.util.List"))
            return("new ArrayList()");
        if (classpath.equals("java.util.Vector"))
            return("new Vector()");
        return(null);
    }
    
    private static String listName(String classpath) {
        if (classpath == null)
            return(null);
        int ind = classpath.lastIndexOf('.');
        if (ind == -1)
            return(classpath);
        else
            return(classpath.substring(ind+1));
    }
}
