//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Sample/RoughnessItems.cpp
//! @brief     Implements classes RoughnessItems.
//!
//! @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/RoughnessItems.h"
#include "GUI/Model/Util/UtilXML.h"
#include "Sample/Interface/AutocorrelationModels.h"

namespace {
namespace Tag {

const QString BaseData("BaseData");
const QString HeightDistributionModel("HeightDistributionModel");
const QString CrosscorrelationModel("CrosscorrelationModel");

const QString MaxSpatialFrequency("MaxSpatialFrequency");

const QString Sigma("Sigma");
const QString Hurst("Hurst");
const QString LateralCorrelationLength("LateralCorrelationLength");

const QString ParticleVolume("ParticleVolume");
const QString DampingExp1("DampingExp1");
const QString DampingExp2("DampingExp2");
const QString DampingExp3("DampingExp3");
const QString DampingExp4("DampingExp4");

} // namespace Tag
} // namespace

void RoughnessItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeTaggedElement(w, Tag::HeightDistributionModel, m_interlayer_model);
    XML::writeTaggedElement(w, Tag::CrosscorrelationModel, m_crosscorr_model);
    m_max_spatial_frequency.writeTo2(w, Tag::MaxSpatialFrequency);
}

void RoughnessItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        if (tag == Tag::HeightDistributionModel)
            XML::readTaggedElement(r, tag, m_interlayer_model);
        else if (tag == Tag::CrosscorrelationModel)
            XML::readTaggedElement(r, tag, m_crosscorr_model);
        else if (tag == Tag::MaxSpatialFrequency)
            m_max_spatial_frequency.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

RoughnessItem::RoughnessItem(double max_spat_frequency)
{
    m_interlayer_model.simpleInit("Interlayer profile",
                                  "Laterally averaged profile of the interlayer transition (or "
                                  "roughness height distribution)",
                                  InterlayerCatalog::Type::Tanh);
    m_crosscorr_model.simpleInit("Crosscorrelation",
                                 "Model of roughness crosscorrelation between interfaces",
                                 CrosscorrelationCatalog::Type::None);
    m_max_spatial_frequency.init("Max spatial frequency (1/nm)",
                                 "The upper limit of the roughness spatial frequency.\n "
                                 "Minimum spatial scale of relief (in nm) is 1/max_frequency.",
                                 max_spat_frequency, 3, RealLimits::positive(),
                                 "maxSpatialFrequency");
}

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

K_CorrelationRoughnessItem::K_CorrelationRoughnessItem(double sigma, double hurst,
                                                       double corr_length,
                                                       double max_spat_frequency)
    : RoughnessItem(max_spat_frequency)
{
    m_sigma.init("Sigma (nm)", "height scale of the roughness", sigma, "sigma");
    m_hurst.init("Hurst",
                 "Hurst parameter which describes how jagged the interface,\n "
                 "dimensionless (0.0, 1.0], where 0.0 gives more spikes, \n1.0 more smoothness.",
                 hurst, 3, RealLimits::limited(0.0, 1.0), "hurst");
    m_lateral_correlation_length.init("Correlation length (nm)",
                                      "Lateral correlation length of the roughness", corr_length,
                                      "corrLen");
}

void K_CorrelationRoughnessItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<RoughnessItem>(w, Tag::BaseData, this);
    m_sigma.writeTo2(w, Tag::Sigma);
    m_hurst.writeTo2(w, Tag::Hurst);
    m_lateral_correlation_length.writeTo2(w, Tag::LateralCorrelationLength);
}

void K_CorrelationRoughnessItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        if (tag == Tag::BaseData)
            XML::readBaseElement<RoughnessItem>(r, tag, this);
        else if (tag == Tag::Sigma)
            m_sigma.readFrom2(r, tag);
        else if (tag == Tag::Hurst)
            m_hurst.readFrom2(r, tag);
        else if (tag == Tag::LateralCorrelationLength)
            m_lateral_correlation_length.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

std::unique_ptr<AutocorrelationModel> K_CorrelationRoughnessItem::createModel() const
{
    return std::make_unique<K_CorrelationModel>(m_sigma.dVal(), m_hurst.dVal(),
                                                m_lateral_correlation_length.dVal(),
                                                m_max_spatial_frequency.dVal());
}

DoubleProperties K_CorrelationRoughnessItem::roughnessProperties()
{
    DoubleProperties result = {&m_sigma, &m_hurst, &m_lateral_correlation_length};
    const auto base_pars = RoughnessItem::roughnessProperties();
    for (const auto base_par : base_pars)
        result.push_back(base_par);
    return result;
}

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

LinearGrowthRoughnessItem::LinearGrowthRoughnessItem(double cluster_volume, double damp1,
                                                     double damp2, double damp3, double damp4,
                                                     double max_spat_frequency)
    : RoughnessItem(max_spat_frequency)
{
    m_cluster_volume.init("Cluster volume (nm^3)",
                          "Volume of cluster or particle arriving at a surface during deposition",
                          cluster_volume, "clusterVolume");
    m_damp1.init("Damping exp 1",
                 "Exponent damping factor for spatial frequency raised to the power of 1", damp1,
                 "damping1");
    m_damp2.init("Damping exp 2 (nm)",
                 "Exponent damping factor for spatial frequency raised to the power of 2", damp2,
                 "damping2");
    m_damp3.init("Damping exp 3 (nm^2)",
                 "Exponent damping factor for spatial frequency raised to the power of 3", damp3,
                 "damping3");
    m_damp4.init("Damping exp 4 (nm^3)",
                 "Exponent damping factor for spatial frequency raised to the power of 4", damp4,
                 "damping4");
}

void LinearGrowthRoughnessItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<RoughnessItem>(w, Tag::BaseData, this);
    m_cluster_volume.writeTo2(w, Tag::ParticleVolume);
    m_damp1.writeTo2(w, Tag::DampingExp1);
    m_damp2.writeTo2(w, Tag::DampingExp2);
    m_damp3.writeTo2(w, Tag::DampingExp3);
    m_damp4.writeTo2(w, Tag::DampingExp4);
}

void LinearGrowthRoughnessItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        if (tag == Tag::BaseData)
            XML::readBaseElement<RoughnessItem>(r, tag, this);
        else if (tag == Tag::ParticleVolume)
            m_cluster_volume.readFrom2(r, tag);
        else if (tag == Tag::DampingExp1)
            m_damp1.readFrom2(r, tag);
        else if (tag == Tag::DampingExp2)
            m_damp2.readFrom2(r, tag);
        else if (tag == Tag::DampingExp3)
            m_damp3.readFrom2(r, tag);
        else if (tag == Tag::DampingExp4)
            m_damp4.readFrom2(r, tag);
        else
            r->skipCurrentElement();
    }
}

std::unique_ptr<AutocorrelationModel> LinearGrowthRoughnessItem::createModel() const
{
    return std::make_unique<LinearGrowthModel>(m_cluster_volume.dVal(), m_damp1.dVal(),
                                               m_damp2.dVal(), m_damp3.dVal(), m_damp4.dVal(),
                                               m_max_spatial_frequency.dVal());
}

DoubleProperties LinearGrowthRoughnessItem::roughnessProperties()
{
    DoubleProperties result = {&m_cluster_volume, &m_damp1, &m_damp2, &m_damp3, &m_damp4};
    const auto base_pars = RoughnessItem::roughnessProperties();
    for (const auto base_par : base_pars)
        result.push_back(base_par);
    return result;
}
