package inference;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import exceptions.SubstituteFailedException;
import models.algebra.Variable;
import models.formulas.Formula;
import models.formulas.meta.MetaEquationFormula;
import models.terms.RDLTerm;
import models.terms.meta.MatchConstraint;
import models.terms.meta.MetaEvaluatableTermVariable;
import models.terms.meta.MetaRDLTerm;
import models.terms.meta.MetaResource;
import models.terms.meta.OrderConstraint;
import models.terms.meta.OrderVariableConstraint;
import utils.ExpressionUitls;
import utils.Permutation;

public class Constantness extends InferenceRule{

	public Constantness() {
		//todo
		super("composite mapping", List.of(), null, new InferenceOrderConstraint(new Variable("n"), OrderConstraint.LT, new Variable("m")));
	}
	
	
	@Override
	public Set<Formula> apply(List<Formula> assumptions, Set<RDLTerm> existTerms) {
		Set<Formula> result = new HashSet<>();
		for (List<Formula> perm : Permutation.permutation(assumptions, assumptions.size())) {
			Set<MatchConstraint> matchResult = assumptionMatch(perm);
			for (MatchConstraint constraint: matchResult) {
				int m = constraint.getOrderConstraint().get(new Variable("m")).getOrder();
				int n = m - perm.size();
				
				
				if (! constraint.getOrderConstraint().containsKey(new Variable("n"))) {
					constraint.putOrderConstraint(new Variable("n"), new OrderVariableConstraint());
				}
				constraint.setOrderConstraint(new Variable("n"), OrderConstraint.EQ, n);
				
				if (! this.defaultOrderConstraint.check(constraint.getOrderConstraint())) {
					continue;
				}
				
				MetaRDLTerm conclusionLsh = createMetaConclusionLsh(0, m-n);
				MetaEvaluatableTermVariable se = new MetaEvaluatableTermVariable(new Variable("se"), new Variable("n"));
				MetaEquationFormula conclusion = new MetaEquationFormula(conclusionLsh, se);
				
				for (RDLTerm term : existTerms) {
					if (! se.isMatchedBy(term).isEmpty()) {
						constraint.setBinding(new Variable("se"), term);
						try {
							result.add(conclusion.substitution(constraint.getBinding()));
						} catch(SubstituteFailedException e) {
							continue;
						}
					}
				}
			}
		}
		return result;
	}
	
	private Set<MatchConstraint> assumptionMatch(List<Formula> assumptions) {
		Set<MatchConstraint> result = new HashSet<>();
		result = generateAssumption(0, false).isMatchedBy(assumptions.get(0));
		for (int i = 1; i < assumptions.size(); i++) {
			result = generateAssumption(i, i == assumptions.size() - 1).isMatchedBy(assumptions.get(i), result);
		}
		
		return result;
	}
	
	private MetaEquationFormula generateAssumption(int i, boolean isLast) {
		if (isLast) {
			MetaRDLTerm metaAssumptionLsh = new MetaRDLTerm(
					new MetaResource(new Variable("r" + i), ExpressionUitls.parse("n + 1")),
					new MetaEvaluatableTermVariable(new Variable("x" + i)),
					new MetaEvaluatableTermVariable(new Variable("y" + i))
			);
			MetaEvaluatableTermVariable metaAssumptionRsh = new MetaEvaluatableTermVariable(new Variable("t" + i));
			return new MetaEquationFormula(metaAssumptionLsh, metaAssumptionRsh);
		} else {
			MetaRDLTerm metaAssumptionLsh = new MetaRDLTerm(
					new MetaResource(new Variable("r" + i), ExpressionUitls.parse("m - " + i)),
					new MetaEvaluatableTermVariable(new Variable("x" + i)),
					new MetaEvaluatableTermVariable(new Variable("y" + i))
			);
			MetaEvaluatableTermVariable metaAssumptionRsh = new MetaEvaluatableTermVariable(new Variable("t" + i));
			return new MetaEquationFormula(metaAssumptionLsh, metaAssumptionRsh);
		}
	}
	
	private MetaRDLTerm createMetaConclusionLsh(int depth, int maxRecursion) {
		int i = maxRecursion - depth - 1;
		if (depth == maxRecursion - 1) {
			return new MetaRDLTerm(
					new MetaEvaluatableTermVariable(new Variable("se"), new Variable("n")),
					new MetaResource(new Variable("r" + i), new Variable("m")),
					new MetaEvaluatableTermVariable(new Variable("t" + i))
			);
		}
		return new MetaRDLTerm(
				createMetaConclusionLsh(depth + 1, maxRecursion),
				new MetaResource(new Variable("r" + i),ExpressionUitls.parse("m - " + i)),
				new MetaEvaluatableTermVariable(new Variable("t" + i))
		);
	}
	
}
