package hu.swankey.ammo.common.yggdrasil;

import hu.swankey.ammo.common.yggdrasil.basics.BooleanField;
import hu.swankey.ammo.common.yggdrasil.basics.Reference;
import hu.swankey.ammo.common.yggdrasil.basics.SimpleObject;
import hu.swankey.ammo.common.yggdrasil.basics.YContainer;
import hu.swankey.ammo.common.yggdrasil.basics.YObject;
import hu.swankey.ammo.common.yggdrasil.definition.ComplexClassDefinition;
import hu.swankey.ammo.common.yggdrasil.ext.Root;
import hu.swankey.logging.LoggerInitialiser;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

public class Yggdrasil {

    public static final String PACKAGE_ROOT = "classes";
    public static final String PACKAGE_BASICS = "basics";
    public static final String PACKAGE_EXT = "ext";
    public static final String PACKAGE_DEF = "def";
    public static final String PACKAGE_CONTAINERS = "containers";

    public static final String VAR_WORLD = "world";
    // public static final String VAR_PARENT = "parent";

    private Root root;
    // private Finder finder = new Finder();
    /** Listeners */
    private final Map<YObject, YggdrasilTreeModel> listeners = new HashMap<YObject, YggdrasilTreeModel>();

    public Yggdrasil() {
        LoggerInitialiser.initLog(this.getClass().getName());
    }

    public void init() {

        // MethodDefinition.yclass();
        // PackageDefinition.yclass();
        // VariableDefinition.yclass();
        //		
        // ClassDefinition.yclass();
        ComplexClassDefinition.yclass();
        // ElementDefinition.yclass();
        BooleanField.yclass();

        root = (Root) Root.yclass().create(Reference.ROOT_SYMBOL);
        root.setYggdrasil(this);
        reportNewElement(root);

        Logger.getLogger(this.getClass().getName()).log(Level.INFO,
                "Init completed.");

    }

    public void reportNewElement(YObject element) {
        // System.out.println("New object: " + element);
        // registerNewElement(element);

        // Create an event if it is not the root element:
        if (element.getParent() != null) {

            int elementIndex[] = { element.getParent().indexOf(element) };
            YObject elementArray[] = { element };

            for (YObject listenedElement : listeners.keySet()) {
                List<YObject> path = element.getParent().getPathFrom(
                        listenedElement);

                // Report only to the relevant listeners:
                if (path != null) {

                    // Report the element itself:
                    TreeModelEvent event = new TreeModelEvent(this, path
                            .toArray(), elementIndex, elementArray);
                    listeners.get(listenedElement).treeNodesInserted(event);

                    // Report subelements if 'element' is a node:
                    if (element instanceof YContainer) {
                        YContainer node = (YContainer) element;

                        int[] indexes = new int[node.size()];
                        YObject[] elements = new YObject[node.size()];

                        for (int i = 0; i < node.size(); i++) {
                            indexes[i] = i;
                            elements[i] = node.getElementAt(i);
                        }

                        path.add(element);

                        TreeModelEvent subevent = new TreeModelEvent(this, path
                                .toArray(), indexes, elements);

                        listeners.get(listenedElement).treeNodesInserted(
                                subevent);
                    }
                }
            }

        }
    }

    // public void registerNewElement(Element element) {
    //
    // if (element == null)
    // throw new RuntimeException("Element cannot be null");
    //
    // if (element instanceof Container) {
    // Container container = (Container) element;
    //
    // // for (IType type : getNewTypes(container))
    // // registerType(element.getType());
    //
    // // for (Element subelement : getAllElements(container))
    // // finder.register(subelement);
    // }
    // }

    //	
    // public void registerClass(YClass yclass){
    //		
    // if (yclass instanceof ComplexClass)
    // registerClassDef( ComplexClassDefinition.yclass().create( (ComplexClass)
    // yclass ) );
    // else
    // registerClassDef( ClassDefinition.yclass().create( yclass ) );
    // }

    // public void registerClassDef(ClassDefinition classDef) {
    //		
    //		
    // // String packagePath = getRoot().getClassContainer().getPathString() +
    // Element.DELIMITER + classDef.getPackagePath();
    // //
    // // //Container pack = (Container) finder.get( packagePath );
    // // Container pack = (Container) new
    // SoftLink(packagePath).getTarget(root);
    // //
    // // // TODO: A SoftLink keresési algoritmusát átemelni a Container-be
    // // // Így használható lenne keresésre SoftLink példányosítása nélkül
    // // // nem beszélve arról, hogy míg a SoftLink típus nincs inicializálva,
    // így nem tudok keresni.
    // //
    // // if (pack == null) {
    // // pack = new Container(ClassDefinition.yclass(),
    // classDef.getPackagePath());
    // // getRoot().getClassContainer().put( pack );
    // // }
    // //
    // // if (pack.containsKey(classDef.getName()) )
    // // throw new RuntimeException("'" + packagePath +
    // "' is already registered." );
    // //
    // // pack.put(classDef);
    //			
    //		
    //
    // // TODO: Ősök auto-regisztrálása jelenleg nem megy!
    // // for (Type superior : classDef.getWrappedYClass().getSuperiors()) {
    // // registerClassDef( new ClassDefinition(superior) );
    // // }
    // //}
    // }

    // private List<Element> getAllElements(Container container) {
    // ArrayList<Element> list = new ArrayList<Element>();
    // return getAllElements(container, list);
    // }

    // private List<Element> getAllElements(Element element, List<Element> list)
    // {
    // list.add(element);
    //
    // if (element instanceof Container)
    // for (Element subelement : ((Container) element).getElements())
    // getAllElements(subelement, list);
    //
    // return list;
    // }

    public void reportDeletedElement(YObject element, YContainer container,
            int index) {
        // finder.unregister(element);
        // Element[] children = { element };
        // int[] childIndices = { index };
        // TreeModelEvent event = new TreeModelEvent(this,
        // container.getPath().toArray(), childIndices, children);
        // for (TreeModelListener listener : listeners)
        // listener.treeNodesRemoved(event);
        // throw new UnsupportedOperationException();
        System.out.println("Deleted element reported.");
    }

    public void reportRenamedElement(YObject element) {
        // // TreeModelEvent event = new TreeModelEvent(this,
        // element.getPath().toArray());
        // // for (TreeModelListener listener : listeners)
        // // listener.treeNodesChanged(event);
        throw new UnsupportedOperationException();
    }

    public void reportRelocatedElement(YObject element) {
        throw new UnsupportedOperationException();
    }

    public void reportChangedElement(YObject element) {
        Map<YggdrasilTreeModel, Object[]> paths = getRelevantPaths(element);
        for (YggdrasilTreeModel listener : paths.keySet()) {
            TreeModelEvent event = new TreeModelEvent(this, paths.get(listener));
            listener.reportChangedObject(event);
        }
    }

    // public void reportChangedField(Field field) {
    // TreeModelEvent event = new TreeModelEvent(this, getPath(field));
    // for (TreeModelListener listener : listeners.values())
    // listener.treeNodesChanged(event);
    // }

    public YObject[] getPath(YObject e) {
        YObject[] path = new YObject[e.getLevel() + 1];
        YObject element = e;
        while (element != null) {
            path[element.getLevel()] = element;
            element = (YObject) element.getParent();
        }
        return path;
    }

    public Map<YggdrasilTreeModel, Object[]> getRelevantPaths(YObject element) {
        Map<YggdrasilTreeModel, Object[]> map = new HashMap<YggdrasilTreeModel, Object[]>();

        for (YObject e : listeners.keySet()) {
            List<YObject> path = element.getPathFrom(e);
            if (path != null) {
                map.put(listeners.get(e), path.toArray());
            }
        }

        return map;
    }

    public YObject get(String path) {
        return new Reference(path).getTarget(root);
    }

    public Root getRoot() {
        return root;
    }

    public YggdrasilTreeModel getTreeModel(YObject element) {

        if (element == null)
            throw new NullPointerException("'element' cannot be null");

        YggdrasilTreeModel view = listeners.get(element);

        if (view == null) {
            view = new YggdrasilTreeModel(element);
            listeners.put(element, view);
        }

        return view;
    }

    public static class YggdrasilTreeModel implements TreeModel {

        private final YObject root;
        private ArrayList<TreeModelListener> listeners = new ArrayList<TreeModelListener>();

        // TODO: Átemelni a Client Engine-be.

        public YggdrasilTreeModel(YObject root) {
            this.root = root;
        }

        private TreeSet<YObject> generateTreeSet(YContainer container) {
            TreeSet<YObject> sortedset = new TreeSet<YObject>(
                    new Comparator<YObject>() {
                        @Override
                        public int compare(YObject o1, YObject o2) {
                            return o1.getName().compareTo(o2.getName());
                        }
                    });

            sortedset.addAll(container.getElements());

            return sortedset;
        }

        private YObject[] generateArray(YContainer container) {
            TreeSet<YObject> sortedset = generateTreeSet(container);
            return (YObject[]) sortedset.toArray(new YObject[sortedset.size()]);
        }

        @Override
        public YObject getRoot() {
            return root;
        }

        @Override
        public YObject getChild(Object parent, int index) {
            return generateArray((YContainer) parent)[index];
        }

        @Override
        public int getChildCount(Object parent) {
            if (isLeaf(parent))
                return 0;
            else
                return generateTreeSet((YContainer) parent).size();
        }

        @Override
        public boolean isLeaf(Object element) {
            return (!(element instanceof YContainer));
        }

        @Override
        public void valueForPathChanged(TreePath path, Object newValue) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int getIndexOfChild(Object parent, Object child) {
            ArrayList<YObject> list = new ArrayList<YObject>(
                    generateTreeSet((YContainer) parent));
            return list.indexOf(child);
        }

        @Override
        public void addTreeModelListener(TreeModelListener l) {
            listeners.add(l);
        }

        @Override
        public void removeTreeModelListener(TreeModelListener l) {
            listeners.remove(l);
        }

        public void reportChangedObject(TreeModelEvent event) {


//            TreeSet<YObject> sortedset = generateTreeSet((YContainer) event
//                    .getTreePath().getLastPathComponent());
//            ArrayList<YObject> list = new ArrayList<YObject>(sortedset);
//
//            int elementIndex[] = new int[event.getChildIndices().length];
//            for (int i = 0; i < elementIndex.length; i++) {
//                elementIndex[i] = list.indexOf(event.getChildIndices()[i]);
//            }
//
//            // return (YObject[]) sortedset.toArray(new
//            // YObject[sortedset.size()]);
//            TreeModelEvent newEvent = new TreeModelEvent(this, sortedset
//                    .toArray(), elementIndex, sortedset.toArray());

            for (TreeModelListener listener : listeners)
                listener.treeNodesChanged(event);
            
            System.out.println("Módosul: " + ((YObject)event.getTreePath().getLastPathComponent()).getName());
            System.out.println("  Új érték: " + ((SimpleObject)event.getTreePath().getLastPathComponent()).getValue());
        }

        public void treeNodesInserted(TreeModelEvent event) {
            
            // TODO: Ezzel vmit csinálni
            
            YContainer parent = (YContainer) event.getTreePath().getLastPathComponent(); 
            TreeSet<YObject> sortedset = generateTreeSet(parent);
            ArrayList<YObject> list = new ArrayList<YObject>(sortedset);

            int elementIndex[] = new int[event.getChildIndices().length];
            for (int i = 0; i < elementIndex.length; i++) {
                
//                System.out.println("i: " + i);
//                System.out.println("Gyakorlati index: " + event.getChildIndices()[i]);
//                System.out.println("Gyakorlati elem:  " + parent.getElementAt(event.getChildIndices()[i]));
//                System.out.println("Elméleti index:   " + list.indexOf( parent.getElementAt(event.getChildIndices()[i]) ) );
                elementIndex[i] = list.indexOf( parent.getElementAt(event.getChildIndices()[i]) );
//                System.out.println("Elméleti elem: " + getChild(parent, elementIndex[i]));
//                System.out.println("-----------");
                
            }

            // return (YObject[]) sortedset.toArray(new
            // YObject[sortedset.size()]);
            TreeModelEvent newEvent = new TreeModelEvent(this, event.getTreePath(),
                    elementIndex, sortedset.toArray());

            for (TreeModelListener listener : listeners)
                listener.treeNodesInserted(newEvent);

        }

    }
}
