//  ************************************************************************************************
//
//  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/Compound.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_basis_particle(materials)
    , m_materials(materials)
{
    m_vectorA.init("First lattice vector", "nm", "Coordinates of the first lattice vector",
                   R3(5, 0, 0), 3, true, 0.01, RealLimits::limitless(), "vectorA");
    m_vectorB.init("Second lattice vector", "nm", "Coordinates of the second lattice vector",
                   R3(0, 5, 0), 3, true, 0.01, RealLimits::limitless(), "vectorB");
    m_vectorC.init("Third lattice vector", "nm", "Coordinates of the third lattice vector",
                   R3(0, 0, 5), 3, true, 0.01, RealLimits::limitless(), "vectorC");

    m_outer_shape.simpleInit("Outer Shape", "", FormfactorCatalog::Type::Box);
    m_basis_particle.simpleInit("Basis", "", ParticleCatalog::Type::Particle);
}

void MesocrystalItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<ItemWithParticles>(w, XML::Tag::BaseData, this);
    XML::writeTaggedElement(w, Tag::VectorA, m_vectorA);
    XML::writeTaggedElement(w, Tag::VectorB, m_vectorB);
    XML::writeTaggedElement(w, Tag::VectorC, m_vectorC);
    XML::writeTaggedElement(w, Tag::OuterShape, m_outer_shape);
    XML::writeTaggedElement(w, Tag::BasisParticle, m_basis_particle);
    XML::writeTaggedValue(w, Tag::ExpandMesocrystalGroupbox, expandMesocrystal);
}

void MesocrystalItem::readFrom(QXmlStreamReader* r)
{
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<ItemWithParticles>(r, tag, this);
        else if (tag == Tag::VectorA)
            XML::readTaggedElement(r, tag, m_vectorA);
        else if (tag == Tag::VectorB)
            XML::readTaggedElement(r, tag, m_vectorB);
        else if (tag == Tag::VectorC)
            XML::readTaggedElement(r, tag, m_vectorC);
        else if (tag == Tag::OuterShape)
            XML::readTaggedElement(r, tag, m_outer_shape);
        else if (tag == Tag::BasisParticle) {
            m_basis_particle.readFrom(r, m_materials);
            XML::gotoEndElementOfTag(r, tag);
        } else if (tag == Tag::ExpandMesocrystalGroupbox)
            expandMesocrystal = XML::readTaggedBool(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.certainItem()->createFormfactor();
}

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