//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Sample/MesocrystalItem.cpp
//! @brief     Implements class MesocrystalItem.
//!
//! @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/MesocrystalItem.h"
#include "Base/Util/Vec.h"
#include "GUI/Model/Sample/CompoundItem.h"
#include "GUI/Model/Sample/CoreAndShellItem.h"
#include "GUI/Model/Sample/ParticleItem.h"
#include "Sample/Particle/CoreAndShell.h"
#include "Sample/Particle/Crystal.h"
#include "Sample/Particle/IFormFactor.h"
#include "Sample/Particle/Mesocrystal.h"
#include "Sample/Particle/Particle.h"
#include "Sample/Scattering/Rotations.h"

namespace {

namespace Tag {

const QString VectorA("VectorA");
const QString VectorB("VectorB");
const QString VectorC("VectorC");
const QString OuterShape("OuterShape");
const QString BasisParticle("BasisParticle");
const QString BaseData("BaseData");
const QString ExpandMesocrystalGroupbox("ExpandMesocrystalGroupbox");

} // namespace Tag

const QString abundance_tooltip = "Proportion of this type of mesocrystal normalized to the \n"
                                  "total number of particles in the layout";

const QString position_tooltip = "Relative position of the mesocrystal's reference point \n"
                                 "in the coordinate system of the parent (nm)";

} // namespace

MesocrystalItem::MesocrystalItem(const MaterialsSet* materials)
    : ItemWithParticles(abundance_tooltip, position_tooltip)
    , m_materials(materials)
{
    m_vectorA.init("First lattice vector (nm)", "Coordinates of the first lattice vector",
                   "vectorA");
    m_vectorB.init("Second lattice vector (nm)", "Coordinates of the second lattice vector",
                   "vectorB");
    m_vectorC.init("Third lattice vector (nm)", "Coordinates of the third lattice vector",
                   "vectorC");

    m_outer_shape.initWithArgs("Outer Shape", "", FormfactorsCatalog::Type::Box);
    m_basis_particle.initWithArgs("Basis", "", ParticlesCatalog::Type::Particle, materials);
}

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

    // parameters from base class
    w->writeStartElement(Tag::BaseData);
    ItemWithParticles::writeTo(w);
    w->writeEndElement();

    // lattice vector A
    w->writeStartElement(Tag::VectorA);
    m_vectorA.writeTo(w);
    w->writeEndElement();

    // lattice vector B
    w->writeStartElement(Tag::VectorB);
    m_vectorB.writeTo(w);
    w->writeEndElement();

    // lattice vector C
    w->writeStartElement(Tag::VectorC);
    m_vectorC.writeTo(w);
    w->writeEndElement();

    // outer shape
    w->writeStartElement(Tag::OuterShape);
    m_outer_shape.writeTo(w);
    w->writeEndElement();

    // basis particle
    w->writeStartElement(Tag::BasisParticle);
    m_basis_particle.writeTo(w);
    w->writeEndElement();

    // mesocrystal groupbox: is expanded?
    w->writeStartElement(Tag::ExpandMesocrystalGroupbox);
    XML::writeAttribute(w, XML::Attrib::value, expandMesocrystal);
    w->writeEndElement();
}

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

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

        // parameters from base class
        if (tag == Tag::BaseData) {
            ItemWithParticles::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // lattice vector A
        } else if (tag == Tag::VectorA) {
            m_vectorA.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // lattice vector B
        } else if (tag == Tag::VectorB) {
            m_vectorB.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // lattice vector C
        } else if (tag == Tag::VectorC) {
            m_vectorC.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // outer shape
        } else if (tag == Tag::OuterShape) {
            m_outer_shape.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // basis particle
        } else if (tag == Tag::BasisParticle) {
            m_basis_particle.readFrom(r, m_materials);
            XML::gotoEndElementOfTag(r, tag);

            // mesocrystal groupbox: is expanded?
        } else if (tag == Tag::ExpandMesocrystalGroupbox) {
            XML::readAttribute(r, XML::Attrib::value, &expandMesocrystal);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

std::unique_ptr<Mesocrystal> MesocrystalItem::createMesocrystal() const
{
    const Lattice3D& lattice = getLattice();
    if (!(lattice.unitCellVolume() > 0.0))
        throw std::runtime_error("MesocrystalItem::createMesocrystal(): "
                                 "Lattice volume not strictly positive");
    std::unique_ptr<IParticle> basis = getBasis();
    if (!basis)
        throw std::runtime_error("MesocrystalItem::createMesocrystal(): "
                                 "No basis particle defined");
    Crystal crystal(*basis, lattice);

    std::unique_ptr<IFormFactor> ff = getOuterShape();
    if (!ff)
        throw std::runtime_error("MesocrystalItem::createMesocrystal(): "
                                 "No outer shape defined");

    auto result = std::make_unique<Mesocrystal>(crystal, *ff);
    if (auto r = createRotation(); r && !r->isIdentity())
        result->rotate(*r);
    result->translate(position());

    return result;
}

Lattice3D MesocrystalItem::getLattice() const
{
    return Lattice3D(m_vectorA, m_vectorB, m_vectorC);
}

std::unique_ptr<IParticle> MesocrystalItem::getBasis() const
{
    if (auto* p = dynamic_cast<ParticleItem*>(m_basis_particle.certainItem()))
        return p->createParticle();

    if (auto* p = dynamic_cast<CoreAndShellItem*>(m_basis_particle.certainItem()))
        return p->createCoreAndShell();

    if (auto* p = dynamic_cast<CompoundItem*>(m_basis_particle.certainItem()))
        return p->createCompound();

    if (auto* p = dynamic_cast<MesocrystalItem*>(m_basis_particle.certainItem()))
        return p->createMesocrystal();

    return {};
}

std::unique_ptr<IFormFactor> MesocrystalItem::getOuterShape() const
{
    return m_outer_shape->createFormFactor();
}

std::vector<ItemWithParticles*> MesocrystalItem::containedItemsWithParticles() const
{
    std::vector<ItemWithParticles*> result;
    if (basisItem()) {
        result.push_back(basisItem());
        Vec::concat(result, basisItem()->containedItemsWithParticles());
    }
    return result;
}
