package org.ggp.base.util.prover.aima.substituter;

import java.util.ArrayList;
import java.util.List;

import org.ggp.base.util.gdl.grammar.GdlConstant;
import org.ggp.base.util.gdl.grammar.GdlDistinct;
import org.ggp.base.util.gdl.grammar.GdlFunction;
import org.ggp.base.util.gdl.grammar.GdlLiteral;
import org.ggp.base.util.gdl.grammar.GdlNot;
import org.ggp.base.util.gdl.grammar.GdlOr;
import org.ggp.base.util.gdl.grammar.GdlPool;
import org.ggp.base.util.gdl.grammar.GdlProposition;
import org.ggp.base.util.gdl.grammar.GdlRelation;
import org.ggp.base.util.gdl.grammar.GdlRule;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.gdl.grammar.GdlVariable;
import org.ggp.base.util.prover.aima.substitution.Substitution;


public final class Substituter
{

    private Substituter() {
    }

    public static GdlLiteral substitute(GdlLiteral literal, Substitution theta)
    {
        return substituteLiteral(literal, theta);
    }

    public static GdlSentence substitute(GdlSentence sentence, Substitution theta)
    {
        return substituteSentence(sentence, theta);
    }

    public static GdlRule substitute(GdlRule rule, Substitution theta)
    {
        return substituteRule(rule, theta);
    }

    private static GdlConstant substituteConstant(GdlConstant constant, Substitution theta)
    {
        return constant;
    }

    private static GdlDistinct substituteDistinct(GdlDistinct distinct, Substitution theta)
    {
        if (distinct.isGround())
        {
            return distinct;
        }
        else
        {
            GdlTerm arg1 = substituteTerm(distinct.getArg1(), theta);
            GdlTerm arg2 = substituteTerm(distinct.getArg2(), theta);

            return GdlPool.getDistinct(arg1, arg2);
        }
    }

    private static GdlFunction substituteFunction(GdlFunction function, Substitution theta)
    {
        if (function.isGround())
        {
            return function;
        }
        else
        {
            GdlConstant name = substituteConstant(function.getName(), theta);

            List<GdlTerm> body = new ArrayList<GdlTerm>();
            for (int i = 0; i < function.arity(); i++)
            {
                body.add(substituteTerm(function.get(i), theta));
            }

            return GdlPool.getFunction(name, body);
        }
    }

    private static GdlLiteral substituteLiteral(GdlLiteral literal, Substitution theta)
    {
        if (literal instanceof GdlDistinct)
        {
            return substituteDistinct((GdlDistinct) literal, theta);
        }
        else if (literal instanceof GdlNot)
        {
            return substituteNot((GdlNot) literal, theta);
        }
        else if (literal instanceof GdlOr)
        {
            return substituteOr((GdlOr) literal, theta);
        }
        else
        {
            return substituteSentence((GdlSentence) literal, theta);
        }
    }

    private static GdlNot substituteNot(GdlNot not, Substitution theta)
    {
        if (not.isGround())
        {
            return not;
        }
        else
        {
            GdlLiteral body = substituteLiteral(not.getBody(), theta);
            return GdlPool.getNot(body);
        }
    }

    private static GdlOr substituteOr(GdlOr or, Substitution theta)
    {
        if (or.isGround())
        {
            return or;
        }
        else
        {
            List<GdlLiteral> disjuncts = new ArrayList<GdlLiteral>();
            for (int i = 0; i < or.arity(); i++)
            {
                disjuncts.add(substituteLiteral(or.get(i), theta));
            }

            return GdlPool.getOr(disjuncts);
        }
    }

    private static GdlProposition substituteProposition(GdlProposition proposition, Substitution theta)
    {
        return proposition;
    }

    private static GdlRelation substituteRelation(GdlRelation relation, Substitution theta)
    {
        if (relation.isGround())
        {
            return relation;
        }
        else
        {
            GdlConstant name = substituteConstant(relation.getName(), theta);

            List<GdlTerm> body = new ArrayList<GdlTerm>();
            for (int i = 0; i < relation.arity(); i++)
            {
                body.add(substituteTerm(relation.get(i), theta));
            }

            return GdlPool.getRelation(name, body);
        }
    }

    private static GdlSentence substituteSentence(GdlSentence sentence, Substitution theta)
    {
        if (sentence instanceof GdlProposition)
        {
            return substituteProposition((GdlProposition) sentence, theta);
        }
        else
        {
            return substituteRelation((GdlRelation) sentence, theta);
        }
    }

    private static GdlTerm substituteTerm(GdlTerm term, Substitution theta)
    {
        if (term instanceof GdlConstant)
        {
            return substituteConstant((GdlConstant) term, theta);
        }
        else if (term instanceof GdlVariable)
        {
            return substituteVariable((GdlVariable) term, theta);
        }
        else
        {
            return substituteFunction((GdlFunction) term, theta);
        }
    }

    private static GdlTerm substituteVariable(GdlVariable variable, Substitution theta)
    {
        if (!theta.contains(variable))
        {
            return variable;
        }
        else
        {
            GdlTerm result = theta.get(variable);
            GdlTerm betterResult = null;

            while (!(betterResult = substituteTerm(result, theta)).equals(result))
            {
                result = betterResult;
            }

            theta.put(variable, result);
            return result;
        }
    }

    private static GdlRule substituteRule(GdlRule rule, Substitution theta)
    {
        GdlSentence head = substitute(rule.getHead(), theta);

        List<GdlLiteral> body = new ArrayList<GdlLiteral>();
        for ( GdlLiteral literal : rule.getBody() )
        {
            body.add(substituteLiteral(literal, theta));
        }

        return GdlPool.getRule(head, body);
    }
}
