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

import hu.swankey.ammo.common.controll.AmmoException;
import hu.swankey.ammo.common.script.yunits.YClass;
import hu.swankey.ammo.common.script.yunits.YMethod;
import hu.swankey.ammo.common.yggdrasil.Yggdrasil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class YContainer extends YObject {
	
    
    private ArrayList<YObject> elements = new ArrayList<YObject>();
    private HashMap<String, YObject> elementsByName = new HashMap<String, YObject>();
    
//    private Comparator<YObject> comparator = new Comparator<YObject>() {
//        @Override
//        public int compare(YObject o1, YObject o2) {
//            return o1.getName().compareTo(o2.getName());
//        }
//    };
  

    
    public YContainer(YClass allowedType, String name) {
        super(name, ContainerClass.getContainerClass(allowedType));
    }
    
    
    protected YContainer(String name, ContainerClass type) {
        super(name, type); 
    }
    
    
    public static ContainerClass yclass() {
    	return ContainerClass.singleton();
    }
    
    public static ContainerClass yclass(YClass yclass){
    	return ContainerClass.getContainerClass(yclass);
    }
    
    
    public void put(YObject element) {

        if (element == null)
            throw new RuntimeException("'Element' cannot be null");
        
        typeCheck(getYClass().allowedType, element.getYClass(), element.getName());

        if (containsKey(element.getName()))
        	throw new RuntimeException(getPathString() + " already contains " + element.getName());
        

        elements.add(element);
        //Collection.sort(elements, comparator);
        elementsByName.put(element.getName(), element);
        element.setParent(this);

        reportNewElement(element);
        reportNewElementInRange(element);
    }
    
    public void replace(String id, YObject newElement) {
    	YObject element = get(id);
    	
        if (newElement == null)
            throw new RuntimeException("'Element' cannot be null");    	
    	
    	if (element == null)
    		throw new RuntimeException("Element not exists: " + getPathString() + DELIMITER + id);
    	
    	YClass expectedType = getYClass().getAllowedType(); 
    	YClass foundedType = newElement.getYClass();
    	
    	typeCheck(expectedType, foundedType, id);

		newElement.setName(id);
		newElement.setParent(this);		
        elements.add(newElement);
        elements.remove(element);
        elementsByName.put(newElement.getName(), newElement);
        

        reportChangedElement(newElement);
    }    
    
    
    protected void typeCheck(YClass expectedType, YClass foundedType, String elementName) {
    	
    	int dist = foundedType.distanceFromSuperior( expectedType );
    	
    	if ( dist < 0 ) 
    		throw new AmmoException(
    				"Type mismatch." +
    				"\n Expected type: '" + expectedType.getPathString() + "' (in '" + getPathString() + ")" +
    				"\n Founded type: '" + foundedType.getPathString() + "' ('" + elementName + "')");
    }
    

    public boolean containsKey(String name) {
        return elementsByName.containsKey(name);
    }

    public YObject remove(String name) {
        return remove(elementsByName.get(name));
    }

    public YObject remove(YObject element) {

        if (!isRemovable(element))
            throw new RuntimeException("'" + element.getName() + "' is not a removable element.");
        
        int index = elements.indexOf(element);
        
        elementsByName.remove(element.getName());
        elements.remove(element);
        element.setParent(null);
        reportRemovedElement(element, this, index);
        reportRemovedElementInRange(element);
        return element;
    }

    public boolean isRemovable(String name) {
        return true;
    }

    public boolean isRemovable(YObject element) {
        return true;
    }

    @Override
    public ContainerClass getYClass() {
        return (ContainerClass) super.getYClass();
    }


    public void reportNewElement(YObject element) {
        if (getParent() != null)
            getParent().reportNewElement(element);
    }


    public void reportRemovedElement(YObject element, YContainer parent, int index) {
        if (getParent() != null)
            getParent().reportRemovedElement(element, parent, index);
    }


    public void reportNewElementInRange(YObject element) {
        if (getParent() != null)
            getParent().reportNewElementInRange(element);
    }


    public void reportRemovedElementInRange(YObject element) {
        if (getParent() != null)
            getParent().reportRemovedElementInRange(element);
    }

    public void reportRenamedElement(YObject element, String oldname) {

        if (!(isRemovable(oldname)))
            throw new RuntimeException("Unremovable element shouldn't be renamed (" + element.getPathString() + ", old name: " + oldname + ")");

        elementsByName.remove(oldname);
        elementsByName.put(element.getName(), element);

        if (getParent() != null)
            getParent().reportRenamedElement(element, oldname);

    }


    public void reportRelocatedElement(YObject element, YContainer oldparent) {
        throw new UnsupportedOperationException("Type cannot be relocated, yet");
    }


    public void reportChangedElement(YObject element) {
        if (container != null)
            container.reportChangedElement(element);
    }


    public boolean containsValue(YObject element) {
        return elements.contains(element);
    }


    public boolean isContainsValueDeep(YObject element) {
        if (!containsValue(element)) {
            for (YObject subelement : elements)
                if (subelement instanceof YContainer && ((YContainer) subelement).isContainsValueDeep(element))
                    return true;
            return false;
        } else
            return true;
    }


    public int indexOf(YObject element) {
        return elements.indexOf(element);
    }


    public int size() {
        return elements.size();
    }
    
    public int fullsize(){
    	int size = size();
    	for(YObject element: elements)
    		if (element instanceof YContainer)
    			size += ((YContainer) element).fullsize();
    		
    	return size;
    }


    public YObject get(String name) {
    	YObject element = elementsByName.get(name);
    	return element;
    }
    
    public StringField getStringField(String name){
    	return (StringField) get(name);
    }
    
    public IntegerField getIntegerField(String name){
        return (IntegerField) get(name);
    }
    
    public Reference getSofTLink(String name){
    	return (Reference) get(name);
    }    


    public YObject getElementAt(int index) {
        return elements.get(index);
    }

    /** Returns all contained elements */
    public Collection<YObject> getElements() {
        return elements;
    }
    
//	public ClassContainer getClassContainer(){
//		
//		ClassContainer types = (ClassContainer) get( YggdrasilManager.CLASS_CONTAINER_NAME );
//	
//		if (types == null && getParent() != null) types = getParent().getClassContainer(); 
//		
//		if (types == null)
//			throw new RuntimeException("TypeContainer not found!");
//		
//		return types;
//	}
	
	
	public static class ContainerClass extends YClass {

		private static final String[] PACKAGE_PATH = { Yggdrasil.PACKAGE_CONTAINERS };		
		private static final String CLASS_NAME = "YContainer";        
        private static final String CLASS_NAME_DELIMITER = "";
        
        private static HashMap<YClass, ContainerClass> containerTypes = new HashMap<YClass, ContainerClass>();
        private static ContainerClass singleton;
        
        private final YClass allowedType;
        
        public static ContainerClass singleton() {
        	if (singleton == null) {    	
        		singleton = new ContainerClass(CLASS_NAME, PACKAGE_PATH, YObject.yclass());
        		singleton.addSuperior( YObject.yclass() );
        		singleton.addMethod( Put.function );
        		placeClass(singleton);
        	}
        	return singleton;
        }   
        
        
		public YContainer create(String name) {
			return new YContainer(name, this);
		}

        protected ContainerClass(String name, String[] packagePath, YClass allowedType) {
            super(name, (packagePath != null ? packagePath : PACKAGE_PATH) );
            this.allowedType = allowedType;          
        }

        protected static ContainerClass getContainerClass(YClass type) {
        	
            if (type == null)
                throw new RuntimeException("'type' cannot be null");
            
            ContainerClass containerType = containerTypes.get(type);

            if (containerType == null) {
                            
                if (type == YObject.yclass()) {
                    containerType = YContainer.yclass();
                } else {
                    String name = type.getName() + CLASS_NAME_DELIMITER + CLASS_NAME;
                    containerType = new ContainerClass(name, PACKAGE_PATH, type);
                    
                    for(YClass superior: type.getSuperiors())
                    	containerType.addSuperior(getContainerClass(superior) );
                    
                    placeClass(containerType);
                }

                containerTypes.put(type, containerType);
                
                // Register:
                
            }

            return containerType;
        }


        public YClass getAllowedType(){
        	return allowedType;
        }       
	}
	
	
	private static class Put extends YMethod {

		private static final String FUNCTION_NAME = "put";
		private static final String KEY_OBJ = "obj";
		private static final YClass YCLASS_OBJ = YObject.yclass();

		public static final Put function = new Put();

		protected Put() {
			super(FUNCTION_NAME);
			addParameter(KEY_OBJ, YCLASS_OBJ, null);
			setReturnType( YObject.yclass() );
		}

		@Override
		public YObject run(Map<String, YObject> params/*, Map<String, YObject> env*/, YObject thisElement) {
			
			YContainer thisE = (YContainer) thisElement;
			YObject obj = params.get(KEY_OBJ);
			
			thisE.put(obj);
			
			return obj;
		}
	}		

}