//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Sample/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/Sample/MaterialItem.h"
#include "Base/Util/Assert.h"
#include "GUI/Support/XML/DeserializationException.h"
#include "GUI/Support/XML/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, Unit::unitless,
                 3, RealLimits::limitless(), "delta");
    m_beta.init("Beta", "Beta of refractive index (n = 1 - delta + i*beta)", 0.0, Unit::unitless, 3,
                RealLimits::limitless(), "beta");

    Unit sld_units = Unit::angstromMinus2;
    QString sld_units_str = unitAsString(sld_units);
    m_sldRe.init("SLD, real", "Real part of SLD (SLD = real - i*imag), " + sld_units_str, 0.0,
                 sld_units, 3, RealLimits::limitless(), "sldRe");
    m_sldIm.init("SLD, imaginary", "Imaginary part of SLD (SLD = real - i*imag), " + sld_units_str,
                 0.0, sld_units, 3, RealLimits::limitless(), "sldIm");

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

MaterialItem::MaterialItem(const MaterialItem& other)
    : QObject()
    , m_name(other.m_name)
    , m_id(other.m_id)
    , m_color(other.m_color)
    , m_magnetization(other.m_magnetization)
    , m_useRefractiveIndex(other.m_useRefractiveIndex)
    , m_delta(other.m_delta)
    , m_beta(other.m_beta)
    , m_sldRe(other.m_sldRe)
    , m_sldIm(other.m_sldIm)
{
}

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

    m_useRefractiveIndex = true;

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

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

    m_useRefractiveIndex = false;

    m_sldRe.setValue(sld.real());
    m_sldIm.setValue(sld.imag());
    emit dataChanged();
}

DoubleProperty& MaterialItem::delta()
{
    ASSERT(hasRefractiveIndex());
    return m_delta;
}

const DoubleProperty& MaterialItem::delta() const
{
    ASSERT(hasRefractiveIndex());
    return m_delta;
}

DoubleProperty& MaterialItem::beta()
{
    ASSERT(hasRefractiveIndex());
    return m_beta;
}

const DoubleProperty& MaterialItem::beta() const
{
    ASSERT(hasRefractiveIndex());
    return m_beta;
}

DoubleProperty& MaterialItem::sldRe()
{
    ASSERT(!hasRefractiveIndex());
    return m_sldRe;
}

const DoubleProperty& MaterialItem::sldRe() const
{
    ASSERT(!hasRefractiveIndex());
    return m_sldRe;
}

DoubleProperty& MaterialItem::sldIm()
{
    ASSERT(!hasRefractiveIndex());
    return m_sldIm;
}

const DoubleProperty& MaterialItem::sldIm() const
{
    ASSERT(!hasRefractiveIndex());
    return m_sldIm;
}


bool MaterialItem::hasRefractiveIndex() const
{
    return m_useRefractiveIndex;
}

QString MaterialItem::matItemName() const
{
    return m_name;
}

void MaterialItem::setMatItemName(const QString& name)
{
    if (m_name != name) {
        m_name = name;
        emit dataChanged();
    }
}

QString MaterialItem::identifier() const
{
    return m_id;
}

void MaterialItem::createNewIdentifier()
{
    m_id = QUuid::createUuid().toString();
}

QColor MaterialItem::color() const
{
    return m_color;
}

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_sldRe.value(),
                                                    m_sldIm.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_useRefractiveIndex);
    w->writeEndElement();

    if (m_useRefractiveIndex) {
        // 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_sldRe.writeTo(w);
        w->writeEndElement();

        // sld imaginary
        w->writeStartElement(Tag::SldIm);
        m_sldIm.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_useRefractiveIndex);
            XML::gotoEndElementOfTag(r, tag);

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

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

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

            // sld imaginary
        } else if (tag == Tag::SldIm && !m_useRefractiveIndex) {
            m_sldIm.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 = other.m_magnetization;

    m_useRefractiveIndex = other.m_useRefractiveIndex;

    m_delta = other.m_delta;
    m_beta = other.m_beta;
    m_sldRe = other.m_sldRe;
    m_sldIm = other.m_sldIm;

    emit dataChanged();
}

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

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


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