package uk.co.zonetora.fj.passes;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

import uk.co.zonetora.fj.ast.analysis.DepthFirstAdapter;
import uk.co.zonetora.fj.ast.node.ACastTerm;
import uk.co.zonetora.fj.ast.node.AFieldAccessTerm;
import uk.co.zonetora.fj.ast.node.AMethodCallTerm;
import uk.co.zonetora.fj.ast.node.AObjectCreationNonLeftRecTerm;
import uk.co.zonetora.fj.ast.node.ATermListTermList;
import uk.co.zonetora.fj.ast.node.AThisNonLeftRecTerm;
import uk.co.zonetora.fj.ast.node.AVariableNonLeftRecTerm;
import uk.co.zonetora.fj.ast.node.Node;
import uk.co.zonetora.fj.ast.node.PTerm;
import uk.co.zonetora.fj.model.ArgumentName;
import uk.co.zonetora.fj.model.Cast;
import uk.co.zonetora.fj.model.ClassName;
import uk.co.zonetora.fj.model.FieldAccess;
import uk.co.zonetora.fj.model.FieldName;
import uk.co.zonetora.fj.model.Method;
import uk.co.zonetora.fj.model.MethodInvocation;
import uk.co.zonetora.fj.model.MethodName;
import uk.co.zonetora.fj.model.ObjectCreation;
import uk.co.zonetora.fj.model.Term;
import uk.co.zonetora.fj.model.Variable;

public class TermBuilder extends DepthFirstAdapter {

	final Deque<Term> termStack;
	final Deque<Deque<Term>> backupStack;
	
	public TermBuilder() {
		this.termStack = new ArrayDeque<Term>();
		this.backupStack = new ArrayDeque<Deque<Term>>();
	}
	
	public Term buildTerm(PTerm term) {
		term.apply(this);
		return termStack.pop();
	}
	
	@Override
	public void caseAVariableNonLeftRecTerm(AVariableNonLeftRecTerm node) {
		ArgumentName aname = new ArgumentName(node.getIdentifier().getText().trim());
		termStack.push(new Variable(aname));
	};

	@Override
	public void caseAThisNonLeftRecTerm(AThisNonLeftRecTerm node) {
		termStack.push(new Variable(Method.getThisArgName()));
	}
	
	@Override
	public void caseATermListTermList(ATermListTermList node) {
		node.getTerm().apply(this);
		for(Node n : (List<? extends Node>) node.getCommaTerm()) {
			n.apply(this);
		}
	}
	
	@Override
	public void caseAFieldAccessTerm(AFieldAccessTerm node) {
		node.getNonLeftRecTerm().apply(this);
		Term path = termStack.pop();
		
		FieldName field = new FieldName(node.getIdentifier().getText().trim());
		termStack.push(new FieldAccess(path, field));
	}
	
	@Override
	public void caseAMethodCallTerm(AMethodCallTerm node) {
		node.getNonLeftRecTerm().apply(this);
		Term path = termStack.pop();
		
		MethodName methodName = new MethodName(node.getIdentifier().getText().trim());
		
		preserveTermStack();
		node.getTermList().apply(this);
		List<Term> visitedTerms = restoreTermStack();
		
		termStack.push(new MethodInvocation(path, methodName, visitedTerms));
	}
	
	@Override
	public void caseAObjectCreationNonLeftRecTerm(AObjectCreationNonLeftRecTerm node) {
		ClassName className = new ClassName(node.getIdentifier().getText().trim());
		
		preserveTermStack();
		node.getTermList().apply(this);
		List<Term> visitedTerms = restoreTermStack();
		
		termStack.push(new ObjectCreation(className, visitedTerms));
	};
	
	@Override
	public void caseACastTerm(ACastTerm node) {
		ClassName className = new ClassName(node.getIdentifier().getText().trim());
		
		node.getNonLeftRecTerm().apply(this);
		
		Term castedTerm = termStack.pop();
		
		termStack.push(new Cast(className, castedTerm));
	}
	
	private void preserveTermStack() {
		backupStack.push(new ArrayDeque<Term>(termStack));
	}

	private List<Term> restoreTermStack() {
		Deque<Term> old = backupStack.pop();
		List<Term> current = new ArrayList<Term>(termStack);
		termStack.clear();
		termStack.addAll(old);
		return current;
	}

}
