//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/File/DatafileItem.cpp
//! @brief     Implements class DatafileItem.
//!
//! @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/File/DatafileItem.h"
#include "Base/Axis/Frame.h"
#include "Base/Axis/Scale.h"
#include "Device/Data/DataUtil.h"
#include "Device/Data/Datafield.h"
#include "GUI/Model/Axis/AmplitudeAxisItem.h"
#include "GUI/Model/Data/Data1DItem.h"
#include "GUI/Model/Data/Data2DItem.h"
#include "GUI/Model/Util/Backup.h"
#include "GUI/Model/Util/Path.h"
#include "GUI/Model/Util/UtilXML.h"

namespace {
namespace Tag {

const QString BinaryData("BinaryData");
const QString Data("Data");
const QString Name("Name");
const QString NativeData("NativeData");           // obsolete since v22.0
const QString NativeDataUnits("NativeDataUnits"); // obsolete since v22.0

} // namespace Tag

std::unique_ptr<DataItem> createDataItem(size_t rank)
{
    if (rank == 1)
        return std::make_unique<Data1DItem>();
    return std::make_unique<Data2DItem>();
}

} // namespace


DatafileItem::DatafileItem() = default;

DatafileItem::DatafileItem(const QString& name, const Datafield& df)
    : NamedItem(name)
    , m_data_item(::createDataItem(df.rank()))
{
    updateFileName();
    m_data_item->setDatafield(df);
}

DatafileItem::~DatafileItem() = default;

DatafileItem* DatafileItem::clone() const
{
    DatafileItem* result = new DatafileItem;
    GUI::Util::copyContents(this, result);
    if (m_data_item)
        result->m_data_item->setDatafield(*m_data_item->c_field());
    return result;
}

void DatafileItem::setDatafileItemName(const QString& name)
{
    NamedItem::setName(name);
    updateFileName();
}

DataItem* DatafileItem::dataItem() const
{
    return m_data_item.get();
}

Data2DItem* DatafileItem::data2DItem() const
{
    return dynamic_cast<Data2DItem*>(dataItem());
}

Data1DItem* DatafileItem::data1DItem() const
{
    return dynamic_cast<Data1DItem*>(dataItem());
}

size_t DatafileItem::rank() const
{
    return m_data_item->rank();
}

bool DatafileItem::holdsDimensionalData() const
{
    const Frame& frame = m_data_item->c_field()->frame();
    for (size_t k = 0; k < frame.rank(); k++)
        if (frame.axis(k).unit() != "bin")
            return true;

    return false;
}

void DatafileItem::rotateData()
{
    if (rank() == 1) // rotation only for 2D items possible
        return;

    // -- now rotate data
    const Datafield& input = *data2DItem()->c_field();
    const Datafield output = DataUtil::rotatedDatafield(input, 1);

    // upd AxesItems
    data2DItem()->axItemX()->resize(output.xAxis().size());
    data2DItem()->axItemY()->resize(output.yAxis().size());

    // apply rotated data
    data2DItem()->setDatafield(output);
    data2DItem()->setAxesRangeToData();
}

//! Updates the name of file to store intensity data.

void DatafileItem::updateFileName()
{
    if (DataItem* item = dataItem())
        item->setFileName(GUI::Path::intensityDataFileName(name(), "realdata"));
}

void DatafileItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(2));

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

    // data
    if (m_data_item) {
        w->writeStartElement(Tag::Data);
        XML::writeAttribute(w, XML::Attrib::type, m_data_item->TYPE);
        m_data_item->writeTo(w);
        w->writeEndElement();
    }
}

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

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

        // name
        if (tag == Tag::Name) {
            setName(XML::readAttributeString(r, XML::Attrib::value));
            XML::gotoEndElementOfTag(r, tag);

            // data
        } else if (tag == Tag::Data) {
            QString type;
            XML::readAttribute(r, XML::Attrib::type, &type);
            ASSERT(!m_data_item);
            if (type == Data1DItem::M_TYPE)
                m_data_item = std::make_unique<Data1DItem>();
            else if (type == Data2DItem::M_TYPE)
                m_data_item = std::make_unique<Data2DItem>();
            else
                ASSERT_NEVER;
            m_data_item->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

void DatafileItem::saveDatafield(const QString& projectDir) const
{
    if (m_data_item)
        m_data_item->saveDatafield(projectDir);
}

void DatafileItem::loadDatafield(const QString& projectDir)
{
    if (m_data_item)
        m_data_item->loadDatafield(projectDir, rank());
}

std::vector<int> DatafileItem::axdims() const
{
    ASSERT(dataItem());
    return m_data_item->axdims();
}
