package inference.equivalence;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import exceptions.CoefficientNotOneException;
import exceptions.SubstituteFailedException;
import exceptions.TooManyVariablesException;
import lombok.Getter;
import models.algebra.Expression;
import models.algebra.Variable;
import models.terms.RDLTerm;
import models.terms.meta.MetaRDLTerm;
import models.terms.meta.MetaVariable;
import models.terms.meta.OrderVariableConstraint;
import utils.ExpressionUitls;
import utils.Permutation;

@Getter
public class MetaSemanticEquivalenceRelation {
	
	private MetaRDLTerm leftSideHand;
	private MetaRDLTerm rightSideHand;
	private Expression order;
	private Variable orderVariable;
	private int orderConstant = 0;
	
	public MetaSemanticEquivalenceRelation(MetaRDLTerm lsh, MetaRDLTerm rsh, Expression order) {
		leftSideHand = lsh;
		rightSideHand = rsh;
		this.order = order;
		Map<Variable, Integer> coefficients = new HashMap<>(); 
		orderConstant = 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;
		}
	}
	
	public boolean isMatchedBy(SemanticEquivalenceRelation relation) {
		Map<Variable, RDLTerm> binding = new HashMap<>();
		Map<Variable, OrderVariableConstraint> orderConstraint = new HashMap<>();
		return isMatchedBy(relation, binding, orderConstraint);
	}
	
	public boolean isMatchedBy(SemanticEquivalenceRelation relation, Map<Variable, RDLTerm> binding, Map<Variable, OrderVariableConstraint> orderConstraint) {
		return leftSideHand.isMatchedBy(relation.getLeftSideHand(), binding, orderConstraint)
					&& rightSideHand.isMatchedBy(relation.getRightSideHand(), binding, orderConstraint);
	}
	
	public Set<SemanticEquivalenceRelation> substitute(Map<Variable, RDLTerm> binding, Map<Variable, OrderVariableConstraint> orderConstraint, Collection<RDLTerm> existedTerms) {
		Set<SemanticEquivalenceRelation> result = new HashSet<>();
		Map<Variable, MetaVariable> allVariables = new HashMap<>();
		for(MetaVariable variable : leftSideHand.getAllVariables()) {
			allVariables.put(variable.getVariableName(), variable);
		}
		for(MetaVariable variable : rightSideHand.getAllVariables()) {
			allVariables.put(variable.getVariableName(), variable);
		}
		for (Variable variable : binding.keySet()) {
			allVariables.remove(variable);
		}
		List<Variable> leftVariables = new ArrayList<>(allVariables.keySet());
		for(List<RDLTerm> useTerms: Permutation.permutation(existedTerms, leftVariables.size())) {
			Map<Variable, RDLTerm> tempBinding = new HashMap<>();
			tempBinding.putAll(binding);
			boolean flg = false;
			for (int i = 0; i < leftVariables.size(); i++) {
				if (!allVariables.get(leftVariables.get(i)).isMatchedBy(useTerms.get(i))) {
					flg = true;
					break;
				}
				tempBinding.put(leftVariables.get(i), useTerms.get(i));
			}
			if (flg) {
				continue;
			}
			RDLTerm leftTerm = leftSideHand.substitute(tempBinding);
			RDLTerm rightTerm = rightSideHand.substitute(tempBinding);
			Map<Variable, OrderVariableConstraint> tempOrderConstraint = new HashMap<>();
			for(Variable key : orderConstraint.keySet()) {
				tempOrderConstraint.put(key, (OrderVariableConstraint)orderConstraint.get(key).clone());
			}
			if (leftSideHand.isMatchedBy(leftTerm, binding, tempOrderConstraint) && rightSideHand.isMatchedBy(rightTerm, binding, tempOrderConstraint)) {
				if (! tempOrderConstraint.containsKey(orderVariable)) {
					continue;
				}
				int order = tempOrderConstraint.get(this.orderVariable).getOrder();
				if (order == -1) {
					continue;
				}
				order += orderConstant;
				result.add(new SemanticEquivalenceRelation(leftTerm, rightTerm, order));
			}
			for(Variable variable: leftVariables) {
				binding.remove(variable);
			}
		}
		return result;
	}
	
	public Set<SemanticEquivalenceRelation> substitute(RDLTerm term, Collection<RDLTerm> existedTerms) {
		Set<SemanticEquivalenceRelation> result = new HashSet<>();
		Set<SemanticEquivalenceRelation> leftSub = leftSubstitute(term, existedTerms);
		if (leftSub != null) {
			result.addAll(leftSub);
		}
		
		Set<SemanticEquivalenceRelation> rightSub = rightSubstitute(term, existedTerms);
		if (rightSub != null) {
			result.addAll(rightSub);
		}
		return result;
	}
	
	private Set<SemanticEquivalenceRelation> leftSubstitute(RDLTerm term, Collection<RDLTerm> existedTerms) {
		Set<SemanticEquivalenceRelation> result = new HashSet<>();
		Map<Variable, RDLTerm> binding = new HashMap<>();
		Map<Variable, OrderVariableConstraint> orderConstraint = new HashMap<>();
		if (! leftSideHand.isMatchedBy(term, binding, orderConstraint)) {
			return null;
		}
		if (! orderConstraint.containsKey(orderVariable)) {
			return null;
		}
		int order = orderConstraint.get(this.orderVariable).getOrder();
		if (order == -1) {
			return null;
		}
		order += orderConstant;
		
		Set<Variable> allVariables = new HashSet<>(rightSideHand.getVariables().values());
		allVariables.removeAll(binding.keySet());
		List<Variable> leftVariables = new ArrayList<>(allVariables);
		for (List<RDLTerm> useVariables: Permutation.permutation(existedTerms, leftVariables.size())) {
			Map<Variable, RDLTerm> tempBinding = new HashMap<>();
			tempBinding.putAll(binding);
			for (int i = 0; i < leftVariables.size(); i++) {
				tempBinding.put(leftVariables.get(i), useVariables.get(i));
			}
			RDLTerm tempTerm = rightSideHand.substitute(tempBinding);
			if (! rightSideHand.isMatchedBy(tempTerm, binding, orderConstraint)) {
				continue;
			}
			result.add(new SemanticEquivalenceRelation(leftSideHand.substitute(binding), rightSideHand.substitute(binding), order));
			for (int i = 0; i < leftVariables.size(); i++) {
				binding.remove(leftVariables.get(i));
			}
		}
		try {
			result.add(new SemanticEquivalenceRelation(leftSideHand.substitute(binding), rightSideHand.substitute(binding), order));
		} catch (SubstituteFailedException e){}
		return result;
	}
	
	private Set<SemanticEquivalenceRelation> rightSubstitute(RDLTerm term, Collection<RDLTerm> existedTerms) {
		Set<SemanticEquivalenceRelation> result = new HashSet<>();
		Map<Variable, RDLTerm> binding = new HashMap<>();
		Map<Variable, OrderVariableConstraint> orderConstraint = new HashMap<>();
		if (! rightSideHand.isMatchedBy(term, binding, orderConstraint)) {
			return null;
		};
		if (! orderConstraint.containsKey(orderVariable)) {
			return null;
		}
		int order = orderConstraint.get(this.orderVariable).getOrder();
		if (order == -1) {
			return null;
		}
		order += orderConstant;
		
		Set<Variable> allVariables = new HashSet<>();
		for (MetaVariable vari : leftSideHand.getAllVariables()) {
			allVariables.add(vari.getVariableName());
		}
		allVariables.removeAll(binding.keySet());
		List<Variable> leftVariables = new ArrayList<>(allVariables);
		for (List<RDLTerm> useVariables: Permutation.permutation(existedTerms, leftVariables.size())) {
			Map<Variable, RDLTerm> tempBinding = new HashMap<>();
			tempBinding.putAll(binding);
			for (int i = 0; i < leftVariables.size(); i++) {
				tempBinding.put(leftVariables.get(i), useVariables.get(i));
			}
			RDLTerm tempTerm = leftSideHand.substitute(tempBinding);
			if (! leftSideHand.isMatchedBy(tempTerm, binding, orderConstraint)) {
				for (int i = 0; i < leftVariables.size(); i++) {
					binding.remove(leftVariables.get(i));
				}
				continue;
			}
			result.add(new SemanticEquivalenceRelation(leftSideHand.substitute(binding), rightSideHand.substitute(binding), order));
			for (int i = 0; i < leftVariables.size(); i++) {
				binding.remove(leftVariables.get(i));
			}
		}
		try {
			result.add(new SemanticEquivalenceRelation(leftSideHand.substitute(binding), rightSideHand.substitute(binding), order));
		} catch (SubstituteFailedException e){}
		return result;
		
	}
	
	
	@Override
	public String toString() {
		return leftSideHand.toString() + " ===(" + order + ") " + rightSideHand.toString();
	}
	
	@Override
	public boolean equals(Object another) {
		if (! (another instanceof SemanticEquivalenceRelation)) {
			return false;
		}
		SemanticEquivalenceRelation relation = (SemanticEquivalenceRelation) another;
		return leftSideHand.equals(relation.getLeftSideHand()) && rightSideHand.equals(relation.getRightSideHand());
	}
	
	@Override
	public int hashCode() {
		return toString().hashCode();
	}
	
}
