/*
 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
 *
 * This file is part of the KGantt library.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#ifndef KGANTTDATETIMEGRID_H
#define KGANTTDATETIMEGRID_H

#include "kganttabstractgrid.h"

#include <QDateTime>
#include <QSet>
#include <QPair>

namespace KGantt {

    class DateTimeScaleFormatter;
    class DateTimeTimeLine;


    /*!\class KGantt::DateTimeGrid
     * \ingroup KGantt
     *
     * This implementation of AbstractGrid works with QDateTime
     * and shows days and week numbers in the header
     */
    class KGANTT_EXPORT DateTimeGrid : public AbstractGrid
    {
        Q_OBJECT
        KGANTT_DECLARE_PRIVATE_DERIVED( DateTimeGrid )
    public:
        enum Scale {
            ScaleAuto, 
            ScaleHour,
            ScaleDay,
            ScaleWeek,
            ScaleMonth,
            ScaleUserDefined
        };
        enum HeaderType {
            NoHeader,
            UpperHeader,
            LowerHeader
        };

        DateTimeGrid();
        ~DateTimeGrid() override;

        /*! \returns The QDateTime used as start date for the grid.
         *
         * The default is three days before the current date.
         */
        QDateTime startDateTime() const;

        /*! \param dt The start date of the grid. It is used as the beginning of the
         * horizontal scrollbar in the view.
         *
         * Emits gridChanged() after the start date has changed.
         */
        void setStartDateTime( const QDateTime& dt );

        /*! \returns The width in pixels for each day in the grid.
         *
         * The default is 100 pixels.
         */
        qreal dayWidth() const;

        /*! \param w The width in pixels for each day in the grid.
         *
         * The signal gridChanged() is emitted after the day width is changed.
         */
        void setDayWidth( qreal );

        /*! Maps a given point in time \a dt to an X value in the scene.
         */
        qreal mapFromDateTime( const QDateTime& dt) const;

        /*! Maps a given X value \a x in scene coordinates to a point in time.
         */
        QDateTime mapToDateTime( qreal x ) const;

        /*! \param ws The start day of the week.
         *
         * A solid line is drawn on the grid to mark the beginning of a new week.
         * Emits gridChanged() after the start day has changed.
         */
        void setWeekStart( Qt::DayOfWeek );

        /*! \returns The start day of the week */
        Qt::DayOfWeek weekStart() const;

        /*! \param fd A set of days to mark as free in the grid.
         *
         * Free days are filled with the alternate base brush of the
         * palette used by the view.
         * The signal gridChanged() is emitted after the free days are changed.
         */
        void setFreeDays( const QSet<Qt::DayOfWeek>& fd );

        /*! \returns The days marked as free in the grid. */
        QSet<Qt::DayOfWeek> freeDays() const;

        /*! Sets the brush to use to paint free days.
        */
        void setFreeDaysBrush(const QBrush brush);

        /*!
          \returns The brush used to paint free days.
        */
        QBrush freeDaysBrush() const;

        /*! \param s The scale to be used to paint the grid.
         *
         * The signal gridChanged() is emitted after the scale has changed.
         * \sa Scale
         *
         * Following example demonstrates how to change the format of the header to use
         * a date-scaling with the header-label displayed with the ISO date-notation.
         * \code
         * DateTimeScaleFormatter* formatter = new DateTimeScaleFormatter(DateTimeScaleFormatter::Day, QString::fromLatin1("yyyy-MMMM-dddd"));
         * grid->setUserDefinedUpperScale( formatter );
         * grid->setUserDefinedLowerScale( formatter );
         * grid->setScale( DateTimeGrid::ScaleUserDefined );
         * \endcode
         */
        void setScale( Scale s );

        /*! \returns The scale used to paint the grid.
         *
         * The default is ScaleAuto, which means the day scale will be used
         * as long as the day width is less or equal to 500.
         * \sa Scale
         */
        Scale scale() const;

        /*! Sets the scale formatter for the lower part of the header to the
         *  user defined formatter to \a lower. The DateTimeGrid object takes
         *  ownership of the formatter, which has to be allocated with new.
         *
         * You have to set the scale to ScaleUserDefined for this setting to take effect.
         * \sa DateTimeScaleFormatter
         */
        void setUserDefinedLowerScale( DateTimeScaleFormatter* lower );

        /*! Sets the scale formatter for the upper part of the header to the
         *  user defined formatter to \a upper. The DateTimeGrid object takes
         *  ownership of the formatter, which has to be allocated with new.
         *
         * You have to set the scale to ScaleUserDefined for this setting to take effect.
         * \sa DateTimeScaleFormatter
         */
        void setUserDefinedUpperScale( DateTimeScaleFormatter* upper );

        /*! \return The DateTimeScaleFormatter being used to render the lower scale.
         */
        DateTimeScaleFormatter* userDefinedLowerScale() const;


        /*! \return The DateTimeScaleFormatter being used to render the upper scale.
         */
        DateTimeScaleFormatter* userDefinedUpperScale() const;

        /*! \returns true if row separators are used. */
        bool rowSeparators() const;

        /*! \param enable Whether to use row separators or not. */
        void setRowSeparators( bool enable );

        /*! Sets the brush used to display rows where no data is found.
         * Default is a red pattern. If set to QBrush() rows with no
         * information will not be marked.
         */
        void setNoInformationBrush( const QBrush& brush );

        /*! \returns the brush used to mark rows with no information.
         */
        QBrush noInformationBrush() const;

        /*! \param idx The index to get the Span for.
         * \returns The start and end pixels, in a Span, of the specified index.
         */
        /*reimp*/ Span mapToChart( const QModelIndex& idx ) const override;

        /*! Maps the supplied Span to QDateTimes, and puts them as start time and
         * end time for the supplied index.
         *
         * \param span The span used to map from.
         * \param idx The index used for setting the start time and end time in the model.
         * \param constraints A list of hard constraints to match against the start time and
         * end time mapped from the span.
         *
         * \returns true if the start time and time was successfully added to the model, or false
         * if unsuccessful.
         * Also returns false if any of the constraints isn't satisfied. That is, if the start time of
         * the constrained index is before the end time of the dependency index, or the end time of the
         * constrained index is before the start time of the dependency index.
         */
        /*reimp*/ bool mapFromChart( const Span& span, const QModelIndex& idx,
                                     const QList<Constraint>& constraints=QList<Constraint>() ) const override;

        /*!
        * \param value The datetime to get the x value for.
        * \returns The x value corresponding to \a value or -1.0 if \a value is not a datetime variant.
        */
        /*reimp*/ qreal mapToChart( const QVariant& value ) const override;

        /*!
         * \param x The x value to get the datetime for.
         * \returns The datetime corresponding to \a x or an invalid datetime if x cannot be mapped.
         */
        /*reimp*/ QVariant mapFromChart( qreal x ) const override;
        /*reimp*/ void paintGrid( QPainter* painter, 
                                  const QRectF& sceneRect, const QRectF& exposedRect,
                                  AbstractRowController* rowController = nullptr,
                                  QWidget* widget = nullptr ) override;
        /*reimp*/ void paintHeader( QPainter* painter, 
                                    const QRectF& headerRect, const QRectF& exposedRect,
                                    qreal offset, QWidget* widget = nullptr ) override;

        /**
         * @return the timeline control object
         */
        DateTimeTimeLine *timeLine() const;

        HeaderType sectionHandleAtPos(int x, int y, const QRect &headerRect) const;

    protected:
        /*! Paints the hour scale header.
         * \sa paintHeader()
         */
        virtual void paintHourScaleHeader( QPainter* painter, 
                           const QRectF& headerRect, const QRectF& exposedRect,
                           qreal offset, QWidget* widget = nullptr );

        /*! Paints the day scale header.
         * \sa paintHeader()
         */
        virtual void paintDayScaleHeader( QPainter* painter, 
                          const QRectF& headerRect, const QRectF& exposedRect,
                          qreal offset, QWidget* widget = nullptr );

        /*! Paints the week scale header.
         * \sa paintHeader()
         */
        virtual void paintWeekScaleHeader( QPainter* painter,
                                           const QRectF& headerRect, const QRectF& exposedRect,
                                           qreal offset, QWidget* widget = nullptr );

        /*! Paints the week scale header.
         * \sa paintHeader()
         */
        virtual void paintMonthScaleHeader( QPainter* painter,
                                            const QRectF& headerRect, const QRectF& exposedRect,
                                            qreal offset, QWidget* widget = nullptr );

        virtual void paintUserDefinedHeader( QPainter* painter, 
                                     const QRectF& headerRect, const QRectF& exposedRect, 
                                     qreal offset, const DateTimeScaleFormatter* formatter, 
                                     QWidget* widget = nullptr );


        /*! Draw the background for a day.
        */
        virtual void drawDayBackground(QPainter* painter, const QRectF& rect, const QDate& date);

        /*! Draw the foreground for a day.
        */
        virtual void drawDayForeground(QPainter* painter, const QRectF& rect, const QDate& date);

        /** Return the rectangle that represents the date-range.
        */
        QRectF computeRect(const QDateTime& from, const QDateTime& to, const QRectF& rect) const;


        /** Return a date-range represented by the rectangle.
        */
        QPair<QDateTime, QDateTime> dateTimeRange(const QRectF& rect) const;

        /* reimp */ void drawBackground(QPainter* paint, const QRectF& rect) override;
        /* reimp */ void drawForeground(QPainter* paint, const QRectF& rect) override;
    };

    /*!\class KGantt::DateTimeScaleFormatter
     * \ingroup KGantt
     *
     * This class formats dates and times used in DateTimeGrid follawing a given format.
     *
     * The format follows the format of QDateTime::toString(), with one addition:
     * "w" is replaced with the week number of the date as number without a leading zero (1-53)
     * "ww" is replaced with the week number of the date as number with a leading zero (01-53)
     *
     * For example:
     *
     * \code
     *  // formatter to print the complete date over the current week
     *  // This leads to the first day of the week being printed
     *  DateTimeScaleFormatter formatter = DateTimeScaleFormatter( DateTimeScaleFormatter::Week, "yyyy-MM-dd" );
     * \endcode
     *
     * Optionally, you can set an user defined text alignment flag. The default value is Qt::AlignCenter.
     * \sa DateTimeScaleFormatter::DateTimeScaleFormatter
     *
     * This class even controls the range of the grid sections.
     * \sa KGanttDateTimeScaleFormatter::Range
     */
    class KGANTT_EXPORT DateTimeScaleFormatter
    {
        KGANTT_DECLARE_PRIVATE_BASE_POLYMORPHIC( DateTimeScaleFormatter )
    public:
        enum Range {
            Second,
            Minute,
            Hour,
            Day,
            Week,
            Month,
            Year
        };

        /*! Creates a DateTimeScaleFormatter using \a range and \a format.
         *  The text on the header is aligned following \a alignment.
         */
        DateTimeScaleFormatter( Range range, const QString& formatString,
                                Qt::Alignment alignment = Qt::AlignCenter );
        DateTimeScaleFormatter( Range range, const QString& formatString,
                                const QString& templ, Qt::Alignment alignment = Qt::AlignCenter );

        /*! \returns The format being used for formatting dates and times.
         */
        DateTimeScaleFormatter( const DateTimeScaleFormatter& other );
        virtual ~DateTimeScaleFormatter();

        DateTimeScaleFormatter& operator=( const DateTimeScaleFormatter& other );

        /*! \returns The format string
         */
        QString format() const;

        /*! \returns The range of each item on a DateTimeGrid header.
         * \sa DateTimeScaleFormatter::Range */
        Range range() const;
        Qt::Alignment alignment() const;

        /*! \returns the QDateTime being the begin of the range after the one containing \a datetime
         *  \sa currentRangeBegin
         */
        virtual QDateTime nextRangeBegin( const QDateTime& datetime ) const;

        /*! \returns the QDateTime being the begin of the range containing \a datetime
         *  \sa nextRangeBegin
         */
        virtual QDateTime currentRangeBegin( const QDateTime& datetime ) const;

        /*! \returns The \a datetime as string respecting the format.
         */
        QString format( const QDateTime& datetime ) const;
        virtual QString text( const QDateTime& datetime ) const;
    };
}



#ifndef QT_NO_DEBUG_STREAM
QDebug KGANTT_EXPORT operator<<( QDebug dbg, KGantt::DateTimeScaleFormatter::Range );
#endif

#endif /* KGANTTDATETIMEGRID_H */

