package models.dataConstraintModel;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;

import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.InvalidMessage;
import models.algebra.Position;
import models.algebra.Term;
import models.algebra.ValueUndefined;
import models.algebra.Variable;
import models.dataFlowModel.ResolvingMultipleDefinitionIsFutureWork;

public class StateTransition {
	private Expression curStateExpression = null;
	private Expression nextStateExpression = null;
	private Expression messageExpression = null;
	
	public Expression getCurStateExpression() {
		return curStateExpression;
	}
	
	public void setCurStateExpression(Expression curStateExpression) {
		this.curStateExpression = curStateExpression;
	}
	
	public Expression getNextStateExpression() {
		return nextStateExpression;
	}
	
	public void setNextStateExpression(Expression nextStateExpression) {
		this.nextStateExpression = nextStateExpression;
	}

	public Expression getMessageExpression() {
		return messageExpression;
	}

	public void setMessageExpression(Expression messageExpression) {
		this.messageExpression = messageExpression;
	}
	
	public boolean isRightUnary() {
		for (Variable var: curStateExpression.getVariables().values()) {
			if (nextStateExpression.contains(var)) return false;
		}
		return true;
	}
	
	public boolean isRightPartial() {
		for (Variable var: curStateExpression.getVariables().values()) {
			if (messageExpression.contains(var)) return true;
		}
		if (isRightUnary()) return false;
		for (Variable var: messageExpression.getVariables().values()) {
			if (nextStateExpression.contains(var)) return true;
		}
		return false;
	}

	public Expression deriveMessageConstraintFor(Expression curStateValue, Expression nextStateValue, Set<Position> substitutedPositions) throws InvalidMessage, ResolvingMultipleDefinitionIsFutureWork {
		HashMap<Variable, ArrayList<Expression>> bindings = new HashMap<>();
		
		Expression curStateTerm = getCurStateExpression();
		HashMap<Position, Variable> curStateVars = curStateTerm.getVariables();		
		for (Entry<Position, Variable> curStateVarEnt: curStateVars.entrySet()) {
			Variable var = curStateVarEnt.getValue();
			Position varPos = curStateVarEnt.getKey();
			Expression valueCalc = curStateTerm.getInverseMap(curStateValue, varPos);
			if (valueCalc != null) {
				if (valueCalc instanceof Term && !(valueCalc instanceof Constant)) {
					valueCalc = ((Term) valueCalc).reduce();
				}
				ArrayList<Expression> values = bindings.get(var);
				if (values == null) {
					values = new ArrayList<Expression>();
					bindings.put(var, values);
				}
				values.add(valueCalc);
			}
		}
		
		Expression nextStateTerm = (Expression) getNextStateExpression().clone();
		for (Variable var: bindings.keySet()) {
			HashMap<Position, Variable> vars2 = nextStateTerm.getVariables();
			for (Variable var2: vars2.values()) {
				if (var.equals(var2) && bindings.get(var).size() == 1) {
					if (nextStateTerm instanceof Term) {
						nextStateTerm = ((Term) nextStateTerm).substitute(var, bindings.get(var).get(0));
					} else if (nextStateTerm instanceof Variable && nextStateTerm.equals(var)) {
						nextStateTerm = bindings.get(var).get(0);
					}
				}
			}
		}
		
		HashMap<Position, Variable> nextStateVars = nextStateTerm.getVariables();
		for (Entry<Position, Variable> nextStateVarEnt: nextStateVars.entrySet()) {
			Variable var = nextStateVarEnt.getValue();
			Position varPos = nextStateVarEnt.getKey();
			Expression valueCalc = nextStateTerm.getInverseMap(nextStateValue, varPos);
			if (valueCalc != null) {
				if (valueCalc instanceof Term) {
					valueCalc = ((Term) valueCalc).reduce();
				}
				ArrayList<Expression> values = bindings.get(var);
				if (values == null) {
					values = new ArrayList<Expression>();
					bindings.put(var, values);
				}
				values.add(valueCalc);
			}
		}
		
		Expression messageTerm = getMessageExpression();
		return substituteValues(messageTerm, bindings, substitutedPositions);
	}

	public Expression deriveMessageConstraintFor(Expression curStateValue, Set<Position> substitutedPositions) throws InvalidMessage, ResolvingMultipleDefinitionIsFutureWork {
		HashMap<Variable, ArrayList<Expression>> bindings = new HashMap<>();
		
		Expression curStateTerm = getCurStateExpression();
		HashMap<Position, Variable> curStateVars = curStateTerm.getVariables();		
		for (Entry<Position, Variable> curStateVarEnt: curStateVars.entrySet()) {
			Variable var = curStateVarEnt.getValue();
			Position varPos = curStateVarEnt.getKey();
			Expression valueCalc = curStateTerm.getInverseMap(curStateValue, varPos);
			if (valueCalc != null) {
				if (valueCalc instanceof Term && !(valueCalc instanceof Constant)) {
					valueCalc = ((Term) valueCalc).reduce();
				}
				ArrayList<Expression> values = bindings.get(var);
				if (values == null) {
					values = new ArrayList<Expression>();
					bindings.put(var, values);
				}
				values.add(valueCalc);
			}
		}
		
		Expression messageTerm = getMessageExpression();
		return substituteValues(messageTerm, bindings, substitutedPositions);
	}

	private Expression substituteValues(Expression messageTerm, HashMap<Variable, ArrayList<Expression>> bindings, Set<Position> substitutedPositions)
			throws InvalidMessage, ResolvingMultipleDefinitionIsFutureWork {
		if (!(messageTerm instanceof Term) && !(messageTerm instanceof Variable)) throw new InvalidMessage();
		HashMap<Position, Variable> messageVars = messageTerm.getVariables();
		for (Entry<Position, Variable> varEnt: messageVars.entrySet()) {
			Variable var = varEnt.getValue();
			if (bindings.get(var) != null) {
				if (bindings.get(var).size() > 1) throw new ResolvingMultipleDefinitionIsFutureWork();
				if (messageTerm instanceof Term) {
					substitutedPositions.add(varEnt.getKey());
					messageTerm = ((Term) messageTerm).substitute(var, bindings.get(var).iterator().next());
				} else if (messageTerm instanceof Variable) {
					if (messageTerm.equals(var)) {
						substitutedPositions.add(new Position());
						return bindings.get(var).iterator().next();
					}
				}
			}
		}
		return messageTerm;
	}

	public Expression deriveNextStateExpressionFor(Expression curStateValue, Term concreteMessage) 
			throws ResolvingMultipleDefinitionIsFutureWork, ValueUndefined {
		HashMap<Variable, Expression> bindings = new HashMap<>();

		Expression curStateTerm = getCurStateExpression();
		HashMap<Position, Variable> curStateVars = curStateTerm.getVariables();
		for (Entry<Position, Variable> curStateVarEnt: curStateVars.entrySet()) {
			Variable var = curStateVarEnt.getValue();
			Position varPos = curStateVarEnt.getKey();
			Expression valueCalc = curStateTerm.getInverseMap(curStateValue, varPos);
			if (valueCalc != null) {
				if (valueCalc instanceof Term && !(valueCalc instanceof Constant)) {
					valueCalc = ((Term) valueCalc).reduce();
				}
				if (bindings.get(var) != null) throw new ResolvingMultipleDefinitionIsFutureWork();
				bindings.put(var, valueCalc);
			}
		}
		
		Expression messageTerm = getMessageExpression();
		HashMap<Position, Variable> messageVars = messageTerm.getVariables();
		if (concreteMessage != null) {
			for (Entry<Position, Variable> messageVarEnt: messageVars.entrySet()) {
				Variable var = messageVarEnt.getValue();
				Position varPos = messageVarEnt.getKey();
				Expression valueCalc = concreteMessage.getSubTerm(varPos);
				if (valueCalc != null) {
					if (bindings.get(var) != null) throw new ResolvingMultipleDefinitionIsFutureWork();
					bindings.put(var, valueCalc);
				}
			}
		}
		
		Expression nextStateTerm = getNextStateExpression();
		if (nextStateTerm instanceof Variable) {
			nextStateTerm = bindings.get((Variable) nextStateTerm);
			if (nextStateTerm == null) throw new ValueUndefined();
		} else {
			for (Variable var: bindings.keySet()) {
				nextStateTerm = ((Term) nextStateTerm).substitute(var, bindings.get(var));
			}
		}
		if (nextStateTerm instanceof Term && !(nextStateTerm instanceof Constant)) {
			nextStateTerm = ((Term) nextStateTerm).reduce();
		}
		return nextStateTerm;
	}
}
