//  ************************************************************************************************
//
//  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"); // obsolete since v22.0
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
{
    auto* 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();
}

size_t DatafileItem::axdim(int i) const
{
    ASSERT(dataItem());
    return m_data_item->axdim(i);
}

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::writeTaggedValue(w, Tag::Name, name());

    // 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)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::Name)
            setName(XML::readTaggedString(r, tag));
        else if (tag == Tag::Data) {
            QString type;
            type = XML::readString(r, XML::Attrib::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;
            XML::readTaggedElement(r, tag, *m_data_item);

        } 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());
}
