//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Sample/InterferenceItems.cpp
//! @brief     Implements class InterferenceItem and subclasses.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2021
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/Model/Sample/InterferenceItems.h"
#include "Base/Const/Units.h"
#include "Sample/Aggregate/Interferences.h"

namespace {
namespace Tag {

const QString PositionVariance("PositionVariance");
const QString Length("Length");
const QString RotationAngle("RotationAngle");
const QString IntegrateOverXi("IntegrateOverXi");
const QString DampingLength("DampingLength");
const QString DomainSize("DomainSize");
const QString DomainSize1("DomainSize1");
const QString DomainSize2("DomainSize2");
const QString Radius("Radius");
const QString Density("Density");
const QString PeakDistance("PeakDistance");
const QString Kappa("Kappa");
const QString DecayFunction("DecayFunction");
const QString LatticeType("LatticeType");
const QString PDF("PDF");
const QString PDF1("PDF1");
const QString PDF2("PDF2");
const QString BaseData("BaseData");

} // namespace Tag
} // namespace

InterferenceItem::InterferenceItem()
{
    m_position_variance.init("PositionVariance (nm^2)",
                             "Variance of the position in each dimension", 0.0, "PositionVariance");
}

void InterferenceItem::writeTo(QXmlStreamWriter* w) const
{
    // position variance
    m_position_variance.writeTo2(w, Tag::PositionVariance);
}

void InterferenceItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::PositionVariance) {
            m_position_variance.readFrom2(r, tag);

        } else
            r->skipCurrentElement();
    }
}

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

Interference1DLatticeItem::Interference1DLatticeItem()
{
    m_length.init("Length (nm)", "Lattice length", 20.0, "Length");
    m_rotation_angle.init(
        "Xi (deg)",
        "Rotation of lattice with respect to x-axis of reference frame (beam direction)", 0.0,
        "xi");
    m_decay_function.simpleInit("Decay Function",
                                "One-dimensional decay function (finite size effects)",
                                Profile1DCatalog::Type::Cauchy);
}

std::unique_ptr<IInterference> Interference1DLatticeItem::createInterference() const
{
    auto result = std::make_unique<Interference1DLattice>(m_length.dVal(),
                                                          Units::deg2rad(m_rotation_angle.dVal()));
    result->setDecayFunction(*m_decay_function.certainItem()->createProfile());
    result->setPositionVariance(m_position_variance.dVal());
    return std::unique_ptr<IInterference>(result.release());
}

void Interference1DLatticeItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<InterferenceItem>(w, XML::Tag::BaseData, this);

    // length
    m_length.writeTo2(w, Tag::Length);

    // rotation angle
    m_rotation_angle.writeTo2(w, Tag::RotationAngle);
    XML::writeTaggedElement(w, Tag::DecayFunction, m_decay_function);
}

void Interference1DLatticeItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<InterferenceItem>(r, tag, this);
        else if (tag == Tag::Length) {
            m_length.readFrom2(r, tag);
        } else if (tag == Tag::RotationAngle) {
            m_rotation_angle.readFrom2(r, tag);
        } else if (tag == Tag::DecayFunction)
            XML::readTaggedElement(r, tag, m_decay_function);
        else
            r->skipCurrentElement();
    }
}

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

void Interference2DAbstractLatticeItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<InterferenceItem>(w, XML::Tag::BaseData, this);
    XML::writeTaggedValue(w, Tag::IntegrateOverXi, m_xi_integration);
    XML::writeTaggedElement(w, Tag::LatticeType, m_lattice_type);
}

void Interference2DAbstractLatticeItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<InterferenceItem>(r, tag, this);
        else if (tag == Tag::IntegrateOverXi)
            m_xi_integration = XML::readTaggedBool(r, tag);
        else if (tag == Tag::LatticeType)
            XML::readTaggedElement(r, tag, m_lattice_type);
        else
            r->skipCurrentElement();
    }
}

Interference2DAbstractLatticeItem::Interference2DAbstractLatticeItem(bool xiIntegration)
    : m_xi_integration(xiIntegration)
{
    m_lattice_type.simpleInit("Lattice type", "", Lattice2DCatalog::Type::Basic);
    m_lattice_type.setCertainItem(new HexagonalLattice2DItem());
}

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

Interference2DLatticeItem::Interference2DLatticeItem()
    : Interference2DAbstractLatticeItem(false)
{
    m_decay_function.simpleInit("Decay Function",
                                "Two-dimensional decay function (finite size effects)",
                                Profile2DCatalog::Type::Cauchy);
}

std::unique_ptr<IInterference> Interference2DLatticeItem::createInterference() const
{
    Lattice2DItem* latticeItem = latticeTypeItem();
    std::unique_ptr<Interference2DLattice> result(
        new Interference2DLattice(*latticeItem->createLattice()));

    result->setDecayFunction(*m_decay_function.certainItem()->createProfile());
    result->setIntegrationOverXi(xiIntegration());
    result->setPositionVariance(m_position_variance.dVal());

    return std::unique_ptr<IInterference>(result.release());
}

void Interference2DLatticeItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<Interference2DAbstractLatticeItem>(w, XML::Tag::BaseData, this);
    XML::writeTaggedElement(w, Tag::DecayFunction, m_decay_function);
}

void Interference2DLatticeItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<Interference2DAbstractLatticeItem>(r, tag, this);
        else if (tag == Tag::DecayFunction)
            XML::readTaggedElement(r, tag, m_decay_function);
        else
            r->skipCurrentElement();
    }
}

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

Interference2DParacrystalItem::Interference2DParacrystalItem()
    : Interference2DAbstractLatticeItem(true)
{
    m_damping_length.init("Damping length (nm)",
                          "The damping (coherence) length of the paracrystal", 0.0, "dampingLen");
    m_domain_size1.init("Domain size 1 (nm)",
                        "Size of the coherent domain along the first basis vector", 20000.0,
                        "size1");
    m_domain_size2.init("Domain size 2 (nm)",
                        "Size of the coherent domain along the second basis vector", 20000.0,
                        "size2");
    m_pdf1.simpleInit("PDF 1", "Probability distribution in first lattice direction",
                      Profile2DCatalog::Type::Cauchy);
    m_pdf2.simpleInit("PDF 2", "Probability distribution in second lattice direction",
                      Profile2DCatalog::Type::Cauchy);
}

std::unique_ptr<IInterference> Interference2DParacrystalItem::createInterference() const
{
    Lattice2DItem* latticeItem = latticeTypeItem();

    std::unique_ptr<Interference2DParacrystal> result(
        new Interference2DParacrystal(*latticeItem->createLattice(), 0, 0, 0));

    result->setDampingLength(m_damping_length.dVal());
    result->setDomainSizes(m_domain_size1.dVal(), m_domain_size2.dVal());
    result->setIntegrationOverXi(xiIntegration());
    result->setProbabilityDistributions(*m_pdf1.certainItem()->createProfile(),
                                        *m_pdf2.certainItem()->createProfile());
    result->setPositionVariance(m_position_variance.dVal());
    return std::unique_ptr<IInterference>(result.release());
}

void Interference2DParacrystalItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<Interference2DAbstractLatticeItem>(w, XML::Tag::BaseData, this);

    // damping length
    m_damping_length.writeTo2(w, Tag::DampingLength);

    // domain size 1
    m_domain_size1.writeTo2(w, Tag::DomainSize1);

    // domain size 2
    m_domain_size2.writeTo2(w, Tag::DomainSize2);
    XML::writeTaggedElement(w, Tag::PDF1, m_pdf1);
    XML::writeTaggedElement(w, Tag::PDF2, m_pdf2);
}

void Interference2DParacrystalItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<Interference2DAbstractLatticeItem>(r, tag, this);
        else if (tag == Tag::DampingLength) {
            m_damping_length.readFrom2(r, tag);
        } else if (tag == Tag::DomainSize1) {
            m_domain_size1.readFrom2(r, tag);
        } else if (tag == Tag::DomainSize2) {
            m_domain_size2.readFrom2(r, tag);
        } else if (tag == Tag::PDF1)
            XML::readTaggedElement(r, tag, m_pdf1);
        else if (tag == Tag::PDF2)
            XML::readTaggedElement(r, tag, m_pdf2);
        else
            r->skipCurrentElement();
    }
}

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

InterferenceFinite2DLatticeItem::InterferenceFinite2DLatticeItem()
    : Interference2DAbstractLatticeItem(false)
{
}

std::unique_ptr<IInterference> InterferenceFinite2DLatticeItem::createInterference() const
{
    Lattice2DItem* latticeItem = latticeTypeItem();
    auto result = std::make_unique<InterferenceFinite2DLattice>(*latticeItem->createLattice(),
                                                                m_domain_size1, m_domain_size2);

    result->setIntegrationOverXi(xiIntegration());
    result->setPositionVariance(m_position_variance.dVal());

    return result;
}

void InterferenceFinite2DLatticeItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<Interference2DAbstractLatticeItem>(w, XML::Tag::BaseData, this);
    XML::writeTaggedValue(w, Tag::DomainSize1, m_domain_size1);
    XML::writeTaggedValue(w, Tag::DomainSize2, m_domain_size2);
}

void InterferenceFinite2DLatticeItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<Interference2DAbstractLatticeItem>(r, tag, this);
        else if (tag == Tag::DomainSize1)
            m_domain_size1 = XML::readTaggedUInt(r, tag);
        else if (tag == Tag::DomainSize2)
            m_domain_size2 = XML::readTaggedUInt(r, tag);
        else
            r->skipCurrentElement();
    }
}

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

InterferenceHardDiskItem::InterferenceHardDiskItem()
{
    m_radius.init("Radius (nm)", "Hard disk radius", 5.0, "radius");
    m_density.init("Total particle density (nm^-2)", "Particle density in particles per area",
                   0.002, 6, 0.0001 /* step */, RealLimits::nonnegative(), "density");
}

std::unique_ptr<IInterference> InterferenceHardDiskItem::createInterference() const
{
    auto result = std::make_unique<InterferenceHardDisk>(m_radius.dVal(), m_density.dVal());
    result->setPositionVariance(m_position_variance.dVal());
    return std::unique_ptr<IInterference>(result.release());
}

void InterferenceHardDiskItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<InterferenceItem>(w, XML::Tag::BaseData, this);

    // radius
    m_radius.writeTo2(w, Tag::Radius);

    // density
    m_density.writeTo2(w, Tag::Density);
}

void InterferenceHardDiskItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<InterferenceItem>(r, tag, this);
        else if (tag == Tag::Radius) {
            m_radius.readFrom2(r, tag);
        } else if (tag == Tag::Density) {
            m_density.readFrom2(r, tag);

        } else
            r->skipCurrentElement();
    }
}

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

InterferenceRadialParacrystalItem::InterferenceRadialParacrystalItem()
{
    m_peak_distance.init("Peak distance (nm)", "Average distance to the next neighbor", 20.0,
                         "peak");
    m_damping_length.init("Damping length (nm)",
                          "The damping (coherence) length of the paracrystal", 1000.0,
                          "dampingLen");
    m_domain_size.init("Domain size (nm)", "Size of coherence domain along the lattice main axis",
                       0.0, "size");
    m_kappa.init("SizeSpaceCoupling",
                 "Size spacing coupling parameter of the Size Spacing Correlation Approximation",
                 0.0, "kappa");
    m_pdf.simpleInit("PDF", "One-dimensional probability distribution",
                     Profile1DCatalog::Type::Cauchy);
}

std::unique_ptr<IInterference> InterferenceRadialParacrystalItem::createInterference() const
{
    auto result = std::make_unique<InterferenceRadialParacrystal>(m_peak_distance.dVal(),
                                                                  m_damping_length.dVal());
    result->setDomainSize(m_domain_size.dVal());
    result->setKappa(m_kappa.dVal());
    auto pdf = m_pdf.certainItem()->createProfile();
    result->setProbabilityDistribution(*pdf);
    result->setPositionVariance(m_position_variance.dVal());
    return std::unique_ptr<IInterference>(result.release());
}

void InterferenceRadialParacrystalItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<InterferenceItem>(w, XML::Tag::BaseData, this);

    // peak distance
    m_peak_distance.writeTo2(w, Tag::PeakDistance);

    // damping length
    m_damping_length.writeTo2(w, Tag::DampingLength);

    // domain size
    m_domain_size.writeTo2(w, Tag::DomainSize);

    // kappa
    m_kappa.writeTo2(w, Tag::Kappa);
    XML::writeTaggedElement(w, Tag::PDF, m_pdf);
}

void InterferenceRadialParacrystalItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<InterferenceItem>(r, tag, this);
        else if (tag == Tag::PeakDistance) {
            m_peak_distance.readFrom2(r, tag);
        } else if (tag == Tag::DampingLength) {
            m_damping_length.readFrom2(r, tag);
        } else if (tag == Tag::DomainSize) {
            m_domain_size.readFrom2(r, tag);
        } else if (tag == Tag::Kappa) {
            m_kappa.readFrom2(r, tag);
        } else if (tag == Tag::PDF)
            XML::readTaggedElement(r, tag, m_pdf);
        else
            r->skipCurrentElement();
    }
}
