package edu.stanford.smi.protegex.owl.javacode;

import edu.stanford.smi.protegex.owl.model.*;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * A class that can create Java interfaces in the Protege-OWL format from an OWL model.
 *
 * @author Holger Knublauch  <holger@knublauch.com>
 */
public class JavaCodeGenerator {

    private OWLModel owlModel;

    private JavaCodeGeneratorOptions options;

    private static Map primitiveTypesMap = new HashMap();


    static {
        primitiveTypesMap.put(Integer.class, int.class);
        primitiveTypesMap.put(Float.class, float.class);
        primitiveTypesMap.put(Boolean.class, boolean.class);
    }


    public JavaCodeGenerator(OWLModel owlModel, JavaCodeGeneratorOptions options) {
        this.owlModel = owlModel;
        this.options = options;
        File folder = options.getOutputFolder();
        if (folder != null && !folder.exists()) {
            folder.mkdirs();
        }
        String pack = options.getPackage();
        if (pack != null) {
            pack = pack.replace('.', '/');
            File file = folder == null ? new File(pack) : new File(folder, pack);
            file.mkdirs();
            File f = new File(file, "impl");
            f.mkdirs();
        }
        else {
            File file = folder == null ? new File("impl") : new File(folder, "impl");
            file.mkdirs();
        }
    }


    public void createAll() throws IOException {
        for (Iterator it = owlModel.getUserDefinedRDFSNamedClasses().iterator(); it.hasNext();) {
            RDFSNamedClass aClass = (RDFSNamedClass) it.next();
            createInterface(aClass);
            createImplementation(aClass);
        }
        createFactoryClass();
    }


    public void createFactoryClass() throws IOException {
        String localName = options.getFactoryClassName();
        if (localName != null && localName.length() > 0) {
            File file = getInterfaceFile(localName);
            FileWriter fileWriter = new FileWriter(file);
            PrintWriter printWriter = new PrintWriter(fileWriter);
            printFactoryClass(printWriter);
            fileWriter.close();
        }
    }


    private void printFactoryClass(PrintWriter printWriter) {
        printInterfacePackageStatement(printWriter);
        printWriter.println();
        printWriter.println("import edu.stanford.smi.protegex.owl.model.*;");
        printWriter.println();
        printWriter.println("/**");
        printWriter.println(" * Generated by Protege-OWL  (http://protege.stanford.edu/plugins/owl).");
        printWriter.println(" *");
        printWriter.println(" * @version generated on " + new Date());
        printWriter.println(" */");
        printWriter.println("public class " + options.getFactoryClassName() + " {");
        printWriter.println();
        printWriter.println("    private OWLModel owlModel;");
        printWriter.println();
        printWriter.println();
        printWriter.println("    public " + options.getFactoryClassName() + "(OWLModel owlModel) {");
        printWriter.println("        this.owlModel = owlModel;");
        printWriter.println("    }");
        for (Iterator it = owlModel.getUserDefinedRDFSNamedClasses().iterator(); it.hasNext();) {
            RDFSNamedClass cls = (RDFSNamedClass) it.next();
            String interfaceName = getInterfaceName(cls);
            printWriter.println();
            printWriter.println();
            printWriter.println("    public RDFSNamedClass get" + interfaceName + "Class() {");
            printWriter.println("        final String uri = \"" + cls.getURI() + "\";");
            printWriter.println("        final String name = owlModel.getResourceNameForURI(uri);");
            printWriter.println("        return owlModel.getRDFSNamedClass(name);");
            printWriter.println("    }");
            printWriter.println();
            printWriter.println("    public " + interfaceName + " create" + interfaceName + "(String name) {");
            printWriter.println("        final RDFSNamedClass cls = get" + interfaceName + "Class();");
            printWriter.println("        return (" + interfaceName + ") cls.createInstance(name).as(" + interfaceName + ".class);");
            printWriter.println("    }");
            printWriter.println();
            printWriter.println("    public " + interfaceName + " get" + interfaceName + "(String name) {");
            printWriter.println("        return (" + interfaceName + ") owlModel.getRDFResource(name).as(" + interfaceName + ".class);");
            printWriter.println("    }");
        }
        printWriter.println("}");
    }


    public void createImplementation(RDFSNamedClass cls) throws IOException {

        File file = getImplementationFile(cls);
        FileWriter fileWriter = new FileWriter(file);
        PrintWriter printWriter = new PrintWriter(fileWriter);
        printImplementationCode(printWriter, cls);
        fileWriter.close();

        if (options.getAbstractMode()) {
            createUserImplementation(cls);
        }
    }


    public void createUserImplementation(RDFSNamedClass cls) throws IOException {
        String localName = getImplementationName(cls);
        File file = getImplementationFile(localName);
        if (!file.exists()) {
            FileWriter fileWriter = new FileWriter(file);
            PrintWriter printWriter = new PrintWriter(fileWriter);
            printUserImplementationCode(printWriter, cls);
            fileWriter.close();
        }
    }


    public void printImplementationCode(PrintWriter printWriter, RDFSNamedClass aClass) {
        RDFSClassCode code = new RDFSClassCode(aClass);
        if (options.getPackage() != null) {
            printWriter.println("package " + options.getPackage() + ".impl;");
        }
        else {
            printWriter.println("package impl;");
        }
        printWriter.println();
        printWriter.println("import edu.stanford.smi.protege.model.FrameID;");
        printWriter.println("import edu.stanford.smi.protegex.owl.model.*;");
        printWriter.println("import edu.stanford.smi.protegex.owl.model.impl.*;");
        perhapsAddImportJavaUtil(code, printWriter);
        String pack = options.getPackage();
        if (pack != null) {
            printWriter.println("import " + pack + ".*;");
            printWriter.println();
        }
        printWriter.println("/**");
        printWriter.println(" * Generated by Protege-OWL  (http://protege.stanford.edu/plugins/owl).");
        printWriter.println(" * Source OWL Class: " + aClass.getURI());
        printWriter.println(" *");
        printWriter.println(" * @version generated on " + new Date());
        printWriter.println(" */");
        String implementationName = getImplementationNamePossiblyAbstract(aClass);
        printWriter.println("public class " + implementationName + getImplementationExtendsCode(aClass));
        printWriter.println("         implements " + getInterfaceNamePossiblyAbstract(aClass) + " {");
        printConstructors(printWriter, implementationName);
        for (Iterator it = code.getPropertyCodes().iterator(); it.hasNext();) {
            printWriter.println();
            printWriter.println();
            RDFPropertyAtClassCode propertyCode = (RDFPropertyAtClassCode) it.next();
            printImplementationPropertyCode(printWriter, propertyCode);
        }
        printWriter.println("}");
    }


    public void printUserImplementationCode(PrintWriter printWriter, RDFSNamedClass cls) {
        if (options.getPackage() != null) {
            printWriter.println("package " + options.getPackage() + ".impl;");
        }
        else {
            printWriter.println("package impl;");
        }
        printWriter.println();
        printWriter.println("import edu.stanford.smi.protege.model.FrameID;");
        printWriter.println("import edu.stanford.smi.protegex.owl.model.*;");
        printWriter.println("import edu.stanford.smi.protegex.owl.model.impl.*;");
        String pack = options.getPackage();
        if (pack != null) {
            printWriter.println("import " + pack + ".*;");
            printWriter.println();
        }
        String implementationName = getImplementationName(cls);
        printWriter.println("public class " + implementationName + " extends " + getImplementationNamePossiblyAbstract(cls));
        printWriter.println("         implements " + getInterfaceName(cls) + " {");
        printConstructors(printWriter, implementationName);
        printWriter.println("}");
    }


    private void printConstructors(PrintWriter printWriter, String implementationName) {
        printWriter.println();
        printWriter.println("    public " + implementationName + "(OWLModel owlModel, FrameID id) {");
        printWriter.println("        super(owlModel, id);");
        printWriter.println("    }");
        printWriter.println();
        printWriter.println();
        printWriter.println("    public " + implementationName + "() {");
        printWriter.println("    }");
    }


    private String getImplementationExtendsCode(RDFSNamedClass aClass) {
        String str = " extends ";
        String base = getBaseImplementation(aClass);
        if (base == null) {
            return str + "DefaultRDFIndividual";
        }
        else {
            return str + base;
        }
    }


    public void createInterface(RDFSNamedClass aClass) throws IOException {

        File baseFile = getInterfaceFile(aClass);
        FileWriter fileWriter = new FileWriter(baseFile);
        PrintWriter printWriter = new PrintWriter(fileWriter);
        printInterfaceCode(printWriter, aClass);
        fileWriter.close();

        if (options.getAbstractMode()) {
            createUserInterface(aClass);
        }
    }


    private void createUserInterface(RDFSNamedClass aClass) throws IOException {
        String localName = getInterfaceName(aClass);
        File file = getInterfaceFile(localName);
        if (!file.exists()) {
            FileWriter fileWriter = new FileWriter(file);
            PrintWriter printWriter = new PrintWriter(fileWriter);
            printUserInterfaceCode(printWriter, aClass);
            fileWriter.close();
        }
    }


    public void printInterfaceCode(PrintWriter printWriter, RDFSNamedClass aClass) {
        RDFSClassCode code = new RDFSClassCode(aClass);
        printInterfacePackageStatement(printWriter);
        printWriter.println("import edu.stanford.smi.protegex.owl.model.*;");
        printWriter.println();
        perhapsAddImportJavaUtil(code, printWriter);
        printWriter.println("/**");
        printWriter.println(" * Generated by Protege-OWL  (http://protege.stanford.edu/plugins/owl).");
        printWriter.println(" * Source OWL Class: " + aClass.getURI());
        printWriter.println(" *");
        printWriter.println(" * @version generated on " + new Date());
        printWriter.println(" */");
        printWriter.println("public interface " + getInterfaceNamePossiblyAbstract(aClass) + getInterfaceExtendsCode(aClass) + " {");
        for (Iterator it = code.getPropertyCodes().iterator(); it.hasNext();) {
            RDFPropertyAtClassCode propertyCode = (RDFPropertyAtClassCode) it.next();
            createInterfacePropertyCode(printWriter, propertyCode);
            if (it.hasNext()) {
                printWriter.println();
            }
        }
        printWriter.println("}");
    }


    private void printInterfacePackageStatement(PrintWriter printWriter) {
        if (options.getPackage() != null) {
            printWriter.println("package " + options.getPackage() + ";");
            printWriter.println();
        }
    }


    public void printUserInterfaceCode(PrintWriter printWriter, RDFSNamedClass cls) {
        printInterfacePackageStatement(printWriter);
        printWriter.println("public interface " + getInterfaceName(cls) + " extends " + getInterfaceNamePossiblyAbstract(cls) + " {");
        printWriter.println("}");
    }


    private String getInterfaceExtendsCode(RDFSNamedClass aClass) {
        String str = " extends ";
        String base = getBaseInterface(aClass);
        if (base == null) {
            return str + "RDFIndividual";
        }
        else {
            return str + base;
        }
    }


    public void printImplementationPropertyCode(PrintWriter printWriter, RDFPropertyAtClassCode propertyCode) {
        RDFProperty property = propertyCode.getRDFProperty();
        String name = propertyCode.getUpperCaseJavaName();
        String javaTypeName = getJavaTypeName(propertyCode);
        String simpleJavaTypeName = propertyCode.getJavaType();
        boolean multiple = propertyCode.isMultiple();

        String getProperty = "get" + name + "Property()";

        printWriter.println();
        printWriter.println("    // Property " + property.getURI());
        printWriter.println();
        printWriter.println("    public " + javaTypeName + " get" + name + "() {");
        if (multiple) {
            if (options.getSetMode()) {
                if (propertyCode.isCustomType()) {
                    printWriter.println("        return new HashSet(getPropertyValuesAs(" + getProperty + ", " + simpleJavaTypeName + ".class));");
                }
                else {
                    printWriter.println("        return new HashSet(getPropertyValues(" + getProperty + "));");
                }
            }
            else {
                if (propertyCode.isCustomType()) {
                    printWriter.println("        return getPropertyValuesAs(" + getProperty + ", " + simpleJavaTypeName + ".class);");
                }
                else {
                    printWriter.println("        return getPropertyValues(" + getProperty + ");");
                }
            }
        }
        else {
            if (propertyCode.isPrimitive()) {
                String x = "" + Character.toUpperCase(javaTypeName.charAt(0)) + javaTypeName.substring(1);
                printWriter.println("        return getPropertyValueLiteral(" + getProperty + ").get" + x + "();");
            }
            else if (propertyCode.isCustomType()) {
                printWriter.println("        return (" + javaTypeName + ") getPropertyValueAs(" + getProperty +
                        ", " + simpleJavaTypeName + ".class);");
            }
            else {
                printWriter.println("        return (" + javaTypeName + ") getPropertyValue(" + getProperty + ");");
            }
        }
        printWriter.println("    }");

        printWriter.println();
        printWriter.println();
        printWriter.println("    public RDFProperty get" + name + "Property() {");
        printWriter.println("        final String uri = \"" + property.getURI() + "\";");
        printWriter.println("        final String name = getOWLModel().getResourceNameForURI(uri);");
        printWriter.println("        return getOWLModel().getRDFProperty(name);");
        printWriter.println("    }");

        printWriter.println();
        printWriter.println();
        printWriter.println("    public boolean has" + name + "() {");
        printWriter.println("        return getPropertyValueCount(" + getProperty + ") > 0;");
        printWriter.println("    }");

        String varName = "new" + name;
        if (multiple) {
            printWriter.println();
            printWriter.println();
            printWriter.println("    public Iterator list" + name + "() {");
            if (propertyCode.isCustomType()) {
                printWriter.println("        return listPropertyValuesAs(" + getProperty + ", " + simpleJavaTypeName + ".class);");
            }
            else {
                printWriter.println("        return listPropertyValues(" + getProperty + ");");
            }
            printWriter.println("    }");

            printWriter.println();
            printWriter.println();
            printWriter.println("    public void add" + name + "(" + simpleJavaTypeName + " " + varName + ") {");
            printWriter.println("        addPropertyValue(" + getProperty + ", " + varName + ");");
            printWriter.println("    }");

            printWriter.println();
            printWriter.println();
            String oldVarName = "old" + name;
            printWriter.println("    public void remove" + name + "(" + simpleJavaTypeName + " " + oldVarName + ") {");
            printWriter.println("        removePropertyValue(" + getProperty + ", " + oldVarName + ");");
            printWriter.println("    }");
        }

        printWriter.println();
        printWriter.println();
        printWriter.println("    public void set" + name + "(" + javaTypeName + " " + varName + ") {");
        if (multiple) {
            printWriter.println("        setPropertyValues(" + getProperty + ", " + varName + ");");
        }
        else {
            if (propertyCode.isPrimitive()) {
                String t = null;
                if ("int".equals(javaTypeName)) {
                    t = "Integer";
                }
                else if ("boolean".equals(javaTypeName)) {
                    t = "Boolean";
                }
                else {
                    t = "Float";
                }
                printWriter.println("        setPropertyValue(" + getProperty + ", new " + t + "(" + varName + "));");
            }
            else {
                printWriter.println("        setPropertyValue(" + getProperty + ", " + varName + ");");
            }
        }
        printWriter.println("    }");
    }


    public void createInterfacePropertyCode(PrintWriter printWriter, RDFPropertyAtClassCode propertyCode) {
        RDFProperty property = propertyCode.getRDFProperty();
        String name = propertyCode.getUpperCaseJavaName();
        String javaTypeName = getJavaTypeName(propertyCode);

        printWriter.println();
        printWriter.println("    // Property " + property.getURI());
        printWriter.println();
        printWriter.println("    " + javaTypeName + " get" + name + "();");

        printWriter.println();
        printWriter.println("    RDFProperty get" + name + "Property();");

        printWriter.println();
        printWriter.println("    boolean has" + name + "();");

        if (propertyCode.isMultiple()) {
            String simpleJavaTypeName = propertyCode.getJavaType();
            printWriter.println();
            printWriter.println("    Iterator list" + name + "();");
            printWriter.println();
            printWriter.println("    void add" + name + "(" + simpleJavaTypeName + " new" + name + ");");
            printWriter.println();
            printWriter.println("    void remove" + name + "(" + simpleJavaTypeName + " old" + name + ");");
        }

        printWriter.println();
        printWriter.println("    void set" + name + "(" + javaTypeName + " new" + name + ");");
    }


    private String getJavaTypeName(RDFPropertyAtClassCode propertyCode) {
        if (propertyCode.isMultiple()) {
            return options.getSetMode() ? "Set" : "Collection";
        }
        else {
            return propertyCode.getJavaType();
        }
    }


    public String getBaseImplementation(RDFSNamedClass aClass) {
        for (Iterator it = aClass.getSuperclasses(false).iterator(); it.hasNext();) {
            RDFSClass superclass = (RDFSClass) it.next();
            String name = getSystemJavaClass(superclass);
            if (name != null) {
                return "Default" + name;
            }
            if (superclass instanceof RDFSNamedClass && !owlModel.getOWLThingClass().equals(superclass)) {
                return getImplementationName((RDFSNamedClass) superclass);
            }
        }
        return null;
    }


    public String getBaseInterface(RDFSNamedClass cls) {
        for (Iterator it = cls.getSuperclasses(false).iterator(); it.hasNext();) {
            RDFSClass superclass = (RDFSClass) it.next();
            String name = getSystemJavaClass(superclass);
            if (name != null) {
                return name;
            }
            else if (superclass instanceof RDFSNamedClass && !owlModel.getOWLThingClass().equals(superclass)) {
                return getInterfaceName((RDFSNamedClass) superclass);
            }
        }
        return cls.getProtegeType() instanceof OWLNamedClass ? "OWLIndividual" : "RDFIndividual";
    }


    public File getImplementationFile(RDFSNamedClass aClass) {
        String localName = getImplementationNamePossiblyAbstract(aClass);
        return getImplementationFile(localName);
    }


    private File getImplementationFile(String localName) {
        String pack = options.getPackage();
        if (pack != null) {
            pack = pack.replace('.', '/') + "/";
        }
        else {
            pack = "";
        }
        return new File(options.getOutputFolder(), pack + "impl/" + localName + ".java");
    }


    public String getImplementationName(RDFSNamedClass aClass) {
        return "Default" + getInterfaceName(aClass);
    }


    public String getImplementationNamePossiblyAbstract(RDFSNamedClass aClass) {
        return "Default" + getInterfaceNamePossiblyAbstract(aClass);
    }


    public File getInterfaceFile(RDFSNamedClass aClass) {
        String localName = getInterfaceNamePossiblyAbstract(aClass);
        return getInterfaceFile(localName);
    }


    private File getInterfaceFile(String localName) {
        String pack = options.getPackage();
        if (pack != null) {
            pack = pack.replace('.', '/') + "/";
        }
        else {
            pack = "";
        }
        return new File(options.getOutputFolder(), pack + localName + ".java");
    }


    public String getInterfaceNamePossiblyAbstract(RDFSNamedClass aClass) {
        String str = new RDFSClassCode(aClass).getJavaName();
        if (options.getAbstractMode()) {
            str += "_";
        }
        return str;
    }


    public String getInterfaceName(RDFSNamedClass aClass) {
        return new RDFSClassCode(aClass).getJavaName();
    }


    private String getSystemJavaClass(RDFSClass superclass) {
        if (superclass.equals(owlModel.getOWLObjectPropertyClass())) {
            return "OWLObjectProperty";
        }
        else if (superclass.equals(owlModel.getOWLDatatypePropertyClass())) {
            return "OWLDatatypeProperty";
        }
        else if (superclass.equals(owlModel.getRDFPropertyClass())) {
            return "RDFProperty";
        }
        else if (superclass.equals(owlModel.getOWLNamedClassClass())) {
            return "OWLNamedClass";
        }
        else if (superclass.equals(owlModel.getRDFSNamedClassClass())) {
            return "RDFSNamedClass";
        }
        return null;
    }


    public static String getValidJavaName(String name) {
        for (int i = 1; i < name.length(); i++) {
            char c = name.charAt(i);
            if (!Character.isJavaIdentifierPart(c)) {
                name = name.replace(c, '_');
            }
        }
        return name;
    }


    private void perhapsAddImportJavaUtil(RDFSClassCode code, PrintWriter printWriter) {
        for (Iterator it = code.getPropertyCodes().iterator(); it.hasNext();) {
            RDFPropertyAtClassCode propertyAtClassCode = (RDFPropertyAtClassCode) it.next();
            if (propertyAtClassCode.isMultiple()) {
                printWriter.println("import java.util.*;");
                printWriter.println();
                return;
            }
        }
    }
}
