package inference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import exceptions.SubstituteFailedException;
import models.formulas.DependencyFormula;
import models.formulas.EquationFormula;
import models.formulas.Formula;
import models.formulas.meta.MetaEquationFormula;
import models.formulas.meta.MetaFormula;
import models.terms.DependencyTerm;
import models.terms.EvaluatableTerm;
import models.terms.RDLTerm;
import models.terms.Resource;
import models.terms.meta.MatchConstraint;
import models.terms.meta.MetaRDLTerm;

public class EquationAxiom extends InferenceRule {

	public EquationAxiom(String name, List<MetaFormula> assumptions, MetaEquationFormula conclusion, InferenceOrderConstraint constraint) {
		super(name, assumptions, conclusion, constraint);
	}
	
	public EquationAxiom(String name, List<MetaFormula> assumptions, MetaEquationFormula conclusion) {
		super(name, assumptions, conclusion);
	}
	
	public Set<EquationFormula> apply(List<Formula>assumptions,  EvaluatableTerm leftSideHand) {
		Set<EquationFormula> result = new HashSet<>();
		if (assumptions.size() < this.assumptions.size()) {
			return new HashSet<>();
		}
		MetaEquationFormula metaConclusion = (MetaEquationFormula) this.conclusion;
		Set<MatchConstraint> matchResult = ((MetaRDLTerm) metaConclusion.getLeftSideHand()).isMatchedBy(leftSideHand);
		if (result.isEmpty()) {
			return new HashSet<>();
		}
		for (int i = 0; i < assumptions.size(); i++) {
			Formula assumption = assumptions.get(i);
			MetaFormula metaAssumption = this.assumptions.get(i);
			matchResult = metaAssumption.isMatchedBy(assumption, matchResult);
			if (result.isEmpty()) {
				return new HashSet<>();
			}
		}
		for (MatchConstraint matchRes: matchResult) {
			try {
				result.add(metaConclusion.substitution(matchRes.getBinding(), matchRes.getContext()));
			} catch (SubstituteFailedException e) {
				continue;
			}
		}
		return result;
	}
	
	
	
	private static Set<DependencyFormula> requiredAssumptions(EvaluatableTerm term) {
		if (term instanceof Resource) {
			return new HashSet<>();
		}
		DependencyTerm depTerm = (DependencyTerm) term;
		Set<DependencyFormula> result = new HashSet<>();
		EvaluatableTerm dependingTerm = depTerm.getDependingTerm();
		List<EvaluatableTerm> dependedTerms = depTerm.getDependedTerms();
		List<EvaluatableTerm> argumentTerms = depTerm.getArgumentTerms();
		if (dependingTerm instanceof Resource) {
			result.add(new DependencyFormula(dependingTerm, dependedTerms));
		} else if (dependingTerm instanceof DependencyTerm depending) {
			for (Formula formula : requiredAssumptions(depending)) {
				if (formula instanceof DependencyFormula dependency) {
					DependencyTerm newDependingTerm = new DependencyTerm((EvaluatableTerm) dependency.getDependency().getDependingTerm(), dependedTerms, argumentTerms);
					List<EvaluatableTerm> newDependedTerms = new ArrayList<>();
					for (RDLTerm dependedTerm : dependency.getDependency().getDependedTerms()) {
						newDependedTerms.add(new DependencyTerm((EvaluatableTerm) dependedTerm, dependedTerms, argumentTerms));
					}
					result.add(new DependencyFormula(newDependingTerm, newDependedTerms));
				} 
			}
		}
		return result;
	}
	
	private static boolean conclusionCheck(EvaluatableTerm leftSideHand, Set<Formula> formulas) {
		for (Formula formula: requiredAssumptions(leftSideHand)) {
			if (! formulas.contains(formula)) { 
				return false;
			}
		}
		return true;
	}
	
}
