// $Id: ElementaryEquivalence.java,v 1.15 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.Enumerator;
import com.meyling.principia.argument.FoundException;
import com.meyling.principia.argument.Text;
import com.meyling.principia.argument.VariableList;
import com.meyling.principia.logic.basic.AbstractFormula;
import com.meyling.principia.logic.basic.Conjunction;
import com.meyling.principia.logic.basic.Disjunction;
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.Negation;
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.ModuleCreator;
import com.meyling.principia.module.Version;


/**
 * Implemention of using an elementary equivalence:
 * <p>
 * <code>  a => b </code>
 * <code>  b => a </code>
 * <code>   A(a)  </code>
 * <code>---------</code><p>
 * <code>   A(b)  </code>
 *
 * @version $Revision: 1.15 $
 * @author  Michael Meyling
 */
public class ElementaryEquivalence extends AbstractArgumentList
        implements Argument, Rule {

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

    /** proof line of to apply equivalence */
    private final int n;

    /** reference to first sentence */
    private final String firstSentenceLabel;

    /** reference to second sentence  */
    private final String secondSentenceLabel;

    /** modify this occurence */
    private final int occurrence;


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


    /**
     * Constructs an elementary equivalence rule by two sentence
     * references and an occurence number.
     *
     * @param  arguments proof line reference, sentence references
     *         and occurence
     * @throws ArgumentException if there are not exactly four
     *         arguments or the first argument is no instance of
     *         {@link Counter} (>0) or the second and the third arguments
     *         are no instances
     *         of {@link LinkLabel} or the fourth argument is
     *         no instance of an {@link Counter} (>0)
     */
    public ElementaryEquivalence(final Argument[] arguments)
            throws ArgumentException {
        super (arguments);
        if (arguments.length != 4) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_NUMBER,
                RuleCreator.getName(this.getClass())
                + RuleConstants.OPERATOR_WITH_ONE_ARGUMENT);
        }
        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 (!(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));
        }
        this.firstSentenceLabel = ((LinkReference) arguments[1])
            .getReference();
        if (!(arguments[2] instanceof LinkReference)) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.FIRST_ARGUMENT_TYPE
                + RuleCreator.getName(LinkReference.class));
        }
        this.secondSentenceLabel = ((LinkReference) arguments[2])
            .getReference();
        if (!(arguments[3] instanceof Counter)) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.FOURTH_ARGUMENT_TYPE
                + RuleConstants.COUNTER,
                arguments[3]);
        }
        this.occurrence = ((Counter) arguments[3]).getNumber() - 1;
        if (occurrence < 0) {
            throw new ArgumentException(
                RuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                RuleCreator.getName(this.getClass())
                + RuleConstants.FOURTH_ARGUMENT_TYPE
                + RuleConstants.POSITIVE_INTEGER_EXPECTED,
                arguments[3]);
        }

    }


    /**
     * Check if proof could be extended with formula. This is true
     * if <code>formula</code> is equivalent to the referenced sentence
     * 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 <code>formula</code> is not
     *          equal to the formula referenced by this object
     *          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 impl1;
        try {
            impl1 = (Formula) module.getSentence(firstSentenceLabel)
                .getArgument(0);
        } catch (IllegalArgumentException e) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + " " + e.getMessage(),
                this.getArgument(1));
        }

        final Formula impl2;
        try {
            impl2 = (Formula) module.getSentence(secondSentenceLabel)
                .getArgument(0);
        } catch (IllegalArgumentException e) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + " " + e.getMessage(),
                this.getArgument(2));
        }


        if (!(impl1 instanceof Implication)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.SENTENCE_IS_NO_IMPLICATION,
                getArgument(1));
        }

        if (!(impl2 instanceof Implication)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.SENTENCE_IS_NO_IMPLICATION,
                getArgument(2));
        }
        final Formula firstLeft = (Formula) impl1.getArgument(0);
        final Formula firstRight = (Formula) impl1.getArgument(1);
        final Formula secondLeft = (Formula) impl2.getArgument(0);
        final Formula secondRight = (Formula) impl2.getArgument(1);

        if (!firstLeft.equals(secondRight)
                || !firstRight.equals(secondLeft)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.NO_ELEMENTARY_EQUIVALENCE,
                this);
        }

        final Formula pformula = (Formula) proofLines.getArgument(n)
            .getArgument(0);
        final Formula searchPattern = AbstractFormula
            .createPattern(firstLeft);
        final Formula replacementPattern = AbstractFormula
            .createPattern(firstRight);
        final Formula replaced;
        final Enumerator counter = new Enumerator();
        try {
            replaced = (Formula) pformula.replace(counter, occurrence,
                    searchPattern, replacementPattern);
        } 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_EQUIVALENCE_NOT_POSSIBLE,
                this,
                proofLines.getArgument(n).getArgument(0));
        }
        if (counter.getNumber() != occurrence + 1) { // did a replacement
            throw new ArgumentException(        // occur?
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                + RuleConstants.WRONG_OCCURENCE_NUMBER,
                getArgument(3),
                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));
        }

        final VariableList list;
        try {
            pformula.getSearchParents(new Enumerator(), occurrence,
                searchPattern);
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                + RuleConstants.WRONG_OCCURENCE_NUMBER,
                getArgument(3),
                proofLines.getArgument(n).getArgument(0));
        } catch (FoundException e) {
            list = e.getList();
        }
        for (int i = 1; i < list.getArgumentSize(); i++) {
            final Argument operator = list.getArgument(i);
            if (operator instanceof Disjunction) {
            } else if (operator instanceof Negation) {
            } else if (operator instanceof Conjunction) {
            } else if (operator instanceof Implication) {
            } else if (operator instanceof Equivalence) {
            } else {
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    RuleCreator.getName(this.getClass())
                    + RuleConstants.OPERATOR_NOT_SUPPORTED
                    + ModuleCreator.getName(operator.getClass()),
                    operator,
                    proofLines.getArgument(n).getArgument(0));
            }
        }

    }


    /**
     * 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 SubstLine, ReverseImplication
        //  LeftAddition, RightAddition, ModusPonens
        //  LeftAdditionImplication, RightAdditionImplication
        //  LeftAdditionConjunction, RightAdditionConjunction
        //  LeftAdditionEquivalence, RightAdditionEquivalence
        //  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(ReverseImplication.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(ReverseImplication.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(ModusPonens.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(ModusPonens.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));
        }
        if (!module.isRuleDeclared(LeftAdditionEquivalence.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(LeftAdditionEquivalence.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(RightAdditionEquivalence.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(RightAdditionEquivalence.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(a)                    (lineFormula n)
            //  1.       x =>  y        (formula1, AddSentence/AddAxiom)
            //  2.       y =>  x        (formula2, AddSentence/AddAxiom)
            //  3.       a =>  b        (formula3, SubstLine 1)
            //  4.       b =>  a        (formula4, SubstLine 2)
            //  5a      -a => -b        (formula5, ReverseImplication 4)
            //  6a      -b => -a        (formula6, ReverseImplication 3)
            //  5b.  c v a => c v b     (formula5, LeftAddition 3)
            //  6b.  c v b => c v a     (formula6, LeftAddition 4)
            //  5c.  a v c => b v c     (formula5, RightAddition 3)
            //  6c.  b v c => a v c     (formula6, RightAddition 4)
            //  5d.  c n a => c n b     (formula5, LeftAdditionConjunction 3)
            //  6d.  c n b => c n a     (formula6, LeftAdditionConjunction 4)
            //  5e.  a n c => b n c     (formula5, RightAdditionConjunction 3)
            //  6e.  b n c => a n c     (formula6, RightAdditionConjunction 4)
            //  5f.  c => a => c => b   (formula5, LeftAdditionImplication 4)
            //  6f.  c => b => c => a   (formula6, LeftAdditionImplication 3)
            //  5g.  a => c => b => c   (formula5, RightAdditionImplication 3)
            //  6g.  b => c => a => c   (formula6, RightAdditionImplication 4)
            //  5h.  c <=> a => c <=> b (formula5, LeftAdditionEquivalence 3)
            //  6h.  c <=> b => c <=> a (formula6, LeftAdditionEquivalence 4)
            //  5i.  a <=> c => b <=> c (formula5, RightAdditionEquivalence 3)
            //  6i.  b <=> c => a <=> c (formula6, RightAdditionEquivalence 4)
            //  ..
            //  s    A(a) => A(b)       (dito)
            //  t    A(b)               (formula,  ModusPonens n, s)

            final Formula formula = (Formula) proofLines.getArgument(position)
                .getArgument(0);
            //  A(a)                (lineFormula)
            final Formula lineFormula = (Formula) proofLines.getArgument(n)
                .getArgument(0);
            final ProofLineList newProofLines;
            try {
                newProofLines = new ProofLineList(new Argument[] {});
            } catch (ArgumentException e) {
                throw new IllegalArgumentException(e.toString());
            }

            final Formula searchPattern;
            final Formula replacementPattern;
            //  1.       x =>  y        (formula1, AddSentence/AddAxiom)
            {
                final RuleDeclaration declaration = module.getRuleDeclaration(
                    this.getClass());

                final LinkReference reference
                    = (LinkReference) this.getArgument(1);
                final String label = reference.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(),
                        reference.getArgument(0));
                }
                if (argument instanceof Sentence) {
                    newProofLines.add(
                        new ProofLine(new Argument[] {
                            argument.getArgument(0),
                            new AddSentence(
                                new Argument[] {
                                    reference
                                }
                            )
                        }));
                } else {
                    newProofLines.add(
                        new ProofLine(new Argument[] {
                            argument.getArgument(0),
                            new AddAxiom(
                                new Argument[] {
                                    reference
                                }
                            )
                        }));
                }
                searchPattern = AbstractFormula.createPattern(
                    (Formula) argument.getArgument(0).getArgument(0));
                replacementPattern = AbstractFormula.createPattern(
                    (Formula) argument.getArgument(0).getArgument(1));

            }

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

                final LinkReference reference
                    = (LinkReference) this.getArgument(2);
                final String label = reference.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(),
                        reference.getArgument(0));
                }
                if (argument instanceof Sentence) {
                    newProofLines.add(
                        new ProofLine(new Argument[] {
                            argument.getArgument(0),
                            new AddSentence(
                                new Argument[] {
                                    reference
                                }
                            )
                        }));
                } else {
                    newProofLines.add(
                        new ProofLine(new Argument[] {
                            argument.getArgument(0),
                            new AddAxiom(
                                new Argument[] {
                                    reference
                                }
                            )
                        }));
                }
            }

            final VariableList list1;
            try {
                lineFormula.getSearchParents(new Enumerator(), occurrence,
                    searchPattern);
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    RuleCreator.getName(this.getClass())
                    + RuleConstants.WRONG_OCCURENCE_NUMBER,
                    getArgument(3),
                    proofLines.getArgument(n).getArgument(0));
            } catch (FoundException e) {
                list1 = e.getList();
            }

            final VariableList list2;
            try {
                lineFormula.getReplacementParents(new Enumerator(),
                    occurrence, searchPattern, replacementPattern);
                throw new ArgumentException(
                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    RuleCreator.getName(this.getClass())
                    + RuleConstants.WRONG_OCCURENCE_NUMBER,
                    getArgument(3),
                    proofLines.getArgument(n).getArgument(0));
            } catch (FoundException e) {
                list2 = e.getList();
            }

            if (list1.getArgumentSize() != list2.getArgumentSize()) {
                throw new IllegalArgumentException(
                    "both list sizes must be equal");
            }


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

            //  4.       b =>  a        (formula4, SubstLine 2)
            {
                final Formula formula2 = new Implication(
                    (Formula) list2.getArgument(0),
                    (Formula) list1.getArgument(0)
                );
                newProofLines.add(
                    new ProofLine(
                        new Argument[] {
                            formula2,
                            new SubstLine(
                                new Argument[] {
                                    new Counter(position + 2)
                                }
                            )
                        }
                    )
                );
            }

            Formula oldFirst = (Formula) list1.getArgument(0);
            Formula oldSecond = (Formula) list2.getArgument(0);
            for (int i = 1; i < list1.getArgumentSize(); i++) {
                final Formula first = (Formula) list1.getArgument(i);
                final Formula second = (Formula) list2.getArgument(i);
                final Formula formula5 = new Implication(
                    first,
                    second
                );
                final Formula formula6 = new Implication(
                    second,
                    first
                );
                if (first instanceof Disjunction) {
                    if (first.getArgument(1).equals(oldFirst)
                            && second.getArgument(1).equals(oldSecond)) {
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula5,
                                    new LeftAddition(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + (2 * i) - 1),
                                            first.getArgument(0)
                                        }
                                    )
                                }
                            )
                        );
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula6,
                                    new LeftAddition(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + 2 * i),
                                            first.getArgument(0)
                                        }
                                    )
                                }
                            )
                        );
                    } else {
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula5,
                                    new RightAddition(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + (2 * i) - 1),
                                            first.getArgument(1)
                                        }
                                    )
                                }
                            )
                        );
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula6,
                                    new RightAddition(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + 2 * i),
                                            first.getArgument(1)
                                        }
                                    )
                                }
                            )
                        );
                    }
                } else if (first instanceof Negation) {
                    newProofLines.add(
                        new ProofLine(
                            new Argument[] {
                                formula5,
                                new ReverseImplication(
                                    new Argument[] {
                                        new Counter(position + 2
                                            + 2 * i)
                                    }
                                )
                            }
                        )
                    );
                    newProofLines.add(
                        new ProofLine(
                            new Argument[] {
                                formula6,
                                new ReverseImplication(
                                    new Argument[] {
                                        new Counter(position + 2
                                            + (2 * i - 1))
                                    }
                                )
                            }
                        )
                    );
                } else if (first instanceof Implication) {
                    if (first.getArgument(1).equals(oldFirst)
                            && second.getArgument(1).equals(oldSecond)) {
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula5,
                                    new LeftAdditionImplication(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + (2 * i - 1)),
                                            first.getArgument(0)
                                        }
                                    )
                                }
                            )
                        );
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula6,
                                    new LeftAdditionImplication(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + 2 * i),
                                            first.getArgument(0)
                                        }
                                    )
                                }
                            )
                        );
                    } else {
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula5,
                                    new RightAdditionImplication(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + 2 * i),
                                            first.getArgument(1)
                                        }
                                    )
                                }
                            )
                        );
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula6,
                                    new RightAdditionImplication(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + 2 * i - 1),
                                            first.getArgument(1)
                                        }
                                    )
                                }
                            )
                        );
                    }
                } else if (first instanceof Conjunction) {
                    if (first.getArgument(1).equals(oldFirst)
                            && second.getArgument(1).equals(oldSecond)) {
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula5,
                                    new LeftAdditionConjunction(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + (2 * i) - 1),
                                            first.getArgument(0)
                                        }
                                    )
                                }
                            )
                        );
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula6,
                                    new LeftAdditionConjunction(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + 2 * i),
                                            first.getArgument(0)
                                        }
                                    )
                                }
                            )
                        );
                    } else {
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula5,
                                    new RightAdditionConjunction(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + (2 * i) - 1),
                                            first.getArgument(1)
                                        }
                                    )
                                }
                            )
                        );
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula6,
                                    new RightAdditionConjunction(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + 2 * i),
                                            first.getArgument(1)
                                        }
                                    )
                                }
                            )
                        );
                    }
                } else if (first instanceof Equivalence) {
                    if (first.getArgument(1).equals(oldFirst)
                            && second.getArgument(1).equals(oldSecond)) {
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula5,
                                    new LeftAdditionEquivalence(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + (2 * i - 1)),
                                            new Counter(position + 2
                                                + 2 * i),
                                            first.getArgument(0)
                                        }
                                    )
                                }
                            )
                        );
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula6,
                                    new LeftAdditionEquivalence(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + 2 * i),
                                            new Counter(position + 2
                                                + (2 * i - 1)),
                                            first.getArgument(0)
                                        }
                                    )
                                }
                            )
                        );
                    } else {
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula5,
                                    new RightAdditionEquivalence(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + (2 * i - 1)),
                                            new Counter(position + 2
                                                + 2 * i),
                                            first.getArgument(1)
                                        }
                                    )
                                }
                            )
                        );
                        newProofLines.add(
                            new ProofLine(
                                new Argument[] {
                                    formula6,
                                    new RightAdditionEquivalence(
                                        new Argument[] {
                                            new Counter(position + 2
                                                + 2 * i),
                                            new Counter(position + 2
                                                + (2 * i - 1)),
                                            first.getArgument(1)
                                        }
                                    )
                                }
                            )
                        );
                    }
                } else {
                    throw new IllegalArgumentException(
                        "Unsupported operator "
                        + ModuleCreator.getName(first.getClass()));
                }
                oldFirst = first;
                oldSecond = second;
            }

            //  s    A(a) => A(b)       (dito)
            //  t    A(b)               (formula,  ModusPonens n, s)
            {
                newProofLines.add(
                    new ProofLine(
                        new Argument[] {
                            formula,
                            new ModusPonens(
                                new Argument[] {
                                    new Counter(this.n + 1),
                                    new Counter(position
                                        + 2 * list1.getArgumentSize() + 1)
                                }
                            )
                        }
                    )
                );
            }



            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), getArgument(2),
                    getArgument(3)});
        } catch (ArgumentException e) {
            throw new IllegalArgumentException(e.toString());
        } catch (IndexOutOfBoundsException e) {
            throw new IllegalArgumentException(e.toString());
        }
    }


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


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


    /** @inherited */
    public final String toString() {
        return "ElementaryEquivalence in " + getArgument(0) + " with "
            + getArgument(1) + " and " + getArgument(2)
            + " at " + getArgument(3);
    }


}
