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.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 reportDeletedElement(YObject element, YContainer container, int index) {
        System.out.println("Deleted element reported.");
    }

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

    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);
            
            
//            if ( event.getTreePath().getLastPathComponent() instanceof SimpleObject  ) {
//                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];
            YObject elementArray[] = new YObject[elementIndex.length];
            for (int i = 0; i < elementIndex.length; i++) {
                YObject element = parent.getElementAt(event.getChildIndices()[i]);
                elementIndex[i] = list.indexOf( element );
                elementArray[i] = element;
            }

            TreeModelEvent newEvent = new TreeModelEvent(this, event.getTreePath(),
                    elementIndex, elementArray);

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

        }

    }
}
