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

#include <QtCharts/QCandlestickSeries>
#include <QtCharts/QCandlestickSet>
#include <QtCharts/QChartView>
#include <QtTest/QtTest>
#include "tst_definitions.h"

QT_USE_NAMESPACE

Q_DECLARE_METATYPE(QCandlestickSet *)
Q_DECLARE_METATYPE(QList<QCandlestickSet *>)

class tst_QCandlestickSeries : public QObject
{
    Q_OBJECT

public Q_SLOTS:
    void initTestCase();
    void cleanupTestCase();
    void init();
    void cleanup();

private Q_SLOTS:
    void qCandlestickSeries();
    void append();
    void remove();
    void appendList();
    void removeList();
    void insert();
    void take();
    void clear();
    void sets();
    void count();
    void type();
    void maximumColumnWidth_data();
    void maximumColumnWidth();
    void minimumColumnWidth_data();
    void minimumColumnWidth();
    void bodyWidth_data();
    void bodyWidth();
    void bodyOutlineVisible();
    void capsWidth_data();
    void capsWidth();
    void capsVisible();
    void increasingColor();
    void decreasingColor();
    void brush();
    void pen();
    void mouseClicked();
    void mouseHovered();
    void mousePressed();
    void mouseReleased();
    void mouseDoubleClicked();

private:
    QPointer<QCandlestickSeries> m_series;
    struct SetList : QList<QPointer<QCandlestickSet>> {
        operator QList<QCandlestickSet *> () const {
            QList<QCandlestickSet *> list;
            for (int i = 0; i < size(); ++i)
                list.append(at(i));
            return list;
        }
    }; SetList m_sets;
};

void tst_QCandlestickSeries::initTestCase()
{
    qRegisterMetaType<QCandlestickSet *>("QCandlestickSet *");
    qRegisterMetaType<QList<QCandlestickSet *>>("QList<QCandlestickSet *>");
}

void tst_QCandlestickSeries::cleanupTestCase()
{
    QTest::qWait(1); // Allow final deleteLaters to run
}

void tst_QCandlestickSeries::init()
{
    m_series = new QCandlestickSeries();
    m_series->setMaximumColumnWidth(5432.1);
    m_series->setMinimumColumnWidth(2.0);
    m_series->setBodyWidth(0.99);
    m_series->setCapsWidth(0.99);

    for (int i = 0; i < 5; ++i) {
        qreal timestamp = QDateTime::currentMSecsSinceEpoch() + i * 1000000;

        QCandlestickSet *set = new QCandlestickSet(timestamp);
        set->setOpen(4);
        set->setHigh(4);
        set->setLow(1);
        set->setClose(1);

        m_sets.append(set);
    }
}

void tst_QCandlestickSeries::cleanup()
{
    delete m_series;
    qDeleteAll(m_sets);
    m_sets.clear();
}

void tst_QCandlestickSeries::qCandlestickSeries()
{
    QCandlestickSeries *series = new QCandlestickSeries();

    QVERIFY(series != nullptr);

    delete series;
    series = nullptr;
}

void tst_QCandlestickSeries::append()
{
    QCOMPARE(m_series->count(), 0);

    // Try adding set
    QCandlestickSet *set1 = new QCandlestickSet(1234.0);
    QVERIFY(m_series->append(set1));
    QCOMPARE(m_series->count(), 1);

    // Try adding another set
    QCandlestickSet *set2 = new QCandlestickSet(2345.0);
    QVERIFY(m_series->append(set2));
    QCOMPARE(m_series->count(), 2);

    // Try adding same set again
    QVERIFY(!m_series->append(set2));
    QCOMPARE(m_series->count(), 2);

    // Try adding null set
    QVERIFY(!m_series->append(nullptr));
    QCOMPARE(m_series->count(), 2);
}

void tst_QCandlestickSeries::remove()
{
    m_series->append(m_sets);
    QCOMPARE(m_series->count(), m_sets.size());

    // Try to remove null pointer (should not remove, should not crash)
    QVERIFY(!m_series->remove(nullptr));
    QCOMPARE(m_series->count(), m_sets.size());

    // Try to remove invalid pointer (should not remove, should not crash)
    QVERIFY(!m_series->remove((QCandlestickSet *)(m_sets.at(0) + 1)));
    QCOMPARE(m_series->count(), m_sets.size());

    // Remove some sets
    const int removeCount = 3;
    for (int i = 0; i < removeCount; ++i) {
        QVERIFY(m_series->remove(m_sets.at(i)));
        QVERIFY(!m_sets.at(i));
    }
    QCOMPARE(m_series->count(), m_sets.size() - removeCount);

    for (int i = removeCount; i < m_sets.size(); ++i)
        QCOMPARE(m_series->sets().at(i - removeCount), m_sets.at(i));

    // Try removing all sets again (should be ok, even if some sets have already been removed)
    for (int i = 0; i < m_sets.size(); ++i) {
        m_series->remove(m_sets.at(i));
        QVERIFY(!m_sets.at(i));
    }
    QCOMPARE(m_series->count(), 0);
}

void tst_QCandlestickSeries::appendList()
{
    QCOMPARE(m_series->count(), 0);

    // Append new sets (should succeed, count should match the count of sets)
    QVERIFY(m_series->append(m_sets));
    QCOMPARE(m_series->count(), m_series->count());

    // Append same sets again (should fail, count should remain same)
    QVERIFY(!m_series->append(m_sets));
    QCOMPARE(m_series->count(), m_series->count());

    // Try append empty list (should succeed, but count should remain same)
    QList<QCandlestickSet *> invalidList;
    QVERIFY(m_series->append(invalidList));
    QCOMPARE(m_series->count(), m_sets.size());

    // Try append list with one new and one existing set (should fail, count remains same)
    invalidList.append(new QCandlestickSet());
    invalidList.append(m_sets.at(0));
    QVERIFY(!m_series->append(invalidList));
    QCOMPARE(m_series->count(), m_sets.size());
    delete invalidList.at(0);
    invalidList.clear();

    // Try append list with null pointers (should fail, count remains same)
    QVERIFY(invalidList.isEmpty());
    invalidList.append(nullptr);
    invalidList.append(nullptr);
    invalidList.append(nullptr);
    QVERIFY(!m_series->append(invalidList));
    QCOMPARE(m_series->count(), m_sets.size());
}

void tst_QCandlestickSeries::removeList()
{
    m_series->append(m_sets);
    QCOMPARE(m_series->count(), m_sets.size());

    // Try remove empty list (should fail, but count should remain same)
    QList<QCandlestickSet *> invalidList;
    QVERIFY(!m_series->remove(invalidList));
    QCOMPARE(m_series->count(), m_sets.size());

    // Try remove list with one new and one existing set (should fail, count remains same)
    invalidList.append(new QCandlestickSet());
    invalidList.append(m_sets.at(0));
    QVERIFY(!m_series->remove(invalidList));
    QCOMPARE(m_series->count(), m_sets.size());
    delete invalidList.at(0);
    invalidList.clear();

    // Try remove list with null pointers (should fail, count remains same)
    QVERIFY(invalidList.isEmpty());
    invalidList.append(nullptr);
    invalidList.append(nullptr);
    invalidList.append(nullptr);
    QVERIFY(!m_series->remove(invalidList));
    QCOMPARE(m_series->count(), m_sets.size());

    // Remove all sets (should succeed, count should be zero)
    QVERIFY(m_series->remove(m_sets));
    QCOMPARE(m_series->count(), 0);

    // Remove same sets again (should fail, count should remain zero)
    QVERIFY(!m_series->remove(m_sets));
    QCOMPARE(m_series->count(), 0);
}

void tst_QCandlestickSeries::insert()
{
    QCOMPARE(m_series->count(), 0);

    QSignalSpy countSpy(m_series, SIGNAL(countChanged()));
    QSignalSpy addedSpy(m_series, SIGNAL(candlestickSetsAdded(QList<QCandlestickSet *>)));

    for (int i = 0; i < m_sets.size(); ++i) {
        QCandlestickSet *set = m_sets.at(i);
        QVERIFY(m_series->insert(0, set));
        QCOMPARE(m_series->count(), i + 1);
        QTRY_COMPARE(countSpy.size(), i + 1);
        QTRY_COMPARE(addedSpy.size(), i + 1);

        QList<QVariant> args = addedSpy.value(i);
        QCOMPARE(args.size(), 1);
        QList<QCandlestickSet *> sets = qvariant_cast<QList<QCandlestickSet *>>(args.at(0));
        QCOMPARE(sets.size(), 1);
        QCOMPARE(sets.first(), set);
    }
}

void tst_QCandlestickSeries::take()
{
    m_series->append(m_sets);
    QCOMPARE(m_series->count(), m_sets.size());

    QSignalSpy countSpy(m_series, SIGNAL(countChanged()));
    QSignalSpy removedSpy(m_series, SIGNAL(candlestickSetsRemoved(QList<QCandlestickSet *>)));

    for (int i = 0; i < m_sets.size(); ++i) {
        QCandlestickSet *set = m_sets.at(i);
        QVERIFY(m_series->take(set));
        QCOMPARE(m_series->count(), m_sets.size() - i - 1);
        QTRY_COMPARE(countSpy.size(), i + 1);
        QTRY_COMPARE(removedSpy.size(), i + 1);

        QList<QVariant> args = removedSpy.value(i);
        QCOMPARE(args.size(), 1);
        QList<QCandlestickSet *> sets = qvariant_cast<QList<QCandlestickSet *>>(args.at(0));
        QCOMPARE(sets.size(), 1);
        QCOMPARE(sets.first(), set);
    }
}

void tst_QCandlestickSeries::clear()
{
    m_series->append(m_sets);
    QCOMPARE(m_series->count(), m_sets.size());

    m_series->clear();
    QCOMPARE(m_series->count(), 0);
}

void tst_QCandlestickSeries::sets()
{
    m_series->append(m_sets);
    QCOMPARE(m_series->sets(), (QList<QCandlestickSet *>)m_sets);

    for (int i = 0; i < m_sets.size(); ++i)
        QCOMPARE(m_series->sets().at(i), m_sets.at(i));

    m_series->clear();
    QCOMPARE(m_series->sets(), QList<QCandlestickSet *>());
}

void tst_QCandlestickSeries::count()
{
    m_series->append(m_sets);
    QCOMPARE(m_series->count(), m_sets.size());
    QCOMPARE(m_series->count(), m_series->sets().size());
}

void tst_QCandlestickSeries::type()
{
    QCOMPARE(m_series->type(), QAbstractSeries::SeriesTypeCandlestick);
}

void tst_QCandlestickSeries::maximumColumnWidth_data()
{
    QTest::addColumn<qreal>("maximumColumnWidth");
    QTest::addColumn<qreal>("expectedMaximumColumnWidth");

    QTest::newRow("maximum column width less than -1.0") << -3.0 << -1.0;
    QTest::newRow("maximum column equals to -1.0") << -1.0 << -1.0;
    QTest::newRow("maximum column width greater than -1.0, but less than zero") << -0.5 << -1.0;
    QTest::newRow("maximum column width equals zero") << 0.0 << 0.0;
    QTest::newRow("maximum column width greater than zero") << 1.0 << 1.0;
    QTest::newRow("maximum column width contains a fractional part") << 3.4 << 3.4;
}

void tst_QCandlestickSeries::maximumColumnWidth()
{
    QFETCH(qreal, maximumColumnWidth);
    QFETCH(qreal, expectedMaximumColumnWidth);

    QSignalSpy spy(m_series, SIGNAL(maximumColumnWidthChanged()));

    m_series->setMaximumColumnWidth(maximumColumnWidth);
    QCOMPARE(m_series->maximumColumnWidth(), expectedMaximumColumnWidth);
    QCOMPARE(spy.size(), 1);

    // Try set same maximum column width
    m_series->setMaximumColumnWidth(expectedMaximumColumnWidth);
    QCOMPARE(m_series->maximumColumnWidth(), expectedMaximumColumnWidth);
    QCOMPARE(spy.size(), 1);
}

void tst_QCandlestickSeries::minimumColumnWidth_data()
{
    QTest::addColumn<qreal>("minimumColumnWidth");
    QTest::addColumn<qreal>("expectedMinimumColumnWidth");

    QTest::newRow("minimum column width less than -1.0") << -3.0 << -1.0;
    QTest::newRow("minimum column equals to -1.0") << -1.0 << -1.0;
    QTest::newRow("minimum column width greater than -1.0, but less than zero") << -0.5 << -1.0;
    QTest::newRow("minimum column width equals zero") << 0.0 << 0.0;
    QTest::newRow("minimum column width greater than zero") << 1.0 << 1.0;
    QTest::newRow("minimum column width contains a fractional part") << 3.4 << 3.4;
}

void tst_QCandlestickSeries::minimumColumnWidth()
{
    QFETCH(qreal, minimumColumnWidth);
    QFETCH(qreal, expectedMinimumColumnWidth);

    QSignalSpy spy(m_series, SIGNAL(minimumColumnWidthChanged()));

    m_series->setMinimumColumnWidth(minimumColumnWidth);
    QCOMPARE(m_series->minimumColumnWidth(), expectedMinimumColumnWidth);
    QCOMPARE(spy.size(), 1);

    // Try set same minimum column width
    m_series->setMinimumColumnWidth(expectedMinimumColumnWidth);
    QCOMPARE(m_series->minimumColumnWidth(), expectedMinimumColumnWidth);
    QCOMPARE(spy.size(), 1);
}

void tst_QCandlestickSeries::bodyWidth_data()
{
    QTest::addColumn<qreal>("bodyWidth");
    QTest::addColumn<qreal>("expectedBodyWidth");

    QTest::newRow("body width less than zero") << -1.0 << 0.0;
    QTest::newRow("body width equals zero") << 0.0 << 0.0;
    QTest::newRow("body width greater than zero and less than one") << 0.5 << 0.5;
    QTest::newRow("body width equals one") << 1.0 << 1.0;
    QTest::newRow("body width greater than one") << 2.0 << 1.0;
}

void tst_QCandlestickSeries::bodyWidth()
{
    QFETCH(qreal, bodyWidth);
    QFETCH(qreal, expectedBodyWidth);

    QSignalSpy spy(m_series, SIGNAL(bodyWidthChanged()));

    m_series->setBodyWidth(bodyWidth);
    QCOMPARE(m_series->bodyWidth(), expectedBodyWidth);
    QCOMPARE(spy.size(), 1);

    // Try set same body width
    m_series->setBodyWidth(bodyWidth);
    QCOMPARE(m_series->bodyWidth(), expectedBodyWidth);
    QCOMPARE(spy.size(), 1);
}

void tst_QCandlestickSeries::bodyOutlineVisible()
{
    QSignalSpy spy(m_series, SIGNAL(bodyOutlineVisibilityChanged()));

    bool visible = !m_series->bodyOutlineVisible();
    m_series->setBodyOutlineVisible(visible);
    QCOMPARE(m_series->bodyOutlineVisible(), visible);
    QCOMPARE(spy.size(), 1);

    // Try set same body outline visibility
    m_series->setBodyOutlineVisible(visible);
    QCOMPARE(m_series->bodyOutlineVisible(), visible);
    QCOMPARE(spy.size(), 1);
}

void tst_QCandlestickSeries::capsWidth_data()
{
    QTest::addColumn<qreal>("capsWidth");
    QTest::addColumn<qreal>("expectedCapsWidth");

    QTest::newRow("caps width less than zero") << -1.0 << 0.0;
    QTest::newRow("caps width equals zero") << 0.0 << 0.0;
    QTest::newRow("caps width greater than zero and less than one") << 0.5 << 0.5;
    QTest::newRow("caps width equals one") << 1.0 << 1.0;
    QTest::newRow("caps width greater than one") << 2.0 << 1.0;
}

void tst_QCandlestickSeries::capsWidth()
{
    QFETCH(qreal, capsWidth);
    QFETCH(qreal, expectedCapsWidth);

    QSignalSpy spy(m_series, SIGNAL(capsWidthChanged()));

    m_series->setCapsWidth(capsWidth);
    QCOMPARE(m_series->capsWidth(), expectedCapsWidth);
    QCOMPARE(spy.size(), 1);

    // Try set same caps width
    m_series->setCapsWidth(capsWidth);
    QCOMPARE(m_series->capsWidth(), expectedCapsWidth);
    QCOMPARE(spy.size(), 1);
}

void tst_QCandlestickSeries::capsVisible()
{
    QSignalSpy spy(m_series, SIGNAL(capsVisibilityChanged()));

    bool visible = !m_series->capsVisible();
    m_series->setCapsVisible(visible);
    QCOMPARE(m_series->capsVisible(), visible);
    QCOMPARE(spy.size(), 1);

    // Try set same caps visibility
    m_series->setCapsVisible(visible);
    QCOMPARE(m_series->capsVisible(), visible);
    QCOMPARE(spy.size(), 1);
}

void tst_QCandlestickSeries::increasingColor()
{
    QSignalSpy spy(m_series, SIGNAL(increasingColorChanged()));

    // Try set new increasing color
    QColor newColor(200, 200, 200, 200);
    m_series->setIncreasingColor(newColor);
    QCOMPARE(m_series->increasingColor(), newColor);
    QCOMPARE(spy.size(), 1);

    // Try set same increasing color again
    m_series->setIncreasingColor(newColor);
    QCOMPARE(m_series->increasingColor(), newColor);
    QCOMPARE(spy.size(), 1);

    // Try set invalid increasing color (should change to default color)
    QColor defaultColor = m_series->brush().color();
    defaultColor.setAlpha(128);
    m_series->setIncreasingColor(QColor());
    QCOMPARE(m_series->increasingColor(), defaultColor);
    QCOMPARE(spy.size(), 2);

    // Set new brush, increasing color should change accordingly
    QBrush brush(newColor);
    defaultColor = brush.color();
    defaultColor.setAlpha(128);
    m_series->setBrush(brush);
    QCOMPARE(m_series->increasingColor(), defaultColor);
    QCOMPARE(spy.size(), 3);
}

void tst_QCandlestickSeries::decreasingColor()
{
    QSignalSpy spy(m_series, SIGNAL(decreasingColorChanged()));

    // Try set new decreasing color
    QColor newColor(200, 200, 200, 200);
    m_series->setDecreasingColor(newColor);
    QCOMPARE(m_series->decreasingColor(), newColor);
    QCOMPARE(spy.size(), 1);

    // Try set same decreasing color again
    m_series->setDecreasingColor(newColor);
    QCOMPARE(m_series->decreasingColor(), newColor);
    QCOMPARE(spy.size(), 1);

    // Try set invalid decreasing color (should change to default color)
    m_series->setDecreasingColor(QColor());
    QCOMPARE(m_series->decreasingColor(), m_series->brush().color());
    QCOMPARE(spy.size(), 2);

    // Set new brush, decreasing color should change accordingly
    m_series->setBrush(QBrush(newColor));
    QCOMPARE(m_series->decreasingColor(), m_series->brush().color());
    QCOMPARE(spy.size(), 3);
}

void tst_QCandlestickSeries::brush()
{
    QSignalSpy spy(m_series, SIGNAL(brushChanged()));

    QBrush brush(QColor(128, 128, 128, 128));
    QColor increasingColor(brush.color());
    increasingColor.setAlpha(128);
    QColor decreasingColor(brush.color());
    m_series->setBrush(brush);
    QCOMPARE(m_series->brush(), brush);
    QCOMPARE(m_series->increasingColor(), increasingColor);
    QCOMPARE(m_series->decreasingColor(), decreasingColor);
    QCOMPARE(spy.size(), 1);

    // Try set same brush
    m_series->setBrush(brush);
    QCOMPARE(m_series->brush(), brush);
    QCOMPARE(m_series->increasingColor(), increasingColor);
    QCOMPARE(m_series->decreasingColor(), decreasingColor);
    QCOMPARE(spy.size(), 1);
}

void tst_QCandlestickSeries::pen()
{
    QSignalSpy spy(m_series, SIGNAL(penChanged()));

    QPen pen(QColor(128, 128, 128, 128));
    m_series->setPen(pen);
    QCOMPARE(m_series->pen(), pen);
    QCOMPARE(spy.size(), 1);

    // Try set same pen
    m_series->setPen(pen);
    QCOMPARE(m_series->pen(), pen);
    QCOMPARE(spy.size(), 1);
}

void tst_QCandlestickSeries::mouseClicked()
{
    SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();

    QVERIFY(m_series->append(m_sets));
    QCOMPARE(m_series->count(), m_sets.size());

    QCandlestickSet *set1 = m_series->sets().at(1);
    QCandlestickSet *set2 = m_series->sets().at(2);

    QSignalSpy seriesSpy(m_series, SIGNAL(clicked(QCandlestickSet *)));
    QSignalSpy setSpy1(set1, SIGNAL(clicked()));
    QSignalSpy setSpy2(set2, SIGNAL(clicked()));

    QChartView view(new QChart());
    view.resize(400, 300);
    view.chart()->addSeries(m_series);
    view.chart()->createDefaultAxes();
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    // Calculate expected layout for candlesticks
    QRectF plotArea = view.chart()->plotArea();
    qreal candlestickWidth = plotArea.width() / m_series->count();
    qreal candlestickHeight = plotArea.height();

    QMap<QCandlestickSet *, QRectF> layout;
    layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1),
                               plotArea.top(), candlestickWidth, candlestickHeight));
    layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2),
                               plotArea.top(), candlestickWidth, candlestickHeight));

    // Click set 1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, layout.value(set1).center().toPoint());
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 1);
    QCOMPARE(setSpy1.size(), 1);
    QCOMPARE(setSpy2.size(), 0);

    QList<QVariant> seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(seriesSpyArgs.size(), 1);
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(0)), set1);
    seriesSpyArgs.clear();

    QVERIFY(setSpy1.takeFirst().isEmpty());

    // Click set 2
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, layout.value(set2).center().toPoint());
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 1);
    QCOMPARE(setSpy1.size(), 0);
    QCOMPARE(setSpy2.size(), 1);

    seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(seriesSpyArgs.size(), 1);
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(0)), set2);
    seriesSpyArgs.clear();

    QVERIFY(setSpy2.takeFirst().isEmpty());
}

void tst_QCandlestickSeries::mouseHovered()
{
    SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();
    SKIP_IF_FLAKY_MOUSE_MOVE();

    QVERIFY(m_series->append(m_sets));
    QCOMPARE(m_series->count(), m_sets.size());

    QCandlestickSet *set1 = m_series->sets().at(1);
    QCandlestickSet *set2 = m_series->sets().at(2);

    QSignalSpy seriesSpy(m_series, SIGNAL(hovered(bool, QCandlestickSet *)));
    QSignalSpy setSpy1(set1, SIGNAL(hovered(bool)));
    QSignalSpy setSpy2(set2, SIGNAL(hovered(bool)));

    QChartView view(new QChart());
    view.resize(400, 300);
    view.chart()->addSeries(m_series);
    view.chart()->createDefaultAxes();
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    // This is hack since view does not get events otherwise
    view.setMouseTracking(true);

    // Calculate expected layout for candlesticks
    QRectF plotArea = view.chart()->plotArea();
    qreal candlestickWidth = plotArea.width() / m_series->count();
    qreal candlestickHeight = plotArea.height();

    QMap<QCandlestickSet *, QRectF> layout;
    layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1),
                               plotArea.top(), candlestickWidth, candlestickHeight));
    layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2),
                               plotArea.top(), candlestickWidth, candlestickHeight));

    // Move mouse to left border
    QTest::mouseMove(view.viewport(), QPoint(0, layout.value(set1).center().y()));
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 0);
    QCOMPARE(setSpy1.size(), 0);
    QCOMPARE(setSpy2.size(), 0);

    // Move mouse on top of set 1
    QTest::mouseMove(view.viewport(), layout.value(set1).center().toPoint());
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 1);
    QCOMPARE(setSpy1.size(), 1);
    QCOMPARE(setSpy2.size(), 0);

    QList<QVariant> seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(1)), set1);
    QCOMPARE(seriesSpyArgs.at(0).metaType().id(), QMetaType::Bool);
    QCOMPARE(seriesSpyArgs.at(0).toBool(), true);
    seriesSpyArgs.clear();

    QList<QVariant> setSpyArgs = setSpy1.takeFirst();
    QCOMPARE(setSpyArgs.at(0).metaType().id(), QMetaType::Bool);
    QCOMPARE(setSpyArgs.at(0).toBool(), true);
    setSpyArgs.clear();

    // Move mouse from top of set 1 to top of set 2
    QTest::mouseMove(view.viewport(), layout.value(set2).center().toPoint());
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 2);
    QCOMPARE(setSpy1.size(), 1);
    QCOMPARE(setSpy2.size(), 1);

    // Should leave set 1
    seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(1)), set1);
    QCOMPARE(seriesSpyArgs.at(0).metaType().id(), QMetaType::Bool);
    QCOMPARE(seriesSpyArgs.at(0).toBool(), false);
    // Don't call seriesSpyArgs.clear() here

    setSpyArgs = setSpy1.takeFirst();
    QCOMPARE(setSpyArgs.at(0).metaType().id(), QMetaType::Bool);
    QCOMPARE(setSpyArgs.at(0).toBool(), false);
    // Don't call setSpyArgs.clear() here

    // Should enter set 2
    seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(1)), set2);
    QCOMPARE(seriesSpyArgs.at(0).metaType().id(), QMetaType::Bool);
    QCOMPARE(seriesSpyArgs.at(0).toBool(), true);
    seriesSpyArgs.clear();

    setSpyArgs = setSpy2.takeFirst();
    QCOMPARE(setSpyArgs.at(0).metaType().id(), QMetaType::Bool);
    QCOMPARE(setSpyArgs.at(0).toBool(), true);
    setSpyArgs.clear();

    // Move mouse from top of set 2 to background
    QTest::mouseMove(view.viewport(), QPoint(layout.value(set2).center().x(), 0));
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 1);
    QCOMPARE(setSpy1.size(), 0);
    QCOMPARE(setSpy2.size(), 1);

    // Should leave set 2
    seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(1)), set2);
    QCOMPARE(seriesSpyArgs.at(0).metaType().id(), QMetaType::Bool);
    QCOMPARE(seriesSpyArgs.at(0).toBool(), false);
    seriesSpyArgs.clear();

    setSpyArgs = setSpy2.takeFirst();
    QCOMPARE(setSpyArgs.at(0).metaType().id(), QMetaType::Bool);
    QCOMPARE(setSpyArgs.at(0).toBool(), false);
    setSpyArgs.clear();
}

void tst_QCandlestickSeries::mousePressed()
{
    SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();

    QVERIFY(m_series->append(m_sets));
    QCOMPARE(m_series->count(), m_sets.size());

    QCandlestickSet *set1 = m_series->sets().at(1);
    QCandlestickSet *set2 = m_series->sets().at(2);

    QSignalSpy seriesSpy(m_series, SIGNAL(pressed(QCandlestickSet *)));
    QSignalSpy setSpy1(set1, SIGNAL(pressed()));
    QSignalSpy setSpy2(set2, SIGNAL(pressed()));

    QChartView view(new QChart());
    view.resize(400, 300);
    view.chart()->addSeries(m_series);
    view.chart()->createDefaultAxes();
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    // Calculate expected layout for candlesticks
    QRectF plotArea = view.chart()->plotArea();
    qreal candlestickWidth = plotArea.width() / m_series->count();
    qreal candlestickHeight = plotArea.height();

    QMap<QCandlestickSet *, QRectF> layout;
    layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1),
                               plotArea.top(), candlestickWidth, candlestickHeight));
    layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2),
                               plotArea.top(), candlestickWidth, candlestickHeight));

    // Press set 1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, layout.value(set1).center().toPoint());
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 1);
    QCOMPARE(setSpy1.size(), 1);
    QCOMPARE(setSpy2.size(), 0);

    QList<QVariant> seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(seriesSpyArgs.size(), 1);
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(0)), set1);
    seriesSpyArgs.clear();

    QVERIFY(setSpy1.takeFirst().isEmpty());

    // Press set 2
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, layout.value(set2).center().toPoint());
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 1);
    QCOMPARE(setSpy1.size(), 0);
    QCOMPARE(setSpy2.size(), 1);

    seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(seriesSpyArgs.size(), 1);
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(0)), set2);
    seriesSpyArgs.clear();

    QVERIFY(setSpy2.takeFirst().isEmpty());
}

void tst_QCandlestickSeries::mouseReleased()
{
    SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();

    QVERIFY(m_series->append(m_sets));
    QCOMPARE(m_series->count(), m_sets.size());

    QCandlestickSet *set1 = m_series->sets().at(1);
    QCandlestickSet *set2 = m_series->sets().at(2);

    QSignalSpy seriesSpy(m_series, SIGNAL(released(QCandlestickSet *)));
    QSignalSpy setSpy1(set1, SIGNAL(released()));
    QSignalSpy setSpy2(set2, SIGNAL(released()));

    QChartView view(new QChart());
    view.resize(400, 300);
    view.chart()->addSeries(m_series);
    view.chart()->createDefaultAxes();
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    // Calculate expected layout for candlesticks
    QRectF plotArea = view.chart()->plotArea();
    qreal candlestickWidth = plotArea.width() / m_series->count();
    qreal candlestickHeight = plotArea.height();

    QMap<QCandlestickSet *, QRectF> layout;
    layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1),
                               plotArea.top(), candlestickWidth, candlestickHeight));
    layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2),
                               plotArea.top(), candlestickWidth, candlestickHeight));

    // Release mouse over set 1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, layout.value(set1).center().toPoint());
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 1);
    QCOMPARE(setSpy1.size(), 1);
    QCOMPARE(setSpy2.size(), 0);

    QList<QVariant> seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(seriesSpyArgs.size(), 1);
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(0)), set1);
    seriesSpyArgs.clear();

    QVERIFY(setSpy1.takeFirst().isEmpty());

    // Release mouse over set 2
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, layout.value(set2).center().toPoint());
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 1);
    QCOMPARE(setSpy1.size(), 0);
    QCOMPARE(setSpy2.size(), 1);

    seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(seriesSpyArgs.size(), 1);
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(0)), set2);
    seriesSpyArgs.clear();

    QVERIFY(setSpy2.takeFirst().isEmpty());
}

void tst_QCandlestickSeries::mouseDoubleClicked()
{
    SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();

    QVERIFY(m_series->append(m_sets));
    QCOMPARE(m_series->count(), m_sets.size());

    QCandlestickSet *set1 = m_series->sets().at(1);
    QCandlestickSet *set2 = m_series->sets().at(2);

    QSignalSpy seriesSpy(m_series, SIGNAL(doubleClicked(QCandlestickSet *)));
    QSignalSpy setSpy1(set1, SIGNAL(doubleClicked()));
    QSignalSpy setSpy2(set2, SIGNAL(doubleClicked()));

    QChartView view(new QChart());
    view.resize(400, 300);
    view.chart()->addSeries(m_series);
    view.chart()->createDefaultAxes();
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    // Calculate expected layout for candlesticks
    QRectF plotArea = view.chart()->plotArea();
    qreal candlestickWidth = plotArea.width() / m_series->count();
    qreal candlestickHeight = plotArea.height();

    QMap<QCandlestickSet *, QRectF> layout;
    layout.insert(set1, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set1),
                               plotArea.top(), candlestickWidth, candlestickHeight));
    layout.insert(set2, QRectF(plotArea.left() + candlestickWidth * m_sets.indexOf(set2),
                               plotArea.top(), candlestickWidth, candlestickHeight));

    // Double-click set 1
    QTest::mouseDClick(view.viewport(), Qt::LeftButton, {}, layout.value(set1).center().toPoint());
    QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);

    QCOMPARE(seriesSpy.size(), 1);
    QCOMPARE(setSpy1.size(), 1);
    QCOMPARE(setSpy2.size(), 0);

    QList<QVariant> seriesSpyArgs = seriesSpy.takeFirst();
    QCOMPARE(seriesSpyArgs.size(), 1);
    QCOMPARE(qvariant_cast<QCandlestickSet *>(seriesSpyArgs.at(0)), set1);
    seriesSpyArgs.clear();

    QVERIFY(setSpy1.takeFirst().isEmpty());
}

QTEST_MAIN(tst_QCandlestickSeries)

#include "tst_qcandlestickseries.moc"
