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

#include <QtCharts/QCandlestickModelMapper>
#include <QtCharts/QCandlestickSeries>
#include <QtCharts/QCandlestickSet>
#include <QtCore/QAbstractItemModel>
#include <private/qcandlestickmodelmapper_p.h>

#include <algorithm>

QT_BEGIN_NAMESPACE

/*!
    \class QCandlestickModelMapper
    \since 5.8
    \inmodule QtCharts
    \brief Abstract model mapper class for candlestick series.

    Model mappers allow the use of a QAbstractItemModel-derived model as a data source for a chart
    series, creating a connection between a QCandlestickSeries and the model object. A model mapper
    maintains an equal size across all \l {QCandlestickSet} {QCandlestickSets}.

    \note The model used must support adding and removing rows/columns and modifying the data of the
    cells.
*/

/*!
    \property QCandlestickModelMapper::model
    \brief Defines the model that is used by the mapper.
*/

/*!
    \property QCandlestickModelMapper::series
    \brief Defines the QCandlestickSeries object that is used by the mapper.

    \note All data in the series is discarded when it is set to the mapper. When a new series is
    specified, the old series is disconnected (preserving its data).
*/

/*!
    \fn Qt::Orientation QCandlestickModelMapper::orientation() const
    Returns the orientation that is used when QCandlestickModelMapper accesses the model. This
    determines whether the consecutive values of the set are read from rows (Qt::Horizontal) or from
    columns (Qt::Vertical).
*/

/*!
    \fn void QCandlestickModelMapper::modelReplaced()
    \brief Emitted when the model, to which the mapper is connected, has changed.
    \sa model
*/

/*!
    \fn void QCandlestickModelMapper::seriesReplaced()
    \brief Emitted when the series to which mapper is connected to has changed.
    \sa series
*/

/*!
    Constructs a model mapper object as a child of \a parent.
*/
QCandlestickModelMapper::QCandlestickModelMapper(QObject *parent)
    : QObject(parent),
      d_ptr(new QCandlestickModelMapperPrivate(this))
{
}

void QCandlestickModelMapper::setModel(QAbstractItemModel *model)
{
    Q_D(QCandlestickModelMapper);

    if (d->m_model == model)
        return;

    if (d->m_model)
        disconnect(d->m_model, 0, d, 0);

    d->m_model = model;
    emit modelReplaced();

    if (!d->m_model)
        return;

    d->initializeCandlestickFromModel();
    // connect signals from the model
    connect(d->m_model, SIGNAL(modelReset()), d, SLOT(initializeCandlestickFromModel()));
    connect(d->m_model, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
            d, SLOT(modelDataUpdated(QModelIndex, QModelIndex)));
    connect(d->m_model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)),
            d, SLOT(modelHeaderDataUpdated(Qt::Orientation, int, int)));
    connect(d->m_model, SIGNAL(rowsInserted(QModelIndex, int, int)),
            d, SLOT(modelRowsInserted(QModelIndex, int, int)));
    connect(d->m_model, SIGNAL(rowsRemoved(QModelIndex, int, int)),
            d, SLOT(modelRowsRemoved(QModelIndex, int, int)));
    connect(d->m_model, SIGNAL(columnsInserted(QModelIndex, int, int)),
            d, SLOT(modelColumnsInserted(QModelIndex, int, int)));
    connect(d->m_model, SIGNAL(columnsRemoved(QModelIndex, int, int)),
            d, SLOT(modelColumnsRemoved(QModelIndex, int, int)));
    connect(d->m_model, SIGNAL(destroyed()), d, SLOT(modelDestroyed()));
}

QAbstractItemModel *QCandlestickModelMapper::model() const
{
    Q_D(const QCandlestickModelMapper);

    return d->m_model;
}

void QCandlestickModelMapper::setSeries(QCandlestickSeries *series)
{
    Q_D(QCandlestickModelMapper);

    if (d->m_series == series)
        return;

    if (d->m_series)
        disconnect(d->m_series, 0, d, 0);

    d->m_series = series;
    emit seriesReplaced();

    if (!d->m_series)
        return;

    d->initializeCandlestickFromModel();
    // connect the signals from the series
    connect(d->m_series, SIGNAL(candlestickSetsAdded(QList<QCandlestickSet *>)),
            d, SLOT(candlestickSetsAdded(QList<QCandlestickSet *>)));
    connect(d->m_series, SIGNAL(candlestickSetsRemoved(QList<QCandlestickSet*>)),
            d, SLOT(candlestickSetsRemoved(QList<QCandlestickSet *>)));
    connect(d->m_series, SIGNAL(destroyed()), d, SLOT(seriesDestroyed()));
}

QCandlestickSeries *QCandlestickModelMapper::series() const
{
    Q_D(const QCandlestickModelMapper);

    return d->m_series;
}

/*!
    Sets the row/column of the model that contains the \a timestamp values of the sets in the
    series. Default value is -1 (invalid mapping).
*/
void QCandlestickModelMapper::setTimestamp(int timestamp)
{
    Q_D(QCandlestickModelMapper);

    timestamp = qMax(timestamp, -1);

    if (d->m_timestamp == timestamp)
        return;

    d->m_timestamp = timestamp;
    emit d->timestampChanged();
    d->initializeCandlestickFromModel();
}

/*!
    Returns the row/column of the model that contains the timestamp values of the sets in the
    series. Default value is -1 (invalid mapping).
*/
int QCandlestickModelMapper::timestamp() const
{
    Q_D(const QCandlestickModelMapper);

    return d->m_timestamp;
}

/*!
    Sets the row/column of the model that contains the \a open values of the sets in the series.
    Default value is -1 (invalid mapping).
*/
void QCandlestickModelMapper::setOpen(int open)
{
    Q_D(QCandlestickModelMapper);

    open = qMax(open, -1);

    if (d->m_open == open)
        return;

    d->m_open = open;
    emit d->openChanged();
    d->initializeCandlestickFromModel();
}

/*!
    Returns the row/column of the model that contains the open values of the sets in the series.
    Default value is -1 (invalid mapping).
*/
int QCandlestickModelMapper::open() const
{
    Q_D(const QCandlestickModelMapper);

    return d->m_open;
}

/*!
    Sets the row/column of the model that contains the \a high values of the sets in the series.
    Default value is -1 (invalid mapping).
*/
void QCandlestickModelMapper::setHigh(int high)
{
    Q_D(QCandlestickModelMapper);

    high = qMax(high, -1);

    if (d->m_high == high)
        return;

    d->m_high = high;
    emit d->highChanged();
    d->initializeCandlestickFromModel();
}

/*!
    Returns the row/column of the model that contains the high values of the sets in the series.
    Default value is -1 (invalid mapping).
*/
int QCandlestickModelMapper::high() const
{
    Q_D(const QCandlestickModelMapper);

    return d->m_high;
}

/*!
    Sets the row/column of the model that contains the \a low values of the sets in the series.
    Default value is -1 (invalid mapping).
*/
void QCandlestickModelMapper::setLow(int low)
{
    Q_D(QCandlestickModelMapper);

    low = qMax(low, -1);

    if (d->m_low == low)
        return;

    d->m_low = low;
    emit d->lowChanged();
    d->initializeCandlestickFromModel();
}

/*!
    Returns the row/column of the model that contains the low values of the sets in the series.
    Default value is -1 (invalid mapping).
*/
int QCandlestickModelMapper::low() const
{
    Q_D(const QCandlestickModelMapper);

    return d->m_low;
}

/*!
    Sets the row/column of the model that contains the \a close values of the sets in the series.
    Default value is -1 (invalid mapping).
*/
void QCandlestickModelMapper::setClose(int close)
{
    Q_D(QCandlestickModelMapper);

    close = qMax(close, -1);

    if (d->m_close == close)
        return;

    d->m_close = close;
    emit d->closeChanged();
    d->initializeCandlestickFromModel();
}

/*!
    Returns the row/column of the model that contains the close values of the sets in the series.
    Default value is -1 (invalid mapping).
*/
int QCandlestickModelMapper::close() const
{
    Q_D(const QCandlestickModelMapper);

    return d->m_close;
}

/*!
    Sets the section of the model that is used as the data source for the first candlestick set.
    Parameter \a firstSetSection specifies the section of the model. Default value is -1.
*/
void QCandlestickModelMapper::setFirstSetSection(int firstSetSection)
{
    Q_D(QCandlestickModelMapper);

    firstSetSection = qMax(firstSetSection, -1);

    if (d->m_firstSetSection == firstSetSection)
        return;

    d->m_firstSetSection = firstSetSection;
    emit d->firstSetSectionChanged();
    d->initializeCandlestickFromModel();
}

/*!
    Returns the section of the model that is used as the data source for the first candlestick set.
    Default value is -1 (invalid mapping).
*/
int QCandlestickModelMapper::firstSetSection() const
{
    Q_D(const QCandlestickModelMapper);

    return d->m_firstSetSection;
}

/*!
    Sets the section of the model that is used as the data source for the last candlestick set.
    Parameter \a lastSetSection specifies the section of the model. Default value is -1.
*/
void QCandlestickModelMapper::setLastSetSection(int lastSetSection)
{
    Q_D(QCandlestickModelMapper);

    lastSetSection = qMax(lastSetSection, -1);

    if (d->m_lastSetSection == lastSetSection)
        return;

    d->m_lastSetSection = lastSetSection;
    emit d->lastSetSectionChanged();
    d->initializeCandlestickFromModel();
}

/*!
    Returns the section of the model that is used as the data source for the last candlestick set.
    Default value is -1 (invalid mapping).
*/
int QCandlestickModelMapper::lastSetSection() const
{
    Q_D(const QCandlestickModelMapper);

    return d->m_lastSetSection;
}

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

QCandlestickModelMapperPrivate::QCandlestickModelMapperPrivate(QCandlestickModelMapper *q)
    : QObject(q),
      m_model(nullptr),
      m_series(nullptr),
      m_timestamp(-1),
      m_open(-1),
      m_high(-1),
      m_low(-1),
      m_close(-1),
      m_firstSetSection(-1),
      m_lastSetSection(-1),
      m_modelSignalsBlock(false),
      m_seriesSignalsBlock(false),
      q_ptr(q)
{
}

void QCandlestickModelMapperPrivate::initializeCandlestickFromModel()
{
    if (!m_model || !m_series)
        return;

    blockSeriesSignals();
    // clear current content
    m_series->clear();
    m_sets.clear();

    // create the initial candlestick sets
    QList<QCandlestickSet *> sets;
    for (int i = m_firstSetSection; i <= m_lastSetSection; ++i) {
        QModelIndex timestampIndex = candlestickModelIndex(i, m_timestamp);
        QModelIndex openIndex = candlestickModelIndex(i, m_open);
        QModelIndex highIndex = candlestickModelIndex(i, m_high);
        QModelIndex lowIndex = candlestickModelIndex(i, m_low);
        QModelIndex closeIndex = candlestickModelIndex(i, m_close);
        if (timestampIndex.isValid()
            && openIndex.isValid()
            && highIndex.isValid()
            && lowIndex.isValid()
            && closeIndex.isValid()) {
            QCandlestickSet *set = new QCandlestickSet();
            set->setTimestamp(m_model->data(timestampIndex, Qt::DisplayRole).toReal());
            set->setOpen(m_model->data(openIndex, Qt::DisplayRole).toReal());
            set->setHigh(m_model->data(highIndex, Qt::DisplayRole).toReal());
            set->setLow(m_model->data(lowIndex, Qt::DisplayRole).toReal());
            set->setClose(m_model->data(closeIndex, Qt::DisplayRole).toReal());

            connect(set, SIGNAL(timestampChanged()), this, SLOT(candlestickSetChanged()));
            connect(set, SIGNAL(openChanged()), this, SLOT(candlestickSetChanged()));
            connect(set, SIGNAL(highChanged()), this, SLOT(candlestickSetChanged()));
            connect(set, SIGNAL(lowChanged()), this, SLOT(candlestickSetChanged()));
            connect(set, SIGNAL(closeChanged()), this, SLOT(candlestickSetChanged()));

            sets.append(set);
        } else {
            break;
        }
    }
    m_series->append(sets);
    m_sets.append(sets);
    blockSeriesSignals(false);
}

void QCandlestickModelMapperPrivate::modelDataUpdated(QModelIndex topLeft, QModelIndex bottomRight)
{
    Q_Q(QCandlestickModelMapper);

    if (!m_model || !m_series)
        return;

    if (m_modelSignalsBlock)
        return;

    blockSeriesSignals();
    QModelIndex index;
    for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
        for (int column = topLeft.column(); column <= bottomRight.column(); ++column) {
            index = topLeft.sibling(row, column);
            QCandlestickSet *set = candlestickSet(index);
            if (set) {
                int pos = (q->orientation() == Qt::Vertical) ? row : column;
                if (pos == m_timestamp)
                    set->setTimestamp(m_model->data(index).toReal());
                else if (pos == m_open)
                    set->setOpen(m_model->data(index).toReal());
                else if (pos == m_high)
                    set->setHigh(m_model->data(index).toReal());
                else if (pos == m_low)
                    set->setLow(m_model->data(index).toReal());
                else if (pos == m_close)
                    set->setClose(m_model->data(index).toReal());
            }
        }
    }
    blockSeriesSignals(false);
}

void QCandlestickModelMapperPrivate::modelHeaderDataUpdated(Qt::Orientation orientation, int first,
                                                            int last)
{
    Q_UNUSED(orientation);
    Q_UNUSED(first);
    Q_UNUSED(last);
}

void QCandlestickModelMapperPrivate::modelRowsInserted(QModelIndex parent, int start, int end)
{
    Q_UNUSED(parent);

    Q_Q(QCandlestickModelMapper);

    if (m_modelSignalsBlock)
        return;

    blockSeriesSignals();
    if (q->orientation() == Qt::Vertical)
        insertData(start, end);
    else if (start <= m_firstSetSection || start <= m_lastSetSection)
        initializeCandlestickFromModel();  // if the changes affect the map - reinitialize
    blockSeriesSignals(false);
}

void QCandlestickModelMapperPrivate::modelRowsRemoved(QModelIndex parent, int start, int end)
{
    Q_UNUSED(parent);

    Q_Q(QCandlestickModelMapper);

    if (m_modelSignalsBlock)
        return;

    blockSeriesSignals();
    if (q->orientation() == Qt::Vertical)
        removeData(start, end);
    else if (start <= m_firstSetSection || start <= m_lastSetSection)
        initializeCandlestickFromModel();  // if the changes affect the map - reinitialize
    blockSeriesSignals(false);
}

void QCandlestickModelMapperPrivate::modelColumnsInserted(QModelIndex parent, int start, int end)
{
    Q_UNUSED(parent);

    Q_Q(QCandlestickModelMapper);

    if (m_modelSignalsBlock)
        return;

    blockSeriesSignals();
    if (q->orientation() == Qt::Horizontal)
        insertData(start, end);
    else if (start <= m_firstSetSection || start <= m_lastSetSection)
        initializeCandlestickFromModel();  // if the changes affect the map - reinitialize
    blockSeriesSignals(false);
}

void QCandlestickModelMapperPrivate::modelColumnsRemoved(QModelIndex parent, int start, int end)
{
    Q_UNUSED(parent);

    Q_Q(QCandlestickModelMapper);

    if (m_modelSignalsBlock)
        return;

    blockSeriesSignals();
    if (q->orientation() == Qt::Horizontal)
        removeData(start, end);
    else if (start <= m_firstSetSection || start <= m_lastSetSection)
        initializeCandlestickFromModel();  // if the changes affect the map - reinitialize
    blockSeriesSignals(false);
}

void QCandlestickModelMapperPrivate::modelDestroyed()
{
    m_model = 0;
}

void QCandlestickModelMapperPrivate::candlestickSetsAdded(const QList<QCandlestickSet *> &sets)
{
    Q_Q(QCandlestickModelMapper);

    if (m_seriesSignalsBlock)
        return;

    if (sets.isEmpty())
        return;

    int firstIndex = m_series->sets().indexOf(sets.at(0));
    if (firstIndex == -1)
        return;

    m_lastSetSection += sets.size();

    blockModelSignals();
    if (q->orientation() == Qt::Vertical)
        m_model->insertColumns(firstIndex + m_firstSetSection, sets.size());
    else
        m_model->insertRows(firstIndex + m_firstSetSection, sets.size());

    for (int i = 0; i < sets.size(); ++i) {
        int section = i + firstIndex + m_firstSetSection;
        m_model->setData(candlestickModelIndex(section, m_timestamp), sets.at(i)->timestamp());
        m_model->setData(candlestickModelIndex(section, m_open), sets.at(i)->open());
        m_model->setData(candlestickModelIndex(section, m_high), sets.at(i)->high());
        m_model->setData(candlestickModelIndex(section, m_low), sets.at(i)->low());
        m_model->setData(candlestickModelIndex(section, m_close), sets.at(i)->close());
    }
    blockModelSignals(false);
    initializeCandlestickFromModel();
}

void QCandlestickModelMapperPrivate::candlestickSetsRemoved(const QList<QCandlestickSet *> &sets)
{
    Q_Q(QCandlestickModelMapper);

    if (m_seriesSignalsBlock)
        return;

    if (sets.isEmpty())
        return;

    QList<int> removedIndices;
    for (auto &set : sets) {
        int index = m_sets.indexOf(set);
        if (index != -1)
            removedIndices << index;
    }

    if (removedIndices.isEmpty())
        return;

    std::sort(removedIndices.begin(), removedIndices.end());

    for (int i = removedIndices.size() - 1; i >= 0; --i) {
        m_sets.removeAt(removedIndices[i]);
        --m_lastSetSection;
    }

    blockModelSignals();

    // There is no guarantee removed sets are continuous, so remove them one by one
    for (int i = removedIndices.size() - 1; i >= 0; --i) {
        if (q->orientation() == Qt::Vertical)
            m_model->removeColumns(removedIndices[i] + m_firstSetSection, 1);
        else
            m_model->removeRows(removedIndices[i] + m_firstSetSection, 1);
    }

    blockModelSignals(false);
    initializeCandlestickFromModel();
}

void QCandlestickModelMapperPrivate::candlestickSetChanged()
{
    if (m_seriesSignalsBlock)
        return;

    QCandlestickSet *set = qobject_cast<QCandlestickSet *>(QObject::sender());
    if (!set)
        return;

    int section = m_series->sets().indexOf(set);
    if (section < 0)
        return;

    section += m_firstSetSection;

    blockModelSignals();
    m_model->setData(candlestickModelIndex(section, m_timestamp), set->timestamp());
    m_model->setData(candlestickModelIndex(section, m_open), set->open());
    m_model->setData(candlestickModelIndex(section, m_high), set->high());
    m_model->setData(candlestickModelIndex(section, m_low), set->low());
    m_model->setData(candlestickModelIndex(section, m_close), set->close());
    blockModelSignals(false);
}

void QCandlestickModelMapperPrivate::seriesDestroyed()
{
    m_series = 0;
}

QCandlestickSet *QCandlestickModelMapperPrivate::candlestickSet(QModelIndex index)
{
    Q_Q(QCandlestickModelMapper);

    if (!index.isValid())
        return 0;

    int section = (q->orientation() == Qt::Vertical) ? index.column() : index.row();
    int pos = (q->orientation() == Qt::Vertical) ? index.row() : index.column();

    if (section < m_firstSetSection || section > m_lastSetSection)
        return 0; // This part of model has not been mapped to any candlestick set.

    if (pos != m_timestamp && pos != m_open && pos != m_high && pos != m_low && pos != m_close)
        return 0; // This part of model has not been mapped to any candlestick set.

    return m_series->sets().at(section - m_firstSetSection);
}

QModelIndex QCandlestickModelMapperPrivate::candlestickModelIndex(int section, int pos)
{
    Q_Q(QCandlestickModelMapper);

    if (section < m_firstSetSection || section > m_lastSetSection)
        return QModelIndex(); // invalid

    if (pos != m_timestamp && pos != m_open && pos != m_high && pos != m_low && pos != m_close)
        return QModelIndex(); // invalid

    if (q->orientation() == Qt::Vertical)
        return m_model->index(pos, section);
    else
        return m_model->index(section, pos);
}

void QCandlestickModelMapperPrivate::insertData(int start, int end)
{
    Q_UNUSED(start);
    Q_UNUSED(end);

    // Currently candlestickchart needs to be fully recalculated when change is made.
    initializeCandlestickFromModel();
}

void QCandlestickModelMapperPrivate::removeData(int start, int end)
{
    Q_UNUSED(start);
    Q_UNUSED(end);

    // Currently candlestickchart needs to be fully recalculated when change is made.
    initializeCandlestickFromModel();
}

void QCandlestickModelMapperPrivate::blockModelSignals(bool block)
{
    m_modelSignalsBlock = block;
}

void QCandlestickModelMapperPrivate::blockSeriesSignals(bool block)
{
    m_seriesSignalsBlock = block;
}

QT_END_NAMESPACE

#include "moc_qcandlestickmodelmapper.cpp"
#include "moc_qcandlestickmodelmapper_p.cpp"
