#include "xyseriesadapter.h"

#include <QDebug>

#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcLogEngine)

XYSeriesAdapter::XYSeriesAdapter(QObject *parent) : QObject(parent)
{

}

LogsModel *XYSeriesAdapter::logsModel() const
{
    return m_model;
}

void XYSeriesAdapter::setLogsModel(LogsModel *logsModel)
{
    if (m_model != logsModel) {
        m_model = logsModel;
        emit logsModelChanged();
//        update();
        connect(logsModel, &LogsModel::logEntryAdded, this, &XYSeriesAdapter::logEntryAdded);
    }
}

QtCharts::QXYSeries *XYSeriesAdapter::xySeries() const
{
    return m_series;
}

void XYSeriesAdapter::setXySeries(QtCharts::QXYSeries *series)
{
    if (m_series != series) {
        m_series = series;
        emit xySeriesChanged();

        ensureSamples(QDateTime::currentDateTime(), QDateTime::currentDateTime().addMSecs(2 * 60000));
    }
}

QtCharts::QXYSeries *XYSeriesAdapter::baseSeries() const
{
    return m_baseSeries;
}

void XYSeriesAdapter::setBaseSeries(QtCharts::QXYSeries *series)
{
    if (m_baseSeries != series) {
        m_baseSeries = series;
        emit baseSeriesChanged();

        connect(m_baseSeries, &QtCharts::QXYSeries::pointAdded, this, [=](int index){
            if (m_series->count() > index) {
                qreal value = calculateSampleValue(index);
                m_series->replace(index, m_series->at(index).x(), value);
                if (value < m_minValue) {
                    m_minValue = value;
//                    qDebug() << "New min:" << m_minValue;
                    emit minValueChanged();
                }
                if (value > m_maxValue) {
                    m_maxValue = value;
//                    qDebug() << "New max:" << m_maxValue;
                    emit maxValueChanged();
                }
            }
        });
        connect(m_baseSeries, &QtCharts::QXYSeries::pointReplaced, this, [=](int index){
            if (m_series->count() > index) {
                qreal value = calculateSampleValue(index);
                m_series->replace(index, m_series->at(index).x(), value);
                if (value < m_minValue) {
                    m_minValue = value;
//                    qDebug() << "New min:" << m_minValue;
                    emit minValueChanged();
                }
                if (value > m_maxValue) {
                    m_maxValue = value;
//                    qDebug() << "New max:" << m_maxValue;
                    emit maxValueChanged();
                }
            }
        });
    }
}

XYSeriesAdapter::SampleRate XYSeriesAdapter::sampleRate() const
{
    return m_sampleRate;
}

void XYSeriesAdapter::setSampleRate(XYSeriesAdapter::SampleRate sampleRate)
{
    if (m_sampleRate != sampleRate) {
        m_sampleRate = sampleRate;
        emit sampleRateChanged();
    }
}

bool XYSeriesAdapter::smooth() const
{
    return m_smooth;
}

void XYSeriesAdapter::setSmooth(bool smooth)
{
    if (m_smooth != smooth) {
        m_smooth = smooth;
        emit smoothChanged();
    }
}

bool XYSeriesAdapter::inverted() const
{
    return m_inverted;
}

void XYSeriesAdapter::setInverted(bool inverted)
{
    if (m_inverted != inverted) {
        m_inverted = inverted;
        emit invertedChanged();
    }
}

qreal XYSeriesAdapter::maxValue() const
{
    return m_maxValue;
}

qreal XYSeriesAdapter::minValue() const
{
    return m_minValue;
}

void XYSeriesAdapter::ensureSamples(const QDateTime &from, const QDateTime &to)
{
    if (!m_series) {
        return;
    }

    if (m_samples.isEmpty()) {
        Sample *sample = new Sample();
        sample->timestamp = from.addSecs(m_sampleRate / 2);
//        qWarning() << "Added first" << from << sample->timestamp;
        m_newestSample = sample->timestamp;
        m_oldestSample = m_newestSample;
        m_samples.append(sample);
        m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), 0));
    }

    while (to > m_newestSample) {
        Sample *sample = new Sample();
        sample->timestamp = m_newestSample.addSecs(m_sampleRate);
        Sample *oldNewest = m_samples.first();
        if (oldNewest->entries.count() > 0) {
            sample->startingPoint = oldNewest->entries.last();
        } else if (oldNewest->startingPoint != nullptr) {
            sample->startingPoint = oldNewest->startingPoint;
        }
        m_newestSample = sample->timestamp;
        m_samples.prepend(sample);
        m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), m_series->at(0).y()));
    }

    while (from < m_oldestSample.addSecs(-m_sampleRate)) {
//        qWarning() << "Added one before" << from << m_oldestSample.addMSecs(-m_sampleRate) << m_oldestSample;
        Sample *sample = new Sample();
        sample->timestamp = m_oldestSample.addSecs(-m_sampleRate);
        m_oldestSample = sample->timestamp;
        m_samples.append(sample);
        m_series->append(sample->timestamp.toMSecsSinceEpoch(), 0);
    }
//    qWarning() << "Ensuring samples:" << from.toString("yyyy-MM-dd hh:mm:ss") << to.toString("yyyy-MM-dd hh:mm:ss") << "Oldest:" << m_oldestSample.toString("yyyy-MM-dd hh:mm:ss") << "Newest:" << m_newestSample.toString("yyyy-MM-dd hh:mm:ss") ;
}

void XYSeriesAdapter::logEntryAdded(LogEntry *entry)
{
    if (!m_series) {
        return;
    }

    ensureSamples(entry->timestamp(), entry->timestamp());

    int idx = entry->timestamp().secsTo(m_newestSample) / m_sampleRate;
    if (idx > m_samples.count()) {
        qCWarning(dcLogEngine) << objectName() << "Overflowing integer size for XYSeriesAdapter!";
        return;
    }
//    qWarning() << objectName() << "Inserting sample at:" << idx << entry->timestamp().toString("yyyy-MM-dd hh:mm:ss");
    Sample *sample = m_samples.at(idx);
    LogEntry *oldLast = nullptr;
    // In theory we'd need to insert sorted, but only the last one actually matters for subsequent samples
    // For the current sample we're calculating the median anyways, so just append/prepend t be a bit faster
    if (sample->entries.count() > 0 && sample->entries.last()->timestamp() < entry->timestamp()) {
        oldLast = sample->entries.last();
        sample->entries.append(entry);
    } else {
        sample->entries.prepend(entry);
    }
    LogEntry *newLast = sample->entries.last();

    qreal value = calculateSampleValue(idx);
    m_series->replace(idx, sample->timestamp.toMSecsSinceEpoch(), value);
//    qWarning() << "sample value updated" << idx << sample->timestamp.toString("yyyy-MM-dd hh:mm:ss") << value;

    if (value < m_minValue) {
        m_minValue = value;
//        qDebug() << "New min:" << m_minValue;
        emit minValueChanged();
    }
    if (value > m_maxValue) {
        m_maxValue = value;
//        qDebug() << "New max:" << m_maxValue;
        emit maxValueChanged();
    }

    // check if we need to update more samples
    for (int i = idx - 1; i >= 0; i--) {
        Sample *nextSample = m_samples.at(i);
        if (nextSample->startingPoint == oldLast) {
            nextSample->startingPoint = newLast;
            qreal value = calculateSampleValue(i);
//            qWarning() << "Updating" << i << value;
            m_series->replace(i, nextSample->timestamp.toMSecsSinceEpoch(), value);

        } else {
            break;
        }
    }
}

qreal XYSeriesAdapter::calculateSampleValue(int index)
{
    Sample *sample = m_samples.at(index);
    qreal value = 0;
    int count = 0;
    if (sample->startingPoint) {
        value = sample->startingPoint->value().toDouble();
        count++;
    }

    foreach (LogEntry *entry, sample->entries) {
        value += entry->value().toDouble();
        count++;
    }

    if (count > 1) {
        value /= count;
    }

    if (m_baseSeries && m_baseSeries->count() > index) {
        value += m_baseSeries->at(index).y();
    }

    if (m_inverted) {
        value *= -1;
    }

    return value;
}
