/***************************************************************************
    File                 : AbstractSimpleFilter.h
    Project              : SciDAVis
    --------------------------------------------------------------------
    Copyright            : (C) 2007 by Knut Franke, Tilman Benkert
    Email (use @ for *)  : knut.franke*gmx.de, thzs*gmx.net
    Description          : Simplified filter interface for filters with
                           only one output port.

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

/***************************************************************************
 *                                                                         *
 *  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_SIMPLE_FILTER
#define ABSTRACT_SIMPLE_FILTER

#include "core/AbstractFilter.h"
#include "core/AbstractColumn.h"
#include "lib/IntervalAttribute.h"
#include "lib/XmlStreamReader.h"
#include <QUndoCommand>
#include <QXmlStreamWriter>

// forward declaration - class follows
class SimpleFilterColumn;

/**
 * \brief Simplified filter interface for filters with only one output port.
 *
 * This class is only meant to simplify implementation of a restricted subtype of filter.
 * It should not be instantiated directly. You should always either derive from
 * AbstractFilter or (if necessary) provide an actual (non-abstract) implementation.
 *
 * The trick here is that, in a sense, the filter is its own output port. This means you
 * can implement a complete filter in only one class and don't have to coordinate data
 * transfer between a filter class and a data source class.
 * Additionaly, AbstractSimpleFilter offers some useful convenience methods which make writing
 * filters as painless as possible.
 *
 * For the data type of the output, all types supported by AbstractColumn (currently double, QString
 * and QDateTime) are supported.
 *
 * \section tutorial1 Tutorial, Step 1
 * The simplest filter you can write assumes there's also only one input port and rows on the
 * input correspond 1:1 to rows in the output. All you need to specify is what data type you
 * want to have (in this example double) on the input port and how to compute the output values:
 *
 * \code
 * 01 #include "AbstractSimpleFilter.h"
 * 02 class TutorialFilter1 : public AbstractSimpleFilter
 * 03 {
 * 04	protected:
 * 05		virtual bool inputAcceptable(int, AbstractColumn *source) {
 * 06			return (source->dataType() == SciDAVis::TypeDouble);
 * 07		}
 * 08	public:
 * 09		virtual SciDAVis::ColumnDataType dataType() const { return SciDAVis::TypeDouble; }
 * 10
 * 11		virtual double valueAt(int row) const {
 * 12			if (!d_inputs.value(0)) return 0.0;
 * 13			double input_value = d_inputs.value(0)->valueAt(row);
 * 14			return input_value * input_value;
 * 15		}
 * 16 };
 * \endcode
 *
 * This filter reads an input value (line 13) and returns its square (line 14).
 * Reimplementing inputAcceptable() makes sure that the data source really is of type
 * double (lines 5 to 7). Otherwise, the source will be rejected by AbstractFilter::input().
 * The output type is repoted by reimplementing dataType() (line 09).
 * Before you actually use d_inputs.value(0), make sure that the input port has
 * been connected to a data source (line 12).
 * Otherwise line 13 would result in a crash. That's it, we've already written a
 * fully-functional filter!
 *
 * Equivalently, you can write 1:1-filters for QString or QDateTime inputs by checking for
 * SciDAVis::TypeQString or SciDAVis::TypeQDateTime in line 6. You would then use
 * AbstractColumn::textAt(row) or AbstractColumn::dateTimeAt(row) in line 13 to access the input
 * data. For QString output, you need to implement AbstractColumn::textAt(row). For QDateTime
 * output, you have to implement three methods: \code virtual QDateTime dateTimeAt(int row) const;
 * virtual QDate dateAt(int row) const;
 * virtual QTime timeAt(int row) const;
 * \endcode
 *
 * \section tutorial2 Tutorial, Step 2
 * Now for something slightly more interesting: a filter that uses only every second row of its
 * input. We no longer have a 1:1 correspondence between input and output rows, so we'll have
 * to do a bit more work in order to have everything come out as expected.
 * We'll use double-typed input and output again:
 * \code
 * 01 #include "AbstractSimpleFilter.h"
 * 02 class TutorialFilter2 : public AbstractSimpleFilter
 * 03 {
 * 04	protected:
 * 05		virtual bool inputAcceptable(int, AbstractColumn *source) {
 * 06			return (source->dataType() == SciDAVis::TypeDouble);
 * 07		}
 * 08	public:
 * 09		virtual SciDAVis::ColumnDataType dataType() const { return SciDAVis::TypeDouble; }
 * \endcode
 * Even rows (including row 0) get dropped, odd rows are renumbered:
 * \code
 * 10	public:
 * 11 	virtual double valueAt(int row) const {
 * 12		if (!d_inputs.value(0)) return 0.0;
 * 13		return d_inputs.value(0)->valueAt(2*row + 1);
 * 14 	}
 * \endcode
 */
class AbstractSimpleFilter : public AbstractFilter
{
    Q_OBJECT

public:
    //! Ctor
    AbstractSimpleFilter();
    //! Default to one input port.
    virtual int inputCount() const { return 1; }
    //! We manage only one output port (don't override unless you really know what you are doing).
    virtual int outputCount() const { return 1; }
    //! Return a pointer to #d_output_column on port 0 (don't override unless you really know what you are doing).
    virtual AbstractColumn *output(int port);
    virtual const AbstractColumn *output(int port) const;
    //! Copy plot designation of input port 0.
    virtual SciDAVis::PlotDesignation plotDesignation() const
    {
        return d_inputs.value(0) ? d_inputs.at(0)->plotDesignation() : SciDAVis::noDesignation;
    }
    //! Return the data type of the input
    virtual SciDAVis::ColumnDataType dataType() const
    {
        // calling this function while d_input is empty is a sign of very bad code
        // nevertheless it will return some rather meaningless value to
        // avoid crashes
        return d_inputs.value(0) ? d_inputs.at(0)->dataType() : SciDAVis::TypeQString;
    }
    //! Return the column mode
    /**
     * This function is most used by tables but can also be used
     * by plots. The column mode specifies how to interpret
     * the values in the column additional to the data type.
     */
    virtual SciDAVis::ColumnMode columnMode() const
    {
        // calling this function while d_input is empty is a sign of very bad code
        // nevertheless it will return some rather meaningless value to
        // avoid crashes
        return d_inputs.value(0) ? d_inputs.at(0)->columnMode() : SciDAVis::ColumnMode::Text;
    }
    //! Return the content of row 'row'.
    /**
     * Use this only when dataType() is QString
     */
    virtual QString textAt(int row) const
    {
        return d_inputs.value(0) ? d_inputs.at(0)->textAt(row) : QString();
    }
    //! Return the date part of row 'row'
    /**
     * Use this only when dataType() is QDateTime
     */
    virtual QDate dateAt(int row) const
    {
        return d_inputs.value(0) ? d_inputs.at(0)->dateAt(row) : QDate();
    }
    //! Return the time part of row 'row'
    /**
     * Use this only when dataType() is QDateTime
     */
    virtual QTime timeAt(int row) const
    {
        return d_inputs.value(0) ? d_inputs.at(0)->timeAt(row) : QTime();
    }
    //! Set the content of row 'row'
    /**
     * Use this only when dataType() is QDateTime
     */
    virtual QDateTime dateTimeAt(int row) const
    {
        return d_inputs.value(0) ? d_inputs.at(0)->dateTimeAt(row) : QDateTime();
    }
    //! Return the double value in row 'row'
    /**
     * Use this only when dataType() is double
     */
    virtual double valueAt(int row) const
    {
        return d_inputs.value(0) ? d_inputs.at(0)->valueAt(row) : 0.0;
    }

    //!\name assuming a 1:1 correspondence between input and output rows
    //@{
    virtual int rowCount() const { return d_inputs.value(0) ? d_inputs.at(0)->rowCount() : 0; }
    virtual QList<Interval<int>> dependentRows(Interval<int> input_range) const
    {
        return QList<Interval<int>>() << input_range;
    }
    //@}

    //!\name Masking
    //@{
    //! Return whether a certain row is masked
    virtual bool isMasked(int row) const { return d_masking.isSet(row); }
    //! Return whether a certain interval of rows rows is fully masked
    virtual bool isMasked(Interval<int> i) const { return d_masking.isSet(i); }
    //! Return all intervals of masked rows
    virtual QList<Interval<int>> maskedIntervals() const { return d_masking.intervals(); }
    //! Clear all masking information
    virtual void clearMasks();
    //! Set an interval masked
    /**
     * \param i the interval
     * \param mask true: mask, false: unmask
     */
    virtual void setMasked(Interval<int> i, bool mask = true);
    //! Overloaded function for convenience
    virtual void setMasked(int row, bool mask = true) { setMasked(Interval<int>(row, row), mask); }
    //@}

    //! Return whether a certain row contains an invalid value
    virtual bool isInvalid(int row) const
    {
        return d_inputs.value(0) ? d_inputs.at(0)->isInvalid(row) : false;
    }
    //! Return whether a certain interval of rows contains only invalid values
    virtual bool isInvalid(Interval<int> i) const
    {
        return d_inputs.value(0) ? d_inputs.at(0)->isInvalid(i) : false;
    }
    //! Return all intervals of invalid rows
    virtual QList<Interval<int>> invalidIntervals() const
    {
        return d_inputs.value(0) ? d_inputs.at(0)->invalidIntervals() : QList<Interval<int>>();
    }

    //! \name XML related functions
    //@{
    //! Save to XML
    virtual void save(QXmlStreamWriter *writer) const;
    //! Load from XML
    virtual bool load(XmlStreamReader *reader);
    //! Override this in derived classes if they have other attributes than filter_name
    virtual void writeExtraAttributes(QXmlStreamWriter *writer) const { Q_UNUSED(writer) }
    //@}

protected:
    IntervalAttribute<bool> d_masking;

    //!\name signal handlers
    //@{
    virtual void inputPlotDesignationAboutToChange(const AbstractColumn *);
    virtual void inputPlotDesignationChanged(const AbstractColumn *);
    virtual void inputModeAboutToChange(const AbstractColumn *);
    virtual void inputModeChanged(const AbstractColumn *);
    virtual void inputDataAboutToChange(const AbstractColumn *);
    virtual void inputDataChanged(const AbstractColumn *);

    virtual void inputRowsAboutToBeInserted(const AbstractColumn *source, int before, int count);
    virtual void inputRowsInserted(const AbstractColumn *source, int before, int count);
    virtual void inputRowsAboutToBeRemoved(const AbstractColumn *source, int first, int count);
    virtual void inputRowsRemoved(const AbstractColumn *source, int first, int count);
    //@}

    SimpleFilterColumn *d_output_column;
};

class SimpleFilterColumn : public AbstractColumn
{
    Q_OBJECT

public:
    SimpleFilterColumn(AbstractSimpleFilter *owner) : AbstractColumn(owner->name()), d_owner(owner)
    {
    }

    virtual SciDAVis::ColumnDataType dataType() const { return d_owner->dataType(); }
    virtual SciDAVis::ColumnMode columnMode() const { return d_owner->columnMode(); }
    virtual int rowCount() const { return d_owner->rowCount(); }
    virtual SciDAVis::PlotDesignation plotDesignation() const { return d_owner->plotDesignation(); }
    virtual bool isInvalid(int row) const { return d_owner->isInvalid(row); }
    virtual bool isInvalid(Interval<int> i) const { return d_owner->isInvalid(i); }
    virtual QList<Interval<int>> invalidIntervals() const { return d_owner->invalidIntervals(); }
    virtual bool isMasked(int row) const { return d_owner->isMasked(row); }
    virtual bool isMasked(Interval<int> i) const { return d_owner->isMasked(i); }
    virtual QList<Interval<int>> maskedIntervals() const { return d_owner->maskedIntervals(); }
    virtual void clearMasks() { d_owner->clearMasks(); }
    virtual QString textAt(int row) const { return d_owner->textAt(row); }
    virtual QDate dateAt(int row) const { return d_owner->dateAt(row); }
    virtual QTime timeAt(int row) const { return d_owner->timeAt(row); }
    virtual QDateTime dateTimeAt(int row) const { return d_owner->dateTimeAt(row); }
    virtual double valueAt(int row) const { return d_owner->valueAt(row); }

private:
    AbstractSimpleFilter *d_owner;

    friend class AbstractSimpleFilter;
};

#endif // ifndef ABSTRACT_SIMPLE_FILTER
