//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Material/MaterialItem.cpp
//! @brief     Implements class MaterialItem.
//!
//! @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/Material/MaterialItem.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Util/UtilXML.h"
#include "Sample/Material/MaterialFactoryFuncs.h"
#include <QUuid>

namespace {
namespace Tag {

const QString Color("Color");
const QString Id("Id");
const QString Name("Name");
const QString Delta("Delta");
const QString Beta("Beta");
const QString SldRe("SldRe");
const QString SldIm("SldIm");
const QString UseRefractiveIndex("UseRefractiveIndex");
const QString Magnetization("Magnetization");

} // namespace Tag

} // namespace


MaterialItem::MaterialItem()
    : m_color(Qt::red)
{
    m_id = QUuid::createUuid().toString();

    m_delta.init("Delta", "Delta of refractive index (n = 1 - delta + i*beta)", 0.0, 3,
                 RealLimits::limitless(), "delta");
    m_beta.init("Beta", "Beta of refractive index (n = 1 - delta + i*beta)", 0.0, 3,
                RealLimits::limitless(), "beta");

    m_sld_re.init("SLD, real", "Real part SLD (SLD = real - i*imag) (Å^-2)", 0.0, 3,
                  RealLimits::limitless(), "sldRe");
    m_sld_im.init("SLD, imaginary", "Imaginary part of SLD (SLD = real - i*imag) (Å^-2), ", 0.0, 3,
                  RealLimits::limitless(), "sldIm");

    m_magnetization.init("Magnetization", "Magnetization (A/m)", "magnetization");
}

void MaterialItem::setRefractiveIndex(const double delta, const double beta)
{
    if (hasRefractiveIndex() && m_delta.value() == delta && m_beta.value() == beta)
        return;

    m_use_refractive_index = true;

    m_delta.setValue(delta);
    m_beta.setValue(beta);
    emit dataChanged();
}

void MaterialItem::setScatteringLengthDensity(const complex_t sld)
{
    if (!hasRefractiveIndex() && m_sld_re.value() == sld.real() && m_sld_im.value() == sld.imag())
        return;

    m_use_refractive_index = false;

    m_sld_re.setValue(sld.real());
    m_sld_im.setValue(sld.imag());
    emit dataChanged();
}
const DoubleProperty& MaterialItem::delta() const
{
    return m_delta;
}
const DoubleProperty& MaterialItem::beta() const
{
    return m_beta;
}
const DoubleProperty& MaterialItem::sldRe() const
{
    return m_sld_re;
}
const DoubleProperty& MaterialItem::sldIm() const
{
    return m_sld_im;
}
void MaterialItem::setMatItemName(const QString& name)
{
    if (m_name != name) {
        m_name = name;
        emit dataChanged();
    }
}
void MaterialItem::createNewIdentifier()
{
    m_id = QUuid::createUuid().toString();
}
void MaterialItem::setColor(const QColor& color)
{
    if (m_color != color) {
        m_color = color;
        emit dataChanged();
    }
}


void MaterialItem::setMagnetization(const R3& magnetization)
{
    if (m_magnetization.r3() != magnetization) {
        m_magnetization.setR3(magnetization);
        emit dataChanged();
    }
}

std::unique_ptr<Material> MaterialItem::createMaterial() const
{
    if (hasRefractiveIndex())
        return std::make_unique<Material>(RefractiveMaterial(
            matItemName().toStdString(), m_delta.value(), m_beta.value(), m_magnetization));

    return std::make_unique<Material>(MaterialBySLD(matItemName().toStdString(), m_sld_re.value(),
                                                    m_sld_im.value(), m_magnetization));
}

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

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

    // id
    w->writeStartElement(Tag::Id);
    XML::writeAttribute(w, XML::Attrib::value, m_id);
    w->writeEndElement();

    // color
    w->writeStartElement(Tag::Color);
    XML::writeAttribute(w, XML::Attrib::value, m_color);
    w->writeEndElement();

    // magnetization
    w->writeStartElement(Tag::Magnetization);
    m_magnetization.writeTo(w);
    w->writeEndElement();

    // use refractive index?
    w->writeStartElement(Tag::UseRefractiveIndex);
    XML::writeAttribute(w, XML::Attrib::value, m_use_refractive_index);
    w->writeEndElement();

    if (m_use_refractive_index) {
        // delta
        w->writeStartElement(Tag::Delta);
        m_delta.writeTo(w);
        w->writeEndElement();

        // beta
        w->writeStartElement(Tag::Beta);
        m_beta.writeTo(w);
        w->writeEndElement();
    } else {
        // sld real
        w->writeStartElement(Tag::SldRe);
        m_sld_re.writeTo(w);
        w->writeEndElement();

        // sld imaginary
        w->writeStartElement(Tag::SldIm);
        m_sld_im.writeTo(w);
        w->writeEndElement();
    }
}

void MaterialItem::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) {
            XML::readAttribute(r, XML::Attrib::value, &m_name);
            XML::gotoEndElementOfTag(r, tag);

            // id
        } else if (tag == Tag::Id) {
            XML::readAttribute(r, XML::Attrib::value, &m_id);
            XML::gotoEndElementOfTag(r, tag);

            // color
        } else if (tag == Tag::Color) {
            XML::readAttribute(r, XML::Attrib::value, &m_color);
            XML::gotoEndElementOfTag(r, tag);

            // magnetization
        } else if (tag == Tag::Magnetization) {
            m_magnetization.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // use refractive index?
        } else if (tag == Tag::UseRefractiveIndex) {
            XML::readAttribute(r, XML::Attrib::value, &m_use_refractive_index);
            XML::gotoEndElementOfTag(r, tag);

            // delta
        } else if (tag == Tag::Delta && m_use_refractive_index) {
            m_delta.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // beta
        } else if (tag == Tag::Beta && m_use_refractive_index) {
            m_beta.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // sld real
        } else if (tag == Tag::SldRe && !m_use_refractive_index) {
            m_sld_re.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // sld imaginary
        } else if (tag == Tag::SldIm && !m_use_refractive_index) {
            m_sld_im.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

void MaterialItem::updateFrom(const MaterialItem& other)
{
    if (*this == other)
        return;
    m_name = other.m_name;
    m_color = other.m_color;
    m_magnetization.setR3(other.m_magnetization.r3());

    m_use_refractive_index = other.m_use_refractive_index;

    m_delta.setValue(other.m_delta.value());
    m_beta.setValue(other.m_beta.value());
    m_sld_re.setValue(other.m_sld_re.value());
    m_sld_im.setValue(other.m_sld_im.value());

    emit dataChanged();
}

bool MaterialItem::operator==(const MaterialItem& other) const
{
    if (m_use_refractive_index != other.m_use_refractive_index)
        return false;

    if (hasRefractiveIndex()) {
        if ((m_delta != other.m_delta) || (m_beta != other.m_beta))
            return false;
    } else if ((m_sld_re != other.m_sld_re) || (m_sld_im != other.m_sld_im))
        return false;


    return (m_id == other.m_id) && (m_name == other.m_name) && (m_color == other.m_color)
           && (m_magnetization == other.m_magnetization);
}
