/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License");  you may not use this file except in 
 * compliance with the License.  You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is Protege-2000.
 *
 * The Initial Developer of the Original Code is Stanford University. Portions
 * created by Stanford University are Copyright (C) 2007.  All Rights Reserved.
 *
 * Protege was developed by Stanford Medical Informatics
 * (http://www.smi.stanford.edu) at the Stanford University School of Medicine
 * with support from the National Library of Medicine, the National Science
 * Foundation, and the Defense Advanced Research Projects Agency.  Current
 * information about Protege can be obtained at http://protege.stanford.edu.
 *
 */

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.protege.model.ValueType;
import edu.stanford.smi.protege.util.ComponentUtilities;
import edu.stanford.smi.protegex.owl.model.OWLModel;
import edu.stanford.smi.protegex.owl.model.RDFProperty;
import edu.stanford.smi.protegex.owl.model.RDFResource;
import edu.stanford.smi.protegex.owl.model.RDFSNamedClass;
import edu.stanford.smi.protegex.owl.ui.components.ComponentUtil;

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 final static String OBJECT_JAVA_CLASS_NAME = "Object";
	
    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();
        }
        
        String javaTypeName = null;
        String modelAnnotations = null;
        if (((Cls) aClass).getTemplateSlotAllowsMultipleValues(property)) {
        	//multiple cardinality
            javaTypeName = "java.util.List";
            if (!((Cls) aClass).getTemplateSlotAllowedClses(property).isEmpty()) {
                RDFSNamedClass range = (RDFSNamedClass) ((Cls) aClass).getTemplateSlotAllowedClses(property).iterator().next();
                modelAnnotations = " type=\"" + getInterfaceName(range) + "\"";
            }
        }
        else {
        	//simple cardinality        	
        	ValueType valueType = ((Cls) aClass).getTemplateSlotValueType(property);
        	
        	if (valueType == ValueType.INSTANCE) {
        		
        		if (property.hasRange(false)) {
            		//only the first range is considered
        			RDFResource range = property.getRange();
        			if (range instanceof RDFSNamedClass) {
        				javaTypeName = getInterfaceName((RDFSNamedClass) range);
        			} else {
        				javaTypeName = OBJECT_JAVA_CLASS_NAME;
        			}
        		} else {
        			javaTypeName = OBJECT_JAVA_CLASS_NAME;
        		}
        	} else {        	
        		Class javaType = valueType.getJavaType();
        		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;
    }
}
