package de.ugoe.cs.quest.eventcore.guimodel;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;
import java.util.logging.Level;

import de.ugoe.cs.util.console.Console;

/**
 * TODO comment
 * 
 * TODO rename class to GUIElementFactory
 * 
 * @version $Revision: $ $Date: 13.05.2012$
 * @author 2012, last modified by $Author: patrick$
 */
public class GUIElementFactory implements IGUIElementFactory {
    
    /** */
    private static GUIElementFactory instance = new GUIElementFactory();

    /**
     * TODO: comment
     * 
     */
    private GUIElementFactory() {
    }

    /**
     * TODO: comment
     * 
     * @return
     */
    public static synchronized GUIElementFactory getInstance() {
        return instance;
    }

    
    /** */
    private Properties mappingsFromConfiguration;

    /**
     * TODO: comment
     * 
     * @param object1
     * @param object2
     * @return
     */
    protected boolean equals(Object object1, Object object2) {
        if (object1 == object2) {
            return true;
        }
        else if (object1 != null) {
            return object1.equals(object2);
        }
        else {
            // object 1 is null but object 2 not --> return false
            return false;
        }
    }

    /**
     * TODO: comment
     * 
     * @param parameterTypes
     * @param parameters
     * @return
     * @throws GUIModelConfigurationException
     */
    @Override
    public IGUIElement instantiateGUIElement(IGUIElementSpec specification, IGUIElement parent)
        throws GUIModelConfigurationException
    {
        Properties mappings = getMappingsFromConfiguration();
        IGUIElement guiElement = null;

        String className = mappings.getProperty(specification.getType());
        if (className != null) {
            try {
                Class<?> clazz = this.getClass().getClassLoader().loadClass(className);

                if (!IGUIElement.class.isAssignableFrom(clazz)) {
                    Console.traceln(Level.WARNING, "configured GUI element representing class " +
                                    className + " is no valid GUIElement derivate.");

                    return null;
                }

                Constructor<?> constructor = null;
                Class<?> parentClass = (parent == null) ? null : parent.getClass();
                
                // search for a constructor, that perfectly matches the types
                for (Constructor<?> candidate : clazz.getConstructors()) {
                    if ((parentClass != null) &&
                        (candidate.getParameterTypes().length == 2) &&
                        (candidate.getParameterTypes()[0].equals(specification.getClass())) &&
                        (candidate.getParameterTypes()[1].equals(parentClass)))
                    {
                        constructor = candidate;
                        break;
                    }
                    else if (parentClass == null) {
                        if ((candidate.getParameterTypes().length >= 1) &&
                            (candidate.getParameterTypes()[0].equals(specification.getClass())))
                        {
                            constructor = candidate;
                            break;
                        }
                    }
                }
                
                if (constructor == null) {
                    // search for an assignable constructor
                    for (Constructor<?> candidate : clazz.getConstructors()) {
                        if ((candidate.getParameterTypes().length == 2) &&
                            (candidate.getParameterTypes()[0].isInstance(specification)) &&
                            (candidate.getParameterTypes()[1].isInstance(parent)))
                        {
                            constructor = candidate;
                            break;
                        }
                    }
                    
                }
                
                if (constructor != null) {
                    guiElement = (IGUIElement) constructor.newInstance(specification, parent);
                }
                else {
                    throw new NoSuchMethodException
                        ("no constructor with two parameters and assignable parameter types for " +
                         specification.getClass() + " and " +
                         (parent != null ? parent.getClass() : "null") + " found in class " +
                         clazz);
                }

            }
            catch (ClassNotFoundException e) {
                Console.traceln(Level.WARNING, "configured GUI element representing class " +
                                className + " can not be loaded.");
                throw new GUIModelConfigurationException
                    ("configured GUI element representing class " + className +
                     " can not be loaded.", e);
            }
            catch (SecurityException e) {
                Console.traceln(Level.WARNING, "configured GUI element representing class " +
                                className + " can not be instantiated due to security reasons.");
                throw new GUIModelConfigurationException
                    ("configured GUI element representing class " + className +
                     " can not be instantiated due to security reasons.", e);
            }
            catch (NoSuchMethodException e) {
                Console.traceln(Level.WARNING, "configured GUI element representing class " +
                                className + " does not provide an appropriate constructor.");
                throw new GUIModelConfigurationException
                    ("configured GUI element representing class " + className +
                     " does not provide an appropriate constructor.", e);
            }
            catch (IllegalArgumentException e) {
                Console.traceln(Level.WARNING, "configured GUI element representing class " +
                                className + " does not provide an appropriate constructor " +
                                "accepting the provided parameters.");
                throw new GUIModelConfigurationException
                    ("configured GUI element representing class " + className + " does not " +
                     "provide an appropriate constructor accepting the provided parameters.", e);
            }
            catch (InstantiationException e) {
                Console.traceln(Level.WARNING, "configured GUI element representing class " +
                                className + " can not be instantiated.");
                throw new GUIModelConfigurationException
                    ("configured GUI element representing class " + className +
                     " can not be instantiated.", e);
            }
            catch (IllegalAccessException e) {
                Console.traceln(Level.WARNING, "configured GUI element representing class " +
                                className + " can not be instantiated.");
                throw new GUIModelConfigurationException
                    ("configured GUI element representing class " + className +
                     " can not be instantiated.", e);
            }
            catch (InvocationTargetException e) {
                Console.traceln(Level.WARNING, "configured GUI element representing class " +
                                className + " can not be instantiated.");
                throw new GUIModelConfigurationException
                    ("configured GUI element representing class " + className +
                     " can not be instantiated.", e);
            }
        }
        
        if (guiElement == null ) {
            Console.traceln(Level.WARNING, "no class representing GUI elements of type " +
                            specification.getType() + " found. Please extend GUI element " +
                            "mapping files.");
            throw new GUIModelConfigurationException
                ("no class representing GUI elements of type " + specification.getType() +
                 " found. Please extend GUI element mapping files");
        }

        return guiElement;
    }

    /**
     * TODO: comment
     * 
     * @return
     */
    private synchronized Properties getMappingsFromConfiguration()
        throws GUIModelConfigurationException
    {
        if (mappingsFromConfiguration != null) {
            return mappingsFromConfiguration;
        }
        else {
            mappingsFromConfiguration = new Properties();
            
            File mappingsFolder = new File("data/guimappings");
            File[] children = mappingsFolder.listFiles();
            
            if (children != null) {
                for (File mappingsFile : children) {
                    if (!mappingsFile.isDirectory() &&
                        mappingsFile.getName().startsWith("guimapping") &&
                        mappingsFile.getName().endsWith(".txt"))
                    {
                        InputStream inStream = null;
                        try {
                            inStream = new FileInputStream(mappingsFile);
                            mappingsFromConfiguration.load(inStream);
                        }
                        catch (FileNotFoundException e) {
                            throw new GUIModelConfigurationException
                                ("could not read mapping configuration file " + mappingsFile, e);
                        }
                        catch (IOException e) {
                            throw new GUIModelConfigurationException
                                ("could not read mapping configuration file " + mappingsFile, e);
                        }
                        finally {
                            if (inStream != null) {
                                try {
                                    inStream.close();
                                }
                                catch (IOException e) {
                                    // ignore
                                }
                            }
                        }
                    }
                }
            }
            else {
                throw new GUIModelConfigurationException
                    ("no GUI mappings file provided in folder " + mappingsFolder);
            }

            return mappingsFromConfiguration;
        }
    }
}
