// $Id: Proposition.java,v 1.22 2004/02/27 16:30:00 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.paragraph;


import java.util.HashMap;
import java.util.Map;

import com.meyling.principia.argument.AbstractDynamicArgumentList;
import com.meyling.principia.argument.Argument;
import com.meyling.principia.argument.ArgumentException;
import com.meyling.principia.logic.basic.Formula;
import com.meyling.principia.logic.rule.Rule;
import com.meyling.principia.module.Module;
import com.meyling.principia.module.ModuleCreator;
import com.meyling.principia.module.Version;


/**
 * Logical sentence.
 *
 * @version $Revision: 1.22 $
 * @author  Michael Meyling
 */
public class Proposition extends AbstractDynamicArgumentList
        implements Argument, ParagraphCheck {


    /**
     * Constructs a mathematical proposition with its proof.
     *
     * @param   arguments     sentence and its proof
     * @throws  ArgumentException    if there is not exactly two
     *          arguments or the first argument is not an instance
     *          of {@link Sentence} or the second argument
     *          is no instance of {@link ProofLineList}.
     */
    public Proposition(final Argument[] arguments)
            throws ArgumentException {
        super(arguments);

        if (arguments.length != 2) {
            throw new ArgumentException(
                ParagraphConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_NUMBER,
                ModuleCreator.getName(this.getClass())
                + ParagraphConstants.OPERATOR_WITH_TWO_ARGUMENTS);
        }
        if (!(arguments[0] instanceof Sentence)) {
            System.out.println("Heia");
            throw new ArgumentException(
                ParagraphConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                ModuleCreator.getName(this.getClass())
                + ParagraphConstants.FIRST_ARGUMENT_TYPE
                + ModuleCreator.getName(Sentence.class),
                arguments[0]);
        }
        if (arguments[0].containsPatternVariables()) {
            throw new ArgumentException(
                ParagraphConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                ParagraphConstants.NO_PATTERN_VARIABLE_IN_PROPOSITION,
                arguments[0]);
        }
        if (!(arguments[1] instanceof ProofLineList)) {
            throw new ArgumentException(
                ParagraphConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                ModuleCreator.getName(this.getClass())
                + ParagraphConstants.SECOND_ARGUMENT_TYPE
                + ModuleCreator.getName(ProofLineList.class),
                arguments[1]);
        }
    }


    /**
     * Check if proof is correct.
     *
     * @param   module   module
     * @param   label    label of paragraph
     * @throws  ArgumentException    if proof is not correct
     */
    public final void check(final Module module, final String label)
            throws ArgumentException {

        final ProofLineList proofLines = (ProofLineList) getArgument(1);
        final int lines = proofLines.getArgumentSize();
        for (int n = 0; n < lines; n++) {
            final Rule rule
                = ((Rule) proofLines.getArgument(n).getArgument(1));
            try {
                module.getRuleDeclaration(rule.getClass());
            } catch (IllegalArgumentException e) {
                throw new ArgumentException(
                    ParagraphConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                    ModuleCreator.getName(rule.getClass())
                        + ParagraphConstants.RULE_NOT_DECLARED,
                    rule);
            }
            try {
                rule.check(module, proofLines, n,
                    (Formula) proofLines.getArgument(n).getArgument(0));
            } catch (ArgumentException e) { // TODO is that neccessary?
                if (e.getArgument() == null) {
                    System.out.println("Resetting argument");   // TODO
                    e.setArgument(proofLines.getArgument(n));
                }
                throw e;
            }
        }
        Formula result
            = (Formula) proofLines.getArgument(lines - 1).getArgument(0);
        if (!result.equals(getArgument(0).getArgument(0))) {
            throw new ArgumentException(
                ParagraphConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                ModuleCreator.getName(this.getClass())
                    + ParagraphConstants.LAST_LINE_NOT_EQUAL_WITH_EXPECTED,
                proofLines.getArgument(lines - 1));
        }
        module.addSentence(label, (Sentence) getArgument(0), module);
    }


    /**
     * Remove all double and unused proof lines.
     *
     * @throws  ArgumentException  if proof is there were any illegal
     *              line references
     * @throws  IllegalArgumentException if there was a programming error
     */
    public final void compress()
            throws ArgumentException {

        final ProofLineList oldProofLines = (ProofLineList) getArgument(1);
        final int oldLines = oldProofLines.getArgumentSize();

        // mark used proof lines
        final boolean[] lineUsed = new boolean[oldLines];
        if (oldLines > 0) {
            lineUsed[oldLines - 1] = true;
            for (int n = oldLines - 1; n >= 0; n--) {
                if (lineUsed[n]) {
                    final int[] lines = ((Rule) oldProofLines.getArgument(n)
                        .getArgument(1)).getProofLines();
                    for (int j = 0; j < lines.length; j++) {
                        if (lines[j] < 0 || j >= oldLines) {
                            throw new ArgumentException(
                                ParagraphConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                                ModuleCreator.getName(this.getClass())
                                    + ParagraphConstants.PROOF_LINES_OUT_OF_RANGE,
                                oldProofLines.getArgument(n));
                        }
                        lineUsed[lines[j]] = true;
                    }
                } else {
                }
            }
        }


        // mark only first use
        final int[] lineMap = new int[oldLines];
        {
            int m = 0;
            final Map formulaMap = new HashMap(oldLines);
            for (int i = 0; i < oldLines; i++) {
                if (lineUsed[i]) {  // analyze only used proof lines
                    final Argument formula = oldProofLines.getArgument(i)
                            .getArgument(0);
                    if (formulaMap.containsKey(formula)) {
                        final int k = ((Integer) formulaMap.get(formula))
                            .intValue();
                        lineMap[i] = k;
                        lineUsed[i] = false;    // not used any longer
                    } else {
                        formulaMap.put(formula, new Integer(m));
                        lineMap[i] = m++;
                    }
                }
            }
        }


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

        // remap use
        for (int n = 0; n < oldLines; n++) {
            if (lineUsed[n]) {  // step over used proof lines only
                newProofLines.add(new ProofLine(new Argument[] {
                    oldProofLines.getArgument(n).getArgument(0),
                    ((Rule) oldProofLines.getArgument(n).getArgument(1))
                    .changeProofLines(lineMap)}));
            }
        }
        remove(1);
        add(newProofLines);

    }


    /**
     * Reduce needed rule version and change proof lines in that way.
     *
     * @param   module  this is the connected module
     * @throws  ArgumentException    if a proof line has major
     *              errors in their line references
     * @throws  UnsupportedOperationException if there is a rule
     *              which couldn't yet be reduced to simper rules
     *              because the reduction for that rule isn't
     *              implemented
     * @throws  IllegalArgumentException if there was a programming error
     */
    public final void reduceRuleVersion(final Module module,
        final Version version)
            throws ArgumentException {

        compress(); /* checks also correctness of line references */
        try {
            boolean replace = true;
            while (replace) {
                replace = false;
                for (int n = 0; n < getArgument(1).getArgumentSize(); n++) {
                    final Rule rule;
                    if ((rule = (Rule) getArgument(1).getArgument(n)
                            .getArgument(1)).getVersion()
                            .greaterThan(version)
                            ){
                        final ProofLineList newProofLines
                            = rule.extendWithout(
                            module, (ProofLineList) this.getArgument(1),
                            n);
                        if (newProofLines.getArgumentSize() > 0) {
                            replaceProofLineByProofLines(newProofLines, n);
                            replace = true;
                        }
                    }
                }
                compress();
            }

        } catch (ArgumentException e) { // must be an programming error
            throw new IllegalArgumentException(e.toString());
        }
// TODO This is the old version, it looks better, but it produces
// TODO  longer .qedeq files. Why that???
/*
        for (int n = 0; n < getArgument(1).getArgumentSize(); n++) {
            Rule rule;
            while ( (rule = (Rule) getArgument(1).getArgument(n)
                    .getArgument(1)).getVersion().greaterThan(version)) {
                final ProofLineList newProofLines
                    = rule.extendWithout(
                    module, (ProofLineList) this.getArgument(1),
                    n);
                replaceProofLineByProofLines(newProofLines, n);
            }
        }
        compress();
*/
    }


    /**
     * Replace a single proof line by some (other) proof lines.
     * If the replacement lines are empty, nothing is done;
     * by calling <code>compress</code> the caller could remove
     * the proof line.
     *
     * @param   proofLines   these proof lines should be inserted,
     *                           the line references must be already
     *                           correct
     * @param   position        this line should be replaced by the new
     *                           proof lines
     * @throws  IllegalArgumentException    if <code>position</code> is
     *                           not a valid one, or the remaping failed
     *                          because the given proof lines had illegal
     *                          references
     */
    public final void replaceProofLineByProofLines(
            final ProofLineList proofLines,
            final int position) {

        final ProofLineList oldProofLines = (ProofLineList) getArgument(1);
        final int oldLines = oldProofLines.getArgumentSize();
        final int newLines = proofLines.getArgumentSize();

        if (position < 0 || position >= oldLines) {
            throw new IllegalArgumentException(
                "insert position out of range: " + position);
        }
        if (newLines == 0) {
            return;
        }
        final int[] lineMap = new int[oldLines];
        for (int i = 0; i < position; i++) {
            lineMap[i] = i;
        }
        for (int i = position; i < oldLines; i++) {
            lineMap[i] = i + newLines - 1;
        }
        final ProofLineList newProofLines;
        try {
            newProofLines = new ProofLineList(new Argument[] {});
        } catch (ArgumentException e) {
            throw new IllegalArgumentException(e.toString());
        }

        for (int i = 0; i < position; i++) {
            newProofLines.add(oldProofLines.getArgument(i));
        }
        for (int i = 0; i < newLines; i++) {
            newProofLines.add(proofLines.getArgument(i));
        }
/*
        for (int i = position + 1; i < oldLines; i++) {
            newProofLines.add(oldProofLines.getArgument(i));
        }
*/
        // remap line references
        try {
            for (int i = position + 1; i < oldLines; i++) {
                newProofLines.add(new ProofLine(new Argument[] {
                    oldProofLines.getArgument(i).getArgument(0),
                    ((Rule) oldProofLines.getArgument(i).getArgument(1))
                    .changeProofLines(lineMap)}));
            }
        } catch (ArgumentException e) {
            throw new IllegalArgumentException(e.toString());
        }
        remove(1);
        add(newProofLines);
    }


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


    /** @inherited */
    public final String toString() {
        return "Proposition " + getArgument(0).toString() + "\n"
            + getArgument(1);
    }


}
