// $Id: ApplySentence.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.AbstractFormula;
import com.meyling.principia.logic.basic.BasicPatternVariables;
import com.meyling.principia.logic.basic.Formula;
import com.meyling.principia.logic.basic.Implication;
import com.meyling.principia.logic.paragraph.ParagraphCreator;
import com.meyling.principia.logic.paragraph.ProofLine;
import com.meyling.principia.logic.paragraph.ProofLineList;
import com.meyling.principia.logic.paragraph.RuleDeclaration;
import com.meyling.principia.logic.paragraph.Sentence;
import com.meyling.principia.module.Module;
import com.meyling.principia.module.Version;


/**
 * Implementation of applying a Sentence:
 * <p>
 * <code> A1 => A2</code><p>
 * <code>       A3</code><p>
 * <code>---------</code><p>
 * <code>       A4</code><p>
 *
 * if A1 => A2 matches A3 => A4
 *
 * @version $Revision: 1.10 $
 * @author  Michael Meyling
 */
public class ApplySentence extends AbstractArgumentList
        implements Argument, Rule {

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

    /** proof line of <code>A1 => A2</code>*/
    private final int n;

    /** reference to sentence that should be applyed */
    private final String sentenceLabel;


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


    /**
     * Constructs an apply sentence rule by a proof line reference and
     * a sentence reference.
     *
     * @param  arguments    proof line reference, sentence reference
     * @throws ArgumentException if there are not exactly two
     *         arguments or the arguments
     *          or if the first argument is not
     *          an instance of {@link Counter} or is not bigger than zero,
     *          or if the second argument is no instance of a
     *          {@link LinkLabel}
     */
    public ApplySentence(final Argument[] arguments)
            throws ArgumentException {
        super (arguments);
        if (arguments.length != 2) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_NUMBER,
                RuleCreator.getName(this.getClass())
                + RuleConstants.OPERATOR_WITH_TWO_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 LinkReference)) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.FIRST_ARGUMENT_TYPE
                + RuleCreator.getName(LinkReference.class),
                arguments[1]);
        }
        this.sentenceLabel = ((LinkReference) arguments[1]).getReference();

    }


    /**
     * Check if proof could be extended with <code>formula</code>.
     * This is true
     * if formula is equivalent to the referenced axiom formula.
     *
     * @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 the implication made of
     *          the referenced proof line of this object and the
     *          formula of this object dosn't match the
     *          referenced sentence
     *          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 Sentence sentence;
        try {
            sentence = module.getSentence(sentenceLabel);
        } catch (IllegalArgumentException e) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + " " + e.getMessage(),
                this.getArgument(0));
        }
        final Formula sentenceFormula = (Formula) sentence.getArgument(0);
        if (!(sentenceFormula instanceof Implication)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.AXIOM_IS_NO_IMPLICATION,
                this);

        }

        final Formula pattern = AbstractFormula
            .createPattern(sentenceFormula);
        final Implication impl = new Implication(new Argument[] {
            proofLines.getArgument(n).getArgument(0), formula});
        if (!impl.matches(pattern, new BasicPatternVariables())) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.RESULT_NOT_EQUAL_WITH_EXPECTED,
                this);
        }
    }


    /**
     * Return proof lines that could replace the proof line
     * <code>position</code>.
     *
     * @param   module      the belonging module
     * @param   proofLines  proof lines
     * @param   position    referenced proof line (starting with 0)
     *                       in <code>proofLines</code>
     * @return  proof lines that could substitute the single
     *           proof line <code>position</code>
     * @throws  IllegalArgumentException  if extension is impossible
     *           or other problems occured
     */
    public final ProofLineList extendWithout(final Module module,
            final ProofLineList proofLines, final int position)
            throws IllegalArgumentException {

        if (!this.equals(proofLines.getArgument(position)
                .getArgument(1))) {
            throw new IllegalArgumentException(
                "referenced proof line not equal with this one");
        }

        try {
            // a           (lineFormula)
            // 1. x => y   (AddSentence)
            // 2. a => b   (formula2, SubstLine)
            // 3. b        (formula3, ModusPonens)

            // a        (lineFormula)
            final Formula lineFormula = (Formula) proofLines.getArgument(n)
                .getArgument(0);

            // b        (formula3)
            final Formula formula3 = (Formula) proofLines.getArgument(position)
                .getArgument(0);

            final ProofLineList newProofLines;
            try {
                newProofLines = new ProofLineList(new Argument[] {});
            } catch (ArgumentException e) {
                throw new IllegalArgumentException(e.toString());
            }

            // x => y   (AddSentence)
            {
                newProofLines.add(
                    new ProofLine(new Argument[] {
                        module.getSentence(sentenceLabel).getArgument(0),
                        new AddSentence(
                            new Argument[] {
                                getArgument(1)
                            }
                        )
                    }));
            }

            // a => b   (formula2, SubstLine)
            {
                final Formula formula2 = new Implication(
                    lineFormula,
                    formula3
                );
                newProofLines.add(
                    new ProofLine(
                        new Argument[] {
                            formula2,
                            new SubstLine(
                                new Argument[] {
                                    new Counter(position + 1)
                                }
                            )
                        }
                    )
                );
            }

            // b        (formula3, ModusPonens)
            {
                newProofLines.add(
                    new ProofLine(
                        new Argument[] {
                            formula3,
                            new ModusPonens(
                                new Argument[] {
                                    new Counter(n + 1),
                                    new Counter(position + 2)
                                }
                            )
                        }
                    )
                );
            }

            return newProofLines;
        } catch (ArgumentException e) {
            throw new IllegalArgumentException(e.toString());
        }
    }


    /** @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), getArgument(1)});
        } 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);
        }
        // check if AddSentence, SubstLine and ModusPonens
        //  had been declared before
        if (!module.isRuleDeclared(AddSentence.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(AddSentence.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(SubstLine.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(SubstLine.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(ModusPonens.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(ModusPonens.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
    }


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


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


    /** @inherited */
    public final String toString() {
        return "Apply Sentence " + getArgument(1) + " to line "
            + getArgument(0);
    }

}
