package hu.swankey.ammo.common.yggdrasil;

import hu.swankey.ammo.common.yggdrasil.basics.BooleanField;
import hu.swankey.ammo.common.yggdrasil.basics.YContainer;
import hu.swankey.ammo.common.yggdrasil.basics.Reference;
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){
	        for(TreeModelListener listener: listeners)
	            listener.treeNodesChanged(event);
	    }
	    
	    public void treeNodesInserted(TreeModelEvent e) {
	        for(TreeModelListener listener: listeners)
	            listener.treeNodesInserted(e);
	    }


	}	
}
