package hu.swankey.ammo.common.script.yunits;

import hu.swankey.ammo.common.script.AmmoScriptException;
import hu.swankey.ammo.common.script.statements.Block;
import hu.swankey.ammo.common.yggdrasil.basics.YObject;
import hu.swankey.ammo.common.yggdrasil.definition.Definition;
import hu.swankey.ammo.common.yggdrasil.definition.MethodDefinition;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class YMethod extends YUnit /* implements Wrappable*/ {
	
	public static final int PARAMETER_INCOMPATIBLE = -1;
	public static final int PARAMETER_NOT_FOUND = -2;

	//private String name;
	private YClass returnType;
    private final HashMap<String, YClass> parameterList;
    private final Map<String, YClass> unmodifiedSignature;
    private final HashMap<String, YObject> defaultvalues;
    private final Block block;
//    private YClass ownerClass;
    
    private MethodDefinition definition;

    
    public YMethod(String name){
    	this(name, null, null, null, null);
    }
    
    public YMethod(String name, YClass returnType, HashMap<String, YClass> parameters, HashMap<String, YObject> defaultvalues, Block block) {
    	super(name);
        
    	if (name == null)
    		throw new NullPointerException("'name' cannot be null");
    	
    	if (parameters == null)
    		parameters = new HashMap<String, YClass>();
    	
    	//this.name = name;
    	this.returnType = returnType;
    	this.parameterList = parameters;
    	this.unmodifiedSignature = Collections.unmodifiableMap(parameters);
    	this.defaultvalues = defaultvalues;
    	this.block = block;
    }
    
    
    public YObject run(Map<String, YObject> parameters, YObject thisElement){
    	
    	if (parameters == null)
    		parameters = new HashMap<String, YObject>();
    		
    	//parameters.putAll(envrionment);
    	return block.evaulate(parameters, thisElement);
    };
    
    
//    public boolean isValid(Map<String, YClass> paramTypes, Map<String, YClass> envTypes, YObject thisElement) {
//    	
//    	if (paramTypes == null)
//    		paramTypes = new HashMap<String, YClass>();
//    		
//    	paramTypes.putAll(envTypes);
//    	return block == null || block.isValid(paramTypes, thisElement);
//    }
    
//    public static MethodType yclass(){
//    	return MethodType.singleton();
//    }
    
    
    public void addParameter(String name, YClass type, YObject defaultvalue){
    	
    	if (name == null)
    		throw new RuntimeException("Parameter name cannot be null.");
    	
    	if (type == null)
    		throw new RuntimeException("Parameter type cannot be null.");
    	
    	parameterList.put(name, type);
    	
    	if (defaultvalue != null)
    		defaultvalues.put(name, defaultvalue);
    }
    
    public void removeParameters(){
    	parameterList.clear();
    	defaultvalues.clear();
    }
    
    public Map<String, YClass> getParameters(){
    	return unmodifiedSignature;
    }
    
    
    public void setReturnType(YClass newReturnType){
    	returnType = newReturnType;
    }
    
    public YClass getReturnType(){
    	
//    	if (returnType == null)
//    		return YVoid.yclass();
//    	else
//    		return returnType;
    	
    	return returnType;
    }
    
//	@Override
	public void setWrapperElement(Definition wrapper) {
		this.definition = (MethodDefinition)wrapper;
	}    
    
	@Override
	public boolean hasWrapperElement() {
		return definition != null;
	}        
    
	@Override
	public MethodDefinition getWrapperElement() {
		if (definition == null)
			definition = MethodDefinition.yclass().create(this);
		return definition;
	}    


//    protected void compute(Complex location, Map<String, Element> params, Map<String, Element> envrionment) throws AmmoScriptException {
//
//    	if (params == null || params.size() == 0)
//    		return;
//
//        for (String paramName: params.keySet())
//        	params.put(paramName, computeParam(params.get(paramName), location, envrionment ));
//    }
    
//    protected YObject compute(Map<String, Expression> params, Map<String, YObject> env, YObject thisElement, String key) throws AmmoScriptException {
//    	
//    	YObject param = params.get(key).evaulate(env, thisElement);
//    	if (param == null)
//    		param = env.get(key);
//    	
//    	if (param == null)
//    		throw new RuntimeException("Variable not found: " + key);
//        
//        return compute(param, env, thisElement);
//    }
    
//    protected YObject compute(YObject element, Map<String, YObject> env, YObject thisElement){
//    	
//    	if (element instanceof SoftLink) {
//    		Container location = (thisElement instanceof Container ? (Container)thisElement : thisElement.getParent());
//    		
//    		YObject computed = ((SoftLink)element).getFinalTarget(location, env);
//    		if (computed == null)
//    			throw new AmmoScriptRuntimeException("Broken link: " + element.getPathString()
//    					+ "\n value: " + ((SoftLink)element).getValue()
//    					+ "\n location: " + thisElement.getPathString()
//    					+ "\n envrionment: " + env );
//    		return computed;
//    	}
//        
//        if (element instanceof MethodDefinition)
//        	return ((MethodDefinition) element).getWrapped().run(null, env, thisElement);
//
//        return element;
//    }


    protected void checkParams(Map<String, YObject> parameters) throws AmmoScriptException {

        if (parameters == null)
            return;

//        if (paramTypes == null || parameters.size() > paramTypes.size())
//            throw new AmmoScriptException("Too many parameters for function \"" + getName() + "\":" + details(parameters));
//
//        if (parameters == null || parameters.size() < paramTypes.size())
//            throw new AmmoScriptException("Not enought parameters for function \"" + getName() + "\":" + details(parameters));

//        for (int i = 0; i < parameters.size(); i++) {
//            IElement element = parameters.get(i);
//            if (!paramTypes.get(i).isTypeOf(element))
//                throw new AmmoScriptException("Type mismatch in function \"" + getName() + "\"" + details(parameters));
//        }
        
        for(String paramName: parameterList.keySet() ){
        	
        	YObject value = parameters.get(paramName);
        	if (value == null) {
        		
        		if (!defaultvalues.containsKey(paramName))
        			throw new AmmoScriptException("Missing parameter: '" + paramName + "'");
        		
        	} else {
        		
        		YClass wantedType = parameterList.get(paramName);        		
        		if (!wantedType.isTypeOf(value))
        			throw new AmmoScriptException("Type mismatch at parameter '" + paramName + "': "
        					+ wantedType.getName() +" expected, but " + value.getYClass().getName() + " found.");
        	}
        		
        }
    }

    
    public int calculateDistance( Map<String, YClass> signature2 ){
    	

    	
    	int distSum = 0;
    	
		for (String paramName: parameterList.keySet()) {
			
			int dist = PARAMETER_NOT_FOUND;
			
			if (signature2.containsKey(paramName)) {
				
				YClass expectedType = parameterList.get(paramName);
				YClass foundedType = signature2.get(paramName);
				
				dist = foundedType.distanceFromSuperior( expectedType );
			} 
			
			if (dist == PARAMETER_INCOMPATIBLE
					|| (dist == PARAMETER_NOT_FOUND && defaultvalues != null && !defaultvalues.containsKey(paramName) ))
				return PARAMETER_INCOMPATIBLE;
			
			distSum += dist;
		}
		
		return distSum;
		
    }
    
    public String toString(){
    	
    	// TODO: Van itt egy InvocationException-ünk
    	String str = getName() + "(";
	
    	Iterator<String> it = parameterList.keySet().iterator();
    	while (it.hasNext()) {
    		String paramName = it.next();
    		str += paramName + ":" + parameterList.get(paramName).getName();
    		
    		if (defaultvalues != null && defaultvalues.containsKey(paramName))
    			str += "(default: " + defaultvalues.get(paramName) + ")";
    			
    		if (it.hasNext()) str += " ";
    	}
	
    	str += ");";
	
    	return str;
    }


//	public String getName() {
//		return name;
//	}
	
//	public YClass getOwnerClass(){
//		return ownerClass;
//	}
//    
//	public void setOwnerClass(YClass ownerClass){
//		this.ownerClass = ownerClass;
//	}	
	
//    public static class MethodType extends FieldClass {
//
//        private static final String CLASS_NAME = "Method";
//        private static final String DEFAULT_VALUE = "";
//        private static MethodType SINGLETON; 
//
//    	private static MethodType singleton(){
//    		if (SINGLETON == null) {
//    			SINGLETON = new MethodType(CLASS_NAME);
//    			SINGLETON.addSuperior(Field.yclass());
//    		}
//    		return SINGLETON;
//    	}
//
//        protected MethodType(String name) {
//            super(name);
//        }
//
//
//
//        @Override
//        public Object getDefaultValue() {
//            return DEFAULT_VALUE;
//        }
//
//
//
//        /** Get the name of the type */
//        @Override
//        public String getName() {
//            return CLASS_NAME;
//        }
//
//    }
    
}
