//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Job/ParameterTreeUtils.cpp
//! @brief     Implements ParameterTreeUtils namespace
//!
//! @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/Job/ParameterTreeUtils.h"
#include "GUI/Model/Fit/ParameterTreeItems.h"
#include "GUI/Model/Group/GroupItem.h"
#include "GUI/Model/Group/PropertyItem.h"
#include "GUI/Model/Instrument/InstrumentItems.h"
#include "GUI/Model/Job/JobItem.h"
#include "GUI/Model/Material/MaterialItem.h"
#include "GUI/Model/Sample/InterferenceItems.h"
#include "GUI/Model/Sample/Lattice2DItems.h"
#include "GUI/Model/Sample/LayerItem.h"
#include "GUI/Model/Sample/MultiLayerItem.h"
#include "GUI/Model/Sample/ParticleCoreShellItem.h"
#include "GUI/Model/Sample/ParticleItem.h"
#include "GUI/Model/Sample/ParticleLayoutItem.h"
#include "GUI/Model/Session/ModelPath.h"
#include "GUI/Model/Types/VectorDescriptor.h"
#include "GUI/Util/Error.h"
#include <QStack>
#include <boost/polymorphic_cast.hpp>

using boost::polymorphic_downcast;

namespace {

void handleItem(ParameterContainerItem* container, SessionItem* tree, SessionItem* source,
                bool recreateBackupValues)
{
    if (tree->hasModelType<ParameterLabelItem>())
        tree->setDisplayName(source->itemName());

    else if (tree->hasModelType<ParameterItem>()) {
        tree->setDisplayName(source->itemName());

        const double sourceValue = source->value().toDouble();
        tree->setValue(QVariant(sourceValue));
        auto* parItem = polymorphic_downcast<ParameterItem*>(tree);
        parItem->linkToSessionItem(source);
        if (recreateBackupValues)
            container->setBackupValue(parItem->link(), sourceValue);
        return;
    }

    else
        return;

    for (SessionItem* child : source->children()) {

        // later in handleItems the position of a shell particle has to be ignored.
        if (auto* c = dynamic_cast<ParticleCoreShellItem*>(child))
            if (c->shell())
                c->shell()->positionItem()->setEnabled(false);

        // later in handleItems the rotation of an interference may have to be ignored.
        if (auto* c = dynamic_cast<Interference2DAbstractLatticeItem*>(child))
            if (c->latticeType().currentItem())
                c->latticeType().currentItem()->enableRotationAngle(!c->xiIntegration());

        if (child->isVisible() && child->isEnabled()) {
            if (child->hasModelType<PropertyItem>()) {
                if (child->value().type() == QVariant::Double) {
                    auto* branch = tree->model()->insertItem<ParameterItem>(tree);
                    handleItem(container, branch, child, recreateBackupValues);
                }
            } else if (child->hasModelType<GroupItem>()) {
                SessionItem* currentItem = dynamic_cast<GroupItem*>(child)->currentItem();
                if (currentItem && currentItem->numberOfChildren() > 0) {
                    auto* branch = tree->model()->insertItem<ParameterLabelItem>(tree);
                    handleItem(container, branch, currentItem, recreateBackupValues);
                }
            } else {
                auto* branch = tree->model()->insertItem<ParameterLabelItem>(tree);
                handleItem(container, branch, child, recreateBackupValues);
            }
        }
    }
}

//! Populates ParameterContainer with ParameterItem's corresponding to all properties found
//! in a source item.

void populateParameterContainer(ParameterContainerItem* container, SessionItem* source,
                                bool recreateBackupValues)
{
    auto* sourceLabel = container->model()->insertItem<ParameterLabelItem>(container);
    handleItem(container, sourceLabel, source, recreateBackupValues);
}

} // namespace

void GUI::Model::ParameterTreeUtils::createParameterTree(JobItem* jobItem,
                                                         bool recreateBackupValues)
{
    auto* container = jobItem->parameterContainerItem();
    if (!container)
        container = jobItem->createParameterContainerItem();

    // add the job's materials
    auto* materialTopLabel = container->model()->insertItem<ParameterLabelItem>(container);
    materialTopLabel->setDisplayName("Materials");
    for (auto* material : jobItem->materialItems().materialItems()) {
        auto* materialLabel = container->model()->insertItem<ParameterLabelItem>(container);
        materialLabel->setDisplayName(material->materialName());

        DoubleDescriptors descriptors;
        if (material->hasRefractiveIndex())
            descriptors << material->delta() << material->beta();
        else
            descriptors << material->sldRe() << material->sldIm();

        // TODO: remove when specular instrument is ready for magnetization
        if (!jobItem->isSpecularJob())
            descriptors << material->magnetizationVector().x << material->magnetizationVector().y
                        << material->magnetizationVector().z;

        for (const auto& d : descriptors) {
            auto* materialValue = materialLabel->model()->insertItem<ParameterItem>(materialLabel);
            materialValue->setDisplayName(d.label);
            materialValue->setValue(QVariant(d.get()));
            materialValue->linkToDescriptor(d);
            if (recreateBackupValues)
                container->setBackupValue(materialValue->link(), d.get());
        }
    }

    // add sample.
    // #baMigration To ignore thickness/roughness, they are disabled. This has to be changed after
    // SessionModel migration. Compare also to handling in
    // LayerForm::updateLayerPositionDependentElements()
    // dto. for totalDensity of particle layout
    for (auto* layer : jobItem->sampleItem()->layers()) {
        const bool isFirstLayer = jobItem->sampleItem()->layers().first() == layer;
        const bool isLastLayer = jobItem->sampleItem()->layers().last() == layer;

        layer->setRoughnessEnabled(!isFirstLayer);
        layer->setThicknessEnabled(!isFirstLayer && !isLastLayer);

        for (auto* layout : layer->layouts())
            layout->enableDensity(!layout->totalDensityIsDefinedByInterference());
    }

    populateParameterContainer(container, jobItem->sampleItem(), recreateBackupValues);

    // add instrument
    populateParameterContainer(container, jobItem->instrumentItem(), recreateBackupValues);
}