// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses

#include <QtCharts/QBarCategoryAxis>
#include <QtCharts/QCandlestickLegendMarker>
#include <QtCharts/QCandlestickSeries>
#include <QtCharts/QCandlestickSet>
#include <QtCharts/QValueAxis>
#include <QtCore/QDateTime>
#include <private/candlestickanimation_p.h>
#include <private/candlestickchartitem_p.h>
#include <private/chartdataset_p.h>
#include <private/charttheme_p.h>
#include <private/qcandlestickseries_p.h>
#include <private/qcandlestickset_p.h>
#include <private/qchart_p.h>

QT_BEGIN_NAMESPACE

/*!
    \class QCandlestickSeries
    \since 5.8
    \inmodule QtCharts
    \brief The QCandlestickSeries class presents data as candlesticks.

    This class acts as a container for single candlestick items. Each item is drawn to its own category
    when using QBarCategoryAxis. QDateTimeAxis and QValueAxis can be used as alternatives to
    QBarCategoryAxis. In this case, each candlestick item is drawn according to its timestamp value.

    \note The timestamps must be unique within a QCandlestickSeries. When using QBarCategoryAxis,
    only the first one of the candlestick items sharing a timestamp is drawn. If the chart includes
    multiple instances of QCandlestickSeries, items from different series sharing a timestamp are
    drawn to the same category. When using QValueAxis or QDateTimeAxis, candlestick items sharing a
    timestamp will overlap each other.

    See the \l {Charts with Widgets Gallery} to learn how to create
    a candlestick chart.
    \image examples_candlestickchart.png

    \sa QCandlestickSet, QBarCategoryAxis, QDateTimeAxis, QValueAxis
*/

/*!
    \qmltype CandlestickSeries
    \since QtCharts 2.2
    \nativetype QCandlestickSeries
    \inqmlmodule QtCharts
    \inherits AbstractSeries
    \brief Represents a series of data as candlesticks.

    The CandlestickSeries type acts as a container for single candlestick items.
    Each item is drawn to its own category
    when using BarCategoryAxis. DateTimeAxis and ValueAxis can be used as an alternative to
    BarCategoryAxis. In this case, each candlestick item is drawn according to its timestamp value.

    \note The timestamps must be unique within a CandlestickSeries. When using BarCategoryAxis, only
    the first one of the candlestick items sharing a timestamp is drawn. If the chart includes
    multiple instances of CandlestickSeries, items from different series sharing a timestamp are
    drawn to the same category. When using ValueAxis or DateTimeAxis, candlestick items sharing a
    timestamp will overlap each other.

    The following QML shows how to create a simple candlestick chart:
    \code
    import QtQuick 2.5
    import QtCharts 2.2

    ChartView {
        title: "Candlestick Series"
        width: 400
        height: 300

        CandlestickSeries {
            name: "Acme Ltd."
            increasingColor: "green"
            decreasingColor: "red"

            CandlestickSet { timestamp: 1435708800000; open: 690; high: 694; low: 599; close: 660 }
            CandlestickSet { timestamp: 1435795200000; open: 669; high: 669; low: 669; close: 669 }
            CandlestickSet { timestamp: 1436140800000; open: 485; high: 623; low: 485; close: 600 }
            CandlestickSet { timestamp: 1436227200000; open: 589; high: 615; low: 377; close: 569 }
            CandlestickSet { timestamp: 1436313600000; open: 464; high: 464; low: 254; close: 254 }
        }
    }
    \endcode

    \beginfloatleft
    \image examples_qmlcandlestick.png
    \endfloat
    \clearfloat

    \sa CandlestickSet, BarCategoryAxis, DateTimeAxis, ValueAxis
*/

/*!
    \qmlproperty AbstractAxis CandlestickSeries::axisX
    The x-axis used for the series. If you leave both axisX and axisXTop
    undefined, a value axis is created for the series.
    \sa axisXTop, ValueAxis
*/

/*!
    \qmlproperty AbstractAxis CandlestickSeries::axisY
    The y-axis used for the series. If you leave both axisY and axisYRight
    undefined, a value axis is created for the series.
    \sa axisYRight, ValueAxis
*/

/*!
    \qmlproperty AbstractAxis CandlestickSeries::axisXTop
    The x-axis used for the series, drawn on top of the chart view.

    \note You can only provide either axisX or axisXTop, not both.
    \sa axisX
*/

/*!
    \qmlproperty AbstractAxis CandlestickSeries::axisYRight
    The y-axis used for the series, drawn to the right on the chart view.

    \note You can only provide either axisY or axisYRight, not both.
    \sa axisY
*/

/*!
    \property QCandlestickSeries::count
    \brief The number of candlestick items in a series.
*/

/*!
    \qmlproperty int CandlestickSeries::count
    The number of candlestick items in a series.
*/

/*!
    \property QCandlestickSeries::maximumColumnWidth
    \brief The maximum width of the candlestick items in pixels. Setting a negative value means
    there is no maximum width. All negative values are converted to -1.0.
*/

/*!
    \qmlproperty real CandlestickSeries::maximumColumnWidth
    The maximum width of the candlestick items in pixels. Setting a negative value means
    there is no maximum width. All negative values are converted to -1.0.
*/

/*!
    \property QCandlestickSeries::minimumColumnWidth
    \brief The minimum width of the candlestick items in pixels. Setting a negative value means
    there is no minimum width. All negative values are converted to -1.0.
*/

/*!
    \qmlproperty real CandlestickSeries::minimumColumnWidth
    The minimum width of the candlestick items in pixels. Setting a negative value means
    there is no minimum width. All negative values are converted to -1.0.
*/

/*!
    \property QCandlestickSeries::bodyWidth
    \brief The relative width of the candlestick item within its own slot, in the range
    from 0.0 to 1.0.

    Values outside this range are clamped to 0.0 or 1.0.
*/

/*!
    \qmlproperty real CandlestickSeries::bodyWidth
    The relative width of the candlestick item within its own slot, in the range
    from 0.0 to 1.0. Values outside this range are clamped to 0.0 or 1.0.
*/

/*!
    \property QCandlestickSeries::bodyOutlineVisible
    \brief The visibility of the candlestick body outline.
*/

/*!
    \qmlproperty bool CandlestickSeries::bodyOutlineVisible
    The visibility of the candlestick body outlines.
*/

/*!
    \property QCandlestickSeries::capsWidth
    \brief The relative width of the caps within a candlestick, in the range from 0.0
    to 1.0.

    Values outside this range are clamped to 0.0 or 1.0.
*/

/*!
    \qmlproperty real CandlestickSeries::capsWidth
    The relative width of the caps within a candlestick, in the range from 0.0
    to 1.0. Values outside this range are clamped to 0.0 or 1.0.
*/

/*!
    \property QCandlestickSeries::capsVisible
    \brief The visibility of the caps.
*/

/*!
    \qmlproperty bool CandlestickSeries::capsVisible
    The visibility of the caps.
*/

/*!
    \property QCandlestickSeries::increasingColor
    \brief The color of the increasing candlestick item body.

    A candlestick is \e increasing when its close value is higher than the open
    value. By default, this property is set to the brush color. The default
    color is used also when the property is set to an invalid color value.
*/

/*!
    \qmlproperty color CandlestickSeries::increasingColor
    The color of the increasing candlestick item body.
    A candlestick is \e increasing when its close value is higher than the open
    value. By default, this property is set to the brush color. The default
    color is used also when the property is set to an invalid color value.
*/

/*!
    \property QCandlestickSeries::decreasingColor
    \brief The color of the decreasing candlestick item body.

    A candlestick is \e decreasing when its open value is higher than the close
    value. By default, this property is set to the brush color with the alpha
    channel set to 128. The default color is used also when the property is set
    to an invalid color value.
*/

/*!
    \qmlproperty color CandlestickSeries::decreasingColor
    The color of the decreasing candlestick item body.
    A candlestick is \e decreasing when its open value is higher than the close
    value. By default, this property is set to the brush color with the alpha
    channel set to 128. The default color is used also when the property is set
    to an invalid color value.
*/

/*!
    \property QCandlestickSeries::brush
    \brief The brush used to fill the candlestick items.
*/

/*!
    \property QCandlestickSeries::pen
    \brief The pen used to draw the lines of the candlestick items.
*/

/*!
    \qmlproperty string CandlestickSeries::brushFilename
    The name of the file used as a brush image for the series.
*/

/*!
    \fn void QCandlestickSeries::clicked(QCandlestickSet *set)
    This signal is emitted when the candlestick item specified by \a set
    is clicked on the chart.
*/

/*!
    \qmlsignal CandlestickSeries::clicked(CandlestickSet set)
    This signal is emitted when the candlestick item specified by \a set
    is clicked on the chart.

    The corresponding signal handler is \c {onClicked}.
*/

/*!
    \fn void QCandlestickSeries::hovered(bool status, QCandlestickSet *set)
    This signal is emitted when a mouse is hovered over the candlestick
    item specified by \a set in a chart.

    When the mouse moves over the item, \a status turns \c true, and when the
    mouse moves away again, it turns \c false.
*/

/*!
    \qmlsignal CandlestickSeries::hovered(bool status, CandlestickSet set)
    This signal is emitted when a mouse is hovered over the candlestick
    item specified by \a set in a chart.

    When the mouse moves over the item, \a status turns \c true, and when the
    mouse moves away again, it turns \c false.

    The corresponding signal handler is \c {onHovered}.
*/

/*!
    \fn void QCandlestickSeries::pressed(QCandlestickSet *set)
    This signal is emitted when the user clicks the candlestick item
    specified by \a set and holds down the mouse button.
*/

/*!
    \qmlsignal CandlestickSeries::pressed(CandlestickSet set)
    This signal is emitted when the user clicks the candlestick item
    specified by \a set and holds down the mouse button.

    The corresponding signal handler is \c {onPressed}.
*/

/*!
    \fn void QCandlestickSeries::released(QCandlestickSet *set)
    This signal is emitted when the user releases the mouse press on the
    candlestick item specified by \a set.
*/

/*!
    \qmlsignal CandlestickSeries::released(CandlestickSet set)
    This signal is emitted when the user releases the mouse press on the
    candlestick item specified by \a set.

    The corresponding signal handler is \c {onReleased}.
*/

/*!
    \fn void QCandlestickSeries::doubleClicked(QCandlestickSet *set)
    This signal is emitted when the candlestick item specified by \a set
    is double-clicked on the chart.
*/

/*!
    \qmlsignal CandlestickSeries::doubleClicked(CandlestickSet set)
    This signal is emitted when the candlestick item specified by \a set
    is double-clicked on the chart.

    The corresponding signal handler is \c {onDoubleClicked}.
*/

/*!
    \fn void QCandlestickSeries::candlestickSetsAdded(const QList<QCandlestickSet *> &sets)
    This signal is emitted when the candlestick items specified by \a
    sets are added to the series.
*/

/*!
    \qmlsignal CandlestickSeries::candlestickSetsAdded(list<CandlestickSet> sets)
    This signal is emitted when the candlestick items specified by
    \a sets are added to the series.

    The corresponding signal handler is \c {onCandlestickSetsAdded}.
*/

/*!
    \fn void QCandlestickSeries::candlestickSetsRemoved(const QList<QCandlestickSet *> &sets)
    This signal is emitted when the candlestick items specified by
    \a sets are removed from the series.
*/

/*!
    \qmlsignal CandlestickSeries::candlestickSetsRemoved(list<CandlestickSet> sets)
    This signal is emitted when the candlestick items specified by
    \a sets are removed from the series.

    The corresponding signal handler is \c {onCandlestickSetsRemoved}.
*/

/*!
    \fn void QCandlestickSeries::countChanged()
    This signal is emitted when the number of candlestick items in the
    series changes.
    \sa count
*/

/*!
    \fn void QCandlestickSeries::maximumColumnWidthChanged()
    This signal is emitted when there is a change in the maximum column width of candlestick items.
    \sa maximumColumnWidth
*/

/*!
    \fn void QCandlestickSeries::minimumColumnWidthChanged()
    This signal is emitted when there is a change in the minimum column width of candlestick items.
    \sa minimumColumnWidth
*/

/*!
    \fn void QCandlestickSeries::bodyWidthChanged()
    This signal is emitted when the candlestick item width changes.
    \sa bodyWidth
*/

/*!
    \fn void QCandlestickSeries::bodyOutlineVisibilityChanged()
    This signal is emitted when the visibility of the candlestick item body outline changes.
    \sa bodyOutlineVisible
*/

/*!
    \fn void QCandlestickSeries::capsWidthChanged()
    This signal is emitted when the candlestick item caps width changes.
    \sa capsWidth
*/

/*!
    \fn void QCandlestickSeries::capsVisibilityChanged()
    This signal is emitted when the visibility of the candlestick item caps changes.
    \sa capsVisible
*/

/*!
    \fn void QCandlestickSeries::increasingColorChanged()
    This signal is emitted when the candlestick item increasing color changes.
    \sa increasingColor
*/

/*!
    \fn void QCandlestickSeries::decreasingColorChanged()
    This signal is emitted when the candlestick item decreasing color changes.
    \sa decreasingColor
*/

/*!
    \fn void QCandlestickSeries::brushChanged()
    This signal is emitted when the candlestick item brush changes.

    \sa brush
*/

/*!
    \fn void QCandlestickSeries::penChanged()
    This signal is emitted when the candlestick item pen changes.

    \sa pen
*/

/*!
    \qmlmethod CandlestickSeries::at(int index)
    Returns the candlestick item at the position specified by \a index. Returns
    null if the index is not valid.
*/

/*!
    Constructs an empty QCandlestickSeries. The \a parent is optional.
*/
QCandlestickSeries::QCandlestickSeries(QObject *parent)
    : QAbstractSeries(*new QCandlestickSeriesPrivate(this), parent)
{
}

/*!
    Destroys the series. Removes the series from the chart.
*/
QCandlestickSeries::~QCandlestickSeries()
{
    Q_D(QCandlestickSeries);
    if (d->m_chart)
        d->m_chart->removeSeries(this);
}

/*!
    \qmlmethod CandlestickSeries::append(CandlestickSet set)
    Adds a single candlestick item specified by \a set to the series and takes
    ownership of it. If the item is null or it is already in the series, it
    is not appended.

    Returns \c true if appending succeeded, \c false otherwise.
*/

/*!
    Adds a single candlestick item specified by \a set to the series and takes
    ownership of it. If the item is null or it is already in the series, it
    is not appended.
    Returns \c true if appending succeeded, \c false otherwise.
*/
bool QCandlestickSeries::append(QCandlestickSet *set)
{
    QList<QCandlestickSet *> sets;
    sets.append(set);

    return append(sets);
}

/*!
    \qmlmethod CandlestickSeries::remove(CandlestickSet set)
    Removes a single candlestick item, specified by \a set, from the series.

    Returns \c true if the item is successfully deleted, \c false otherwise.
*/

/*!
    Removes a single candlestick item, specified by \a set, from the series.
    Returns \c true if the item is successfully deleted, \c false otherwise.
*/
bool QCandlestickSeries::remove(QCandlestickSet *set)
{
    QList<QCandlestickSet *> sets;
    sets.append(set);

    return remove(sets);
}

/*!
    Adds a list of candlestick items specified by \a sets to the series and
    takes ownership of it. If any of the items are null, already belong to
    the series, or appear in the list more than once, nothing is appended.
    Returns \c true if all items were appended successfully, \c false otherwise.
*/
bool QCandlestickSeries::append(const QList<QCandlestickSet *> &sets)
{
    Q_D(QCandlestickSeries);

    bool success = d->append(sets);
    if (success) {
        emit candlestickSetsAdded(sets);
        emit countChanged();
    }

    return success;
}

/*!
    Removes a list of candlestick items specified by \a sets from the series. If
    any of the items are null, were already removed from the series, or appear
    in the list more than once, nothing is removed. Returns \c true if all items
    were removed successfully, \c false otherwise.
*/
bool QCandlestickSeries::remove(const QList<QCandlestickSet *> &sets)
{
    Q_D(QCandlestickSeries);

    bool success = d->remove(sets);
    if (success) {
        emit candlestickSetsRemoved(sets);
        emit countChanged();
        foreach (QCandlestickSet *set, sets)
            delete set;
    }

    return success;
}

/*!
    \qmlmethod CandlestickSeries::insert(int index, CandlestickSet set)
    Inserts the candlestick item specified by \a set to the series at the
    position specified by \a index. Takes ownership of the item. If the
    item is null or already belongs to the series, it is not inserted.

    Returns \c true if inserting succeeded, \c false otherwise.
*/

/*!
    Inserts the candlestick item specified by \a set to the series at the
    position specified by \a index. Takes ownership of the item. If the
    item is null or already belongs to the series, it is not inserted.
    Returns \c true if inserting succeeded, \c false otherwise.
*/
bool QCandlestickSeries::insert(int index, QCandlestickSet *set)
{
    Q_D(QCandlestickSeries);

    bool success = d->insert(index, set);
    if (success) {
        QList<QCandlestickSet *> sets;
        sets.append(set);
        emit candlestickSetsAdded(sets);
        emit countChanged();
    }

    return success;
}

/*!
    Takes a single candlestick item, specified by \a set, from the series. Does
    not delete the item. Returns \c true if the take operation was successful, \c false otherwise.
    \note The series remains the item's parent object. You must set the parent
    object to take full ownership.
*/
bool QCandlestickSeries::take(QCandlestickSet *set)
{
    Q_D(QCandlestickSeries);

    QList<QCandlestickSet *> sets;
    sets.append(set);

    bool success = d->remove(sets);
    if (success) {
        emit candlestickSetsRemoved(sets);
        emit countChanged();
    }

    return success;
}

/*!
    \qmlmethod CandlestickSeries::clear()
    Removes all candlestick items from the series and permanently deletes them.
*/

/*!
    Removes all candlestick items from the series and permanently deletes them.
*/
void QCandlestickSeries::clear()
{
    Q_D(QCandlestickSeries);

    QList<QCandlestickSet *> sets = this->sets();

    bool success = d->remove(sets);
    if (success) {
        emit candlestickSetsRemoved(sets);
        emit countChanged();
        foreach (QCandlestickSet *set, sets)
            delete set;
    }
}

/*!
    Returns the list of candlestick items in the series. Ownership of the
    items does not change.
 */
QList<QCandlestickSet *> QCandlestickSeries::sets() const
{
    Q_D(const QCandlestickSeries);

    return d->m_sets;
}

/*!
    Returns the number of the candlestick items in the series.
*/
int QCandlestickSeries::count() const
{
    return sets().size();
}

/*!
    Returns the type of the series (QAbstractSeries::SeriesTypeCandlestick).
*/
QAbstractSeries::SeriesType QCandlestickSeries::type() const
{
    return QAbstractSeries::SeriesTypeCandlestick;
}

void QCandlestickSeries::setMaximumColumnWidth(qreal maximumColumnWidth)
{
    Q_D(QCandlestickSeries);

    if (maximumColumnWidth < 0.0 && maximumColumnWidth != -1.0)
        maximumColumnWidth = -1.0;

    if (d->m_maximumColumnWidth == maximumColumnWidth)
        return;

    d->m_maximumColumnWidth = maximumColumnWidth;

    emit d->updatedLayout();
    emit maximumColumnWidthChanged();
}

qreal QCandlestickSeries::maximumColumnWidth() const
{
    Q_D(const QCandlestickSeries);

    return d->m_maximumColumnWidth;
}

void QCandlestickSeries::setMinimumColumnWidth(qreal minimumColumnWidth)
{
    Q_D(QCandlestickSeries);

    if (minimumColumnWidth < 0.0 && minimumColumnWidth != -1.0)
        minimumColumnWidth = -1.0;

    if (d->m_minimumColumnWidth == minimumColumnWidth)
        return;

    d->m_minimumColumnWidth = minimumColumnWidth;

    d->updatedLayout();
    emit minimumColumnWidthChanged();
}

qreal QCandlestickSeries::minimumColumnWidth() const
{
    Q_D(const QCandlestickSeries);

    return d->m_minimumColumnWidth;
}

void QCandlestickSeries::setBodyWidth(qreal bodyWidth)
{
    Q_D(QCandlestickSeries);

    if (bodyWidth < 0.0)
        bodyWidth = 0.0;
    else if (bodyWidth > 1.0)
        bodyWidth = 1.0;

    if (d->m_bodyWidth == bodyWidth)
        return;

    d->m_bodyWidth = bodyWidth;

    emit d->updatedLayout();
    emit bodyWidthChanged();
}

qreal QCandlestickSeries::bodyWidth() const
{
    Q_D(const QCandlestickSeries);

    return d->m_bodyWidth;
}

void QCandlestickSeries::setBodyOutlineVisible(bool bodyOutlineVisible)
{
    Q_D(QCandlestickSeries);

    if (d->m_bodyOutlineVisible == bodyOutlineVisible)
        return;

    d->m_bodyOutlineVisible = bodyOutlineVisible;

    emit d->updated();
    emit bodyOutlineVisibilityChanged();
}

bool QCandlestickSeries::bodyOutlineVisible() const
{
    Q_D(const QCandlestickSeries);

    return d->m_bodyOutlineVisible;
}

void QCandlestickSeries::setCapsWidth(qreal capsWidth)
{
    Q_D(QCandlestickSeries);

    if (capsWidth < 0.0)
        capsWidth = 0.0;
    else if (capsWidth > 1.0)
        capsWidth = 1.0;

    if (d->m_capsWidth == capsWidth)
        return;

    d->m_capsWidth = capsWidth;

    emit d->updatedLayout();
    emit capsWidthChanged();
}

qreal QCandlestickSeries::capsWidth() const
{
    Q_D(const QCandlestickSeries);

    return d->m_capsWidth;
}

void QCandlestickSeries::setCapsVisible(bool capsVisible)
{
    Q_D(QCandlestickSeries);

    if (d->m_capsVisible == capsVisible)
        return;

    d->m_capsVisible = capsVisible;

    emit d->updated();
    emit capsVisibilityChanged();
}

bool QCandlestickSeries::capsVisible() const
{
    Q_D(const QCandlestickSeries);

    return d->m_capsVisible;
}

void QCandlestickSeries::setIncreasingColor(const QColor &increasingColor)
{
    Q_D(QCandlestickSeries);

    QColor color;
    if (increasingColor.isValid()) {
        color = increasingColor;
        d->m_customIncreasingColor = true;
    } else {
        color = d->m_brush.color();
        color.setAlpha(128);
        d->m_customIncreasingColor = false;
    }

    if (d->m_increasingColor == color)
        return;

    d->m_increasingColor = color;

    emit d->updated();
    emit increasingColorChanged();
}

QColor QCandlestickSeries::increasingColor() const
{
    Q_D(const QCandlestickSeries);

    return d->m_increasingColor;
}

void QCandlestickSeries::setDecreasingColor(const QColor &decreasingColor)
{
    Q_D(QCandlestickSeries);

    QColor color;
    if (decreasingColor.isValid()) {
        color = decreasingColor;
        d->m_customDecreasingColor = true;
    } else {
        color = d->m_brush.color();
        d->m_customDecreasingColor = false;
    }

    if (d->m_decreasingColor == color)
        return;

    d->m_decreasingColor = color;

    emit d->updated();
    emit decreasingColorChanged();
}

QColor QCandlestickSeries::decreasingColor() const
{
    Q_D(const QCandlestickSeries);

    return d->m_decreasingColor;
}

void QCandlestickSeries::setBrush(const QBrush &brush)
{
    Q_D(QCandlestickSeries);

    if (d->m_brush == brush)
        return;

    d->m_brush = brush;
    if (!d->m_customIncreasingColor) {
        QColor color = d->m_brush.color();
        color.setAlpha(128);
        if (d->m_increasingColor != color) {
            d->m_increasingColor = color;
            emit increasingColorChanged();
        }
    }
    if (!d->m_customDecreasingColor && d->m_decreasingColor != d->m_brush.color()) {
        d->m_decreasingColor = d->m_brush.color();
        emit decreasingColorChanged();
    }

    emit d->updated();
    emit brushChanged();
}

QBrush QCandlestickSeries::brush() const
{
    Q_D(const QCandlestickSeries);

    return d->m_brush;
}

void QCandlestickSeries::setPen(const QPen &pen)
{
    Q_D(QCandlestickSeries);

    if (d->m_pen == pen)
        return;

    d->m_pen = pen;

    emit d->updated();
    emit penChanged();
}

QPen QCandlestickSeries::pen() const
{
    Q_D(const QCandlestickSeries);

    return d->m_pen;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

QCandlestickSeriesPrivate::QCandlestickSeriesPrivate(QCandlestickSeries *q)
    : QAbstractSeriesPrivate(q),
      m_maximumColumnWidth(-1.0),
      m_minimumColumnWidth(5.0),
      m_bodyWidth(0.5),
      m_bodyOutlineVisible(true),
      m_capsWidth(0.5),
      m_capsVisible(false),
      m_increasingColor(QColor(Qt::transparent)),
      m_decreasingColor(QChartPrivate::defaultBrush().color()),
      m_customIncreasingColor(false),
      m_customDecreasingColor(false),
      m_brush(QChartPrivate::defaultBrush()),
      m_pen(QChartPrivate::defaultPen()),
      m_animation(nullptr)
{
}

QCandlestickSeriesPrivate::~QCandlestickSeriesPrivate()
{
    disconnect(this, 0, 0, 0);
    qDeleteAll(m_sets);
}

void QCandlestickSeriesPrivate::initializeDomain()
{
    qreal minX(domain()->minX());
    qreal maxX(domain()->maxX());
    qreal minY(domain()->minY());
    qreal maxY(domain()->maxY());

    if (m_sets.size()) {
        QCandlestickSet *set = m_sets.first();
        minX = set->timestamp();
        maxX = set->timestamp();
        minY = set->low();
        maxY = set->high();
        for (int i = 1; i < m_sets.size(); ++i) {
            set = m_sets.at(i);
            minX = qMin(minX, qreal(set->timestamp()));
            maxX = qMax(maxX, qreal(set->timestamp()));
            minY = qMin(minY, set->low());
            maxY = qMax(maxY, set->high());
        }
        qreal extra = (maxX - minX) / m_sets.size() / 2;
        minX = minX - extra;
        maxX = maxX + extra;
    }

    domain()->setRange(minX, maxX, minY, maxY);
}

void QCandlestickSeriesPrivate::initializeAxes()
{
    foreach (QAbstractAxis* axis, m_axes) {
        if (axis->type() == QAbstractAxis::AxisTypeBarCategory) {
            if (axis->orientation() == Qt::Horizontal)
                populateBarCategories(qobject_cast<QBarCategoryAxis *>(axis));
        }
    }
}

void QCandlestickSeriesPrivate::initializeTheme(int index, ChartTheme* theme, bool forced)
{
    Q_Q(QCandlestickSeries);

    if (forced || QChartPrivate::defaultBrush() == m_brush) {
        const QList<QGradient> gradients = theme->seriesGradients();
        const QGradient gradient = gradients.at(index % gradients.size());
        const QBrush brush(ChartThemeManager::colorAt(gradient, 0.5));
        q->setBrush(brush);
    }

    if (forced || QChartPrivate::defaultPen() == m_pen) {
        QPen pen = theme->outlinePen();
        pen.setCosmetic(true);
        q->setPen(pen);
    }
}

void QCandlestickSeriesPrivate::initializeGraphics(QGraphicsItem *parent)
{
    Q_Q(QCandlestickSeries);

    CandlestickChartItem *item = new CandlestickChartItem(q, parent);
    m_item.reset(item);
    QAbstractSeriesPrivate::initializeGraphics(parent);

    if (m_chart) {
        connect(m_chart->d_ptr->m_dataset, SIGNAL(seriesAdded(QAbstractSeries*)),
                this, SLOT(handleSeriesChange(QAbstractSeries*)));
        connect(m_chart->d_ptr->m_dataset, SIGNAL(seriesRemoved(QAbstractSeries*)),
                this, SLOT(handleSeriesRemove(QAbstractSeries*)));

        item->handleCandlestickSeriesChange();
    }
}

void QCandlestickSeriesPrivate::initializeAnimations(QChart::AnimationOptions options, int duration,
                                                     QEasingCurve &curve)
{
    CandlestickChartItem *item = static_cast<CandlestickChartItem *>(m_item.get());
    Q_ASSERT(item);

    if (item->animation())
        item->animation()->stopAndDestroyLater();

    if (options.testFlag(QChart::SeriesAnimations))
        m_animation = new CandlestickAnimation(item, duration, curve);
    else
        m_animation = nullptr;
    item->setAnimation(m_animation);

    QAbstractSeriesPrivate::initializeAnimations(options, duration, curve);
}

QList<QLegendMarker *> QCandlestickSeriesPrivate::createLegendMarkers(QLegend *legend)
{
    Q_Q(QCandlestickSeries);

    QList<QLegendMarker *> list;

    return list << new QCandlestickLegendMarker(q, legend);
}

QAbstractAxis::AxisType QCandlestickSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const
{
    if (orientation == Qt::Horizontal)
        return QAbstractAxis::AxisTypeBarCategory;

    if (orientation == Qt::Vertical)
        return QAbstractAxis::AxisTypeValue;

    return QAbstractAxis::AxisTypeNoAxis;
}

QAbstractAxis* QCandlestickSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const
{
    const QAbstractAxis::AxisType axisType = defaultAxisType(orientation);

    if (axisType == QAbstractAxis::AxisTypeBarCategory)
        return new QBarCategoryAxis;

    if (axisType == QAbstractAxis::AxisTypeValue)
        return new QValueAxis;

    return 0; // axisType == QAbstractAxis::AxisTypeNoAxis
}

bool QCandlestickSeriesPrivate::append(const QList<QCandlestickSet *> &sets)
{
    foreach (QCandlestickSet *set, sets) {
        if ((set == 0) || m_sets.contains(set) || set->d_ptr->m_series)
            return false; // Fail if any of the sets is null or is already appended.
        if (sets.count(set) != 1)
            return false; // Also fail if the same set occurs more than once in the given list.
    }

    foreach (QCandlestickSet *set, sets) {
        m_sets.append(set);
        connect(set->d_func(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout()));
        connect(set->d_func(), SIGNAL(updatedCandlestick()), this, SIGNAL(updatedCandlesticks()));
        set->d_ptr->m_series = this;
    }

    return true;
}

bool QCandlestickSeriesPrivate::remove(const QList<QCandlestickSet *> &sets)
{
    if (sets.size() == 0)
        return false;

    foreach (QCandlestickSet *set, sets) {
        if ((set == 0) || (!m_sets.contains(set)))
            return false; // Fail if any of the sets is null or is not in series.
        if (sets.count(set) != 1)
            return false; // Also fail if the same set occurs more than once in the given list.
    }

    foreach (QCandlestickSet *set, sets) {
        set->d_ptr->m_series = nullptr;
        m_sets.removeOne(set);
        disconnect(set->d_func(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout()));
        disconnect(set->d_func(), SIGNAL(updatedCandlestick()),this, SIGNAL(updatedCandlesticks()));
    }

    return true;
}

bool QCandlestickSeriesPrivate::insert(int index, QCandlestickSet *set)
{
    if ((m_sets.contains(set)) || (set == 0) || set->d_ptr->m_series)
        return false; // Fail if set is already in list or set is null.

    m_sets.insert(index, set);
    connect(set->d_func(), SIGNAL(updatedLayout()), this, SIGNAL(updatedLayout()));
    connect(set->d_func(), SIGNAL(updatedCandlestick()), this, SIGNAL(updatedCandlesticks()));
    set->d_ptr->m_series = this;

    return true;
}

void QCandlestickSeriesPrivate::handleSeriesChange(QAbstractSeries *series)
{
    Q_UNUSED(series);

    if (m_chart) {
        CandlestickChartItem *item = static_cast<CandlestickChartItem *>(m_item.get());
        if (item)
            item->handleCandlestickSeriesChange();
    }
}

void QCandlestickSeriesPrivate::handleSeriesRemove(QAbstractSeries *series)
{
    Q_Q(const QCandlestickSeries);

    QCandlestickSeries *removedSeries = static_cast<QCandlestickSeries *>(series);

    if (q == removedSeries && m_animation) {
        m_animation->stopAll();
        disconnect(m_chart->d_ptr->m_dataset, 0, removedSeries->d_func(), 0);
    }

    if (q != removedSeries) {
        CandlestickChartItem *item = static_cast<CandlestickChartItem *>(m_item.get());
        if (item)
            item->handleCandlestickSeriesChange();
    }
}

void QCandlestickSeriesPrivate::populateBarCategories(QBarCategoryAxis *axis)
{
    if (axis->categories().isEmpty()) {
        QStringList categories;
        for (int i = 0; i < m_sets.size(); ++i) {
            const qint64 timestamp = qRound64(m_sets.at(i)->timestamp());
            const QString timestampFormat = m_chart->locale().dateTimeFormat(QLocale::ShortFormat);
            categories << QDateTime::fromMSecsSinceEpoch(timestamp).toString(timestampFormat);
        }
        axis->append(categories);
    }
}

QT_END_NAMESPACE

#include "moc_qcandlestickseries.cpp"
#include "moc_qcandlestickseries_p.cpp"
