package models.visualModel;

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

import models.algebra.Expression;
import models.algebra.Symbol;
import models.algebra.Term;
import models.algebra.Variable;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.ResourcePath;
import models.dataConstraintModel.StateTransition;
import models.dataFlowModel.DataTransferChannel;

public class FormulaChannel extends DataTransferChannel {
	private Symbol defaultOperator = null;
	private String formula = null;
	private Expression formulaRhs = null;

	public FormulaChannel(String channelName, Symbol defaultOperator) {
		super(channelName);
		this.defaultOperator = defaultOperator;
	}
	
	public void addChannelMemberAsInput(ChannelMember channelMember) {
//		StateTransition st = new StateTransition();
//		st.setCurStateExpression(new Variable(channelMember.getIdentifierTemplate().getResourceName() + "1"));
//		st.setNextStateExpression(new Variable(channelMember.getIdentifierTemplate().getResourceName() + "2"));
//		channelMember.setStateTransition(st);
		super.addChannelMemberAsInput(channelMember);
		if (formula != null && getInputChannelMembers().size() > 1) {
			formula += " " + defaultOperator + " " + channelMember.getResource().getLeafResourceName();
			if (formulaRhs != null) {
				if (formulaRhs instanceof Variable) {
					Term newTerm = new Term(defaultOperator);
					newTerm.addChild(formulaRhs);
					newTerm.addChild(new Variable(channelMember.getResource().getLeafResourceName()), true);
					formulaRhs = newTerm;
				} else if (formulaRhs instanceof Term) {
					Term newTerm = new Term(defaultOperator);
					newTerm.addChild(formulaRhs);
					newTerm.addChild(new Variable(channelMember.getResource().getLeafResourceName()));
					formulaRhs = newTerm;						
				}
			}
		} else {
			if (formula == null) formula = "";
			formula += channelMember.getResource().getLeafResourceName();
			formulaRhs = new Variable(channelMember.getResource().getLeafResourceName());
		}
		if (formulaRhs != null) {
			setFormulaTerm(formulaRhs);
		}
	}

	public void addChannelMemberAsOutput(ChannelMember channelMember) {
//		StateTransition st = new StateTransition();
//		st.setCurStateExpression(new Variable(channelMember.getIdentifierTemplate().getResourceName() + "1"));
//		channelMember.setStateTransition(st);
		super.addChannelMemberAsOutput(channelMember);
		if (getOutputChannelMembers().size() == 1) {
			if (formula == null) formula = "";
			if (!formula.contains("=")) {
				formula = channelMember.getResource().getLeafResourceName() + " = " + formula;
			}
		}
		if (formulaRhs != null) {
			setFormulaTerm(formulaRhs);				
		}
	}

	public Symbol getDefaultOperator() {
		return defaultOperator;
	}

	public void setDefaultOperator(Symbol defaultOperator) {
		this.defaultOperator = defaultOperator;
	}
	
	public void setFormulaTerm(Expression rhs) {
		formulaRhs = rhs;
		Collection<Variable> variables;
		if (rhs instanceof Variable) {
			variables = new ArrayList<>();
			variables.add((Variable) rhs);
		} else if (rhs instanceof Term) {
			variables = ((Term) rhs).getVariables().values();
		} else {
			return;
		}
		Map<ResourcePath, Variable> curStates = new HashMap<>();
		Map<ResourcePath, Variable> nextStates = new HashMap<>();
		Map<Variable, Variable> resToNextVar = new HashMap<>();
		for (ChannelMember cm: this.getInputChannelMembers()) {
			ResourcePath id = cm.getResource();
			String resName = id.getLeafResourceName();
			Variable curVar = new Variable(resName + "1");
			Variable nextVar = new Variable(resName + "2");
			curStates.put(id, curVar);
			nextStates.put(id, nextVar);
			for (Variable var: variables) {
				if (var.getName().equals(resName)) {
					resToNextVar.put(var, nextVar);
					break;
				}
			}
		}
		Symbol update = new Symbol("update");
		update.setArity(resToNextVar.keySet().size());
		for (ChannelMember cm: getInputChannelMembers()) {
			ResourcePath id = cm.getResource();
			StateTransition st = new StateTransition();
			st.setCurStateExpression(curStates.get(id));
			st.setNextStateExpression(nextStates.get(id));
			Term message = new Term(update);
			for (Variable var: resToNextVar.values()) {
				message.addChild(var);
			}
			st.setMessageExpression(message);
			cm.setStateTransition(st);
		}
		
		if (rhs instanceof Variable) {
			rhs = resToNextVar.get((Variable) rhs);
		} else if (rhs instanceof Term) {
			formulaRhs = rhs;
			for (Variable var: resToNextVar.keySet()) {
				rhs = ((Term) rhs).substitute(var, resToNextVar.get(var));
			}
		}
		for (ChannelMember cm: getOutputChannelMembers()) {
			ResourcePath id = cm.getResource();
			StateTransition st = new StateTransition();
			String resName = id.getLeafResourceName();
			Variable curVar = new Variable(resName + "1");
			st.setCurStateExpression(curVar);
			st.setNextStateExpression(rhs);
			Term message = new Term(update);
			for (Variable var: resToNextVar.values()) {
				message.addChild(var);
			}
			st.setMessageExpression(message);
			cm.setStateTransition(st);
		}
	}
	
	public Expression getFormulaTerm() {
		return formulaRhs;
	}
	
	public void setFormula(String formula) {
		this.formula = formula;
	}
	
	public String getFormula() {
		return formula;
	}
}
