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

#ifndef KCHARTCARTESIANCOORDINATEPLANE_H
#define KCHARTCARTESIANCOORDINATEPLANE_H

#include "KChartAbstractCoordinatePlane.h"

namespace KChart {

    class Chart;
    class PaintContext;
    class AbstractDiagram;
    class CartesianAxis;
    class CartesianGrid;

    /**
      * @brief Cartesian coordinate plane
      */
    class KCHART_EXPORT CartesianCoordinatePlane : public AbstractCoordinatePlane
    {
        Q_OBJECT

        Q_DISABLE_COPY( CartesianCoordinatePlane )
        KCHART_DECLARE_PRIVATE_DERIVED_PARENT( CartesianCoordinatePlane, Chart* )

    friend class CartesianAxis;
    friend class CartesianGrid;

    public:
        explicit CartesianCoordinatePlane ( Chart* parent = nullptr );
        ~CartesianCoordinatePlane() override;

        void addDiagram ( AbstractDiagram* diagram ) override;

        /**
         * If @p onOff is true, enforce that X and Y distances are scaled by the same factor.
         * This makes the plane's height a function of its width, and hasHeightForWidth()
         * will return true.
         */
        void setIsometricScaling ( bool onOff );

        bool doesIsometricScaling() const;

        const QPointF translate ( const QPointF& diagramPoint ) const override;

        /**
         * \sa setZoomFactorX, setZoomCenter
         */
        qreal zoomFactorX() const override;
        /**
         * \sa setZoomFactorY, setZoomCenter
         */
        qreal zoomFactorY() const override;

        /**
         * \sa setZoomFactorX,setZoomFactorY
         */
        void setZoomFactors( qreal factorX, qreal factorY ) override;
        /**
         * \sa zoomFactorX, setZoomCenter
         */
        void setZoomFactorX( qreal factor ) override;
        /**
         * \sa zoomFactorY, setZoomCenter
         */
        void setZoomFactorY( qreal factor ) override;

        /**
         * \sa setZoomCenter, setZoomFactorX, setZoomFactorY
         */
        QPointF zoomCenter() const override;

        /**
         * \sa zoomCenter, setZoomFactorX, setZoomFactorY
         */
        void setZoomCenter( const QPointF& center ) override;

        /**
         * Allows to specify a fixed data-space / coordinate-space relation. If set
         * to true then fixed bar widths are used, so you see more bars as the window
         * is made wider.
         *
         * This allows to completely restrict the size of bars in a graph such that,
         * upon resizing a window, the graphs coordinate plane will grow (add more
         * ticks to x- and y-coordinates) rather than have the image grow.
         */
        void setFixedDataCoordinateSpaceRelation( bool fixed );
        bool hasFixedDataCoordinateSpaceRelation() const;

        /**
          * Allows to fix the lower bound of X axis to zero when diagram is in first quadrant.
          * 
          * The default behavior is to lower x or y bound to be 0. If this behaviour is not wanted,
          * either \a CartesianCoordinatePlane::setHorizontalRange could be used instead of letting
          * KChart auto-adjust the ranges, or this method can be used to disable this behavior.
          */
        void setXAxisStartAtZero(bool fixedStart);
        bool xAxisStartAtZero() const;

        /**
         * \brief Set the boundaries of the visible value space displayed in horizontal direction.
         *
         * This is also known as the horizontal viewport.
         *
         * By default the horizontal range is adjusted to the range covered by the model's data,
         * see setAutoAdjustHorizontalRangeToData for details.
         * Calling setHorizontalRange with a valid range disables this default automatic adjusting,
         * while on the other hand automatic adjusting will set these ranges.
         *
         * To disable use of this range you can either pass an empty pair by using the default
         * constructor QPair() or you can set both values to the same which constitutes
         * a null range.
         *
         * \note By default the visible data range often is larger than the
         * range calculated from the data model (or set by setHoriz.|Vert.Range(), resp.).
         * This is due to the built-in grid calculation feature: The visible start/end
         * values get adjusted so that they match a main-grid line.
         * You can turn this feature off for any of the four bounds by calling
         * GridAttributes::setAdjustBoundsToGrid() for either the global grid-attributes
         * or for the horizontal/vertical attrs separately.
         *
         * \note To set only one of the ends of the range to a fixed value while keeping
         * the other dynamically adjusted, use std::numeric_limits< qreal >::quiet_NaN()
         * for the dynamic value.
         *
         * \note If you use user defined vertical ranges together with logarithmic scale, only
         * positive values are supported. If you set it to negative values, the result is undefined.
         *
         * \param range a pair of values representing the smallest and the largest
         * horizontal value space coordinate displayed.
         *
         * \sa setAutoAdjustHorizontalRangeToData, setVerticalRange
         * \sa GridAttributes::setAdjustBoundsToGrid()
         */
        void setHorizontalRange( const QPair<qreal, qreal> & range );

        /**
         * \brief Set the boundaries of the visible value space displayed in vertical direction.
         *
         * This is also known as the vertical viewport.
         *
         * By default the vertical range is adjusted to the range covered by the model's data,
         * see setAutoAdjustVerticalRangeToData for details.
         * Calling setVerticalRange with a valid range disables this default automatic adjusting,
         * while on the other hand automatic adjusting will set these ranges.
         *
         * To disable use of this range you can either pass an empty pair by using the default
         * constructor QPair() or you can set setting both values to the same which constitutes
         * a null range.
         *
         * \note By default the visible data range often is larger than the
         * range calculated from the data model (or set by setHoriz.|Vert.Range(), resp.).
         * This is due to the built-in grid calculation feature: The visible start/end
         * values get adjusted so that they match a main-grid line.
         * You can turn this feature off for any of the four bounds by calling
         * GridAttributes::setAdjustBoundsToGrid() for either the global grid-attributes
         * or for the horizontal/vertical attrs separately.
         *
         * \note To set only one of the ends of the range to a fixed value while keeping
         * the other dynamically adjusted, use std::numeric_limits< qreal >::quiet_NaN()
         * for the dynamic value.
         *
         * \note If you use user defined vertical ranges together with logarithmic scale, only
         * positive values are supported. If you set it to negative values, the result is undefined.
         *
         * \param range a pair of values representing the smallest and the largest
         * vertical value space coordinate displayed.
         *
         * \sa setAutoAdjustVerticalRangeToData, setHorizontalRange
         * \sa GridAttributes::setAdjustBoundsToGrid()
         */
        void setVerticalRange( const QPair<qreal, qreal> & range );

        /**
         * @return The largest and smallest visible horizontal value space
         * value. If this is not explicitly set,or if both values are the same,
         * the plane will use the union of the dataBoundaries of all
         * associated diagrams.
         * \see KChart::AbstractDiagram::dataBoundaries
         */
        QPair<qreal, qreal> horizontalRange() const;

        /**
         * @return The largest and smallest visible horizontal value space
         * value. If this is not explicitly set, or if both values are the same,
         * the plane will use the union of the dataBoundaries of all
         * associated diagrams.
         * \see KChart::AbstractDiagram::dataBoundaries
         */
        QPair<qreal, qreal> verticalRange() const;

        /**
         * \brief Automatically adjust horizontal range settings to the ranges covered by
         * the model's values, when ever the data have changed, and then emit horizontalRangeAutomaticallyAdjusted.
         *
         * By default the horizontal range is adjusted automatically, if more than 67 percent of
         * the available horizontal space would be empty otherwise.
         *
         * Range setting is adjusted if more than \c percentEmpty percent of the horizontal
         * space covered by the coordinate plane would otherwise be empty.
         * Automatic range adjusting can happen, when either all of the data are positive or all are negative.
         *
         * Set percentEmpty to 100 to disable automatic range adjusting.
         *
         * \param percentEmpty The maximal percentage of horizontal space that may be empty.
         *
         * \sa horizontalRangeAutomaticallyAdjusted
         * \sa autoAdjustHorizontalRangeToData, adjustRangesToData
         * \sa setHorizontalRange, setVerticalRange
         * \sa setAutoAdjustVerticalRangeToData
         */
        void setAutoAdjustHorizontalRangeToData( unsigned int percentEmpty = 67 );

        /**
         * \brief Automatically adjust vertical range settings to the ranges covered by
         * the model's values, when ever the data have changed, and then emit verticalRangeAutomaticallyAdjusted.
         *
         * By default the vertical range is adjusted automatically, if more than 67 percent of
         * the available vertical space would be empty otherwise.
         *
         * Range setting is adjusted if more than \c percentEmpty percent of the horizontal
         * space covered by the coordinate plane would otherwise be empty.
         * Automatic range adjusting can happen, when either all of the data are positive or all are negative.
         *
         * Set percentEmpty to 100 to disable automatic range adjusting.
         *
         * \param percentEmpty The maximal percentage of horizontal space that may be empty.
         *
         * \sa verticalRangeAutomaticallyAdjusted
         * \sa autoAdjustVerticalRangeToData, adjustRangesToData
         * \sa setHorizontalRange, setVerticalRange
         * \sa setAutoAdjustHorizontalRangeToData
         */
        void setAutoAdjustVerticalRangeToData( unsigned int percentEmpty = 67 );

        /**
         * \brief Returns the maximal allowed percent of the horizontal
         * space covered by the coordinate plane that may be empty.
         *
         * \return A percent value indicating how much of the horizontal space may be empty.
         * If more than this is empty, automatic range adjusting is applied.
         * A return value of 100 indicates that no such automatic adjusting is done at all.
         *
         * \sa setAutoAdjustHorizontalRangeToData, adjustRangesToData
         */
        unsigned int autoAdjustHorizontalRangeToData() const;

        /**
         * \brief Returns the maximal allowed percent of the vertical
         * space covered by the coordinate plane that may be empty.
         *
         * \return A percent value indicating how much of the vertical space may be empty.
         * If more than this is empty, automatic range adjusting is applied.
         * A return value of 100 indicates that no such automatic adjusting is done at all.
         *
         * \sa setAutoAdjustVerticalRangeToData, adjustRangesToData
         */
        unsigned int autoAdjustVerticalRangeToData() const;


        /**
         * Set the attributes to be used for grid lines drawn in horizontal
         * direction (or in vertical direction, resp.).
         *
         * To disable horizontal grid painting, for example, your code should like this:
         * \code
         * GridAttributes ga = plane->gridAttributes( Qt::Horizontal );
         * ga.setGridVisible( false );
         * plane-setGridAttributes( Qt::Horizontal, ga );
         * \endcode
         *
         * \note setGridAttributes overwrites the global attributes that
         * were set by AbstractCoordinatePlane::setGlobalGridAttributes.
         * To re-activate these global attributes you can call
         * resetGridAttributes.
         *
         * \sa resetGridAttributes, gridAttributes
         * \sa setAutoAdjustGridToZoom
         * \sa AbstractCoordinatePlane::setGlobalGridAttributes
         * \sa hasOwnGridAttributes
         */
        void setGridAttributes( Qt::Orientation orientation, const GridAttributes & );

        /**
         * Reset the attributes to be used for grid lines drawn in horizontal
         * direction (or in vertical direction, resp.).
         * By calling this method you specify that the global attributes set by
         * AbstractCoordinatePlane::setGlobalGridAttributes be used.
         *
         * \sa setGridAttributes, gridAttributes
         * \sa setAutoAdjustGridToZoom
         * \sa AbstractCoordinatePlane::globalGridAttributes
         * \sa hasOwnGridAttributes
         */
        void resetGridAttributes( Qt::Orientation orientation );

        /**
         * \return The attributes used for grid lines drawn in horizontal
         * direction (or in vertical direction, resp.).
         *
         * \note This function always returns a valid set of grid attributes:
         * If no special grid attributes were set foe this orientation
         * the global attributes are returned, as returned by
         * AbstractCoordinatePlane::globalGridAttributes.
         *
         * \sa setGridAttributes
         * \sa resetGridAttributes
         * \sa AbstractCoordinatePlane::globalGridAttributes
         * \sa hasOwnGridAttributes
         */
        const GridAttributes gridAttributes( Qt::Orientation orientation ) const;

        /**
         * \return Returns whether the grid attributes have been set for the
         * respective direction via setGridAttributes( orientation ).
         *
         * If false, the grid will use the global attributes set
         * by AbstractCoordinatePlane::globalGridAttributes (or the default
         * attributes, resp.)
         *
         * \sa setGridAttributes
         * \sa resetGridAttributes
         * \sa AbstractCoordinatePlane::globalGridAttributes
         */
        bool hasOwnGridAttributes( Qt::Orientation orientation ) const;

        /**
         * Disable / re-enable the built-in grid adjusting feature.
         *
         * By default additional lines will be drawn in a Linear grid when zooming in.
         *
         * \sa autoAdjustGridToZoom, setGridAttributes
         */
        void setAutoAdjustGridToZoom( bool autoAdjust );

        /**
         * Return the status of the built-in grid adjusting feature.
         *
         * \sa setAutoAdjustGridToZoom
         */
#if defined(Q_COMPILER_MANGLES_RETURN_TYPE)
        const bool autoAdjustGridToZoom() const;
#else
        bool autoAdjustGridToZoom() const;
#endif

        AxesCalcMode axesCalcModeY() const;
        AxesCalcMode axesCalcModeX() const;

        /** Specifies the calculation modes for all axes */
        void setAxesCalcModes( AxesCalcMode mode );
        /** Specifies the calculation mode for all Ordinate axes */
        void setAxesCalcModeY( AxesCalcMode mode );
        /** Specifies the calculation mode for all Abscissa axes */
        void setAxesCalcModeX( AxesCalcMode mode );

        /** reimpl */
        void paint( QPainter* ) override;

        /** reimpl */
        AbstractCoordinatePlane* sharedAxisMasterPlane( QPainter* p = nullptr ) override;

        /**
         * Returns the currently visible data range. Might be greater than the
         * range of the grid.
         */
        QRectF visibleDataRange() const;

        /**
         * Returns the logical area, i.e., the rectangle defined by the very top
         * left and very bottom right coordinate.
         */
        QRectF logicalArea() const;

        /**
         * Returns the (physical) area occupied by the diagram. Unless zoom is applied
         * (which is also true when a fixed data coordinate / space relation is used),
         * \code diagramArea() == drawingArea() \endcode .
         * \sa setFixedDataCoordinateSpaceRelation
         * \sa drawingArea
         */
        QRectF diagramArea() const;

        /**
         * Returns the visible part of the diagram area, i.e.
         * \code diagramArea().intersected( drawingArea() ) \endcode
         * \sa diagramArea
         */
        QRectF visibleDiagramArea() const;

        /**
         * Sets whether the horizontal range should be reversed or not, i.e.
         * small values to the left and large values to the right (the default)
         * or vice versa.
         * \param reverse Whether the horizontal range should be reversed or not
         */
        void setHorizontalRangeReversed( bool reverse );

        /**
         * \return Whether the horizontal range is reversed or not
         */
        bool isHorizontalRangeReversed() const;

        /**
         * Sets whether the vertical range should be reversed or not, i.e.
         * small values at the bottom and large values at the top (the default)
         * or vice versa.
         * \param reverse Whether the vertical range should be reversed or not
         */
        void setVerticalRangeReversed( bool reverse );

        /**
         * \return Whether the vertical range is reversed or not
         */
        bool isVerticalRangeReversed() const;

        /**
         * reimplemented from AbstractCoordinatePlane
         */
        void setGeometry( const QRect& r ) override;

        // reimplemented
        Qt::Orientations expandingDirections() const override;


    public Q_SLOTS:
        /**
         * \brief Adjust both, horizontal and vertical range settings to the
         * ranges covered by the model's data values.
         *
         * \sa setHorizontalRange, setVerticalRange
         * \sa adjustHorizontalRangeToData, adjustVerticalRangeToData
         * \sa setAutoAdjustHorizontalRangeToData, setAutoAdjustVerticalRangeToData
         */
        void adjustRangesToData();

        /**
         * Adjust horizontal range settings to the ranges covered by the model's data values.
         * \sa adjustRangesToData
         */
        void adjustHorizontalRangeToData();

        /**
         * Adjust vertical range settings to the ranges covered by the model's data values.
         * \sa adjustRangesToData
         */
        void adjustVerticalRangeToData();

    protected:
        QRectF getRawDataBoundingRectFromDiagrams() const;
        QRectF adjustedToMaxEmptyInnerPercentage(
                const QRectF& r, unsigned int percentX, unsigned int percentY ) const;
        virtual QRectF calculateRawDataBoundingRect() const;
        DataDimensionsList getDataDimensionsList() const override;
        // the whole drawing area, includes diagrams and axes, but maybe smaller
        // than (width, height):
        virtual QRectF drawingArea() const;
    public:
        const QPointF translateBack( const QPointF& screenPoint ) const;
    protected:
        void paintEvent ( QPaintEvent* );
        void layoutDiagrams() override;
        // the following three return true if the new value is different from the old
        bool doneSetZoomFactorX( qreal factor );
        bool doneSetZoomFactorY( qreal factor );
        bool doneSetZoomCenter( const QPointF& center );

        void handleFixedDataCoordinateSpaceRelation( const QRectF& geometry );

        // reimplemented from QLayoutItem, via AbstractLayoutItem, AbstractArea, AbstractCoordinatePlane
        bool hasHeightForWidth() const override;
        int heightForWidth( int w ) const override;
        QSize sizeHint() const override;

    protected Q_SLOTS:
        void slotLayoutChanged( KChart::AbstractDiagram* );

    private:
        void setHasOwnGridAttributes(
            Qt::Orientation orientation, bool on );
    };

}

#endif
