// $Id: AbstractArgument.java,v 1.8 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.argument;


/**
 * Every Operator must inherit from this class. Its main function is to
 * provide the standard implemention of {@link #equals}, {@link #matches}
 * and {@link #hashCode}.
 *
 * @version $Revision: 1.8 $
 * @author  Michael Meyling
 */
public abstract class AbstractArgument implements Argument {


    /** @inherited */
    public abstract int getArgumentSize();


    /** @inherited */
    public abstract Argument getArgument(int i)
        throws IllegalArgumentException;


    /** @inherited */
    public boolean equals(final Object object) {
        if (object.getClass() == this.getClass()) {
            Argument argument = (Argument) object;
            if (getArgumentSize() == argument.getArgumentSize()) {
                for (int i = 0; i < getArgumentSize(); i++) {
                    if (!getArgument(i).equals(argument.getArgument(i))) {
                        return false;
                    }
                }
                return true;
            }
        }
        return false;
    }


    /** @inherited */
    public int hashCode() {
        return toString().hashCode();
    }


    /** @inherited */
    public final Argument replace(final Argument search,
            final Argument replacement) throws ArgumentException {
        if (this.equals(search)) {
            return replacement;
        } else {
            if (getArgumentSize() == 0) {   // this is an atom or an
                return this;                //  operator without arguments
            } else {
                final Argument[] replaced = new Argument[getArgumentSize()];
                boolean unChanged = true;
                for (int i = 0; i < getArgumentSize(); i++) {
                    final Argument part = getArgument(i);
                    replaced[i] = getArgument(i).replace(search, replacement);
                    if (unChanged && part != replaced[i]) {
                        unChanged = false;
                    }
                }
                if (unChanged) {
                    return this;
                } else {
                    return create(replaced);
                }
            }
        }
    }


    /** @inherited */
    public final Argument replace(final PatternVariables variables)
            throws ArgumentException {
        if (this instanceof PatternVariable) {
            PatternVariable variable = (PatternVariable) this;
            if (variables.contains(variable)) { // replace
                return variables.get(variable).getContent();
            } else {                            // not found
                return this;
            }
        } else {
            if (getArgumentSize() == 0) {   // this is an atom or an
                return this;                //  operator without arguments
            } else {                            // work on all Arguments
                final Argument[] replaced = new Argument[getArgumentSize()];
                boolean unChanged = true;
                for (int i = 0; i < getArgumentSize(); i++) {
                    final Argument part = getArgument(i);
                    replaced[i] = getArgument(i).replace(variables);
                    if (unChanged && part != replaced[i]) {
                        unChanged = false;
                    }
                }
                if (unChanged) {
                    return this;
                } else {
                    return create(replaced);
                }
            }
        }
    }


    /** @inherited */
    public final Argument replace(final Enumerator counter,
            final int occurance,
            final Argument searchPattern,
            final Argument replacePattern)
            throws ArgumentException {
        if (occurance >= counter.getNumber()) {
            final PatternVariables variables = new PatternVariables();
            if (matches(searchPattern, variables)) {
                counter.increaseNumber();
                if (occurance < counter.getNumber()) {
                    return replacePattern.replace(variables);
                }
                // else continue search
            }
            if (getArgumentSize() == 0) {   // this is an atom or an
                return this;                //  operator without arguments
            } else {
                final Argument[] replaced = new Argument[getArgumentSize()];
                boolean unChanged = true;
                for (int i = 0; i < getArgumentSize(); i++) {
                    final Argument part = getArgument(i);
                    replaced[i] = getArgument(i).replace(counter, occurance,
                        searchPattern, replacePattern);
                    if (unChanged && part != replaced[i]) {
                        unChanged = false;
                    }
                }
                if (unChanged) {
                    return this;
                } else {
                    return create(replaced);
                }
            }
        } else {    // nothing to do
            return this;
        }
    }


    /** @inherited */
    public final void getSearchParents(final Enumerator counter,
            final int occurance,
            final Argument searchPattern)
            throws FoundException {
        final PatternVariables variables = new PatternVariables();
        if (matches(searchPattern, variables)) {
            counter.increaseNumber();
            if (occurance < counter.getNumber()) {
                throw new FoundException(this);
            }
            // else continue search
        }
        if (getArgumentSize() == 0) {   // this is an atom or an
            return;                     //  operator without arguments
        } else {
            for (int i = 0; i < getArgumentSize(); i++) {
                try {
                    getArgument(i).getSearchParents(
                        counter,
                        occurance,
                        searchPattern);
                } catch (FoundException e) {
                    e.addAndThrow(this);
                }
            }
        }
    }


    /** @inherited */
    public final void getReplacementParents(final Enumerator counter,
            final int occurance,
            final Argument searchPattern,
            final Argument replacePattern)
            throws FoundException,
            ArgumentException {
        final PatternVariables variables = new PatternVariables();
        if (matches(searchPattern, variables)) {
            counter.increaseNumber();
            if (occurance < counter.getNumber()) {
                throw new FoundException(
                    replacePattern.replace(variables));
            }
            // else continue search
        }
        if (getArgumentSize() == 0) {   // this is an atom or an
            return;                     //  operator without arguments
        } else {
            final Argument[] replaced = new Argument[getArgumentSize()];
            for (int i = 0; i < getArgumentSize(); i++) {
                try {
                    replaced[i] = getArgument(i);
                    getArgument(i).getReplacementParents(
                        counter,
                        occurance,
                        searchPattern,
                        replacePattern);
                } catch (FoundException e) {
                    replaced[i] = e.getLastEntry();
                    for (int j = i + 1; j < getArgumentSize(); j++) {
                        replaced[j] = getArgument(j);
                    }
                    e.addAndThrow(create(replaced));
                }
            }
        }
    }


    /** @inherited */
    public final Argument replaceMatches(
            final Argument searchPattern,
            final Argument replacePattern) throws ArgumentException {
        final PatternVariables variables = new PatternVariables();
        if (matches(searchPattern, variables)) {
            return replacePattern.replace(variables);
            // else continue search
        }
        if (getArgumentSize() == 0) {       // this is an atom or an
            return this;                    //  operator without arguments
        } else {
            final Argument[] replaced = new Argument[getArgumentSize()];
            boolean unChanged = true;
            for (int i = 0; i < getArgumentSize(); i++) {
                final Argument part = getArgument(i);
                replaced[i] = getArgument(i).replaceMatches(searchPattern,
                    replacePattern);
                if (unChanged && part != replaced[i]) {
                    unChanged = false;
                }
            }
            if (unChanged) {
                return this;
            } else {
                return create(replaced);
            }
        }
    }


    /** @inherited */
    public final boolean matches(final Argument pattern,
            final PatternVariables variables)
            throws IllegalArgumentException {
        if (this instanceof PatternVariable) {
            // PatternVariables couldn't occur in something
            //  to match with a pattern
            throw new IllegalArgumentException(
                ArgumentConstants.NO_RECURSIVE_MATCH);
        }
        if (pattern instanceof PatternVariable) {
            PatternVariable variable = (PatternVariable) pattern;
            if (variables.contains(variable)) {
                return equals(variables.get(variable).getContent());
            } else if (variable.isMatching(this)) {
                variable = (PatternVariable) variable.copy();
                variable.setContent(this);
                variables.add(variable);
                return true;
            }
        }
        if (pattern.getClass() == this.getClass() &&
                getArgumentSize() == pattern.getArgumentSize()) {
            if (getArgumentSize() == 0) {   // this is an atom or an
                return equals(pattern);     //  operator without arguments
            } else {
                for (int i = 0; i < getArgumentSize(); i++) {
                    if (!getArgument(i).matches(pattern.getArgument(i), variables)) {
                        return false;
                    }
                }
                return true;
            }
        } else {
            return false;
        }
    }


    /** @inherited */
    public final boolean matches(final Argument pattern) {
        PatternVariables variables = new PatternVariables();
        if (matches(pattern, variables)) {
            return true;
        }
        if (getArgumentSize() == 0) {
            return equals(pattern);
        } else {
            for (int i = 0; i < getArgumentSize(); i++) {
                if (getArgument(i).matches(pattern)) {
                    return true;
                }
            }
            return false;
        }
    }


    /**
     * @inherited
     * @return this standard implementation returns always false
     */
    public boolean containsPatternVariables() {
        boolean result = false;
        for (int i = 0; i < getArgumentSize(); i++) {
            result |= getArgument(i).containsPatternVariables();
            if (result) {
                break;
            }
        }
        return result;
    }


    /** @inherited */
    public PatternVariables getPatternVariables() {
        PatternVariables variables = new PatternVariables();
        for (int i = 0; i < getArgumentSize(); i++) {
            variables.addAll(getArgument(i).getPatternVariables());
        }
        return variables;
    }


    /** @inherited */
    public abstract Argument create(Argument[] arguments)
        throws ArgumentException;


    /** @inherited */
    public abstract String toString();


    /**
     * Find the highest value for a <code>Counter</code> in this argument.
     *
     * @param   argument    search this argument
     * @return  maximum
     */
    public final static int getHighestNumber(final Argument argument)
            throws ArgumentException {

        if (argument instanceof Counter) {
            return ((Counter) argument).getNumber();
        }
        int maximum = -1;
        for (int i = 0; i < argument.getArgumentSize(); i++) {
            final int n = getHighestNumber(argument.getArgument(i));
            if (maximum < n) {
                maximum = n;
            }
        }
        return maximum;
    }


}
