// 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 <private/boxplotchartitem_p.h>
#include <private/qboxplotseries_p.h>
#include <private/bar_p.h>
#include <private/qboxset_p.h>
#include <private/qabstractbarseries_p.h>
#include <QtCharts/QBoxSet>
#include <private/boxwhiskers_p.h>
#include <QtGui/QPainter>

QT_BEGIN_NAMESPACE

BoxPlotChartItem::BoxPlotChartItem(QBoxPlotSeries *series, QGraphicsItem *item) :
    ChartItem(series->d_func(), item),
    m_series(series),
    m_animation(0)
{
    setAcceptedMouseButtons({});
    connect(series, SIGNAL(boxsetsRemoved(QList<QBoxSet*>)),
            this, SLOT(handleBoxsetRemove(QList<QBoxSet*>)));
    connect(series, SIGNAL(visibleChanged()), this, SLOT(handleSeriesVisibleChanged()));
    connect(series, SIGNAL(opacityChanged()), this, SLOT(handleOpacityChanged()));
    connect(series->d_func(), SIGNAL(restructuredBoxes()), this, SLOT(handleDataStructureChanged()));
    connect(series->d_func(), SIGNAL(updatedLayout()), this, SLOT(handleLayoutChanged()));
    connect(series->d_func(), SIGNAL(updatedBoxes()), this, SLOT(handleUpdatedBars()));
    connect(series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdatedBars()));
    // QBoxPlotSeriesPrivate calls handleDataStructureChanged(), don't do it here
    setZValue(ChartPresenter::BoxPlotSeriesZValue);
}

BoxPlotChartItem::~BoxPlotChartItem()
{
}

void BoxPlotChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(painter);
    Q_UNUSED(option);
    Q_UNUSED(widget);
}

void BoxPlotChartItem::setAnimation(BoxPlotAnimation *animation)
{
    m_animation = animation;
    if (m_animation) {
        for (auto item : std::as_const(m_boxTable))
            m_animation->addBox(item);
        handleDomainUpdated();
    }
}

void BoxPlotChartItem::handleSeriesVisibleChanged()
{
    setVisible(m_series->isVisible());
}

void BoxPlotChartItem::handleOpacityChanged()
{
    setOpacity(m_series->opacity());
}

void BoxPlotChartItem::handleDataStructureChanged()
{
    int setCount = m_series->count();

    for (int s = 0; s < setCount; s++) {
        QBoxSet *set = m_series->d_func()->boxSetAt(s);

        BoxWhiskers *box = m_boxTable.value(set);
        if (!box) {
            // Item is not yet created, make a box and add it to hash table
            box = new BoxWhiskers(set, domain(), this);
            m_boxTable.insert(set, box);
            connect(box, SIGNAL(clicked(QBoxSet*)), m_series, SIGNAL(clicked(QBoxSet*)));
            connect(box, SIGNAL(hovered(bool,QBoxSet*)),
                    m_series, SIGNAL(hovered(bool,QBoxSet*)));
            connect(box, SIGNAL(pressed(QBoxSet*)), m_series, SIGNAL(pressed(QBoxSet*)));
            connect(box, SIGNAL(released(QBoxSet*)), m_series, SIGNAL(released(QBoxSet*)));
            connect(box, SIGNAL(doubleClicked(QBoxSet*)),
                    m_series, SIGNAL(doubleClicked(QBoxSet*)));
            connect(box, SIGNAL(clicked(QBoxSet*)), set, SIGNAL(clicked()));
            connect(box, SIGNAL(hovered(bool,QBoxSet*)), set, SIGNAL(hovered(bool)));
            connect(box, SIGNAL(pressed(QBoxSet*)), set, SIGNAL(pressed()));
            connect(box, SIGNAL(released(QBoxSet*)), set, SIGNAL(released()));
            connect(box, SIGNAL(doubleClicked(QBoxSet*)), set, SIGNAL(doubleClicked()));

            // Set the decorative issues for the newly created box
            // so that the brush and pen already defined for the set are kept.
            if (set->brush() == Qt::NoBrush)
                box->setBrush(m_series->brush());
            else
                box->setBrush(set->brush());
            if (set->pen() == Qt::NoPen)
                box->setPen(m_series->pen());
            else
                box->setPen(set->pen());
            box->setBoxOutlined(m_series->boxOutlineVisible());
            box->setBoxWidth(m_series->boxWidth());
        }
        updateBoxGeometry(box, s);

        box->updateGeometry(domain());

        if (m_animation)
            m_animation->addBox(box);
    }

    handleDomainUpdated();
}

void BoxPlotChartItem::handleUpdatedBars()
{
    for (auto item : std::as_const(m_boxTable)) {
        item->setBrush(m_series->brush());
        item->setPen(m_series->pen());
        item->setBoxOutlined(m_series->boxOutlineVisible());
        item->setBoxWidth(m_series->boxWidth());
    }
    // Override with QBoxSet specific settings
    for (const auto &[set, item] : std::as_const(m_boxTable).asKeyValueRange()) {
        if (set->brush().style() != Qt::NoBrush)
            item->setBrush(set->brush());
        if (set->pen().style() != Qt::NoPen)
            item->setPen(set->pen());
    }
}

void BoxPlotChartItem::handleBoxsetRemove(const QList<QBoxSet *> &barSets)
{
    for (auto *set : barSets) {
        BoxWhiskers *boxItem = m_boxTable.value(set);
        m_boxTable.remove(set);
        delete boxItem;
    }
}

void BoxPlotChartItem::handleDomainUpdated()
{
    if ((domain()->size().width() <= 0) || (domain()->size().height() <= 0))
        return;

    // Set my bounding rect to same as domain size. Add one pixel at the top (-1.0) and the bottom as 0.0 would
    // snip a bit off from the whisker at the grid line
    m_boundingRect.setRect(0.0, -1.0, domain()->size().width(), domain()->size().height() + 1.0);

    for (auto item : std::as_const(m_boxTable)) {
        item->updateGeometry(domain());

        // If the animation is set, start the animation for each BoxWhisker item
        if (m_animation)
            presenter()->startAnimation(m_animation->boxAnimation(item));
    }
}

void BoxPlotChartItem::handleLayoutChanged()
{
    for (auto item : std::as_const(m_boxTable)) {
        if (m_animation)
            m_animation->setAnimationStart(item);

        item->setBoxWidth(m_series->boxWidth());

        bool dirty = updateBoxGeometry(item, item->m_data.m_index);
        if (dirty && m_animation)
            presenter()->startAnimation(m_animation->boxChangeAnimation(item));
        else
            item->updateGeometry(domain());
    }
}

QRectF BoxPlotChartItem::boundingRect() const
{
    return m_boundingRect;
}

void BoxPlotChartItem::initializeLayout()
{
}

QList<QRectF> BoxPlotChartItem::calculateLayout()
{
    return QList<QRectF>();
}

bool BoxPlotChartItem::updateBoxGeometry(BoxWhiskers *box, int index)
{
    bool changed = false;

    QBoxSet *set = m_series->d_func()->boxSetAt(index);
    BoxWhiskersData &data = box->m_data;

    if ((data.m_lowerExtreme != set->at(0)) || (data.m_lowerQuartile != set->at(1)) ||
        (data.m_median != set->at(2)) || (data.m_upperQuartile != set->at(3)) || (data.m_upperExtreme != set->at(4))) {
        changed = true;
    }

    data.m_lowerExtreme = set->at(0);
    data.m_lowerQuartile = set->at(1);
    data.m_median = set->at(2);
    data.m_upperQuartile = set->at(3);
    data.m_upperExtreme = set->at(4);
    data.m_index = index;
    data.m_boxItems = m_series->count();

    data.m_maxX = domain()->maxX();
    data.m_minX = domain()->minX();
    data.m_maxY = domain()->maxY();
    data.m_minY = domain()->minY();

    data.m_seriesIndex = m_seriesIndex;
    data.m_seriesCount = m_seriesCount;

    return changed;
}

QT_END_NAMESPACE

#include "moc_boxplotchartitem_p.cpp"
