/***************************************************************************
    File                 : AbstractFilter.h
    Project              : SciDAVis
    --------------------------------------------------------------------
    Copyright            : (C) 2007-2009 by Knut Franke, Tilman Benkert
    Email (use @ for *)  : knut.franke*gmx.de, thzs*gmx.net
    Description          : Base class for all analysis operations.

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  This program 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.                           *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/
#ifndef ABSTRACT_FILTER_H
#define ABSTRACT_FILTER_H

#include "AbstractAspect.h"
#include "AbstractColumn.h"
#include <QVector>

/**
 * \brief Base class for all analysis operations.
 *
 * AbstractFilter provides an abstraction for analysis operations. It is modelled on an
 * electronic filtering circuit: From the outside, a filter appears as a black box with
 * a number of input and output ports (obviously, those numbers do not necessarily agree).
 *
 * \section using Using AbstractFilter
 * You can connect one AbstractColumn to each input port using
 * input(int port, AbstractColumn* source). Every output(int port) is realized
 * again by an AbstractColumn, which you can connect to as many other filters, tables
 * or plots as you like.
 * Ownership of the data sources always stays with the class which is providing the data,
 * that is, neither input() nor output() transfer ownership.
 *
 * Furthermore, you can use inputCount() and outputCount() to query the number of
 * input and output ports, respectively and you can obtain label strings for inputs (via
 * inputLabel()) and outputs (via AbstractColumn::label()). This allows generic filter
 * handling routines to be written, which is important for using filters provided by plugins.
 *
 * Its simplicity of use notwithstanding, AbstractFilter provides a powerful and versatile
 * basis also for analysis operations that would not commonly be referred to as "filter".
 * An example of such a more advanced filter implementation is StatisticsFilter.
 *
 * \section subclassing Subclassing AbstractFilter
 * The main design goal was to make implementing new filters as easy as possible.
 * Filters with only one output port can subclass AbstractSimpleFilter, which is even easier
 * to use. Filters with more than one output port have to subclass
 * AbstractFilter directly, which is slightly more involved, because in
 * addition to data transfer between these classes the signals defined by AbstractColumn
 * have to be handled on both inputs and outputs. Signals from data sources connected to the input
 * ports are automatically connected to a matching set of virtual methods, which can be
 * reimplemented by subclasses to handle these events.
 *
 * While AbstractFilter handles the tedious part of connecting a data source to an input port,
 * its subclasses are given a chance to reject such connections (e.g., based on the data type
 * of the source) by reimplementing inputAcceptable().
 *
 * \sa AbstractSimpleFilter
 */
class AbstractFilter : public AbstractAspect
{
    Q_OBJECT

public:
    //! Standard constructor.
    AbstractFilter(const QString &name) : AbstractAspect(name) { }
    //! Destructor.
    virtual ~AbstractFilter() { }

    //! Return the number of input ports supported by the filter or -1 if any number of inputs is acceptable.
    virtual int inputCount() const = 0;
    /**
     * \brief Return the number of output ports provided by the filter.
     *
     * %Note that this number need not be static, but can be dynamically determined, for example
     * based on the inputs provided to the filter.
     */
    virtual int outputCount() const = 0;
    /**
     * \brief Return the index of the highest input port that is connected.
     *
     * Note that this is different from both the number of ports that <em>could</em> be connected,
     * inputCount(), and the number of ports that actually <em>have been</em> connected, which are
     * not necessarily sequential. In conjunction with input(int), this method can be used to
     * traverse the connected inputs.
     */
    int highestConnectedInput() const { return d_inputs.count() - 1; }
    /**
     * \brief Connect the provided data source to the specified input port.
     * \param port the port number to which to connect
     * \param source the data source to connect to the input port
     * \returns true if the connection was accepted, false otherwise.
     *
     * The port number is checked for validity against inputCount() and both port number and data
     * source are passed to inputAcceptable() for review. If both checks succeed,the
     * source is recorded in #d_inputs.
     * If applicable, the previously connected data source is disconnected before replacing it.
     *
     * You can also use this method to disconnect an input without replacing it with a new one by
     * calling it with source=0.
     *
     * \sa inputAcceptable(), #d_inputs
     */
    bool input(int port, const AbstractColumn *source);
    /**
     * \brief Connect all outputs of the provided filter to the corresponding inputs of this filter.
     * \returns true if all connections were accepted, false otherwise
     *
     * Overloaded method provided for convenience.
     */
    bool input(const AbstractFilter *sources);
    //! Return the input currently connected to the specified port, or 0.
    const AbstractColumn *input(int port) const { return d_inputs.value(port); }
    /**
     * \brief Return the label associated to the given input port.
     *
     * Default labels are In1, In2, ... (or translated equivalents), but implementations can
     * reimplement this method to produce more meaningful labels.
     *
     * Output ports are implicitly labeled through AbstractAspect::name().
     */
    virtual QString inputLabel(int port) const;
    /**
     * \brief Get the data source associated with the specified output port.
     *
     * The returned pointer may be 0 even for valid port numbers, for example if not all required
     * input ports have been connected.
     */
    virtual AbstractColumn *output(int port = 0) = 0;
    //! Overloaded method for const access.
    virtual const AbstractColumn *output(int port = 0) const = 0;

    //! Return the input port to which the column is connected or -1 if it's not connected
    int portIndexOf(const AbstractColumn *column)
    {
        for (int i = 0; i < d_inputs.size(); i++)
            if (d_inputs.at(i) == column)
                return i;
        return -1;
    }

protected:
    /**
     * \brief Give implementations a chance to reject connections to their input ports.
     *
     * If not reimplemented, all connections to ports within [0, inputCount()-1] will be accepted.
     */
    virtual bool inputAcceptable(int port, const AbstractColumn *source)
    {
        Q_UNUSED(port);
        Q_UNUSED(source);
        return true;
    }
    /**
     * \brief Called whenever an input is disconnected or deleted.
     *
     * This is only to notify implementations of the event, the default implementation is a
     * no-op.
     */
    virtual void inputAboutToBeDisconnected(const AbstractColumn *source) { Q_UNUSED(source); }

protected slots:

    //!\name signal handlers
    //@{
    /**
     * \brief Name and/or comment of an input will be changed.
     *
     * \param source is always the this pointer of the column that emitted the signal.
     */
    virtual void inputDescriptionAboutToChange(const AbstractColumn *source) { Q_UNUSED(source); }
    void inputDescriptionAboutToChange(const AbstractAspect *aspect)
    {
        const AbstractColumn *col = qobject_cast<const AbstractColumn *>(aspect);
        if (col)
            inputDescriptionAboutToChange(col);
    }
    //!
    /**
     * \brief Name and/or comment of an input changed.
     *
     * \param source is always the this pointer of the column that emitted the signal.
     */
    virtual void inputDescriptionChanged(const AbstractColumn *source) { Q_UNUSED(source); }
    void inputDescriptionChanged(const AbstractAspect *aspect)
    {
        const AbstractColumn *col = qobject_cast<const AbstractColumn *>(aspect);
        if (col && d_inputs.contains(col))
            inputDescriptionChanged(col);
    }
    /**
     * \brief The plot designation of an input is about to change.
     *
     * \param source is always the this pointer of the column that emitted the signal.
     */
    virtual void inputPlotDesignationAboutToChange(const AbstractColumn *source)
    {
        Q_UNUSED(source);
    };
    /**
     * \brief The plot designation of an input changed.
     *
     * \param source is always the this pointer of the column that emitted the signal.
     */
    virtual void inputPlotDesignationChanged(const AbstractColumn *source) { Q_UNUSED(source); }
    /**
     * \brief The display mode and possibly the data type of an input is about to change.
     *
     * \param source is always the this pointer of the column that emitted the signal.
     */
    virtual void inputModeAboutToChange(const AbstractColumn *source) { Q_UNUSED(source); }
    /**
     * \brief The display mode and possibly the data type has changed.
     *
     * \param source is always the this pointer of the column that emitted the signal.
     */
    virtual void inputModeChanged(const AbstractColumn *source) { Q_UNUSED(source); }
    /**
     * \brief The data of an input is about to change.
     *
     * \param source is always the this pointer of the column that emitted the signal.
     */
    virtual void inputDataAboutToChange(const AbstractColumn *source) { Q_UNUSED(source); }
    /**
     * \brief The data of an input has changed.
     *
     * \param source is always the this pointer of the column that emitted the signal.
     */
    virtual void inputDataChanged(const AbstractColumn *source) { Q_UNUSED(source); }
    /**
     * \brief An input is about to be replaced.
     *
     * This signal is handled by AbstractFilter and mapped to input(int,AbstractColumn*),
     * which calls inputDescriptionAboutToChange(), inputPlotDesignationAboutToChange(),
     * inputDataAboutToChange(), inputMaskingAboutToChange(),
     * inputDescripionChanged(), inputMaskingChanged()
     * inputPlotDesignationChanged() and inputDataChanged().
     * inputModeAboutToChange() and inputModeChanged() are
     * called if the new column has a different mode (and thereby possibly data type).
     * Thus, filter implementations won't have to bother with it most of the time.
     */
    virtual void inputAboutToBeReplaced(const AbstractColumn *source,
                                        const AbstractColumn *replacement);

    virtual void inputRowsAboutToBeInserted(const AbstractColumn *source, int before, int count)
    {
        Q_UNUSED(source);
        Q_UNUSED(before);
        Q_UNUSED(count);
    }
    virtual void inputRowsInserted(const AbstractColumn *source, int before, int count)
    {
        Q_UNUSED(source);
        Q_UNUSED(before);
        Q_UNUSED(count);
    }
    virtual void inputRowsAboutToBeRemoved(const AbstractColumn *source, int first, int count)
    {
        Q_UNUSED(source);
        Q_UNUSED(first);
        Q_UNUSED(count);
    }
    virtual void inputRowsRemoved(const AbstractColumn *source, int first, int count)
    {
        Q_UNUSED(source);
        Q_UNUSED(first);
        Q_UNUSED(count);
    }
    virtual void inputMaskingAboutToChange(const AbstractColumn *source) { Q_UNUSED(source); }
    virtual void inputMaskingChanged(const AbstractColumn *source) { Q_UNUSED(source); }
    void inputAboutToBeDestroyed(const AbstractColumn *source) { input(portIndexOf(source), 0); }
    //@}

protected:
    //! The data sources connected to my input ports.
    QVector<const AbstractColumn *> d_inputs;
};

#endif // ifndef ABSTRACT_FILTER_H
