package models.terms.meta;

import java.util.HashMap;
import java.util.Map;

import exceptions.CoefficientNotOneException;
import exceptions.SubstituteFailedException;
import exceptions.TooManyVariablesException;
import lombok.Getter;
import models.algebra.Expression;
import models.algebra.Symbol;
import models.algebra.Variable;
import models.terms.LinearRightNormalizedType;
import models.terms.RDLTerm;
import utils.ExpressionUitls;

@Getter
public abstract class MetaVariable extends MetaRDLTerm {

	protected Variable variableName;
	protected OrderConstraint constraint;
	protected Variable orderVariable;
	protected int orderConstant;
	protected Expression orderExpression;
 	
	protected MetaVariable(Symbol symbol, TermType termType, Variable name, OrderConstraint constraint, Expression order) {
		super(symbol, termType, 1);
		this.variableName = name;
		this.constraint = constraint;
		this.orderExpression = order;
		Map<Variable, Integer> coefficients = new HashMap<>();
		int constant = ExpressionUitls.getCoefficientAndConstantsFromExpression(order, coefficients, 1);
		if (coefficients.size() > 1) {
			// todo: create exception
			throw new TooManyVariablesException("Too many variables");
		}
		if (coefficients.size() == 1) {
			orderVariable = coefficients.keySet().iterator().next();
			if (coefficients.get(orderVariable) != 1) {
				throw new CoefficientNotOneException();
			}
		} else {
			orderVariable = null;
		}
		orderConstant = constant;
	}
	
	protected MetaVariable(Symbol symbol, TermType termType, Variable name, OrderConstraint constraint, Expression order, LinearRightNormalizedType linearRIghtNormalizedType) {
		super(symbol, termType, 1);
		this.variableName = name;
		this.constraint = constraint;
		this.orderExpression = order;
		Map<Variable, Integer> coefficients = new HashMap<>();
		int constant = ExpressionUitls.getCoefficientAndConstantsFromExpression(order, coefficients, 1);
		if (coefficients.size() > 1) {
			// todo: create exception
			throw new TooManyVariablesException("Too many variables");
		}
		if (coefficients.size() == 1) {
			orderVariable = coefficients.keySet().iterator().next();
			if (coefficients.get(orderVariable) != 1) {
				throw new CoefficientNotOneException();
			}
		} else {
			orderVariable = null;
		}
		orderConstant = constant;
		this.linearRightNormalizedType = linearRIghtNormalizedType;
	}

	@Override
	public boolean isVariable() {
		return true;
	}

	@Override
	public boolean isMatchedBy(RDLTerm another, Map<Variable, RDLTerm> binding,
			Map<Variable, OrderVariableConstraint> orderConstraint) {
		if (! this.termType.getBaseTermClass().isAssignableFrom(another.getClass())) {
			return false;
		}
		
		if (! orderConstraintCheck(another, orderConstraint)) {
			return false;
		}
		
		if (! islinearRightNormalizedMatchedBy(another)) {
			return false;
		}
		
		if (! binding.containsKey(this.variableName)) {
			binding.put(this.variableName, another);
			return true;
		}
		return binding.get(this.variableName).equals(another);
	}
	
	@Override
	public RDLTerm substitute(Map<Variable, RDLTerm> binding) {
		if (binding.containsKey(variableName)) {
			return binding.get(variableName);
		}
		throw new SubstituteFailedException();
	}

	protected boolean orderConstraintCheck(RDLTerm another, Map<Variable, OrderVariableConstraint> orderConstraints) {
		if (orderVariable == null) {
			switch (constraint) {
			case ANY:
				return true;
			case EQ:
				return another.getOrder() == orderConstant;
			case GE:
				return another.getOrder() >= orderConstant;
			case GT:
				return another.getOrder() > orderConstant;
			case LE:
				return another.getOrder() <= orderConstant;
			case LT:
				return another.getOrder() < orderConstant;
			}
		} else {
			if (! orderConstraints.containsKey(orderVariable)) {
				orderConstraints.put(orderVariable, new OrderVariableConstraint());
			}
			var orderVarConst = orderConstraints.get(orderVariable);
			return orderVarConst.setConstraint(another.getOrder() - orderConstant, constraint);
		}
		return false;
	}
	
	
	
	
	@Override
	public String toString() {
		return variableName.toString();
	}

	@Override
	public String toStringWithOrder() {
		return toString() +getOrderString();
	}
	
	protected String getOrderString() {
		switch(constraint) {
		case ANY:
			return "(*)";
		case EQ:
			return "(= " + orderExpression.toString() + ")";
		case GE:
			return "(>= " + orderExpression.toString() + ")";
		case GT:
			return "(> " + orderExpression.toString() + ")";
		case LE:
			return "(<= " + orderExpression.toString() + ")";
		case LT:
			return "(< " + orderExpression.toString() + ")";
		}
		return "";
	}
	
	@Override
	public boolean equals(Object another) {
		if (another instanceof MetaVariable) {
			return false;
		}
		MetaVariable anotherVar = (MetaVariable) another;
		return super.equals(another)
				&& variableName.equals(another) 
				&& constraint == anotherVar.getConstraint() 
				&& orderVariable.equals(anotherVar.getOrderVariable())
				&& orderConstant == anotherVar.getOrderConstant();
	}

	@Override
	public int hashCode() {
		return (termType.name() + toString()).hashCode();
	}

	@Override
	public Object clone() {
		// TODO 自動生成されたメソッド・スタブ
		return super.clone();
	}

	
	
}
