package hu.swankey.ammo.common.yggdrasil.definition;

import hu.swankey.ammo.common.script.AmmoScriptClassNotFoundException;
import hu.swankey.ammo.common.script.yunits.YClass;
import hu.swankey.ammo.common.script.yunits.YMethod;
import hu.swankey.ammo.common.yggdrasil.basics.Reference;
import hu.swankey.ammo.common.yggdrasil.basics.StringField;
import hu.swankey.ammo.common.yggdrasil.basics.YContainer;
import hu.swankey.ammo.common.yggdrasil.basics.YObject;
import hu.swankey.ammo.common.yggdrasil.definition.YMethodDefinition.YMethodDefinitionClass;

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

public class YClassDefinition extends Definition {
	
	public static final String KEY_SUPERIORS = "superiors";
	public static final String KEY_METHODS = "methods";

	protected YClassDefinition(String name, YClassDefinitionClass yclass) {
		super(name, yclass);
	}

	public static YClassDefinitionClass yclass() {
		return YClassDefinitionClass.singleton();
	}
	
	public void setWrapped(YClass yclass) {
		wrapped = yclass;
		
		synchronize();
		yclass.setWrapperElement(this);
	}
	
	public YContainer getSuperiorContainer(){
		return (YContainer)get(KEY_SUPERIORS);
	}
	
	public YContainer getMethodContainer(){
		return (YContainer)get(KEY_METHODS);
	}	
	
	
	public void addSuperior(Reference link) {
		YObject.yclass();
		
		YClass yclass = YClass.getYClass(link);
		
		if (yclass == null)
			throw new AmmoScriptClassNotFoundException(link);
		
		getWrapped().addSuperior( yclass );
	}
	
	public void addMethod(YMethodDefinition method) {
		getWrapped().addMethod(method.getWrapped());
	}
	
	public String getClassPath(){
		return getWrapped().getPathString();
	}	
	
	@Override
	public void synchronize(){
		synchronize(true);
	}
	
	protected void synchronize(boolean report){
		
		boolean changed = false;
		
		// Insert new superiors:
		for(YClass superior:  getWrapped().getSuperiors()){
		if (!getSuperiorContainer().containsKey(superior.getName()) )
			getSuperiorContainer().put( new Reference(superior.getName(), superior.getPathString()) );
			changed = true;
		}
		
		// Remove removed superiors:
		Iterator<YObject> it = getSuperiorContainer().getElements().iterator();
		while (it.hasNext()) {
			YObject.yclass();
			//YClass superior = (YClass) YClass.getRootPackage().get((SoftLink));
			YClass superior = YClass.getYClass( (Reference)it.next() );
			
			if ( !getWrapped().getSuperiors().contains(superior))
				it.remove();
		}
		
		// --- Insert new methods:
		Map<String, ArrayList<YMethod>> methods = getWrapped().getMethods();
		YContainer methodContainer = getMethodContainer();
		
		// Iterate on method names:
		for(String methodName: methods.keySet()) {
			YContainer subContainer = (YContainer) methodContainer.get(methodName);
			
			if (subContainer == null) {
				subContainer = new YContainer(YMethodDefinitionClass.singleton(), methodName);
				methodContainer.put(subContainer);
			}
			
			// Iterate on methods with current name:
addMethod:	for(YMethod method: methods.get(methodName) ){
	
				// Iterate on direct MethodDefinitions:
				for(YObject oMethodDef: subContainer.getElements())
				if( ((YMethodDefinition)oMethodDef).getWrapped() == method )
					continue addMethod;
					
				String defIndex = new Integer(subContainer.size()).toString();
				YMethodDefinition methodDef = method.getWrapperElement();				
				methodDef.setName(methodName + "_" + defIndex);				
				subContainer.put( method.getWrapperElement() );
				method.getWrapperElement().setLogicalParent(this);
				changed = true;
			}
			
		}
		
		
		// Source: 
		setSource("<AMMO: Function not implemented!>");
	
		
		if (changed && report)
			reportChange();
	}
	
	@Override
	public void reportRemovedElement(YObject element, YContainer parent, int index){
		synchronize(true);
		super.reportRemovedElement(element, parent, index);
	}
	

	@Override
	public YClass getWrapped() {
		return (YClass) super.getWrapped();
	}

	public static class YClassDefinitionClass extends DefinitionClass {

		private static String CLASS_NAME = "YClassDefinition";
		private static YClassDefinitionClass singleton;

		public static YClassDefinitionClass singleton() {

			if (singleton == null) {
				singleton = new YClassDefinitionClass(CLASS_NAME);
				singleton.addSuperior(Definition.yclass());
				singleton.addMethod(AddSuperior.function);
                singleton.addMethod(AddMethod.function);
				singleton.addMethod(RemoveSuperior.function);
				singleton.addMethod(RemoveMethod.function);
				singleton.addMethod(New.function);
				singleton.addElement( AttributeDefinition.yclass().create(KEY_METHODS, ContainerClass.getContainerClass( ContainerClass.getContainerClass(YMethodDefinition.yclass()) ), null));
	    		singleton.addElement( AttributeDefinition.yclass().create(KEY_SUPERIORS, ContainerClass.getContainerClass(Reference.yclass()), null));	    				
				placeClass(singleton);
			}
			return singleton;
		}

		protected YClassDefinitionClass(String name) {
			super(name, null);
		}
		
		public YClassDefinition create(YClass wrappedYClass) {
			//ClassDefinition newClassDef = create(wrappedYClass.getName());
			YClassDefinition newClassDef = new YClassDefinition(wrappedYClass.getName(), this);
			init(newClassDef);
			newClassDef.setWrapped(wrappedYClass);
			return newClassDef;
		}		

		@Override
		public YClassDefinition create(String name) {
//			ClassDefinition newClassDef = new ClassDefinition(name, this);
//			init(newClassDef);
//			return newClassDef;
			throw new UnsupportedOperationException();
		}
	}

	private static class AddSuperior extends YMethod {

		private static final String FUNCTION_NAME = "addSuperior";
		private static final String KEY_CLASS = "yclass";
		private static final YClass YCLASS_CLASS = YClassDefinition.yclass();

		public static final AddSuperior function = new AddSuperior();

		protected AddSuperior() {
			super(FUNCTION_NAME);
			addParameter(KEY_CLASS, YCLASS_CLASS, null);
		}

		@Override
		public YObject run(Map<String, YObject> params,
				/*Map<String, YObject> env,*/ YObject thisElement) {
			((ComplexClassDefinition) thisElement).getWrapped().addSuperior(
					((YClassDefinition) params.get(KEY_CLASS)).getWrapped());

			return null;
		}
	}

	private static class RemoveSuperior extends YMethod {

		private static final String FUNCTION_NAME = "removeSuperior";
		private static final String KEY_CLASS = "yclass";
		private static final YClass YCLASS_CLASS = YClassDefinition.yclass();

		public static final RemoveSuperior function = new RemoveSuperior();

		protected RemoveSuperior() {
			super(FUNCTION_NAME);
			addParameter(KEY_CLASS, YCLASS_CLASS, null);
		}

		@Override
		public YObject run(Map<String, YObject> params/*, Map<String, YObject> env*/, YObject thisElement) {
			YClass thisClass = ((ComplexClassDefinition) thisElement).getWrapped();
			YClass toRemove = ((YClassDefinition) params.get(KEY_CLASS)).getWrapped();
			remove(thisClass, toRemove);
			
			return null;
		}
		
		private void add(YClass thisClass, YClass toRemove, List<YClass> newSuperiors){
			
			Iterator<YClass> it = newSuperiors.iterator();
			ArrayList<YClass> newSuperiors2 = new ArrayList<YClass>();
			
			while (it.hasNext()) {
				YClass superior = it.next();
				
				if (!superior.isSuperiorOf(thisClass)) {
					if (!toRemove.isSuperiorOf(superior) )
						thisClass.addSuperior(superior);
					else
						for(YClass newSuperior: superior.getSuperiors())
							newSuperiors2.add(newSuperior);
				}
					
				it.remove();
			}
						
			if (!newSuperiors2.isEmpty())
				add(thisClass, toRemove, newSuperiors2);
		}

		
		private void remove(YClass thisClass, YClass toRemove) {
			
			ArrayList<YClass> removeList = new ArrayList<YClass>();
			
			// Megadott ős eltávolítása:
			// Ha nem közvetlen, a tőle leszármazott ősök törlése:
			if (thisClass.getSuperiors().contains(toRemove)) {
				thisClass.removeSuperior(toRemove);
				removeList.add(toRemove);
			} else {

				for (YClass tSuperior : thisClass.getSuperiors())
					if (toRemove.isSuperiorOf(tSuperior))
						removeList.add(tSuperior);

				for (YClass r : removeList){
					thisClass.removeSuperior(r);
				}
			}
			
			add(thisClass, toRemove, removeList);
		}
	}
	
	private static class New extends YMethod {

		private static final String FUNCTION_NAME = "new";
		private static final String KEY_NAME = "name";
		private static final YClass YCLASS_NAME = StringField.yclass();

		public static final New function = new New();

		protected New() {
			super(FUNCTION_NAME);
			addParameter(KEY_NAME, YCLASS_NAME, null);
			setReturnType( YObject.yclass() );
		}

		@Override
		public YObject run(Map<String, YObject> params, YObject thisElement) {
			
			YClassDefinition thisE = (YClassDefinition) thisElement;
			String name = ((StringField)params.get(KEY_NAME)).getValue();
			
			return thisE.getWrapped().create(name);
		}
	}
	
	   private static class AddMethod extends YMethod {

	        private static final String FUNCTION_NAME = "addMethod";
	        private static final String KEY_METHOD = "method";
	        private static final YClass YCLASS_METHOD = YMethodDefinition.yclass();

	        public static final AddMethod function = new AddMethod();

	        protected AddMethod() {
	            super(FUNCTION_NAME);
	            addParameter(KEY_METHOD, YCLASS_METHOD, null);
	            setReturnType( YMethodDefinition.yclass() );
	        }

	        @Override
	        public YObject run(Map<String, YObject> params, YObject thisElement) {
	            
	            YClassDefinition thisE = (YClassDefinition) thisElement;
	            YMethodDefinition method = (YMethodDefinition)params.get(KEY_METHOD);
	            
	            thisE.addMethod(method);
	            
	            return method;
	        }
	    }
	   
       private static class RemoveMethod extends YMethod {

           private static final String FUNCTION_NAME = "removeMethod";
           private static final String KEY_METHOD = "method";
           private static final YClass YCLASS_METHOD = YMethodDefinition.yclass();

           public static final RemoveMethod function = new RemoveMethod();

           protected RemoveMethod() {
               super(FUNCTION_NAME);
               addParameter(KEY_METHOD, YCLASS_METHOD, null);
               setReturnType( YMethodDefinition.yclass() );
           }

           @Override
           public YObject run(Map<String, YObject> params, YObject thisElement) {
               
               YClass thisE = ((YClassDefinition) thisElement).getWrapped();
               YMethodDefinition methodDef = (YMethodDefinition)params.get(KEY_METHOD);
               
               thisE.removeMethod(methodDef.getWrapped());
               
               return methodDef;
           }
       } 	   

}
