//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Job/ParameterTreeItems.cpp
//! @brief     Implements classes for ParameterTreeItems
//!
//! @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/Job/ParameterTreeItems.h"
#include "Base/Util/Assert.h"
#include "GUI/Support/XML/UtilXML.h"
namespace {
namespace Tag {

const QString BackupValues("BackupValues");
const QString BackupValue("BackupValue");

} // namespace Tag

ParameterItem* findParameterItem(QObject* item, const QString& link)
{
    ASSERT(item);
    if (auto* parameter = dynamic_cast<ParameterItem*>(item))
        if (parameter->link() == link)
            return parameter;

    for (auto* child : item->children())
        if (auto* p = findParameterItem(child, link))
            return p;

    return nullptr;
}

} // namespace


ParameterLabelItem::ParameterLabelItem(QObject* parent)
    : QObject(parent)
{
}

ParameterLabelItem::ParameterLabelItem(const QString& title, QObject* parent)
    : QObject(parent)
    , m_title(title)
{
}

QString ParameterLabelItem::title() const
{
    return m_title;
}

void ParameterLabelItem::setTitle(const QString& title)
{
    m_title = title;
}

ParameterItem::ParameterItem(QObject* parent)
    : QObject(parent)
{
}

// ----------------------------------------------------------------------------


QString ParameterItem::title() const
{
    return m_title;
}

void ParameterItem::setTitle(const QString& title)
{
    m_title = title;
}

double ParameterItem::valueOfLink() const
{
    return getPropertyValue();
}

//! Sets current value to the original PropertyItem of SampleItem/InstrumentItem.

void ParameterItem::propagateValueToLink(double newValue)
{
    setPropertyValue(newValue);
}

void ParameterItem::linkToProperty(DoubleProperty& d)
{
    getPropertyValue = [&d]() { return d.value(); };
    setPropertyValue = [&d](double v) { d.setValue(v); };
    m_d = d; // make a copy
}

RealLimits ParameterItem::limitsOfLink() const
{
    return m_d.limits();
}

int ParameterItem::decimalsOfLink() const
{
    return m_d.decimals();
}

QString ParameterItem::link() const
{
    return m_d.uid();
}

QString ParameterItem::titleForFitItem() const
{
    QString result = m_title;

    auto* p = parent();
    while (p) {
        if (const auto* pLabel = dynamic_cast<ParameterLabelItem*>(p))
            result.push_front(pLabel->title() + "/");
        p = p->parent();
    }

    return result;
}

// ----------------------------------------------------------------------------

ParameterContainerItem::ParameterContainerItem()
{
    m_parameterTreeRoot.reset(new QObject());
}

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

    // backup values
    w->writeStartElement(Tag::BackupValues);
    for (auto v = m_backupValues.cbegin(); v != m_backupValues.cend(); v++) {
        w->writeEmptyElement(Tag::BackupValue);
        XML::writeAttribute(w, XML::Attrib::id, v.key());
        XML::writeAttribute(w, XML::Attrib::value, v.value());
    }
    w->writeEndElement();
}

void ParameterContainerItem::readFrom(QXmlStreamReader* r)
{
    m_backupValues.clear();

    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::BackupValues) {
            while (r->readNextStartElement()) {
                QString backupTag = r->name().toString();

                if (backupTag == Tag::BackupValue) {
                    QString link;
                    double d;
                    XML::readAttribute(r, XML::Attrib::id, &link);
                    XML::readAttribute(r, XML::Attrib::value, &d);
                    m_backupValues[link] = d;
                    XML::gotoEndElementOfTag(r, backupTag);

                } else
                    r->skipCurrentElement();
            }
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

void ParameterContainerItem::setBackupValue(const QString& link, double d)
{
    m_backupValues[link] = d;
}

void ParameterContainerItem::restoreBackupValue(QObject* item)
{
    ASSERT(item);
    if (auto* parameter = dynamic_cast<ParameterItem*>(item))
        if (m_backupValues.contains(parameter->link()))
            parameter->propagateValueToLink(m_backupValues[parameter->link()]);

    for (auto* child : item->children())
        restoreBackupValue(child);
}

void ParameterContainerItem::restoreBackupValues()
{
    for (auto* child : m_parameterTreeRoot->children())
        restoreBackupValue(child);
}

ParameterItem* ParameterContainerItem::findParameterItem(const QString& link) const
{
    return ::findParameterItem(m_parameterTreeRoot.get(), link);
}

QObject* ParameterContainerItem::parameterTreeRoot()
{
    return m_parameterTreeRoot.get();
}
