//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Data/DataItem.cpp
//! @brief     Implements class Data2DItem.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/Model/Data/DataItem.h"
#include "Base/Axis/Scale.h"
#include "Base/Util/Assert.h"
#include "Device/Data/Datafield.h"
#include "Device/IO/IOFactory.h"
#include "GUI/Model/Axis/AmplitudeAxisItem.h"
#include "GUI/Model/Axis/BasicAxisItem.h"
#include "GUI/Model/Util/UtilXML.h"
#include <QFile>
#include <utility>

namespace {
namespace Tag {

const QString FileName("FileName");
const QString AxesUnits("AxesUnits"); // obsolete since 22.0
const QString XAxis("XAxis");
const QString YAxis("YAxis");

} // namespace Tag
} // namespace


DataItem::DataItem(QString modelType)
    : TYPE(std::move(modelType))
    , m_fname("undefined")
    , m_x_axis(std::make_unique<BasicAxisItem>())
    , m_y_axis(std::make_unique<AmplitudeAxisItem>())
    , m_last_modified(QDateTime::currentDateTime())
    , m_last_saved(m_last_modified.addSecs(-2023))
{
}

DataItem::~DataItem() = default;

void DataItem::setOriginalDatafield(const Datafield& data)
{
    std::unique_lock<std::mutex> lock(m_update_data_mutex);
    m_datafield = std::make_unique<Datafield>(data);
    setLastModified(QDateTime::currentDateTime());
    emit datafieldChanged();
}

void DataItem::setDatafield(const Datafield& data)
{
    setOriginalDatafield(data.plottableField());
}

void DataItem::setRawDataVector(const std::vector<double>& data)
{
    ASSERT(m_datafield->size() == data.size());
    std::unique_lock<std::mutex> lock(m_update_data_mutex);
    m_datafield->setVector(data);

    setLastModified(QDateTime::currentDateTime());
    emit rawDataVectorChanged(data);
}

QString DataItem::dataFullPath(const QString& projectDir) const
{
    return projectDir + "/" + m_fname;
}

void DataItem::setFileName(const QString& filename)
{
    m_fname = filename;
    setLastModified(QDateTime::currentDateTime());
    emit fnameChanged(filename);
}

void DataItem::loadDatafield(const QString& projectDir, int rank)
{
    if (!QFile::exists(projectDir))
        throw std::runtime_error("Cannot load datafield: project directory "
                                 + projectDir.toStdString() + " does not exist");
    const auto file = dataFullPath(projectDir);

    std::unique_ptr<Datafield> data;
    if (rank == 1)
        data = std::make_unique<Datafield>(
            IO::readData1D(file.toStdString(), IO::Filetype1D::bornagain1D));
    else if (rank == 2)
        data = std::make_unique<Datafield>(
            IO::readData2D(file.toStdString(), IO::Filetype2D::bornagain2D));
    else
        ASSERT_NEVER;

    ASSERT(data);
    setDatafield(*data);
    m_last_saved = m_last_modified;
}

void DataItem::saveDatafield(const QString& projectDir) const
{
    if (!m_datafield || !QFile::exists(projectDir))
        return;

    const auto path = dataFullPath(projectDir);

    if (QFile::exists(path) && !wasModifiedSinceLastSave())
        return;

    IO::writeDatafield(*c_field(), path.toStdString());

    m_last_saved = QDateTime::currentDateTime();
}

void DataItem::setLastModified(const QDateTime& dtime)
{
    m_last_modified = dtime;
}

int DataItem::xSize() const
{
    return axItemX()->size();
}

int DataItem::ySize() const
{
    return axItemY()->size();
}

double DataItem::lowerX() const
{
    return axItemX()->min();
}

double DataItem::upperX() const
{
    return axItemX()->max();
}

void DataItem::setXrange(double lower, double upper)
{
    axItemX()->setMin(lower);
    axItemX()->setMax(upper);
    emit itemAxesRangeChanged();
}

double DataItem::lowerY() const
{
    return axItemY()->min();
}

double DataItem::upperY() const
{
    return axItemY()->max();
}

void DataItem::setYrange(double lower, double upper)
{
    axItemY()->setMin(lower);
    axItemY()->setMax(upper);
    emit itemAxesRangeChanged();
}

void DataItem::checkXranges(DataItem* sourceItem) const
{
    if (xSize() != sourceItem->xSize())
        throw std::runtime_error("Data and simulation have different number of x bins");
}

void DataItem::checkYranges(DataItem* sourceItem) const
{
    if (ySize() != sourceItem->ySize())
        throw std::runtime_error("Data and simulation have different number of x bins");
}

void DataItem::checkXYranges(DataItem* sourceItem) const
{
    checkXranges(sourceItem);
    checkYranges(sourceItem);
}

const BasicAxisItem* DataItem::axItemX() const
{
    return m_x_axis.get();
}

BasicAxisItem* DataItem::axItemX()
{
    return m_x_axis.get();
}

const AmplitudeAxisItem* DataItem::axItemY() const
{
    return m_y_axis.get();
}

AmplitudeAxisItem* DataItem::axItemY()
{
    return m_y_axis.get();
}

QString DataItem::xAxisLabel() const
{
    return m_datafield ? QString::fromStdString(m_datafield->xAxis().axisLabel()) : "";
}

QString DataItem::yAxisLabel() const
{
    return m_datafield ? QString::fromStdString(m_datafield->yAxis().axisLabel()) : "";
}

void DataItem::writeBaseTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(3));

    // file name
    w->writeStartElement(Tag::FileName);
    XML::writeAttribute(w, XML::Attrib::value, m_fname);
    w->writeEndElement();

    // x axis
    w->writeStartElement(Tag::XAxis);
    m_x_axis->writeTo(w);
    w->writeEndElement();

    // y axis
    w->writeStartElement(Tag::YAxis);
    m_y_axis->writeTo(w);
    w->writeEndElement();
}

void DataItem::readBaseFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version);

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // file name
        if (tag == Tag::FileName) {
            XML::readAttribute(r, XML::Attrib::value, &m_fname);
            XML::gotoEndElementOfTag(r, tag);

            // x axis
        } else if (tag == Tag::XAxis) {
            m_x_axis->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // y axis
        } else if (tag == Tag::YAxis) {
            m_y_axis->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

bool DataItem::wasModifiedSinceLastSave() const
{
    // positive number means that m_last_saved is older than m_last_modified
    return m_last_saved.msecsTo(m_last_modified) > 0;
}
