package hu.swankey.ammo.common.progstruct2;

import hu.swankey.ammo.common.yggdrasil.basics.YObject;
import hu.swankey.ammo.common.yggdrasil.basics.YObject.YClass;
import hu.swankey.ammo.common.yggdrasil.script.AmmoScriptException;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DynamicCall extends Expression {
    
    private final Map<String, Expression> parameters;
    
    private final Expression target;
    private final String methodName;

    public DynamicCall(Expression target, String methodName,  Map<String, Expression> parameters){
    	this.target = target;
    	this.methodName = methodName;
    	this.parameters = parameters;
    }
    

	@Override
	public boolean isValid(Map<String, YObject> envrionment, YObject thisElement) {
        return getTarget(envrionment, thisElement) != null;
	}
    
	@Override
	public YObject evaulate(Map<String, YObject> envrionment, YObject thisElement) {
		
		// TODO: A célobjektum keresését még a régi kód végzi, ami nem kezeli a minősítést (tehát csak a thisElement-et nézi)
		
		HashMap<String, YObject> computedParams = computeAll(envrionment, thisElement);
		YObject targetObject = thisElement;
        Method targetMethod = targetObject.getYClass().getMethod(methodName, computedParams);
        
        if (targetMethod == null)
        	throw new AmmoScriptException(
        			"Invalid call '" + toStringEvaulated(computedParams) + "' in '" + thisElement.getPathString() + "'! \n"
        			+ "(" + targetObject.getYClass() + "." + methodName + "(...) )" 
                    + alternativesToString(thisElement.getYClass().getMethods(methodName)) );

    	return targetMethod.run(computedParams, envrionment, thisElement);
	}
	
	
	private Method getTarget(Map<String, YObject> envrionment, YObject thisElement){
		
		Map<String, YClass> paramTypes = new HashMap<String, YClass>();
    	for(String key: parameters.keySet())
    		paramTypes.put(key, parameters.get(key).getYClass(envrionment, thisElement) );

    	
    	YClass targetClass = target.getYClass(envrionment, thisElement);
		Method targetMethod = targetClass.getMethodBySignature(methodName, paramTypes);
		
		return targetMethod;
	}
	
	
	private HashMap<String, YObject> computeAll(Map<String, YObject> env, YObject thisElement){
		HashMap<String, YObject> computedParams = new HashMap<String, YObject>();		
		for(String paramName: parameters.keySet())
			computedParams.put(paramName, compute(parameters, env, thisElement, paramName));		
		return computedParams;
	}
	
	
	
	private String alternativesToString(List<Method> alternatives){
		String str;
	
		if (alternatives == null)
			str = "There are no methods with that name.";
		else {
			str = "There are other method(s) with that name: ";
	
		for(Method funct: alternatives)
			str += "\n - " + funct;
		}
	
		return str;
  	}	
	
	@Override
	public String toString(){
		return toStringGeneral(parameters);
	}

  	private String toStringGeneral(Map<String, Expression> params2){
  		String str = target + "." + methodName + "(";
  		
  		for (String key: params2.keySet())
  			str += key + ":" + params2.get(key).toString();
  		
  		return str + ")";
  	}
  	
  	private String toStringEvaulated(Map<String, YObject> params2){
  		String str = target + "." + methodName + "(";
  		
  		for (String key: params2.keySet())
  			str += key + ":" + params2.get(key).getYClass();
  		
  		return str + ")";
  	}


	@Override
	public YClass getYClass(Map<String, YObject> envrionment, YObject thisElement) {
		return getTarget(envrionment, thisElement).getReturnType();
	}



}