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

import hu.swankey.ammo.common.script.AmmoScriptException;
import hu.swankey.ammo.common.script.AmmoScriptRuntimeException;
import hu.swankey.ammo.common.script.yunits.YMethod;
import hu.swankey.ammo.common.yggdrasil.basics.SoftLink;
import hu.swankey.ammo.common.yggdrasil.basics.YContainer;
import hu.swankey.ammo.common.yggdrasil.basics.YObject;

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

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

    public Call(Expression target, String methodName,  Map<String, Expression> parameters){
    	this.target = target;
    	this.methodName = methodName;
    	this.parameters = parameters;
    }
    
    
	@Override
	public YObject evaulate(Map<String, YObject> envrionment, YObject thisElement) {
				
		HashMap<String, YObject> computedParams = computeAll(envrionment, thisElement);
		YObject targetObject = target.evaulate(envrionment, thisElement);
        YMethod targetMethod = targetObject.getYClass().getMethod(methodName, computedParams);
        
        if (targetMethod == null)
        	throw new AmmoScriptException(
        			"Invalid call '" + toStringEvaulated(computedParams)// + "' from '" + thisElement.getPathString() + " (" + thisElement.getYClass() + ")'! \n"
        			+ "\n Target method: " + targetObject.getYClass() + "." + methodName + "(...)." 
                    + "\n" + alternativesToString(targetObject.getYClass().getAllMethods(methodName)) );

    	return targetMethod.run(computedParams/*, envrionment*/, targetObject);
	}
	
	
	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 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);
  }   
  
  private YObject compute(YObject element, Map<String, YObject> env, YObject thisElement){
      
      if (element instanceof SoftLink) {
          YContainer location = (thisElement instanceof YContainer ? (YContainer)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;
      }
      
      return element;
  }
    
	
	private String alternativesToString(List<YMethod> alternatives){
		String str;
	
		if (alternatives == null || alternatives.isEmpty())
			str = "There are no methods with that name.";
		else {
			str = "Similar methods in that class: ";
	
		for(YMethod 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 + "(";
  		
  		Iterator<String> it = params2.keySet().iterator();
  		while (it.hasNext()) {
  			String key = it.next();
  			str += key + ":" + params2.get(key).getYClass();
  			if (it.hasNext()) str += " ";
  		}
  		
  		return str + ")";
  	}


}