// $Id: ReplacePropositionVariable.java,v 1.10 2004/02/27 16:29:56 m31 Exp $
//
// This file is part of the program suite "Principia Mathematica II"
// which is a working prototype for the main project:
// "Hilbert II" - http://www.qedeq.org
//
// Copyright 2001-2004  Michael Meyling <mime@qedeq.org>.
//
// "Principia Mathematica II" is free software; you can redistribute
// it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

package com.meyling.principia.logic.rule;


import com.meyling.principia.argument.AbstractArgumentList;
import com.meyling.principia.argument.Argument;
import com.meyling.principia.argument.ArgumentException;
import com.meyling.principia.argument.Counter;
import com.meyling.principia.argument.Text;
import com.meyling.principia.logic.basic.Formula;
import com.meyling.principia.logic.basic.PropositionVariable;
import com.meyling.principia.logic.paragraph.ParagraphCreator;
import com.meyling.principia.logic.paragraph.ProofLineList;
import com.meyling.principia.logic.paragraph.RuleDeclaration;
import com.meyling.principia.module.Module;
import com.meyling.principia.module.Version;


/**
 * Replace predicate variables by formulas:
 * <p></p>
 * <p><code>       A[p]</code></p>
 * <p><code> formula F </code></p>
 * <p><code>-----------</code></p>
 * <p><code>       A[F]</code></p>
 *
 * @version $Revision: 1.10 $
 * @author  Michael Meyling
 */
public class ReplacePropositionVariable extends AbstractArgumentList
        implements Argument, Rule {

    /** minimum version required for using this rule */
    private final static Version version;

    /** proof line number to operate on */
    private final int n;

    /** predicate variable which will be replaced */
    private final PropositionVariable prop;

    /** formula to replace the predicate variable with */
    private final Formula replacement;


    /**
     * Initialize version information.
     */
    static {
        try {
            version = new Version(
                    new Argument[] {new Text("1.00.00")});
        } catch (ArgumentException e) {
            throw new IllegalArgumentException(e.toString());
        }
    }


    /**
     * Constructs a replace sentence variable rule.
     *
     * @param   arguments   <ul><li>0: proof line reference (&gt;0)</li>
     *                      <li>1: proposition variable,</li>
     *                      <li>2: formula</li></ul>
     *
     * @throws  ArgumentException if there are not exactly
     *          three arguments that are instances of
     *          {@link Counter}, {@link PropositionVariable},
     *          {@link Formula} (in exactly this order)
     *
     */
    public ReplacePropositionVariable(final Argument[] arguments)
            throws ArgumentException {
        super (arguments);
        if (arguments.length != 3) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_NUMBER,
                RuleCreator.getName(this.getClass())
                + RuleConstants.OPERATOR_WITH_THREE_ARGUMENTS);
        }
        if (!(arguments[0] instanceof Counter)) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.FIRST_ARGUMENT_TYPE
                + RuleConstants.NUMBER,
                arguments[0]);
        }
        this.n = ((Counter) arguments[0]).getNumber() - 1;
        if (n < 0) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.FIRST_ARGUMENT_TYPE
                + RuleConstants.POSITIVE_INTEGER_EXPECTED,
                arguments[0]);
        }
        if (!(arguments[1] instanceof PropositionVariable)) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.SECOND_ARGUMENT_TYPE
                + RuleCreator.getName(PropositionVariable.class),
                arguments[1]);
        }
        this.prop = (PropositionVariable) arguments[1];
        if (!(arguments[2] instanceof Formula)) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.THIRD_ARGUMENT_TYPE
                + RuleConstants.FORMULA,
                arguments[2]);
        }
        this.replacement = (Formula) arguments[2];

    }


    /**
     * Check if proof could be extended with formula if the proposition
     * variable is replaced by the formula of the constructor.
     *
     * @param   module   module
     * @param   proofLines  proof lines
     * @param   position number of proof lines that could be used
     *                    in the rule
     * @param   formula  formula to add
     * @throws  ArgumentException    if addition is not possible
     *          because the proof line number is not allowed (e.g.
     *          between 1 and the minimum of <code>position</code>
     *          and current maximum proof line number)
     *          or the referenced proof line is no formula
     *          or the set of free variables of the formula and
     *           the set of bound variables of the proof lines
     *           aren't disjunct
     *          or the set of bound variables
     *           of the formula and the free variables of the
     *           proof line aren't disjunct
     *          or the sentence variable is in reach of an
     *           quantor with subject variable x, and x
     *           occurs in the replacement formula
     *          or the resulting formula is not equal to
     *          <code>formula</code>
     *          or a version conflict occurs
     */
    public final void check(final Module module,
            final ProofLineList proofLines,
            final int position,
            final Formula formula) throws ArgumentException {

        if (version.greaterThan(module.getRuleVersion())) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + " " + RuleConstants.RULE_VERSION_CONFLICT
                    + version.getText(),
                this);
        }
        if (n < 0 || n >= proofLines.getArgumentSize() || n >= position) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.PROOF_LINES_OUT_OF_RANGE,
                this.getArgument(0));
        }
        final Formula proofLine
            = (Formula) proofLines.getArgument(n).getArgument(0);

        if (!proofLine.getFreeSubjectVariables().emptyIntersection(
                replacement.getBoundSubjectVariables())
                || !proofLine.getBoundSubjectVariables().emptyIntersection(
                replacement.getFreeSubjectVariables())) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.FREE_BOUND_NOT_EMPTY_INTERSECTION,
                this,
                proofLines.getArgument(n).getArgument(0));
        }
        final Formula replaced;
        try {
            replaced = (Formula) proofLine.replace(prop,
                replacement);

            // no previous free variables should have become bound
            if (!replaced.getFreeSubjectVariables()
                    .contains(replacement.getFreeSubjectVariables())) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    RuleCreator.getName(this.getClass())
                        + RuleConstants.PRECONDITIONS_NOT_FULLFILLED,
                    this,
                    proofLines.getArgument(n).getArgument(0));
            }

            // the missing condition was checked by constructing the
            //  replacement: a bound variable of replacement couldn't get
            //  bound again by a quantifier of proofLine

        } catch (ArgumentException e) {     // e.g. if variable
            throw new ArgumentException(    // is double bound
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                + RuleConstants.REPLACING_PROPOSITION_VARIABLE_NOT_POSSIBLE,
                this,
                proofLines.getArgument(n).getArgument(0));

        }
        if (!replaced.equals(formula)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.RESULT_NOT_EQUAL_WITH_EXPECTED,
                this,
                proofLines.getArgument(n).getArgument(0));
        }
    }


    /** @inherited */
    public ProofLineList extendWithout(final Module module,
            final ProofLineList proofLines, final int position)
            throws UnsupportedOperationException {
        throw new UnsupportedOperationException (
            "this rule is neccessary: "
            + RuleCreator.getName(this.getClass()));
    }


    /** @inherited */
    public int[] getProofLines() {
        return new int[] {this.n};
    }


    /** @inherited */
    public Rule changeProofLines(final int[] mapping) {
        try {
            return (Rule) create(new Argument[] {
                new Counter(mapping[n] + 1), prop, replacement});
        } catch (ArgumentException e) {
            throw new IllegalArgumentException(e.toString());
        } catch (IndexOutOfBoundsException e) {
            throw new IllegalArgumentException(e.toString());
        }
    }


    /**
     * Check this rule could be declared.
     *
     * @param   module      the module context
     * @param   declaration the declaration that shall declare this rule
     * @throws  IllegalArgumentException    if an programming error occured
     * @throws  ArgumentException    if declaration failed
     */
    public final static void checkDeclaration(final Module module,
        final RuleDeclaration declaration) throws ArgumentException {
        if (declaration.getArgumentSize() > 2) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                ParagraphCreator.getName(RuleDeclaration.class)
                    + RuleConstants.OPERATOR_WITH_TWO_ARGUMENTS,
                declaration);
        }
    }


    /** @inherited */
    public final Version getVersion() {
        return ReplacePropositionVariable.version;
    }


    /** @inherited */
    public final Argument create(final Argument[] arguments)
            throws ArgumentException {
        return new ReplacePropositionVariable(arguments);
    }


    /** @inherited */
    public final String toString() {
        return "ReplaceSentence " + getArgument(0) + getArgument(1)
            + getArgument(2);
    }


}
