//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Sample/FormFactorItems.cpp
//! @brief     Implements FormFactorItems classes
//!
//! @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/FormFactorItems.h"
#include "Base/Const/Units.h"
#include "GUI/Support/XML/UtilXML.h"
#include "Sample/HardParticle/HardParticles.h"

namespace {
namespace Tag {

const QString Alpha("Alpha");
const QString Asymmetry("Asymmetry");
const QString BaseEdge("BaseEdge");
const QString BaseHeight("BaseHeight");
const QString Edge("Edge");
const QString Height("Height");
const QString HeightFlattening("HeightFlattening");
const QString HeightRatio("HeightRatio");
const QString Length("Length");
const QString Radius("Radius");
const QString RadiusX("RadiusX");
const QString RadiusY("RadiusY");
const QString RemovedLength("RemovedLength");
const QString RemovedTop("RemovedTop");
const QString SliceBottom("SliceBottom");
const QString SliceTop("SliceTop");
const QString UntruncatedHeight("UntruncatedHeight");
const QString Width("Width");

} // namespace Tag
} // namespace

/* ------------------------------------------------ */

Pyramid2Item::Pyramid2Item()
{
    m_length.init("Length", "Length of the rectangular base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the rectangular base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of pyramid", 16.0, Unit::nanometer, "height");
    m_alpha.init("Alpha", "Dihedral angle between base and facet", 80.0, Unit::degree, 2, 0.1,
                 RealLimits::limited(0.0, 90.0), "alpha");
}

std::unique_ptr<IFormFactor> Pyramid2Item::createFormFactor() const
{
    return std::make_unique<Pyramid2>(m_length.value(), m_width.value(), m_height.value(),
                                      m_alpha.value() * Units::deg);
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
    m_alpha.writeTo(w, Tag::Alpha);
}

void Pyramid2Item::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else if (tag == Tag::Alpha)
            m_alpha.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

BarGaussItem::BarGaussItem()
{
    m_length.init("Length", "Length of the base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of the box", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> BarGaussItem::createFormFactor() const
{
    return std::make_unique<BarGauss>(m_length.value(), m_width.value(), m_height.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
}

void BarGaussItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

BarLorentzItem::BarLorentzItem()
{
    m_length.init("Length", "Length of the base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of the box", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> BarLorentzItem::createFormFactor() const
{
    return std::make_unique<BarLorentz>(m_length.value(), m_width.value(), m_height.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
}

void BarLorentzItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

BoxItem::BoxItem()
{
    m_length.init("Length", "Length of the base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of the box", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> BoxItem::createFormFactor() const
{
    return std::make_unique<Box>(m_length.value(), m_width.value(), m_height.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
}

void BoxItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

ConeItem::ConeItem()
{
    m_radius.init("Radius", "Radius of the base", 8.0, Unit::nanometer, "radius");
    m_height.init("Height", "Height of the cone", 16.0, Unit::nanometer, "height");
    m_alpha.init("Alpha", "Angle between the base and the side surface", 65.0, Unit::degree, 2, 0.1,
                 RealLimits::limited(0.0, 90.0), "alpha");
}

std::unique_ptr<IFormFactor> ConeItem::createFormFactor() const
{
    return std::make_unique<Cone>(m_radius.value(), m_height.value(), m_alpha.value() * Units::deg);
}

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

    m_radius.writeTo(w, Tag::Radius);
    m_height.writeTo(w, Tag::Height);
    m_alpha.writeTo(w, Tag::Alpha);
}

void ConeItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Radius)
            m_radius.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else if (tag == Tag::Alpha)
            m_alpha.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

Pyramid6Item::Pyramid6Item()
{
    m_baseEdge.init("Base edge", "Edge of the regular hexagonal base", 8.0, Unit::nanometer,
                    "baseEdge");
    m_height.init("Height", "Height of a truncated pyramid", 16.0, Unit::nanometer, "height");
    m_alpha.init("Alpha", "Dihedral angle between base and facet", 70.0, Unit::degree, 2, 0.1,
                 RealLimits::limited(0.0, 90.0), "alpha");
}

std::unique_ptr<IFormFactor> Pyramid6Item::createFormFactor() const
{
    return std::make_unique<Pyramid6>(m_baseEdge.value(), m_height.value(),
                                      m_alpha.value() * Units::deg);
}

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

    m_baseEdge.writeTo(w, Tag::BaseEdge);
    m_height.writeTo(w, Tag::Height);
    m_alpha.writeTo(w, Tag::Alpha);
}

void Pyramid6Item::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::BaseEdge)
            m_baseEdge.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else if (tag == Tag::Alpha)
            m_alpha.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

Bipyramid4Item::Bipyramid4Item()
{
    m_length.init("Length", "Side length of the common square base", 12.0, Unit::nanometer,
                  "length");

    m_base_height.init("Base height", "Height of the lower pyramid", 16.0, Unit::nanometer,
                       "base_height");

    m_heightRatio.init("Height ratio", "Ratio of heights of top to bottom pyramids", 0.7,
                       Unit::unitless, 3, RealLimits::lowerLimited(0.0), "heightRatio");

    m_alpha.init("Alpha", "Dihedral angle between base and facets", 70.0, Unit::degree, 2, 0.1,
                 RealLimits::limited(0.0, 90.0), "alpha");
}

std::unique_ptr<IFormFactor> Bipyramid4Item::createFormFactor() const
{
    return std::make_unique<Bipyramid4>(m_length.value(), m_base_height.value(),
                                        m_heightRatio.value(), m_alpha.value() * Units::deg);
}

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

    m_length.writeTo(w, Tag::Length);
    m_base_height.writeTo(w, Tag::BaseHeight);
    m_heightRatio.writeTo(w, Tag::HeightRatio);
    m_alpha.writeTo(w, Tag::Alpha);
}

void Bipyramid4Item::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::BaseHeight)
            m_base_height.readFrom(r, tag);
        else if (tag == Tag::HeightRatio)
            m_heightRatio.readFrom(r, tag);
        else if (tag == Tag::Alpha)
            m_alpha.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

CylinderItem::CylinderItem()
{
    m_radius.init("Radius", "Radius of the circular base", 8.0, Unit::nanometer, "radius");
    m_height.init("Height", "Height of the cylinder", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> CylinderItem::createFormFactor() const
{
    return std::make_unique<Cylinder>(m_radius.value(), m_height.value());
}

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

    m_radius.writeTo(w, Tag::Radius);
    m_height.writeTo(w, Tag::Height);
}

void CylinderItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Radius)
            m_radius.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

EllipsoidalCylinderItem::EllipsoidalCylinderItem()
{
    m_radiusX.init("Radius x", "Radius of the ellipse base in the x-direction", 5.0,
                   Unit::nanometer, "radiusX");
    m_radiusY.init("Radius y", "Radius of the ellipse base in the y-direction", 8.0,
                   Unit::nanometer, "radiusY");
    m_height.init("Height", "Height of the ellipsoidal cylinder", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> EllipsoidalCylinderItem::createFormFactor() const
{
    return std::make_unique<EllipsoidalCylinder>(m_radiusX.value(), m_radiusY.value(),
                                                 m_height.value());
}

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

    m_radiusX.writeTo(w, Tag::RadiusX);
    m_radiusY.writeTo(w, Tag::RadiusY);
    m_height.writeTo(w, Tag::Height);
}

void EllipsoidalCylinderItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::RadiusX)
            m_radiusX.readFrom(r, tag);
        else if (tag == Tag::RadiusY)
            m_radiusY.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

SphereItem::SphereItem()
{
    m_radius.init("Radius", "Radius of the sphere", 8.0, Unit::nanometer, "radius");
}

std::unique_ptr<IFormFactor> SphereItem::createFormFactor() const
{
    return std::make_unique<Sphere>(m_radius.value());
}

void SphereItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));
    m_radius.writeTo(w, Tag::Radius);
}

void SphereItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Radius)
            m_radius.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

SpheroidItem::SpheroidItem()
{
    m_radius.init("Radius", "Radius of the circular cross section", 8.0, Unit::nanometer, "radius");
    m_height.init("Height", "Height of the full spheroid", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> SpheroidItem::createFormFactor() const
{
    return std::make_unique<Spheroid>(m_radius.value(), m_height.value());
}

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

    m_radius.writeTo(w, Tag::Radius);
    m_height.writeTo(w, Tag::Height);
}

void SpheroidItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Radius)
            m_radius.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

HemiEllipsoidItem::HemiEllipsoidItem()
{
    m_radiusX.init("Radius x", "Radius of the ellipse base in the x-direction", 8.0,
                   Unit::nanometer, "radiusX");
    m_radiusY.init("Radius y", "Radius of the ellipse base in the y-direction", 6.0,
                   Unit::nanometer, "radiusY");
    m_height.init("Height", "Height of the hemi ellipsoid", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> HemiEllipsoidItem::createFormFactor() const
{
    return std::make_unique<HemiEllipsoid>(m_radiusX.value(), m_radiusY.value(), m_height.value());
}

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

    m_radiusX.writeTo(w, Tag::RadiusX);
    m_radiusY.writeTo(w, Tag::RadiusY);
    m_height.writeTo(w, Tag::Height);
}

void HemiEllipsoidItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::RadiusX)
            m_radiusX.readFrom(r, tag);
        else if (tag == Tag::RadiusY)
            m_radiusY.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

Prism3Item::Prism3Item()
{
    m_baseEdge.init("Base edge", "Length of the base edge", 14.0, Unit::nanometer, "baseEdge");
    m_height.init("Height", "Height", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> Prism3Item::createFormFactor() const
{
    return std::make_unique<Prism3>(m_baseEdge.value(), m_height.value());
}

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

    m_baseEdge.writeTo(w, Tag::BaseEdge);
    m_height.writeTo(w, Tag::Height);
}

void Prism3Item::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::BaseEdge)
            m_baseEdge.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

Prism6Item::Prism6Item()
{
    m_baseEdge.init("Base edge", "Length of the hexagonal base", 8.0, Unit::nanometer, "baseEdge");
    m_height.init("Height", "Height", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> Prism6Item::createFormFactor() const
{
    return std::make_unique<Prism6>(m_baseEdge.value(), m_height.value());
}

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

    m_baseEdge.writeTo(w, Tag::BaseEdge);
    m_height.writeTo(w, Tag::Height);
}

void Prism6Item::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::BaseEdge)
            m_baseEdge.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

Pyramid4Item::Pyramid4Item()
{
    m_baseEdge.init("Base edge", "Length of the square base", 16.0, Unit::nanometer, "baseEdge");
    m_height.init("Height", "Height of the pyramid", 16.0, Unit::nanometer, "height");
    m_alpha.init("Alpha", "Dihedral angle between the base and a side face", 65.0, Unit::degree, 2,
                 0.1, RealLimits::limited(0.0, 90.0), "alpha");
}

std::unique_ptr<IFormFactor> Pyramid4Item::createFormFactor() const
{
    return std::make_unique<Pyramid4>(m_baseEdge.value(), m_height.value(),
                                      m_alpha.value() * Units::deg);
}

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

    m_baseEdge.writeTo(w, Tag::BaseEdge);
    m_height.writeTo(w, Tag::Height);
    m_alpha.writeTo(w, Tag::Alpha);
}

void Pyramid4Item::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::BaseEdge)
            m_baseEdge.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else if (tag == Tag::Alpha)
            m_alpha.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

CosineRippleBoxItem::CosineRippleBoxItem()
{
    m_length.init("Length", "Length of the rectangular base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the rectangular base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of the ripple", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> CosineRippleBoxItem::createFormFactor() const
{
    return std::make_unique<CosineRippleBox>(m_length.value(), m_width.value(), m_height.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
}

void CosineRippleBoxItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

CosineRippleGaussItem::CosineRippleGaussItem()
{
    m_length.init("Length", "Length of the rectangular base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the rectangular base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of the ripple", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> CosineRippleGaussItem::createFormFactor() const
{
    return std::make_unique<CosineRippleGauss>(m_length.value(), m_width.value(), m_height.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
}

void CosineRippleGaussItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

CosineRippleLorentzItem::CosineRippleLorentzItem()
{
    m_length.init("Length", "Length of the rectangular base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the rectangular base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of the ripple", 16.0, Unit::nanometer, "height");
}

std::unique_ptr<IFormFactor> CosineRippleLorentzItem::createFormFactor() const
{
    return std::make_unique<CosineRippleLorentz>(m_length.value(), m_width.value(),
                                                 m_height.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
}

void CosineRippleLorentzItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

SawtoothRippleBoxItem::SawtoothRippleBoxItem()
{
    m_length.init("Length", "Length of the rectangular base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the rectangular base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of the ripple", 16.0, Unit::nanometer, "height");
    m_asymmetry.init("Asymmetry", "Asymmetry length of the triangular profile", 3.0,
                     Unit::nanometer, "asymmetry");
}

std::unique_ptr<IFormFactor> SawtoothRippleBoxItem::createFormFactor() const
{
    return std::make_unique<SawtoothRippleBox>(m_length.value(), m_width.value(), m_height.value(),
                                               m_asymmetry.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
    m_asymmetry.writeTo(w, Tag::Asymmetry);
}

void SawtoothRippleBoxItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else if (tag == Tag::Asymmetry)
            m_asymmetry.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

SawtoothRippleGaussItem::SawtoothRippleGaussItem()
{
    m_length.init("Length", "Length of the rectangular base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the rectangular base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of the ripple", 16.0, Unit::nanometer, "height");
    m_asymmetry.init("Asymmetry", "Asymmetry length of the triangular profile", 3.0,
                     Unit::nanometer, "asymmetry");
}

std::unique_ptr<IFormFactor> SawtoothRippleGaussItem::createFormFactor() const
{
    return std::make_unique<SawtoothRippleGauss>(m_length.value(), m_width.value(),
                                                 m_height.value(), m_asymmetry.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
    m_asymmetry.writeTo(w, Tag::Asymmetry);
}

void SawtoothRippleGaussItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else if (tag == Tag::Asymmetry)
            m_asymmetry.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

SawtoothRippleLorentzItem::SawtoothRippleLorentzItem()
{
    m_length.init("Length", "Length of the rectangular base", 16.0, Unit::nanometer, "length");
    m_width.init("Width", "Width of the rectangular base", 16.0, Unit::nanometer, "width");
    m_height.init("Height", "Height of the ripple", 16.0, Unit::nanometer, "height");
    m_asymmetry.init("Asymmetry", "Asymmetry length of the triangular profile", 3.0,
                     Unit::nanometer, "asymmetry");
}

std::unique_ptr<IFormFactor> SawtoothRippleLorentzItem::createFormFactor() const
{
    return std::make_unique<SawtoothRippleLorentz>(m_length.value(), m_width.value(),
                                                   m_height.value(), m_asymmetry.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_width.writeTo(w, Tag::Width);
    m_height.writeTo(w, Tag::Height);
    m_asymmetry.writeTo(w, Tag::Asymmetry);
}

void SawtoothRippleLorentzItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::Width)
            m_width.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else if (tag == Tag::Asymmetry)
            m_asymmetry.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

Pyramid3Item::Pyramid3Item()
{
    m_baseEdge.init("Base edge", "Length of one edge of the equilateral triangular base", 14.0,
                    Unit::nanometer, "baseEdge");
    m_height.init("Height", "Height of the tetrahedron", 16.0, Unit::nanometer, "height");
    m_alpha.init("Alpha", "Dihedral angle between base and facet", 80.0, Unit::degree, 2, 0.1,
                 RealLimits::limited(0.0, 90.0), "alpha");
}

std::unique_ptr<IFormFactor> Pyramid3Item::createFormFactor() const
{
    return std::make_unique<Pyramid3>(m_baseEdge.value(), m_height.value(),
                                      m_alpha.value() * Units::deg);
}

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

    m_baseEdge.writeTo(w, Tag::BaseEdge);
    m_height.writeTo(w, Tag::Height);
    m_alpha.writeTo(w, Tag::Alpha);
}

void Pyramid3Item::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::BaseEdge)
            m_baseEdge.readFrom(r, tag);
        else if (tag == Tag::Height)
            m_height.readFrom(r, tag);
        else if (tag == Tag::Alpha)
            m_alpha.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

TruncatedCubeItem::TruncatedCubeItem()
{
    m_length.init("Length", "Length of the full cube's edge", 16.0, Unit::nanometer, "length");
    m_removedLength.init("Removed length", "Removed length from each corner of the cube", 4.0,
                         Unit::nanometer, "removedLength");
}

std::unique_ptr<IFormFactor> TruncatedCubeItem::createFormFactor() const
{
    return std::make_unique<TruncatedCube>(m_length.value(), m_removedLength.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_removedLength.writeTo(w, Tag::RemovedLength);
}

void TruncatedCubeItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::RemovedLength)
            m_removedLength.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

TruncatedSphereItem::TruncatedSphereItem()
{
    m_radius.init("Radius", "Radius of the truncated sphere", 8.0, Unit::nanometer, "radius");
    m_untruncated_height.init("UntruncatedHeight", "Height before top removal", 16.0,
                              Unit::nanometer, "untruncated_height");
    m_removedTop.init("Delta height", "Height of the removed top cap", 16.0, Unit::nanometer,
                      "removedTop");
}

std::unique_ptr<IFormFactor> TruncatedSphereItem::createFormFactor() const
{
    return std::make_unique<TruncatedSphere>(m_radius.value(), m_untruncated_height.value(),
                                             m_removedTop.value());
}

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

    m_radius.writeTo(w, Tag::Radius);
    m_untruncated_height.writeTo(w, Tag::UntruncatedHeight);
    m_removedTop.writeTo(w, Tag::RemovedTop);
}

void TruncatedSphereItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Radius)
            m_radius.readFrom(r, tag);
        else if (tag == Tag::UntruncatedHeight)
            m_untruncated_height.readFrom(r, tag);
        else if (tag == Tag::RemovedTop)
            m_removedTop.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

TruncatedSpheroidItem::TruncatedSpheroidItem()
{
    m_radius.init("Radius", "Radius of the truncated spheroid", 8.0, Unit::nanometer, "radius");
    m_untruncated_height.init("UntruncatedHeight", "Height before top removal", 16.0,
                              Unit::nanometer, "untruncated_height");
    m_heightFlattening.init(
        "Height flattening",
        "Ratio of the height of the corresponding full spheroid to its diameter", 2.0,
        Unit::unitless, "heightFlattening");

    m_removedTop.init("Delta height", "Height of the removed top cap", 0.0, Unit::nanometer,
                      "removedTop");
}

std::unique_ptr<IFormFactor> TruncatedSpheroidItem::createFormFactor() const
{
    return std::make_unique<TruncatedSpheroid>(m_radius.value(), m_untruncated_height.value(),
                                               m_heightFlattening.value(), m_removedTop.value());
}

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

    m_radius.writeTo(w, Tag::Radius);
    m_untruncated_height.writeTo(w, Tag::UntruncatedHeight);
    m_heightFlattening.writeTo(w, Tag::HeightFlattening);
    m_removedTop.writeTo(w, Tag::RemovedTop);
}

void TruncatedSpheroidItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Radius)
            m_radius.readFrom(r, tag);
        else if (tag == Tag::UntruncatedHeight)
            m_untruncated_height.readFrom(r, tag);
        else if (tag == Tag::HeightFlattening)
            m_heightFlattening.readFrom(r, tag);
        else if (tag == Tag::RemovedTop)
            m_removedTop.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

CantellatedCubeItem::CantellatedCubeItem()
{
    m_length.init("Length", "Length of the full cube's edge", 16.0, Unit::nanometer, "length");
    m_removedLength.init("Removed length", "Removed length from each edge and vertice of the cube",
                         4.0, Unit::nanometer, "removedLength");
}

std::unique_ptr<IFormFactor> CantellatedCubeItem::createFormFactor() const
{
    return std::make_unique<CantellatedCube>(m_length.value(), m_removedLength.value());
}

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

    m_length.writeTo(w, Tag::Length);
    m_removedLength.writeTo(w, Tag::RemovedLength);
}

void CantellatedCubeItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::RemovedLength)
            m_removedLength.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

HorizontalCylinderItem::HorizontalCylinderItem()
{
    m_radius.init("Radius", "Radius of the horizontal cylinder", 8.0, Unit::nanometer, "radius");
    m_length.init("Length", "Length of the horizontal cylinder", 16.0, Unit::nanometer, "length");
    m_sliceBottom.init("Bottom boundary", "Position of the lower boundary relative to the center",
                       -4.1, Unit::nanometer, "sliceBottom");
    m_sliceTop.init("Top boundary", "Position of the upper boundary relative to the center", +5.2,
                    Unit::nanometer, "sliceTop");
}

std::unique_ptr<IFormFactor> HorizontalCylinderItem::createFormFactor() const
{
    return std::make_unique<HorizontalCylinder>(m_radius.value(), m_length.value(),
                                                m_sliceBottom.value(), m_sliceTop.value());
}

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

    m_radius.writeTo(w, Tag::Radius);
    m_length.writeTo(w, Tag::Length);
    m_sliceBottom.writeTo(w, Tag::SliceBottom);
    m_sliceTop.writeTo(w, Tag::SliceTop);
}

void HorizontalCylinderItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Radius)
            m_radius.readFrom(r, tag);
        else if (tag == Tag::Length)
            m_length.readFrom(r, tag);
        else if (tag == Tag::SliceBottom)
            m_sliceBottom.readFrom(r, tag);
        else if (tag == Tag::SliceTop)
            m_sliceTop.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

/* ------------------------------------------------ */

void PlatonicItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));
    m_edge.writeTo(w, Tag::Edge);
}

void PlatonicItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

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

        if (tag == Tag::Edge)
            m_edge.readFrom(r, tag);
        else
            r->skipCurrentElement();
    }
}

PlatonicOctahedronItem::PlatonicOctahedronItem()
{
    m_edge.init("Edge", "Length of the edge", 16.0, Unit::nanometer, "edge");
}

std::unique_ptr<IFormFactor> PlatonicOctahedronItem::createFormFactor() const
{
    return std::make_unique<PlatonicOctahedron>(m_edge.value());
}

/* ------------------------------------------------ */

PlatonicTetrahedronItem::PlatonicTetrahedronItem()
{
    m_edge.init("Edge", "Length of the edge", 20.0, Unit::nanometer, "edge");
}

std::unique_ptr<IFormFactor> PlatonicTetrahedronItem::createFormFactor() const
{
    return std::make_unique<PlatonicTetrahedron>(m_edge.value());
}

/* ------------------------------------------------ */

DodecahedronItem::DodecahedronItem()
{
    m_edge.init("Edge", "Length of the edge", 7.0, Unit::nanometer, "edge");
}

std::unique_ptr<IFormFactor> DodecahedronItem::createFormFactor() const
{
    return std::make_unique<Dodecahedron>(m_edge.value());
}

/* ------------------------------------------------ */

IcosahedronItem::IcosahedronItem()
{
    m_edge.init("Edge", "Length of the edge", 10.0, Unit::nanometer, "edge");
}

std::unique_ptr<IFormFactor> IcosahedronItem::createFormFactor() const
{
    return std::make_unique<Icosahedron>(m_edge.value());
}
