package Token;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Stack;

import Main.Expression;
import Automata.Automaton;


public class Function extends Token {
	Automaton A;
	String name;
	public Function(int position,String name,Automaton A,int number_of_arguments) throws Exception{
		this.name = name;
		setArity(number_of_arguments);
		setPositionInPredicate(position);
		this.A = A;
		if(A.getArity() != getArity())throw new Exception("function " + name + " requires " + A.getArity() +" arguments: char at " + getPositionInPredicate());
	}
	public String toString(){
		return name;
	}
	public void act(Stack<Expression> S) throws Exception{
		if(S.size() < getArity())throw new Exception("function " + name + " requires " + getArity()+ " arguments");
		Stack<Expression> temp = new Stack<Expression>();
		List<Expression> args = new ArrayList<Expression>();
		for(int i = 0; i < getArity();i++){
			temp.push(S.pop());
		}
		String stringValue = name+"(";
		Automaton M = new Automaton(true);
		List<String> identifiers = new ArrayList<String>();
		List<String> quantify = new ArrayList<String>();
		for(int i = 0 ; i < getArity();i++){
			args.add(temp.pop());
			Expression currentArg = args.get(i);
			if(i == 0)
				stringValue += args.get(i);
			else
				stringValue += ","+args.get(i);
			
			/*if(args.get(i).is(Type.arithmetic) && A.NS.get(i) == null)
				throw new Exception("argument "+ (i+1) +" of function " + name + " cannot be of type arithmetic");
			if(!args.get(i).is(Type.arithmetic) && !args.get(i).is(Type.variable) && !args.get(i).is(Type.numberLiteral))
				throw new Exception("argument "+ (i+1) +" of function " + name + " cannot be of type " +args.get(i).getType());	*/		
			
			switch(currentArg.T){
			case variable:
				identifiers.add(currentArg.identifier);
				break;
			case arithmetic:
				identifiers.add(currentArg.identifier);
				M = M.and(currentArg.M);
				quantify.add(currentArg.identifier);
				break;
			case numberLiteral:
				Automaton constant = currentArg.base.get(currentArg.constant);
				String id = getUniqueString();
				constant.bind(id);
				identifiers.add(id);
				quantify.add(id);
				M = M.and(constant);
				break;
			case automaton:
				if(currentArg.M.getArity() != 1){
					throw new Exception("argument " + (i+1) + " of function " + name + " cannot be an automaton with != 1 inputs");
				}
				if(!currentArg.M.isBound()){
					throw new Exception("argument " + (i+1) + " of function " + name + " cannot be an automaton with unlabeled input");					
				}
				M = M.and(currentArg.M);
				identifiers.add(currentArg.M.getLabel().get(0));
				break;
			default:
				throw new Exception("argument "+ (i+1) +" of function " + name + " cannot be of type " +currentArg.getType());			
			}	
			
		}
		A.bind(identifiers);
		A = A.and(M);
		A.quantify(new HashSet<String>(quantify));
		stringValue += ")";
		S.push(new Expression(stringValue,A));
	}
}
