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

import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Slot;
import edu.stanford.smi.protegex.owl.model.OWLModel;
import edu.stanford.smi.protegex.owl.model.RDFProperty;
import edu.stanford.smi.protegex.owl.model.RDFSNamedClass;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;

/**
 * A class that can create Jave interfaces in the EMF format from an OWL model.
 *
 * @author Holger Knublauch  <holger@knublauch.com>
 */
public class EMFGenerator {

    private OWLModel owlModel;

    private EMFGeneratorOptions options;


    public EMFGenerator(OWLModel owlModel, EMFGeneratorOptions 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();
        }
    }


    public void createAllInterfaces() throws IOException {
        for (Iterator it = owlModel.getUserDefinedOWLNamedClasses().iterator(); it.hasNext();) {
            RDFSNamedClass aClass = (RDFSNamedClass) it.next();
            createInterface(aClass);
        }
    }


    public void createInterface(RDFSNamedClass aClass) throws IOException {
        File file = getInterfaceFile(aClass);
        FileWriter fileWriter = new FileWriter(file);
        PrintWriter printWriter = new PrintWriter(fileWriter);
        createInterfaceCode(printWriter, aClass);
        fileWriter.close();
    }


    public void createInterfaceCode(PrintWriter printWriter, RDFSNamedClass aClass) {
        if (options.getPackage() != null) {
            printWriter.println("package " + options.getPackage() + ";");
            printWriter.println();
        }
        printWriter.println("/**");
        printWriter.println(" * Generated by the EMFGenerator from the Protege OWL Plugin (http://protege.stanford.edu).");
        printWriter.println(" * Source OWL Class: " + aClass.getURI());
        printWriter.println(" * @version generated on " + new Date());
        printWriter.println(" * @model");
        printWriter.println(" */");
        printWriter.println("public interface " + getInterfaceName(aClass) + getExtendsCode(aClass) + " {");
        for (Iterator it = aClass.getUnionDomainProperties().iterator(); it.hasNext();) {
            Slot slot = (Slot) it.next();
            if (slot instanceof RDFProperty) {
                createSlotCode(printWriter, aClass, (RDFProperty) slot);
            }
        }
        printWriter.println("}");
    }


    private String getExtendsCode(RDFSNamedClass aClass) {
        String str = "";
        Iterator it = getBaseInterfaces(aClass).iterator();
        if (it.hasNext()) {
            str += " extends ";
            while (it.hasNext()) {
                String baseInterface = (String) it.next();
                str += baseInterface;
                if (it.hasNext()) {
                    str += ", ";
                }
            }
        }
        return str;
    }


    public void createSlotCode(PrintWriter printWriter, RDFSNamedClass aClass, RDFProperty property) {
        String name = getValidJavaName(property.getName());
        if (name.length() > 1) {
            name = Character.toUpperCase(name.charAt(0)) + name.substring(1);
        }
        else {
            name = name.toUpperCase();
        }
        Class javaType = ((Cls) aClass).getTemplateSlotValueType(property).getJavaType();
        String javaTypeName = null;
        String modelAnnotations = null;
        if (((Cls) aClass).getTemplateSlotAllowsMultipleValues(property)) {
            javaTypeName = "java.util.List";
            if (!((Cls) aClass).getTemplateSlotAllowedClses(property).isEmpty()) {
                RDFSNamedClass range = (RDFSNamedClass) ((Cls) aClass).getTemplateSlotAllowedClses(property).iterator().next();
                modelAnnotations = " type=\"" + getInterfaceName(range) + "\"";
            }
        }
        else {
            javaTypeName = javaType.toString();
            int index = javaTypeName.lastIndexOf('.');
            if (index > 0) {
                javaTypeName = javaTypeName.substring(index + 1);
            }
        }
        printWriter.println();
        printWriter.println("    /**");
        printWriter.println("     * Generated from property #" + property.getLocalName());
        printWriter.println("     * @model" + modelAnnotations);
        printWriter.println("     */");
        printWriter.println("    " + javaTypeName + " get" + name + "();");
        //printWriter.println();
        //printWriter.println("    /**");
        //printWriter.println("     * Generated from property #" + property.getLocalName());
        //printWriter.println("     * @model" + modelAnnotations);
        //printWriter.println("     */");
        //printWriter.println("    void set" + name + "(" + javaTypeName + " new" + name + ");");
    }


    public Collection getBaseInterfaces(RDFSNamedClass aClass) {
        Collection results = new ArrayList();
        for (Iterator it = aClass.getSuperclasses(false).iterator(); it.hasNext();) {
            Cls superCls = (Cls) it.next();
            if (superCls instanceof RDFSNamedClass && !owlModel.getOWLThingClass().equals(superCls)) {
                results.add(getInterfaceName((RDFSNamedClass) superCls));
            }
        }
        return results;
    }


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


    public String getInterfaceName(RDFSNamedClass aClass) {
        String name = aClass.getName();
        return getValidJavaName(name);
    }


    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;
    }
}
