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 hu.swankey.ammo.common.yggdrasil.definition.MethodDefinition;

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 boolean isValid(Map<String, YClass> envTypes, YObject thisElement) {
//		Method target = getTarget(envTypes, thisElement);
//		
//		Map<String, YClass> paramTypes = new HashMap<String, YClass>();
//		for (String id: parameters.keySet())
//			paramTypes.put(id, parameters.get(id).getYClass(envTypes, thisElement));
//		
//        return target != null && target.isValid(paramTypes, envTypes, thisElement);
//	}
    
	@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 Method getTarget(Map<String, YClass> paramTypes, 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(paramTypes, thisElement);
//    	if (targetClass == null) return null;
//    	
//		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 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;
      }
      
      if (element instanceof MethodDefinition)
          return ((MethodDefinition) element).getWrapped().run(null, thisElement);

      return element;
  }
    
//  private HashMap<String, YObject> computeAll(Map<String, YObject> env, YObject thisElement){
//      HashMap<String, YObject> computedParams = new HashMap<String, YObject>();       
//      for(String paramName: params.keySet())
//          computedParams.put(paramName, compute(params, env, thisElement, paramName));        
//      return computedParams;
//  }	
	
	
	
	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 + ")";
  	}


//	@Override
//	public YClass getYClass(Map<String, YClass> envrionment, YObject thisElement) {
//		Method target = getTarget(envrionment, thisElement);
//		if (target != null)
//			return target.getReturnType();
//		else
//			return null;
//	}



}