// $Id: RightAdditionEquivalence.java,v 1.8 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.Conjunction;
import com.meyling.principia.logic.basic.Equivalence;
import com.meyling.principia.logic.basic.Formula;
import com.meyling.principia.logic.basic.Implication;
import com.meyling.principia.logic.basic.PropositionVariable;
import com.meyling.principia.logic.paragraph.Axiom;
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;


/**
 * Implemention of &quot;addition&quot; of an equivalence on the right side.
 * <p>
 * <code>         A1 => A2           </code><p>
 * <code>         A2 => A1           </code><p>
 * <code>----------------------------</code><p>
 * <code> (A1 <=> A3) => (A2 <=> A3) </code><p>
 *
 * @version $Revision: 1.8 $
 * @author  Michael Meyling
 */
public class RightAdditionEquivalence 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;

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

    /** add this formula */
    private final Formula additional;


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


    /**
     * Constructs a left implication addition rule.
     *
     * @param   arguments
     *          <ul>
     *            <li>first argument is a proof line number
     *              to operate on, this proof line must be an implication
     *            </li>
     *            <li>second argument is a proof line number
     *              to operate on, this proof line must be the reverse of
     *              the first one
     *            </li>
     *            <li>third list entry must be a formula
     *            </li>
     *          </ul>
     * @throws  ArgumentException    if there are not exactly three
     *  arguments, if the first two arguments are not
     *  instances of {@link Counter} or are not bigger than zero, or
     *  if the third argument is no instance of a {@link Formula}
     */
    public RightAdditionEquivalence(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 Counter)) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.SECOND_ARGUMENT_TYPE
                + RuleConstants.NUMBER,
                arguments[1]);
        }
        this.m = ((Counter) arguments[1]).getNumber() - 1;
        if (m < 0) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.SECOND_ARGUMENT_TYPE
                + RuleConstants.POSITIVE_INTEGER_EXPECTED,
                arguments[1]);
        }

        if (!(arguments[2] instanceof Formula)) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.SECOND_ARGUMENT_TYPE
                + RuleConstants.FORMULA,
                arguments[2]);
        }
        this.additional = (Formula) arguments[2];
        // check if formula has no pattern variables
        if (additional.containsPatternVariables()) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.THIRD_ARGUMENT
                + RuleConstants.NO_PATTERN_VARIABLE_ALLOWED,
                arguments[1]);
        }

    }


    /**
     * Check if proof could be extended with formula because of
     * right addition of a implication.
     *
     * @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 numbers are not allowed (e.g.
     *          between 0 and the minimum of <code>position - 1</code>
     *          and current maximum proof line number)
     *          or the proof line number is no {@link Implication}
     *          or the implication and the additional formula have
     *           in common a subject variable that is bound in one
     *           formula and free in the other,
     *          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));
        }
        if (!(proofLines.getArgument(n).getArgument(0) instanceof Implication)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.PROOF_LINE_NO_IMPLICATION,
                this,
                proofLines.getArgument(n).getArgument(0));
        }
        if (m < 0 || m >= proofLines.getArgumentSize() || m >= position) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.PROOF_LINES_OUT_OF_RANGE,
                this.getArgument(1));
        }
        if (!(proofLines.getArgument(m).getArgument(0) instanceof Implication)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.PROOF_LINE_NO_IMPLICATION,
                this,
                proofLines.getArgument(m).getArgument(0));
        }
        final Implication formula1 = (Implication) proofLines
            .getArgument(n).getArgument(0);
        final Formula first1 = (Formula) formula1.getArgument(0);
        final Formula second1 = (Formula) formula1.getArgument(1);
        final Implication formula2 = (Implication) proofLines
            .getArgument(m).getArgument(0);
        final Formula first2 = (Formula) formula2.getArgument(0);
        final Formula second2 = (Formula) formula2.getArgument(1);
        final Implication result;

        if (!first1.equals(second2) || !first2.equals(second1)) {
            System.out.println("first:  " + formula1);
            System.out.println("second: " + formula2);
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.REFERENCED_PROOFLINES_NOT_SIMULAR,
                this,
                proofLines.getArgument(n).getArgument(0));
        }

        try {
            result = new Implication(
                new Equivalence(
                    first1,
                    additional
                ),
                new Equivalence(
                    second1,
                    additional
                )
            );
        } catch (ArgumentException e) {     // e.g. if variable
            throw new ArgumentException(    // is double bound
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                + RuleConstants.RIGHT_ADDITION_OF_FORMULA_NOT_POSSIBLE,
                this,
                proofLines.getArgument(n).getArgument(0));
        }
        if (!result.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));
        }
    }


    /**
     * Check if 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() != 4) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                ParagraphCreator.getName(RuleDeclaration.class)
                    + RuleConstants.OPERATOR_WITH_FOUR_ARGUMENTS,
                declaration);
        }
        if (!(declaration.getArgument(2) instanceof LinkReference)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                ParagraphCreator.getName(RuleDeclaration.class)
                + RuleConstants.THIRD_ARGUMENT_TYPE
                + RuleCreator.getName(LinkReference.class),
                declaration.getArgument(2));
        }
        if (!(declaration.getArgument(3) instanceof LinkReference)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                ParagraphCreator.getName(RuleDeclaration.class)
                + RuleConstants.FOURTH_ARGUMENT_TYPE
                + RuleCreator.getName(LinkReference.class),
                declaration.getArgument(3));
        }
        {
            final LinkReference reference
                = (LinkReference) declaration.getArgument(2);
            final String label = reference.getReference();
            final Formula formula;
            try {
                formula = module.getLabeledFormula(label);
            } catch (IllegalArgumentException e) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    ParagraphCreator.getName(RuleDeclaration.class)
                        + " " + e.getMessage(),
                    reference.getArgument(0));
            }

            // this formula must be matched by the referenced one:
            // IMPL (
            //   EQUI (PROP(1), PROP(2)),
            //   AND (IMPL (PROP(1), PROP(2)), IMPL(PROP(1), PROP(2)))
            // )

            final Formula form =
                new Implication(
                    new Equivalence(
                        new PropositionVariable(1),
                        new PropositionVariable(2)
                    ),
                    new Conjunction(
                        new Implication(
                            new PropositionVariable(1),
                            new PropositionVariable(2)
                        ),
                        new Implication(
                            new PropositionVariable(2),
                            new PropositionVariable(1)
                        )
                    )
                );

            final Formula pattern = (Formula) AbstractFormula
                .createPattern(formula);
            if (!form.matches(pattern, new BasicPatternVariables())) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    ParagraphCreator.getName(RuleDeclaration.class)
                        + RuleConstants.RULE_DECLARATION_CONDITION_NOT_FULLFILLED,
                    reference.getArgument(0));
            }

            // check if AddAxiom or AddSentence has been declared before
            final Argument argument;
            try {
                argument = module.getLabeledArgument(label);
            } catch (IllegalArgumentException e) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    ParagraphCreator.getName(RuleDeclaration.class)
                        + " " + e.getMessage(),
                    reference.getArgument(0));
            }
            if (argument instanceof Axiom
                    && !module.isRuleDeclared(AddAxiom.class)) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    RuleCreator.getName(AddAxiom.class)
                        + RuleConstants.RULE_DECLARATION_MISSING,
                    declaration.getArgument(0));
            } else if (argument instanceof Sentence
                    && !module.isRuleDeclared(AddSentence.class)) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    RuleCreator.getName(AddSentence.class)
                        + RuleConstants.RULE_DECLARATION_MISSING,
                    declaration.getArgument(0));
            }

        }
        {
            final LinkReference reference
                = (LinkReference) declaration.getArgument(3);
            final String label = reference.getReference();
            final Formula formula;
            try {
                formula = module.getLabeledFormula(label);
            } catch (IllegalArgumentException e) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    ParagraphCreator.getName(RuleDeclaration.class)
                        + " " + e.getMessage(),
                    reference.getArgument(0));
            }

            // this formula must be matched by the referenced one:
            // IMPL (
            //   AND (IMPL (PROP(1), PROP(2)), IMPL(PROP(1), PROP(2))),
            //   EQUI (PROP(1), PROP(2))
            // )

            final Formula form =
                new Implication(
                    new Conjunction(
                        new Implication(
                            new PropositionVariable(1),
                            new PropositionVariable(2)
                        ),
                        new Implication(
                            new PropositionVariable(2),
                            new PropositionVariable(1)
                        )
                    ),
                    new Equivalence(
                        new PropositionVariable(1),
                        new PropositionVariable(2)
                    )
                );

            final Formula pattern = (Formula) AbstractFormula
                .createPattern(formula);
            if (!form.matches(pattern, new BasicPatternVariables())) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    ParagraphCreator.getName(RuleDeclaration.class)
                        + RuleConstants.RULE_DECLARATION_CONDITION_NOT_FULLFILLED,
                    reference.getArgument(0));
            }

            // check if AddAxiom or AddSentence has been declared before
            final Argument argument;
            try {
                argument = module.getLabeledArgument(label);
            } catch (IllegalArgumentException e) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    ParagraphCreator.getName(RuleDeclaration.class)
                        + " " + e.getMessage(),
                    reference.getArgument(0));
            }
            if (argument instanceof Axiom
                    && !module.isRuleDeclared(AddAxiom.class)) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    RuleCreator.getName(AddAxiom.class)
                        + RuleConstants.RULE_DECLARATION_MISSING,
                    declaration.getArgument(0));
            } else if (argument instanceof Sentence
                    && !module.isRuleDeclared(AddSentence.class)) {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    RuleCreator.getName(AddSentence.class)
                        + RuleConstants.RULE_DECLARATION_MISSING,
                    declaration.getArgument(0));
            }

        }

        // check if SubstLine, HypotheticalSyllogism
        //  LeftAdditionImplication, RightAdditionImplication
        //  RightAdditionConjunction and LeftAdditionConjunction
        //  had been declared before

        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(HypotheticalSyllogism.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(HypotheticalSyllogism.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(LeftAdditionImplication.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(LeftAdditionImplication.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(RightAdditionImplication.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(RightAdditionImplication.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(LeftAdditionConjunction.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(LeftAdditionConjunction.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(RightAdditionConjunction.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(RightAdditionConjunction.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
    }


    /**
     * 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 => b                   (ref 1: lineFormula1)
            //  b => a                   (ref 2: lineFormula2)
            //  1. (x <=> y) => ((x => y) n (y => x))
            //                           (AddAxiom/AddSentence)
            //  2. ((x => y) n (y => x)) => (x <=> y)
            //                           (AddAxiom/AddSentence)
            //  3. (c => a) => (c => b)
            //                           (formula3, LeftAdditionImplication
            //                             ref 1)
            //  4. (a => c) => (b => c)
            //                           (formula4, RightAdditionImplication
            //                             ref 2)
            //  5. ((a => c) n (c => a)) => ((b => c) n (c => a))
            //                           (formula5, RightAdditionConjunction
            //                             4, (c => a))
            //  6. ((b => c) n (c => a)) => ((b => c) n (c => b))
            //                           (formula6, LeftAdditionConjunction
            //                             3, (b => c))
            //  7. ((a => c) n (c => a)) => ((b => c) n (c => b))
            //                          (formula7, HypotheticalSyllogism 5, 6)
            //  8. (a <=> c) => ((a => c) n (c => a))
            //                           (SubstLine 1)
            //  9. (a <=> c) => ((b => c) n (c => b))
            //                           (HypotheticalSyllogism 8, 7)
            // 10. ((b => c) n (c => b)) => (b <=> c)
            //                           (SubstLine 2)
            // 11. (a <=> c) => (b <=> c)
            //                           (HypotheticalSyllogism 9, 10)

            //  a => b                   (ref: lineFormula)
            final Formula lineFormula1 = (Formula) proofLines.getArgument(n)
                .getArgument(0);
            final Formula lineFormula2 = (Formula) proofLines.getArgument(m)
                .getArgument(0);

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

            //  1. (x <=> y) => ((x => y) n (y => x))
            //                           (AddAxiom/AddSentence)
            {
                final RuleDeclaration declaration = module.getRuleDeclaration(
                    this.getClass());

                final String label
                    = ((LinkReference) declaration.getArgument(2)).getReference();
                final Argument argument;
                try {
                    argument = module.getLabeledArgument(label);
                } catch (IllegalArgumentException e) {
                    throw new ArgumentException(
                        RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                        ParagraphCreator.getName(RuleDeclaration.class)
                            + " " + e.getMessage(),
                        declaration.getArgument(2).getArgument(0));
                }
                if (argument instanceof Sentence) {
                    newProofLines.add(
                        new ProofLine(new Argument[] {
                            argument.getArgument(0),
                            new AddSentence(
                                new Argument[] {
                                    declaration.getArgument(2)
                                }
                            )
                        }));
                } else {
                    newProofLines.add(
                        new ProofLine(new Argument[] {
                            argument.getArgument(0),
                            new AddAxiom(
                                new Argument[] {
                                    declaration.getArgument(2)
                                }
                            )
                        }));
                }
            }

            //  2. ((x => y) n (y => x)) => (x <=> y)
            //                           (AddAxiom/AddSentence)
            {
                final RuleDeclaration declaration = module.getRuleDeclaration(
                    this.getClass());

                final String label
                    = ((LinkReference) declaration.getArgument(3)).getReference();
                final Argument argument;
                try {
                    argument = module.getLabeledArgument(label);
                } catch (IllegalArgumentException e) {
                    throw new ArgumentException(
                        RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                        ParagraphCreator.getName(RuleDeclaration.class)
                            + " " + e.getMessage(),
                        declaration.getArgument(3).getArgument(0));
                }
                if (argument instanceof Sentence) {
                    newProofLines.add(
                        new ProofLine(new Argument[] {
                            argument.getArgument(0),
                            new AddSentence(
                                new Argument[] {
                                    declaration.getArgument(3)
                                }
                            )
                        }));
                } else {
                    newProofLines.add(
                        new ProofLine(new Argument[] {
                            argument.getArgument(0),
                            new AddAxiom(
                                new Argument[] {
                                    declaration.getArgument(3)
                                }
                            )
                        }));
                }
            }

            //  3. (c => a) => (c => b)
            //                           (formula3, LeftAdditionImplication
            //                             ref 1)
            final Formula formula3 = new Implication(
                new Implication(
                    additional,
                    (Formula) lineFormula1.getArgument(0)
                ),
                new Implication(
                    additional,
                    (Formula) lineFormula1.getArgument(1)
                )
            );
            newProofLines.add(
                new ProofLine(
                    new Argument[] {
                        formula3,
                        new LeftAdditionImplication(
                            new Argument[] {
                                new Counter(this.n + 1),
                                additional
                            }
                        )
                    }
                )
            );

            //  4. (a => c) => (b => c)
            //                           (formula4, RightAdditionImplication
            //                             ref 2)
            final Formula formula4 = new Implication(
                new Implication(
                    (Formula) lineFormula1.getArgument(0),
                    additional
                ),
                new Implication(
                    (Formula) lineFormula1.getArgument(1),
                    additional
                )
            );
            newProofLines.add(
                new ProofLine(
                    new Argument[] {
                        formula4,
                        new RightAdditionImplication(
                            new Argument[] {
                                new Counter(this.m + 1),
                                additional
                            }
                        )
                    }
                )
            );


            //  5. ((a => c) n (c => a)) => ((b => c) n (c => a))
            //                           (formula5, RightAdditionConjunction
            //                             4, (c => a))
            final Formula formula5 = new Implication(
                new Conjunction(
                    (Formula) formula4.getArgument(0),
                    (Formula) formula3.getArgument(0)
                ),
                new Conjunction(
                    (Formula) formula4.getArgument(1),
                    (Formula) formula3.getArgument(0)
                )
            );
            newProofLines.add(
                new ProofLine(
                    new Argument[] {
                        formula5,
                        new RightAdditionConjunction(
                            new Argument[] {
                                new Counter(position + 4),
                                (Formula) formula3.getArgument(0)
                            }
                        )
                    }
                )
            );

            //  6. ((b => c) n (c => a)) => ((b => c) n (c => b))
            //                           (formula6, LeftAdditionConjunction
            //                             3, (b => c))
            final Formula formula6 = new Implication(
                (Formula) formula5.getArgument(1),
                new Conjunction(
                    (Formula) formula4.getArgument(1),
                    (Formula) formula3.getArgument(1)
                )
            );
            newProofLines.add(
                new ProofLine(
                    new Argument[] {
                        formula6,
                        new LeftAdditionConjunction(
                            new Argument[] {
                                new Counter(position + 3),
                                (Formula) formula4.getArgument(1)
                            }
                        )
                    }
                )
            );


            //  7. ((a => c) n (c => a)) => ((b => c) n (c => b))
            //                          (formula7, HypotheticalSyllogism 5, 6)
            final Formula formula7 = new Implication(
                (Formula) formula5.getArgument(0),
                (Formula) formula6.getArgument(1)
            );
            newProofLines.add(
                new ProofLine(
                    new Argument[] {
                        formula7,
                        new HypotheticalSyllogism(
                            new Argument[] {
                                new Counter(position + 5),
                                new Counter(position + 6)
                            }
                        )
                    }
                )
            );

            //  8. (a <=> c) => ((a => c) n (c => a))
            //                           (SubstLine 1)
            final Formula formula8 = new Implication(
                new Equivalence(
                    (Formula) lineFormula1.getArgument(0),
                    additional
                ),
                (Formula) formula7.getArgument(0)
            );
            newProofLines.add(
                new ProofLine(
                    new Argument[] {
                        formula8,
                        new SubstLine(
                            new Argument[] {
                                new Counter(position + 1)
                            }
                        )
                    }
                )
            );

            //  9. (a <=> c) => ((b => c) n (c => b))
            //                           (HypotheticalSyllogism 8, 7)
            final Formula formula9 = new Implication(
                (Formula) formula8.getArgument(0),
                (Formula) formula7.getArgument(1)
            );
            newProofLines.add(
                new ProofLine(
                    new Argument[] {
                        formula9,
                        new HypotheticalSyllogism(
                            new Argument[] {
                                new Counter(position + 8),
                                new Counter(position + 7)
                            }
                        )
                    }
                )
            );

            // 10. ((b => c) n (c => b)) => (b <=> c)
            //                           (SubstLine 2)
            final Formula formula10 = new Implication(
                (Formula) formula9.getArgument(1),
                new Equivalence(
                    (Formula) lineFormula1.getArgument(1),
                    additional
                )
            );
            newProofLines.add(
                new ProofLine(
                    new Argument[] {
                        formula10,
                        new SubstLine(
                            new Argument[] {
                                new Counter(position + 2)
                            }
                        )
                    }
                )
            );

            // 11. (a <=> c) => (b <=> c)
            //                           (HypotheticalSyllogism 9, 10)
            final Formula formula11 = new Implication(
                (Formula) formula9.getArgument(0),
                (Formula) formula10.getArgument(1)
            );
            newProofLines.add(
                new ProofLine(
                    new Argument[] {
                        formula11,
                        new HypotheticalSyllogism(
                            new Argument[] {
                                new Counter(position + 9),
                                new Counter(position + 10)
                            }
                        )
                    }
                )
            );

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


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


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


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


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


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


}
