//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Sample/ParticleLayoutItem.cpp
//! @brief     Implements class ParticleLayoutItem.
//!
//! @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/ParticleLayoutItem.h"
#include "Base/Math/Functions.h"
#include "Base/Util/Assert.h"
#include "Base/Util/Vec.h"
#include "GUI/Model/Sample/CompoundItem.h"
#include "GUI/Model/Sample/Lattice2DItems.h"
#include "GUI/Model/Sample/MesocrystalItem.h"
#include "GUI/Model/Sample/ParticleCatalog.h"
#include "GUI/Model/Sample/ParticleItem.h"

namespace {
namespace Tag {

const QString OwnDensity("OwnDensity");
const QString InterferenceFunction("InterferenceFunction");
const QString Particle("Particle");
const QString ExpandLayoutGroupbox("ExpandLayoutGroupbox");
const QString ExpandInterferenceGroupbox("ExpandInterferenceGroupbox");

} // namespace Tag
} // namespace

ParticleLayoutItem::ParticleLayoutItem(const MaterialsSet* materials)
    : m_materials(materials)
{
    m_own_density.init("Total particle density (nm^-2)",
                       "Number of particles per area (particle surface density).\n "
                       "Should be defined for disordered and 1d-ordered particle collections.",
                       0.0005, 6, 0.0001 /* step */, RealLimits::nonnegative(), "density");

    m_interference.simpleInit("Interference function", "", InterferenceCatalog::Type::None);
    updateSeed();
}

double ParticleLayoutItem::totalDensityValue() const
{
    if (!totalDensityIsDefinedByInterference())
        return m_own_density.dVal();

    ASSERT(m_interference.certainItem());

    if (const auto* interLatticeItem =
            dynamic_cast<const Interference2DAbstractLatticeItem*>(m_interference.certainItem())) {
        Lattice2DItem* latticeItem = interLatticeItem->latticeTypeItem();
        try {
            const double area = latticeItem->unitCellArea();
            return area == 0.0 ? 0.0 : 1.0 / area;
        } catch (const std::exception&) {
            // nothing to do here; new exception will be caught during job execution
            return 0.0;
        }
    }

    if (const auto* hd =
            dynamic_cast<const InterferenceHardDiskItem*>(m_interference.certainItem()))
        return hd->density().dVal();

    ASSERT_NEVER;
}

std::vector<ItemWithParticles*> ParticleLayoutItem::containedItemsWithParticles() const
{
    std::vector<ItemWithParticles*> result;
    for (auto* t : m_particles) {
        result.push_back(t);
        Vec::concat(result, t->containedItemsWithParticles());
    }
    return result;
}

void ParticleLayoutItem::addItemWithParticleSelection(ItemWithParticles* particle)
{
    m_particles.push_back(particle);
}

void ParticleLayoutItem::removeItemWithParticle(ItemWithParticles* particle)
{
    m_particles.delete_element(particle);
}

bool ParticleLayoutItem::totalDensityIsDefinedByInterference() const
{
    return dynamic_cast<const Interference2DAbstractLatticeItem*>(m_interference.certainItem())
           || dynamic_cast<const InterferenceHardDiskItem*>(m_interference.certainItem());
}

void ParticleLayoutItem::writeTo(QXmlStreamWriter* w) const
{
    m_own_density.writeTo2(w, Tag::OwnDensity);
    XML::writeTaggedElement(w, Tag::InterferenceFunction, m_interference);
    for (auto* t : m_particles)
        XML::writeChosen<ItemWithParticles, ParticleCatalog>(t, w, Tag::Particle);
    XML::writeTaggedValue(w, Tag::ExpandLayoutGroupbox, expandParticleLayout);
    XML::writeTaggedValue(w, Tag::ExpandInterferenceGroupbox, expandInterference);
}

void ParticleLayoutItem::readFrom(QXmlStreamReader* r)
{
    m_particles.clear();
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::OwnDensity) {
            m_own_density.readFrom2(r, tag);
        } else if (tag == Tag::InterferenceFunction)
            XML::readTaggedElement(r, tag, m_interference);
        else if (tag == Tag::Particle)
            m_particles.push_back(
                XML::readChosen<ItemWithParticles, ParticleCatalog>(r, tag, m_materials));
        else if (tag == Tag::ExpandLayoutGroupbox)
            expandParticleLayout = XML::readTaggedBool(r, tag);
        else if (tag == Tag::ExpandInterferenceGroupbox)
            expandInterference = XML::readTaggedBool(r, tag);
        else
            r->skipCurrentElement();
    }
}

void ParticleLayoutItem::updateSeed() const
{
    unsigned s = static_cast<unsigned>(std::chrono::system_clock::now().time_since_epoch().count());
    seed = Math::GenerateNextSeed(s);
}
