//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Detector/DetectorItem.cpp
//! @brief     Implements classes DetectorItems.
//!
//! @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/Detector/DetectorItem.h"
#include "Base/Axis/Scale.h"
#include "Base/Const/Units.h"
#include "Base/Util/Assert.h"
#include "Device/Detector/Detector2D.h"
#include "Device/Mask/IShape2D.h"
#include "Device/Resolution/ResolutionFunction2DGaussian.h"
#include "GUI/Model/Detector/ResolutionFunctionItems.h"
#include "GUI/Model/Mask/MasksSet.h"
#include "GUI/Model/Util/Backup.h"

namespace {
namespace Tag {

const QString ExpandResolutionFuncGroupbox("ExpandResolutionFuncGroupbox");
const QString AlphaAxis("AlphaAxis");
const QString PhiAxis("PhiAxis");
const QString ResolutionFunction("ResolutionFunction");
const QString MaskContainer("MaskContainer");
const QString ProjectionContainer("ProjectionContainer");

} // namespace Tag
} // namespace


DetectorItem::DetectorItem()
    : m_masks(std::make_unique<MasksSet>())
    , m_prjns(std::make_unique<MasksSet>())
{
    m_phi_axis.initWidth("Span (deg)", "Full width in phi direction", 4.0,
                         RealLimits::limited(0, 180));
    m_phi_axis.initCenter("Center (deg)", "Angle phi of detector center", 0.,
                          RealLimits::limited(-90, 90));

    m_alpha_axis.initWidth("Span (deg)", "Full width in alpha direction", 4.0,
                           RealLimits::limited(0, 180));
    m_alpha_axis.initCenter("Center (deg)", "Angle alpha of detector center", 0.,
                            RealLimits::limited(-90, 90));

    m_resolution_function.initWithArgs("Resolution function", "Detector resolution function",
                                       ResolutionFunctionItemCatalog::Type::None);
}

DetectorItem::~DetectorItem() = default;

std::unique_ptr<IDetector> DetectorItem::createDetector() const
{
    const int n_x = m_phi_axis.nbins();
    const double x_wid = Units::deg2rad(m_phi_axis.width());
    const double x_cen = Units::deg2rad(m_phi_axis.center());

    const int n_y = m_alpha_axis.nbins();
    const double y_wid = Units::deg2rad(m_alpha_axis.width());
    const double y_cen = Units::deg2rad(m_alpha_axis.center());

    auto result = std::make_unique<Detector2D>(x_wid, y_wid, n_x, n_y, x_cen, y_cen);

    if (m_masks) {
        for (const auto m : *m_masks) {
            if (m->isVisible()) {
                if (auto* ii = dynamic_cast<const RegionOfInterestItem*>(m)) {
                    result->setRegionOfInterest(ii->xLow(), ii->yLow(), ii->xUp(), ii->yUp());
                } else {
                    std::unique_ptr<IShape2D> shape(m->createShape());
                    result->addBinMask(*shape, m->maskValue());
                }
            }
        }
    }

    if (auto resFunc = createResolutionFunction())
        result->setResolutionFunction(*resFunc);

    return result;
}

void DetectorItem::setMasks(MasksSet* item)
{
    m_masks.reset(item);
}

std::unique_ptr<IResolutionFunction2D> DetectorItem::createResolutionFunction() const
{
    return m_resolution_function->createResolutionFunction();
}

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

    // phi axis
    w->writeStartElement(Tag::PhiAxis);
    m_phi_axis.writeTo(w);
    w->writeEndElement();

    // alpha axis
    w->writeStartElement(Tag::AlphaAxis);
    m_alpha_axis.writeTo(w);
    w->writeEndElement();

    // resolution function
    w->writeStartElement(Tag::ResolutionFunction);
    m_resolution_function.writeTo(w);
    w->writeEndElement();

    // resolution function groupbox: is expanded?
    w->writeStartElement(Tag::ExpandResolutionFuncGroupbox);
    XML::writeAttribute(w, XML::Attrib::value, expandResolutionFunc);
    w->writeEndElement();

    // masks and projections
    w->writeStartElement(Tag::MaskContainer);
    m_masks->writeTo(w);
    w->writeEndElement();

    // projections
    w->writeStartElement(Tag::ProjectionContainer);
    m_prjns->writeTo(w);
    w->writeEndElement();
}

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

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

        // phi axis
        if (tag == Tag::PhiAxis) {
            m_phi_axis.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // alpha axis
        } else if (tag == Tag::AlphaAxis) {
            m_alpha_axis.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // resolution function
        } else if (tag == Tag::ResolutionFunction) {
            m_resolution_function.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // resolution function groupbox: is expanded?
        } else if (tag == Tag::ExpandResolutionFuncGroupbox) {
            XML::readAttribute(r, XML::Attrib::value, &expandResolutionFunc);
            XML::gotoEndElementOfTag(r, tag);

            // masks
        } else if (tag == Tag::MaskContainer) {
            m_masks->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // projections
        } else if (tag == Tag::ProjectionContainer) {
            m_prjns->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}
