// $Id: SubstLine.java,v 1.17 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 java.util.HashMap;
import java.util.Map;

import com.meyling.principia.argument.AbstractArgument;
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.PatternVariable;
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.BasicFormulaPatternVariable;
import com.meyling.principia.logic.basic.BasicPatternVariables;
import com.meyling.principia.logic.basic.BasicSubjectVariablePatternVariable;
import com.meyling.principia.logic.basic.Formula;
import com.meyling.principia.logic.basic.PropositionVariable;
import com.meyling.principia.logic.basic.SubjectVariable;
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.module.Module;
import com.meyling.principia.module.Version;


/**
 * Implementation of substituting variables:
 *
 * <p>
 * <code>       A1</code><p>
 * <code>---------</code><p>
 * <code>       A2</code><p>
 *
 * if A1 matches A2 (e.g. A2 could be derived by
 * substituting some variables of A1)
 *
 * @version $Revision: 1.17 $
 * @author  Michael Meyling
 */
public class SubstLine extends AbstractArgumentList
        implements Argument, Rule {

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

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


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


    /**
     * Constructs a subst line rule by a proof line 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 SubstLine(final Argument[] arguments)
            throws ArgumentException {
        super (arguments);
        if (arguments.length != 1) {
            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 (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]);
        }

    }


    /**
     * Check if proof could be extended with <code>formula</code>.
     * This is true
     * if <code>formula</code> matches the referenced proof line.
     *
     * @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 referenced proof line
     *          doesn't match the formula
     *          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 lineFormula = (Formula) proofLines.getArgument(n)
            .getArgument(0);

        final Formula pattern = (Formula) AbstractFormula
            .createPattern(lineFormula);

        final BasicPatternVariables basicVariables = new BasicPatternVariables();
        final BasicPatternVariables originalVariables = new BasicPatternVariables();

        if (!formula.matches(pattern, basicVariables)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(this.getClass())
                    + RuleConstants.SUBSTITUTION_NOT_SUCCESSFULL,
                this);
        }
        if (!lineFormula.matches(pattern, originalVariables)) {
            throw new IllegalArgumentException(
                "proof line dosn't match itself");
        }

        final VariableList variables = basicVariables.getVariables();

        try {
            for (int i = 0; i < variables.getArgumentSize(); i++) {
                final PatternVariable variable = (PatternVariable)
                    variables.getArgument(i);
                final Argument original = originalVariables.get(variable)
                    .getContent();
                final Argument newOne = variable.getContent();
                if (original instanceof SubjectVariable) {
                    final SubjectVariable oldVar = (SubjectVariable) original;
                    final SubjectVariable newVar = (SubjectVariable) newOne;
                    if (lineFormula.getFreeSubjectVariables().contains(oldVar)) {
                        if (!formula.getFreeSubjectVariables().contains(newVar)) {
                            throw new ArgumentException(
                                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                                RuleCreator.getName(this.getClass())
                                    + RuleConstants.SUBSTITUTION_NOT_SUCCESSFULL,
                                this);
                        }
                    } else if (lineFormula.getBoundSubjectVariables().contains(oldVar)) {
                        if (!formula.getBoundSubjectVariables().contains(newVar)) {
                                throw new ArgumentException(
                                    RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                                    RuleCreator.getName(this.getClass())
                                        + RuleConstants.SUBSTITUTION_NOT_SUCCESSFULL,
                                    this);
                        }
                    } else {
                        throw new IllegalArgumentException(
                            "neither free nor bound subject variable");
                    }
                }
             }
        } catch (ClassCastException e) {
            throw new IllegalArgumentException(e.toString());
        }
    }


    /**
     * 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 {
            int startPosition = position;

            Formula lineFormula = (Formula) proofLines.getArgument(n)
                .getArgument(0);
            final Formula formula = (Formula) proofLines.getArgument(position)
                .getArgument(0);
            final Formula pattern = (Formula) AbstractFormula
                .createPattern(lineFormula);
            final BasicPatternVariables basicVariables = new BasicPatternVariables();
            final BasicPatternVariables originalVariables = new BasicPatternVariables();
            if (!formula.matches(pattern, basicVariables)) {
                throw new IllegalArgumentException(
                    RuleCreator.getName(this.getClass())
                        + " proof line dosn't match referenced proof line");
            }

            if (!lineFormula.matches(pattern, originalVariables)) {
                throw new IllegalArgumentException(
                    RuleCreator.getName(this.getClass())
                        + " proof line dosn't match itself");
            }

            final VariableList variables = basicVariables.getVariables();
            final int startNumber;
            {
                int maximum = basicVariables.getHighestNumber();
                int n = AbstractArgument.getHighestNumber(formula);
                if (maximum < n) {
                    maximum = n;
                }
                startNumber = maximum + 1;
            }

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

            int newLineNumbers = 0;
            for (int i = 0; i < variables.getArgumentSize(); i++) {
                final PatternVariable variable = (PatternVariable)
                    variables.getArgument(i);
                final Argument original = originalVariables.get(variable)
                    .getContent();
                final Argument newOne = variable.getContent();

                final Map bound = new HashMap();

                if (variable instanceof
                        BasicSubjectVariablePatternVariable) {
                    final SubjectVariable subVar = (SubjectVariable) original;

                    if (lineFormula.getFreeSubjectVariables().contains(subVar)) {
                        // free subject variable

                        if (!original.equals(newOne)) {
                            // only if they are different
                            lineFormula = (Formula) lineFormula.replace(original,
                                new SubjectVariable(startNumber + i));
                            newProofLines.add(
                                new ProofLine(new Argument[] {
                                    lineFormula,
                                    new RenameFreeSubjectVariable(
                                        new Argument[] {
                                            new Counter( (0 == newLineNumbers
                                                ? this.n + 1
                                                : startPosition + newLineNumbers)),
                                            subVar,
                                            new SubjectVariable(startNumber + i)
                                        }
                                    )
                                }));
                            newLineNumbers++;
                        }
                    } else {    // bound subject variable

                        // get occurence number
                        int occurences = 0;
                        if (bound.containsKey(subVar)) {
                            occurences = ((Integer) bound
                                .get(subVar)).intValue() + 1;
                            bound.put(subVar,
                                new Integer(occurences));
                        } else {
                            bound.put(subVar,
                                new Integer(occurences));
                        }

                        if (!original.equals(newOne)) {
                            // only if they are different
                            lineFormula = (Formula) lineFormula.replaceBoundSubjectVariable(
                                                new Enumerator(),
                                                occurences,
                                                subVar,
                                                new SubjectVariable(startNumber + i));

                            newProofLines.add(
                                new ProofLine(new Argument[] {
                                    lineFormula,
                                    new RenameBoundSubjectVariable(
                                        new Argument[] {
                                            new Counter( (0 == newLineNumbers ? this.n + 1
                                                : startPosition + newLineNumbers)),
                                            subVar,
                                            new SubjectVariable(startNumber + i),
                                            new Counter(occurences + 1)
                                        }
                                    )
                                }));

                            newLineNumbers++;
                        }
                    }
                } else if (!original.equals(newOne)) {
                        // only if they are different
                    if (variable
                        instanceof BasicFormulaPatternVariable) {
                        lineFormula = (Formula) lineFormula.replace(original,
                            new PropositionVariable(startNumber + i));
                        newProofLines.add(
                            new ProofLine(new Argument[] {
                                lineFormula,
                                new ReplacePropositionVariable(
                                    new Argument[] {
                                        new Counter( (0 == newLineNumbers ? this.n + 1
                                            : startPosition + newLineNumbers)),
                                        original,
                                        new PropositionVariable(startNumber + i)
                                    }
                                )
                            }));

                        newLineNumbers++;

/*
                    } else if (variable instanceof
                        // TODO testing: not working
                            BasicPredicateVariablePatternVariable) {
                        newProofLines.add(
                            new ProofLine(new Argument[] {
                                lineFormula,
                                new ReplacePredicateVariable(
                                    new Argument[] {
                                        new Counter( (0 == i ? this.n + 1
                                            : startPosition + i)),
                                        original,
                                        // TODO testing: not working, change Constructor
                                        new PredicateVariable(
                                            new Argument[] {
                                                new Counter(startNumber + i)
                                            }
                                        )
                                    }
                                )
                            }));
*/
                    } else if (variable instanceof PatternVariable) {
                        // TODO testing
                            throw new UnsupportedOperationException(
                                "replacement of predicate variable not yet supported");

                        // TODO testing: not working
    /*
                        lineFormula = (Formula) lineFormula.replace(original,
                            new Counter(startNumber + i));
                        newProofLines.add(
                            new ProofLine(new Argument[] {
                                lineFormula,
                                new ReplacePredicateVariable(
                                    new Argument[] {
                                        new Counter( (0 == i ? this.n + 1
                                            : startPosition + i)),
                                        new PredicateVariable(
                                            new Argument[] {
                                                original
                                            }
                                        ),
                                        new PredicateVariable(
                                            new Argument[] {
                                                new Counter(startNumber + i)
                                            }
                                        )
                                    }
                                )
                            }));

    */
                    } else {
                        throw new IllegalArgumentException(
                            "unknown basic pattern variable: "
                            + variable.getClass().getName());
                    }
                }
            }

            if (newLineNumbers > 0) {
                startPosition += newLineNumbers;
                newLineNumbers = 0;
                for (int i = 0; i < variables.getArgumentSize(); i++) {
                    final PatternVariable variable
                        = (PatternVariable) variables.getArgument(i);
                    final Argument newOne = variable.getContent();
                    final Argument original = originalVariables.get(variable)
                        .getContent();
                    if (!newOne.equals(original)) {  // only if they are different
                        if (variable instanceof
                                BasicSubjectVariablePatternVariable) {
                            final SubjectVariable subVar
                                = new SubjectVariable(startNumber + i);
                            // check if subject variable is free
                            if (lineFormula.getFreeSubjectVariables().contains(subVar)) {
                                lineFormula = (Formula) lineFormula.replace(
                                    new SubjectVariable(startNumber + i), newOne);
                                newProofLines.add(
                                    new ProofLine(new Argument[] {
                                        lineFormula,
                                        new RenameFreeSubjectVariable(
                                            new Argument[] {
                                                new Counter(startPosition + newLineNumbers),
                                                subVar,
                                                newOne
                                            }
                                        )
                                    }));
                                newLineNumbers++;
                            } else {    // bound subject variable
                                // get occurence number
                                int occurences = 0;
                                lineFormula = (Formula) lineFormula.replaceBoundSubjectVariable(
                                                    new Enumerator(),
                                                    occurences,
                                                    subVar,
                                                    (SubjectVariable) newOne);
                                newProofLines.add(
                                    new ProofLine(new Argument[] {
                                        lineFormula,
                                        new RenameBoundSubjectVariable(
                                            new Argument[] {
                                                new Counter(startPosition + newLineNumbers),
                                                subVar,
                                                (SubjectVariable) newOne,
                                                new Counter(occurences + 1)
                                            }
                                        )
                                    }));

                                newLineNumbers++;
                            }

                        } else if (variable
                                instanceof BasicFormulaPatternVariable) {
                            lineFormula = (Formula) lineFormula.replace(
                                new PropositionVariable(startNumber + i), newOne);
                            newProofLines.add(
                                new ProofLine(new Argument[] {
                                    lineFormula,
                                    new ReplacePropositionVariable(
                                        new Argument[] {
                                            new Counter(startPosition + newLineNumbers),
                                            new PropositionVariable(startNumber + i),
                                            newOne
                                        }
                                    )
                                }));
                            newLineNumbers++;

/*
                        } else if (variable instanceof
                            // TODO testing: not working
                                BasicPredicateVariablePatternVariable) {

                            argument = new PredicateVariable(
                                new Argument[] {variable.getArgument(0)});
                            newProofLines.add(
                                new ProofLine(new Argument[] {
                                    lineFormula,
                                    new ReplacePredicateVariable(
                                        new Argument[] {
                                            new Counter(startPosition + i),
                                            argument,
                                            // TODO testing: not working, change Constructor
                                            new PredicateVariable(
                                                new Argument[] {
                                                    new Counter(startNumber + i)
                                                }
                                            )
                                        }
                                    )
                                }));
        */
                        } else if (variable instanceof PatternVariable) {
                            // TODO testing: not working
                            throw new UnsupportedOperationException(
                                "replacement of predicate variable not yet supported");
                        } else {
                            throw new IllegalArgumentException(
                                "unknown basic pattern variable: "
                                + variable.getClass().getName());
                        }
                    }
                }

            }
            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)});
        } 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 ReplacePropositionVariable, ReplacePredicateVariable,
        //  RenameFreeSubjectVariable and RenameBoundSubjectVariable
        //  had been declared before
        if (!module.isRuleDeclared(ReplacePropositionVariable.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(ReplacePropositionVariable.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(ReplacePredicateVariable.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(ReplacePredicateVariable.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(RenameFreeSubjectVariable.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(RenameFreeSubjectVariable.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
        if (!module.isRuleDeclared(RenameBoundSubjectVariable.class)) {
            throw new ArgumentException(
                RuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                RuleCreator.getName(RenameBoundSubjectVariable.class)
                    + RuleConstants.RULE_DECLARATION_MISSING,
                declaration.getArgument(0));
        }
    }


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


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


    /** @inherited */
    public final String toString() {
        return "Subst Line " + getArgument(0);
    }



}
