package aima.core.logic.fol.inference;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import aima.core.logic.fol.SubstVisitor;
import aima.core.logic.fol.Unifier;
import aima.core.logic.fol.VariableCollector;
import aima.core.logic.fol.parsing.FOLVisitor;
import aima.core.logic.fol.parsing.ast.AtomicSentence;
import aima.core.logic.fol.parsing.ast.ConnectedSentence;
import aima.core.logic.fol.parsing.ast.Constant;
import aima.core.logic.fol.parsing.ast.Function;
import aima.core.logic.fol.parsing.ast.NotSentence;
import aima.core.logic.fol.parsing.ast.Predicate;
import aima.core.logic.fol.parsing.ast.QuantifiedSentence;
import aima.core.logic.fol.parsing.ast.Term;
import aima.core.logic.fol.parsing.ast.TermEquality;
import aima.core.logic.fol.parsing.ast.Variable;

/**
 * Abstract base class for Demodulation and Paramodulation algorithms.
 * 
 * @author Ciaran O'Reilly
 * 
 */
public abstract class AbstractModulation {
	//
	// PROTECTED ATTRIBUTES
	protected VariableCollector variableCollector = new VariableCollector();
	protected Unifier unifier = new Unifier();
	protected SubstVisitor substVisitor = new SubstVisitor();

	//
	// PROTECTED METODS
	//
	protected abstract boolean isValidMatch(Term toMatch,
			Set<Variable> toMatchVariables, Term possibleMatch,
			Map<Variable, Term> substitution);

	protected IdentifyCandidateMatchingTerm getMatchingSubstitution(
			Term toMatch, AtomicSentence expression) {

		IdentifyCandidateMatchingTerm icm = new IdentifyCandidateMatchingTerm(
				toMatch, expression);

		if (icm.isMatch()) {
			return icm;
		}

		// indicates no match
		return null;
	}

	protected class IdentifyCandidateMatchingTerm implements FOLVisitor {
		private Term toMatch = null;
		private Set<Variable> toMatchVariables = null;
		private Term matchingTerm = null;
		private Map<Variable, Term> substitution = null;

		public IdentifyCandidateMatchingTerm(Term toMatch,
				AtomicSentence expression) {
			this.toMatch = toMatch;
			this.toMatchVariables = variableCollector
					.collectAllVariables(toMatch);

			expression.accept(this, null);
		}

		public boolean isMatch() {
			return null != matchingTerm;
		}

		public Term getMatchingTerm() {
			return matchingTerm;
		}

		public Map<Variable, Term> getMatchingSubstitution() {
			return substitution;
		}

		//
		// START-FOLVisitor
		public Object visitPredicate(Predicate p, Object arg) {
			for (Term t : p.getArgs()) {
				// Finish processing if have found a match
				if (null != matchingTerm) {
					break;
				}
				t.accept(this, null);
			}
			return p;
		}

		public Object visitTermEquality(TermEquality equality, Object arg) {
			for (Term t : equality.getArgs()) {
				// Finish processing if have found a match
				if (null != matchingTerm) {
					break;
				}
				t.accept(this, null);
			}
			return equality;
		}

		public Object visitVariable(Variable variable, Object arg) {

			if (null != (substitution = unifier.unify(toMatch, variable))) {
				if (isValidMatch(toMatch, toMatchVariables, variable,
						substitution)) {
					matchingTerm = variable;
				}
			}

			return variable;
		}

		public Object visitConstant(Constant constant, Object arg) {
			if (null != (substitution = unifier.unify(toMatch, constant))) {
				if (isValidMatch(toMatch, toMatchVariables, constant,
						substitution)) {
					matchingTerm = constant;
				}
			}

			return constant;
		}

		public Object visitFunction(Function function, Object arg) {
			if (null != (substitution = unifier.unify(toMatch, function))) {
				if (isValidMatch(toMatch, toMatchVariables, function,
						substitution)) {
					matchingTerm = function;
				}
			}

			if (null == matchingTerm) {
				// Try the Function's arguments
				for (Term t : function.getArgs()) {
					// Finish processing if have found a match
					if (null != matchingTerm) {
						break;
					}
					t.accept(this, null);
				}
			}

			return function;
		}

		public Object visitNotSentence(NotSentence sentence, Object arg) {
			throw new IllegalStateException(
					"visitNotSentence() should not be called.");
		}

		public Object visitConnectedSentence(ConnectedSentence sentence,
				Object arg) {
			throw new IllegalStateException(
					"visitConnectedSentence() should not be called.");
		}

		public Object visitQuantifiedSentence(QuantifiedSentence sentence,
				Object arg) {
			throw new IllegalStateException(
					"visitQuantifiedSentence() should not be called.");
		}

		// END-FOLVisitor
		//
	}

	protected class ReplaceMatchingTerm implements FOLVisitor {
		private Term toReplace = null;
		private Term replaceWith = null;
		private boolean replaced = false;

		public ReplaceMatchingTerm() {
		}

		public AtomicSentence replace(AtomicSentence expression,
				Term toReplace, Term replaceWith) {
			this.toReplace = toReplace;
			this.replaceWith = replaceWith;

			return (AtomicSentence) expression.accept(this, null);
		}

		//
		// START-FOLVisitor
		public Object visitPredicate(Predicate p, Object arg) {
			List<Term> newTerms = new ArrayList<Term>();
			for (Term t : p.getTerms()) {
				Term subsTerm = (Term) t.accept(this, arg);
				newTerms.add(subsTerm);
			}
			return new Predicate(p.getPredicateName(), newTerms);
		}

		public Object visitTermEquality(TermEquality equality, Object arg) {
			Term newTerm1 = (Term) equality.getTerm1().accept(this, arg);
			Term newTerm2 = (Term) equality.getTerm2().accept(this, arg);
			return new TermEquality(newTerm1, newTerm2);
		}

		public Object visitVariable(Variable variable, Object arg) {
			if (!replaced) {
				if (toReplace.equals(variable)) {
					replaced = true;
					return replaceWith;
				}
			}
			return variable;
		}

		public Object visitConstant(Constant constant, Object arg) {
			if (!replaced) {
				if (toReplace.equals(constant)) {
					replaced = true;
					return replaceWith;
				}
			}
			return constant;
		}

		public Object visitFunction(Function function, Object arg) {
			if (!replaced) {
				if (toReplace.equals(function)) {
					replaced = true;
					return replaceWith;
				}
			}

			List<Term> newTerms = new ArrayList<Term>();
			for (Term t : function.getTerms()) {
				Term subsTerm = (Term) t.accept(this, arg);
				newTerms.add(subsTerm);
			}
			return new Function(function.getFunctionName(), newTerms);
		}

		public Object visitNotSentence(NotSentence sentence, Object arg) {
			throw new IllegalStateException(
					"visitNotSentence() should not be called.");
		}

		public Object visitConnectedSentence(ConnectedSentence sentence,
				Object arg) {
			throw new IllegalStateException(
					"visitConnectedSentence() should not be called.");
		}

		public Object visitQuantifiedSentence(QuantifiedSentence sentence,
				Object arg) {
			throw new IllegalStateException(
					"visitQuantifiedSentence() should not be called.");
		}

		// END-FOLVisitor
		//
	}
}
