/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2006 Chris Cannam and QMUL.
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#include "EditableDenseThreeDimensionalModel.h"

#include "base/LogRange.h"

#include <QTextStream>
#include <QStringList>
#include <QMutexLocker>

#include <iostream>

#include <cmath>
#include <cassert>

namespace sv {

using std::vector;

#include "system/System.h"

EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate,
                                                                       int resolution,
                                                                       int yBinCount,
                                                                       bool notifyOnAdd) :
    m_startFrame(0),
    m_sampleRate(sampleRate),
    m_resolution(resolution),
    m_yBinCount(yBinCount),
    m_minimum(0.0),
    m_maximum(0.0),
    m_haveExtents(false),
    m_notifyOnAdd(notifyOnAdd),
    m_sinceLastNotifyMin(-1),
    m_sinceLastNotifyMax(-1),
    m_completion(100)
{
}    

bool
EditableDenseThreeDimensionalModel::isOK() const
{
    return true;
}

bool
EditableDenseThreeDimensionalModel::isReady(int *completion) const
{
    if (completion) *completion = getCompletion();
    return true;
}

sv_samplerate_t
EditableDenseThreeDimensionalModel::getSampleRate() const
{
    return m_sampleRate;
}

sv_frame_t
EditableDenseThreeDimensionalModel::getStartFrame() const
{
    return m_startFrame;
}

void
EditableDenseThreeDimensionalModel::setStartFrame(sv_frame_t f)
{
    QMutexLocker locker(&m_mutex);
    m_startFrame = f; 
}

sv_frame_t
EditableDenseThreeDimensionalModel::getTrueEndFrame() const
{
    return m_resolution * m_data.size() + (m_resolution - 1);
}

int
EditableDenseThreeDimensionalModel::getResolution() const
{
    return m_resolution;
}

void
EditableDenseThreeDimensionalModel::setResolution(int sz)
{
    QMutexLocker locker(&m_mutex);
    m_resolution = sz;
}

int
EditableDenseThreeDimensionalModel::getWidth() const
{
    return int(m_data.size());
}

int
EditableDenseThreeDimensionalModel::getHeight() const
{
    return m_yBinCount;
}

void
EditableDenseThreeDimensionalModel::setHeight(int sz)
{
    QMutexLocker locker(&m_mutex);
    m_yBinCount = sz;
}

float
EditableDenseThreeDimensionalModel::getMinimumLevel() const
{
    return m_minimum;
}

void
EditableDenseThreeDimensionalModel::setMinimumLevel(float level)
{
    QMutexLocker locker(&m_mutex);
    m_minimum = level;
}

float
EditableDenseThreeDimensionalModel::getMaximumLevel() const
{
    return m_maximum;
}

void
EditableDenseThreeDimensionalModel::setMaximumLevel(float level)
{
    QMutexLocker locker(&m_mutex);
    m_maximum = level;
}

EditableDenseThreeDimensionalModel::Column
EditableDenseThreeDimensionalModel::getColumn(int index) const
{
    QMutexLocker locker(&m_mutex);
    if (!in_range_for(m_data, index)) {
        return {};
    }
    Column c = m_data.at(index);
    if (int(c.size()) == m_yBinCount) {
        return c;
    } else {
        Column cc(c);
        cc.resize(m_yBinCount, 0.0);
        return cc;
    }
}

EditableDenseThreeDimensionalModel::Column
EditableDenseThreeDimensionalModel::getColumn(int index, int minbin, int nbins) const
{
    QMutexLocker locker(&m_mutex);
    if (!in_range_for(m_data, index)) {
        return {};
    }
    const Column &c = m_data.at(index);
    if (int(c.size()) == nbins && minbin == 0) {
        return c;
    } else {
        Column cc(nbins, 0.0);
        for (int i = 0; i < nbins; ++i) {
            if (!in_range_for(c, i + minbin)) break;
            cc[i] = c[i + minbin];
        }
        return cc;
    }
}

float
EditableDenseThreeDimensionalModel::getValueAt(int index, int n) const
{
    QMutexLocker locker(&m_mutex);
    if (!in_range_for(m_data, index)) {
        return m_minimum;
    }
    const Column &c = m_data.at(index);
    if (!in_range_for(c, n)) {
        return m_minimum;
    }
    return c.at(n);
}

QString
EditableDenseThreeDimensionalModel::getValueUnit() const
{
    return m_unit;
}

void
EditableDenseThreeDimensionalModel::setValueUnit(QString unit)
{
    m_unit = unit;
}

void
EditableDenseThreeDimensionalModel::setColumn(int index,
                                              const Column &values)
{
    bool allChange = false;
    sv_frame_t windowStart = index;
    windowStart *= m_resolution;

    {
        QMutexLocker locker(&m_mutex);

        while (index >= int(m_data.size())) {
            m_data.push_back(Column());
        }

        for (int i = 0; in_range_for(values, i); ++i) {
            float value = values[i];
            if (std::isnan(value) || std::isinf(value)) {
                continue;
            }
            if (!m_haveExtents || value < m_minimum) {
                m_minimum = value;
                allChange = true;
            }
            if (!m_haveExtents || value > m_maximum) {
                m_maximum = value;
                allChange = true;
            }
            m_haveExtents = true;
        }

        m_data[index] = values;

        if (allChange) {
            m_sinceLastNotifyMin = -1;
            m_sinceLastNotifyMax = -1;
        } else {
            if (m_sinceLastNotifyMin == -1 ||
                windowStart < m_sinceLastNotifyMin) {
                m_sinceLastNotifyMin = windowStart;
            }
            if (m_sinceLastNotifyMax == -1 ||
                windowStart > m_sinceLastNotifyMax) {
                m_sinceLastNotifyMax = windowStart;
            }
        }
    }

    if (m_notifyOnAdd) {
        if (allChange) {
            emit modelChanged(getId());
        } else {
            emit modelChangedWithin(getId(),
                                    windowStart, windowStart + m_resolution);
        }
    } else {
        if (allChange) {
            emit modelChanged(getId());
        }
    }
}

QString
EditableDenseThreeDimensionalModel::getBinName(int n) const
{
    QMutexLocker locker(&m_mutex);
    if (n >= 0 && (int)m_binNames.size() > n) return m_binNames[n];
    else return "";
}

void
EditableDenseThreeDimensionalModel::setBinName(int n, QString name)
{
    {
        QMutexLocker locker(&m_mutex);
        while ((int)m_binNames.size() <= n) m_binNames.push_back("");
        m_binNames[n] = name;
    }
    emit modelChanged(getId());
}

void
EditableDenseThreeDimensionalModel::setBinNames(std::vector<QString> names)
{
    {
        QMutexLocker locker(&m_mutex);
        m_binNames = names;
    }
    emit modelChanged(getId());
}

bool
EditableDenseThreeDimensionalModel::hasBinValues() const
{
    QMutexLocker locker(&m_mutex);
    return !m_binValues.empty();
}

float
EditableDenseThreeDimensionalModel::getBinValue(int n) const
{
    QMutexLocker locker(&m_mutex);
    if (n < (int)m_binValues.size()) return m_binValues[n];
    else return 0.f;
}

void
EditableDenseThreeDimensionalModel::setBinValues(std::vector<float> values)
{
    QMutexLocker locker(&m_mutex);
    m_binValues = values;
}

QString
EditableDenseThreeDimensionalModel::getBinValueUnit() const
{
    QMutexLocker locker(&m_mutex);
    return m_binValueUnit;
}

void
EditableDenseThreeDimensionalModel::setBinValueUnit(QString unit)
{
    QMutexLocker locker(&m_mutex);
    m_binValueUnit = unit;
}

bool
EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const
{
    QMutexLocker locker(&m_mutex);

    vector<double> sample;
    vector<int> n;
    
    for (int i = 0; i < 10; ++i) {
        int index = i * 10;
        if (in_range_for(m_data, index)) {
            const Column &c = m_data.at(index);
            while (c.size() > sample.size()) {
                sample.push_back(0.0);
                n.push_back(0);
            }
            for (int j = 0; in_range_for(c, j); ++j) {
                sample[j] += c.at(j);
                ++n[j];
            }
        }
    }

    if (sample.empty()) return false;
    for (decltype(sample)::size_type j = 0; j < sample.size(); ++j) {
        if (n[j]) sample[j] /= n[j];
    }
    
    return LogRange::shouldUseLogScale(sample);
}

void
EditableDenseThreeDimensionalModel::setCompletion(int completion, bool update)
{
    int prevCompletion = m_completion;
    
    if (prevCompletion != completion) {
        m_completion = completion;

        if (completion == 100) {

            m_notifyOnAdd = true; // henceforth
            emit modelChanged(getId());
            emit ready(getId());

        } else if (!m_notifyOnAdd) {

            if (update &&
                m_sinceLastNotifyMin >= 0 &&
                m_sinceLastNotifyMax >= 0) {
                emit modelChangedWithin(getId(),
                                        m_sinceLastNotifyMin,
                                        m_sinceLastNotifyMax + m_resolution);
                m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
            } else {
                emit completionChanged(getId());
            }
        } else {
            emit completionChanged(getId());
        }            
    }
}

int
EditableDenseThreeDimensionalModel::getCompletion() const
{
    return m_completion;
}

QVector<QString>
EditableDenseThreeDimensionalModel::getStringExportHeaders(DataExportOptions)
    const
{
    QMutexLocker locker(&m_mutex);
    QVector<QString> sv;
    for (int i = 0; i < m_yBinCount; ++i) {
        sv.push_back(QString("Bin%1").arg(i+1));
    }
    return sv;
}    
    
QVector<QVector<QString>>
EditableDenseThreeDimensionalModel::toStringExportRows(DataExportOptions,
                                                       sv_frame_t startFrame,
                                                       sv_frame_t duration)
    const
{
    QMutexLocker locker(&m_mutex);

    QVector<QVector<QString>> rows;

    for (int i = 0; in_range_for(m_data, i); ++i) {
        sv_frame_t fr = m_startFrame + i * m_resolution;
        if (fr >= startFrame && fr < startFrame + duration) {
            QVector<QString> row;
            for (int j = 0; in_range_for(m_data.at(i), j); ++j) {
                row.push_back(QString("%1").arg(m_data.at(i).at(j)));
            }
            rows.push_back(row);
        }
    }
    return rows;
}

void
EditableDenseThreeDimensionalModel::toXml(QTextStream &out,
                                          QString indent,
                                          QString extraAttributes) const
{
    QMutexLocker locker(&m_mutex);

    // For historical reasons we read and write "resolution" as "windowSize".

    // Our dataset doesn't have its own export ID, we just use
    // ours. Actually any model could do that, since datasets aren't
    // in the same id-space as models when re-read

    SVDEBUG << "EditableDenseThreeDimensionalModel::toXml" << endl;

    Model::toXml
        (out, indent,
         QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" startFrame=\"%6\" %7")
         .arg(m_resolution.load())
         .arg(m_yBinCount.load())
         .arg(m_minimum.load())
         .arg(m_maximum.load())
         .arg(getExportId())
         .arg(m_startFrame)
         .arg(extraAttributes));

    out << indent;
    out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n")
        .arg(getExportId());

    for (int i = 0; in_range_for(m_binNames, i); ++i) {
        if (m_binNames[i] != "") {
            out << indent + "  ";
            out << QString("<bin number=\"%1\" name=\"%2\"/>\n")
                .arg(i).arg(m_binNames[i]);
        }
    }

    for (int i = 0; in_range_for(m_data, i); ++i) {
        Column c = getColumn(i);
        out << indent + "  ";
        out << QString("<row n=\"%1\">").arg(i);
        for (int j = 0; in_range_for(c, j); ++j) {
            if (j > 0) out << " ";
            out << c.at(j);
        }
        out << QString("</row>\n");
        out.flush();
    }

    out << indent + "</dataset>\n";
}


} // end namespace sv

