package com.github.chrisblutz.trinity.lang.procedures;

import com.github.chrisblutz.trinity.lang.TYObject;
import com.github.chrisblutz.trinity.lang.errors.Errors;
import com.github.chrisblutz.trinity.lang.scope.TYRuntime;
import com.github.chrisblutz.trinity.lang.types.arrays.TYArray;
import com.github.chrisblutz.trinity.lang.types.procedures.TYProcedureObject;

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


/**
 * @author Christopher Lutz
 */
public class TYProcedure {
    
    private ProcedureAction procedureAction;
    private List<String> mandatoryParameters = new ArrayList<>();
    private Map<String, ProcedureAction> optionalParameters = new HashMap<>();
    private String blockParameter = null, overflowParameter = null;
    
    public TYProcedure(ProcedureAction procedureAction) {
        
        this(procedureAction, new ArrayList<>(), new HashMap<>(), null);
    }
    
    public TYProcedure(ProcedureAction procedureAction, List<String> mandatoryParameters, Map<String, ProcedureAction> optionalParameters, String blockParameter) {
        
        this(procedureAction, mandatoryParameters, optionalParameters, blockParameter, null);
    }
    
    public TYProcedure(ProcedureAction procedureAction, List<String> mandatoryParameters, Map<String, ProcedureAction> optionalParameters, String blockParameter, String overflowParameter) {
        
        this.procedureAction = procedureAction;
        this.mandatoryParameters = mandatoryParameters;
        this.optionalParameters = optionalParameters;
        this.blockParameter = blockParameter;
        this.overflowParameter = overflowParameter;
    }
    
    public ProcedureAction getProcedureAction() {
        
        return procedureAction;
    }
    
    public List<String> getMandatoryParameters() {
        
        return mandatoryParameters;
    }
    
    public Map<String, ProcedureAction> getOptionalParameters() {
        
        return optionalParameters;
    }
    
    public String getBlockParameter() {
        
        return blockParameter;
    }
    
    public void setBlockParameter(String blockParameter) {
        
        this.blockParameter = blockParameter;
    }
    
    public String getOverflowParameter() {
        
        return overflowParameter;
    }
    
    public void setOverflowParameter(String overflowParameter) {
        
        this.overflowParameter = overflowParameter;
    }
    
    public TYObject call(TYRuntime runtime, TYProcedure subProcedure, TYRuntime procedureRuntime, TYObject thisObj, TYObject... params) {
        
        for (String opt : getOptionalParameters().keySet()) {
            
            ProcedureAction action = getOptionalParameters().get(opt);
            runtime.setVariable(opt, action.onAction(runtime, TYObject.NONE));
        }
        
        boolean blockFlag = true;
        
        if (getBlockParameter() != null) {
            
            TYObject obj;
            
            if (subProcedure != null) {
                
                obj = new TYProcedureObject(subProcedure, procedureRuntime);
                runtime.setVariable(getBlockParameter(), obj);
                blockFlag = false;
                
            } else if (!runtime.hasVariable(getBlockParameter())) {
                
                obj = new TYProcedureObject(new TYProcedure((runtime1, thisObj1, params1) -> TYObject.NONE), new TYRuntime());
                runtime.setVariable(getBlockParameter(), obj);
            }
        }
        
        runtime.setProcedure(subProcedure);
        
        List<String> varNames = new ArrayList<>();
        varNames.addAll(getMandatoryParameters());
        varNames.addAll(getOptionalParameters().keySet());
        
        List<TYObject> overflow = new ArrayList<>();
        
        int paramPos, nameIndex = 0;
        for (paramPos = 0; paramPos < params.length; paramPos++) {
            
            TYObject param = params[paramPos];
            
            if (blockFlag && getBlockParameter() != null && param instanceof TYProcedureObject) {
                
                runtime.setVariable(getBlockParameter(), param);
                
            } else if (varNames.size() > nameIndex) {
                
                runtime.setVariable(varNames.get(nameIndex++), param);
                
            } else if (getOverflowParameter() != null) {
                
                overflow.add(param);
                
            } else {
                
                Errors.throwError("Trinity.Errors.InvalidArgumentNumberError", "Procedure takes " + getMandatoryParameters().size() + " parameter(s).", runtime);
            }
        }
        
        if (varNames.size() > nameIndex) {
            
            Errors.throwError("Trinity.Errors.InvalidArgumentNumberError", "Procedure takes " + getMandatoryParameters().size() + " parameter(s).", runtime);
        }
        
        if (getOverflowParameter() != null) {
            
            runtime.setVariable(getOverflowParameter(), new TYArray(overflow));
        }
        
        return getProcedureAction().onAction(runtime, thisObj, params);
    }
}

@@ -3763,24 +3763,78 @@
 .keySet());%0A
+        int optSize = getOptionalParameters().size();%0A
         %0A   
@@ -3822,32 +3822,32 @@
 ize();%0A        %0A
-
         List%3CTYO
@@ -4417,32 +4417,242 @@
                %0A
+            %7D else if (paramPos == params.length - 1 && overflow.isEmpty() && param instanceof TYArray) %7B%0A                %0A                overflow.addAll(((TYArray) param).getInternalList());%0A                %0A
             %7D el
@@ -4988,32 +4988,32 @@
      %7D%0A        %0A
-
         if (varN
@@ -5015,32 +5015,42 @@
 (varNames.size()
+ - optSize
  %3E nameIndex) %7B%0A

Allow arrays to be passed in for overflow params, and account for optional params