package adapters;

import hu.swankey.ammo.common.script.yunits.YClass;
import hu.swankey.ammo.common.script.yunits.YMethod;
import hu.swankey.ammo.common.yggdrasil.Yggdrasil;
import hu.swankey.ammo.common.yggdrasil.definition.MethodDefinition;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import javax.swing.ListModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;

public class MethodListModel implements ListModel, TreeModelListener {
	
	private final YClass yclass;
	private final ArrayList<ListDataListener> listeners = new ArrayList<ListDataListener>();	
	private MethodDefinition[] methods = new MethodDefinition[0];

		
	public static MethodListModel create(YClass yclass, Yggdrasil yggdrasil){
		MethodListModel model = new MethodListModel(yclass);
		yggdrasil.getTreeModel( yclass.getWrapperElement() ).addTreeModelListener(model);
		return model;
	}
	
	private MethodListModel(YClass yclass){
		this.yclass = yclass;
		regenerate();
	}
	
	private void regenerate(){
		
		TreeSet<MethodDefinition> sortedset = new TreeSet<MethodDefinition>(new Comparator<MethodDefinition>(){
			@Override
			public int compare(MethodDefinition m1, MethodDefinition m2) {
				
				YMethod method1 = m1.getWrapped();
//				Method method2 = m2.getWrapped();
				
				List<YMethod> list1 = yclass.getMethods(m1.getWrapped().getName());
				List<YMethod> list2 = yclass.getMethods(m2.getWrapped().getName());

				// 1: local vs inherited				
				boolean m1Local = list1 != null && list1.contains(m1.getWrapped());
				boolean m2Local = list2 != null && list2.contains(m2.getWrapped());				
				if (m1Local != m2Local)
					return (m1Local ? -1 : 1);
				
				
				// 2: Vertical index:
				//int m1Level = m1.getWrapped().getOwnerClass().getLevel();
				//int m2Level = m2.getWrapped().getOwnerClass().getLevel();
				int m1Level = m1.getContainer().getWrapped().getLevel();
				int m2Level = m2.getContainer().getWrapped().getLevel();
				if (m1Level < m2Level)
					return -1;
				if (m1Level > m2Level)
					return 1;
				
				// 3: Horizontal comparison:
//				String path1 = m1.getWrapped().getOwnerClass().getPathString();
//				String path2 = m2.getWrapped().getOwnerClass().getPathString();
				String path1 = m1.getContainer().getWrapped().getPathString();
				String path2 = m2.getContainer().getWrapped().getPathString();				
				
				int comp = path1.compareTo(path2);
				if (comp != 0) return comp;
				
				
				// 4: Method name comparison:
				int ncomp = m1.getWrapped().getName().compareTo(m2.getWrapped().getName());
				if (ncomp != 0) return ncomp;
				
				// 5: Parameter number comparison:
				int size1 = m1.getWrapped().getParameters().size();
				int size2 = m2.getWrapped().getParameters().size();
				if (comp != 0) return (size1 < size2 ? -1: 1);
				
				// 6: Parameter type comparison:
				Comparator<YClass> classComparator = new Comparator<YClass>(){
					@Override
					public int compare(YClass c1, YClass c2) {
						return c1.getWrapperElement().getPathString().compareTo(c2.getWrapperElement().getPathString());
					}
				};
				
				TreeSet<YClass> types1 = new TreeSet<YClass>(classComparator);
				TreeSet<YClass> types2 = new TreeSet<YClass>(classComparator);
				
				Map<String, YClass> paramTypes1 = method1.getParameters();
				for (String key: paramTypes1.keySet()) {
					types1.add(paramTypes1.get(key));
				}
				
				Map<String, YClass> paramTypes2 = method1.getParameters();
				for (String key: paramTypes2.keySet()) {
					types2.add(paramTypes2.get(key));
				}
				
				Iterator<YClass> it1 = types1.iterator();
				Iterator<YClass> it2 = types2.iterator();
				
				while (it1.hasNext()) {
					int level1 = it1.next().getLevel();
					int level2 = it2.next().getLevel();
					if (level1 < level2) return -1;
					if (level1 > level2) return 1;
				}
				
				// 7: Parameter name comparison:
				TreeSet<String> names1 = new TreeSet<String>();
				TreeSet<String> names2 = new TreeSet<String>();
				
				for (String key: paramTypes1.keySet()) {
					types1.add(paramTypes1.get(key));
				}
				
				for (String key: paramTypes2.keySet()) {
					types2.add(paramTypes2.get(key));
				}
				
				Iterator<String> nameIt1 = names1.iterator();
				Iterator<String> nameIt2 = names2.iterator();
				
				while (it1.hasNext()) {
					int nameComp = nameIt1.next().compareTo(nameIt2.next());
					if (nameComp < 0) return -1;
					if (nameComp > 0) return 1;
				}
				
				
				return 0;
				//return m1.getWrapped().getName().compareTo(m2.getWrapped().getName());
				
			}
		});
		
		
		for(String key: yclass.getAllMethods().keySet()) {
			for(YMethod method: yclass.getAllMethods(key))
			sortedset.add( method.getWrapperElement() );
		}
		
		methods = sortedset.toArray(new MethodDefinition[sortedset.size()]);
	}
	
	
	public boolean contains(MethodDefinition methodDef){
		for(MethodDefinition methodDef2: methods)
			if (methodDef == methodDef2)
				return true;
				
		return false;
	}	

	@Override
	public void addListDataListener(ListDataListener l) {
		listeners.add(l);
	}
	
	@Override
	public void removeListDataListener(ListDataListener l) {
		listeners.remove(l);
	}	

	@Override
	public Object getElementAt(int index) {
		return methods[index];
	}

	@Override
	public int getSize() {
		return methods.length;
	}

	@Override
	public void treeNodesChanged(TreeModelEvent e) {
		ListDataEvent event1 = new ListDataEvent(yclass, ListDataEvent.INTERVAL_ADDED, 0 ,methods.length);				
		regenerate();
		ListDataEvent event2 = new ListDataEvent(yclass, ListDataEvent.INTERVAL_ADDED, 0 ,methods.length);
		
		for (ListDataListener l: listeners) {
			l.intervalRemoved(event1);
			l.intervalAdded(event2);
		}
	}

	@Override
	public void treeNodesInserted(TreeModelEvent e) {
	}

	@Override
	public void treeNodesRemoved(TreeModelEvent e) {
	}

	@Override
	public void treeStructureChanged(TreeModelEvent e) {
	}

	public YClass getYClass() {
		return yclass;
	}

}
