//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/coregui/Models/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 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/coregui/Models/MesoCrystalItem.h"
#include "GUI/coregui/Models/ComboProperty.h"
#include "GUI/coregui/Models/FormFactorItems.h"
#include "GUI/coregui/Models/ModelPath.h"
#include "GUI/coregui/Models/ParticleCompositionItem.h"
#include "GUI/coregui/Models/ParticleCoreShellItem.h"
#include "GUI/coregui/Models/ParticleItem.h"
#include "GUI/coregui/Models/SessionItemUtils.h"
#include "GUI/coregui/Models/TransformToDomain.h"
#include "GUI/coregui/Models/VectorItem.h"
#include "GUI/coregui/utils/GUIHelpers.h"
#include "Sample/Particle/Crystal.h"
#include "Sample/Particle/MesoCrystal.h"
#include "Sample/Particle/Particle.h"
#include "Sample/Particle/ParticleCoreShell.h"
#include "Sample/Scattering/IFormFactor.h"

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

const QString lattice_vector1_tooltip = "Coordinates of the first lattice vector";

const QString lattice_vector2_tooltip = "Coordinates of the second lattice vector";

const QString lattice_vector3_tooltip = "Coordinates of the third lattice vector";

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

const QString density_tooltip =
    "Number of mesocrystals per square nanometer (particle surface density).\n "
    "Should be defined for disordered and 1d-ordered particle collections.";

bool IsIParticleName(QString name)
{
    return (name.startsWith("Particle") || name.startsWith("ParticleComposition")
            || name.startsWith("ParticleCoreShell") || name.startsWith("MesoCrystal"));
}

} // namespace

const QString MesoCrystalItem::P_OUTER_SHAPE = "Outer Shape";
const QString MesoCrystalItem::T_BASIS_PARTICLE = "Basis Particle";
const QString MesoCrystalItem::P_VECTOR_A = "First lattice vector";
const QString MesoCrystalItem::P_VECTOR_B = "Second lattice vector";
const QString MesoCrystalItem::P_VECTOR_C = "Third lattice vector";

// TODO make derived from ParticleItem

MesoCrystalItem::MesoCrystalItem() : SessionGraphicsItem("MesoCrystal")
{
    setToolTip("A 3D crystal structure of nanoparticles");

    addGroupProperty(P_OUTER_SHAPE, "Form Factor");

    addProperty(ParticleItem::P_ABUNDANCE, 1.0)
        ->setLimits(RealLimits::limited(0.0, 1.0))
        .setDecimals(3)
        .setToolTip(abundance_tooltip);

    addProperty<VectorItem>(P_VECTOR_A)->setToolTip(lattice_vector1_tooltip);
    addProperty<VectorItem>(P_VECTOR_B)->setToolTip(lattice_vector2_tooltip);
    addProperty<VectorItem>(P_VECTOR_C)->setToolTip(lattice_vector3_tooltip);
    addProperty<VectorItem>(ParticleItem::P_POSITION)->setToolTip(position_tooltip);

    registerTag(T_BASIS_PARTICLE, 0, 1,
                QStringList() << "Particle"
                              << "ParticleCoreShell"
                              << "ParticleComposition"
                              << "MesoCrystal");
    setDefaultTag(T_BASIS_PARTICLE);

    registerTag(ParticleItem::T_TRANSFORMATION, 0, 1, QStringList() << "Rotation");

    addTranslator(VectorParameterTranslator(ParticleItem::P_POSITION, "Position"));
    addTranslator(RotationTranslator());
    QStringList additional_names{QString::fromStdString("Lattice"),
                                 QString::fromStdString("Crystal")};
    addTranslator(VectorParameterTranslator(P_VECTOR_A, "BasisA", additional_names));
    addTranslator(VectorParameterTranslator(P_VECTOR_B, "BasisB", additional_names));
    addTranslator(VectorParameterTranslator(P_VECTOR_C, "BasisC", additional_names));

    mapper()->setOnParentChange([this](SessionItem* parent) {
        if (SessionItemUtils::HasOwnAbundance(parent)) {
            setItemValue(ParticleItem::P_ABUNDANCE, 1.0);
            getItem(ParticleItem::P_ABUNDANCE)->setEnabled(false);
        } else {
            getItem(ParticleItem::P_ABUNDANCE)->setEnabled(true);
        }
    });
}

VectorItem* MesoCrystalItem::positionItem() const
{
    return item<VectorItem>(ParticleItem::P_POSITION);
}

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

    std::unique_ptr<IFormFactor> ff = getOuterShape();
    if (!ff)
        throw GUIHelpers::Error("MesoCrystalItem::createMesoCrystal(): "
                                "No outer shape defined");

    auto result = std::make_unique<MesoCrystal>(crystal, *ff);
    TransformToDomain::setTransformationInfo(result.get(), *this);

    return result;
}

QStringList MesoCrystalItem::translateList(const QStringList& list) const
{
    QStringList result = list;
    // Add CrystalType to path name of basis particle
    if (IsIParticleName(list.back()))
        result << QString::fromStdString("Crystal");
    result = SessionItem::translateList(result);
    return result;
}

Lattice3D MesoCrystalItem::getLattice() const
{
    const kvector_t a1 = item<VectorItem>(P_VECTOR_A)->getVector();
    const kvector_t a2 = item<VectorItem>(P_VECTOR_B)->getVector();
    const kvector_t a3 = item<VectorItem>(P_VECTOR_C)->getVector();
    return Lattice3D(a1, a2, a3);
}

std::unique_ptr<IParticle> MesoCrystalItem::getBasis() const
{
    QVector<SessionItem*> childlist = children();
    for (int i = 0; i < childlist.size(); ++i) {
        if (childlist[i]->modelType() == "Particle") {
            auto* particle_item = static_cast<ParticleItem*>(childlist[i]);
            return particle_item->createParticle();
        } else if (childlist[i]->modelType() == "ParticleCoreShell") {
            auto* particle_coreshell_item = static_cast<ParticleCoreShellItem*>(childlist[i]);
            return particle_coreshell_item->createParticleCoreShell();
        } else if (childlist[i]->modelType() == "ParticleComposition") {
            auto* particlecomposition_item = static_cast<ParticleCompositionItem*>(childlist[i]);
            return particlecomposition_item->createParticleComposition();
        } else if (childlist[i]->modelType() == "MesoCrystal") {
            auto* mesocrystal_item = static_cast<MesoCrystalItem*>(childlist[i]);
            return mesocrystal_item->createMesoCrystal();
        }
    }
    return {};
}

std::unique_ptr<IFormFactor> MesoCrystalItem::getOuterShape() const
{
    auto& ff_item = groupItem<FormFactorItem>(MesoCrystalItem::P_OUTER_SHAPE);
    return ff_item.createFormFactor();
}
