//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Device/InstrumentItems.cpp
//! @brief     Implement class InstrumentItem and all its children
//!
//! @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/Device/InstrumentItems.h"
#include "Base/Axis/Scale.h"
#include "Base/Const/Units.h"
#include "Base/Util/Assert.h"
#include "Device/Beam/Beam.h"
#include "Device/Beam/IFootprint.h"
#include "Device/Coord/CoordSystem1D.h"
#include "Device/Coord/CoordSystem2D.h"
#include "Device/Data/Datafield.h"
#include "Device/Detector/OffspecDetector.h"
#include "GUI/Model/Axis/PointwiseAxisItem.h"
#include "GUI/Model/Beam/BeamAngleItems.h"
#include "GUI/Model/Beam/BeamWavelengthItem.h"
#include "GUI/Model/Beam/FootprintItems.h"
#include "GUI/Model/Beam/GrazingScanItem.h"
#include "GUI/Model/Beam/SourceItems.h"
#include "GUI/Model/Data/DataItem.h"
#include "GUI/Model/Descriptor/DistributionItems.h"
#include "GUI/Model/Detector/OffspecDetectorItem.h"
#include "GUI/Model/Detector/RectangularDetectorItem.h"
#include "GUI/Model/Device/BackgroundItems.h"
#include "GUI/Model/Device/InstrumentItemCatalog.h"
#include "GUI/Model/Device/InstrumentItems.h"
#include "GUI/Model/Device/RealItem.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "GUI/Support/Util/CoordName.h"
#include "GUI/Support/XML/Backup.h"
#include "Param/Distrib/Distributions.h"
#include "Sample/Multilayer/MultiLayer.h"
#include "Sim/Background/IBackground.h"
#include "Sim/Scan/AlphaScan.h"
#include "Sim/Simulation/includeSimulations.h"
#include <numbers>

using std::numbers::pi;

namespace {
namespace Tag {

const QString AlphaAxis("AlphaAxis");
const QString AnalyzerBlochVector("AnalyzerBlochVector");
const QString AnalyzerDirection("AnalyzerDirection");   // OBSOLETE since v21
const QString AnalyzerEfficiency("AnalyzerEfficiency"); // OBSOLETE since v21
const QString Background("Background");
const QString BaseData("BaseData");
const QString Beam("Beam");
const QString Description("Description");
const QString Detector("Detector");
const QString ExpandBeamParametersGroupbox("ExpandBeamParametersGroupbox");
const QString ExpandDetectorGroupbox("ExpandDetectorGroupbox");
const QString ExpandEnvironmentGroupbox("ExpandEnvironmentGroupbox");
const QString ExpandInfoGroupbox("ExpandInfoGroupbox");
const QString ExpandPolarizerAlanyzerGroupbox("ExpandPolarizerAlanyzerGroupbox");
const QString Id("Id");
const QString Name("Name");
const QString Polarization("Polarization"); // OBSOLETE since v21
const QString PolarizerBlochVector("PolarizerBlochVector");
const QString Scan("Scan");
const QString ScanParameters("ScanParameters");
const QString WithPolarizerAnalyzer("WithPolarizerAnalyzer"); // OBSOLETE since v21
const QString WithPolarizer("WithPolarizer");
const QString WithAnalyzer("WithAnalyzer");
const QString ZAxis("ZAxis");

} // namespace Tag

void setBeamDistribution(ParameterDistribution::WhichParameter which,
                         const BeamDistributionItem* item, ISimulation* simulation)
{
    if (!item->distributionItem())
        return;
    if (std::unique_ptr<IDistribution1D> d = item->createDistribution1D())
        simulation->addParameterDistribution(which, *d);
}

} // namespace

//  ************************************************************************************************
//  class InstrumentItem
//  ************************************************************************************************

InstrumentItem::InstrumentItem()
    : m_withPolarizer(false)
    , m_withAnalyzer(false)
{
    m_id = QUuid::createUuid().toString();
    m_polarizerBlochVector.init("Polarizer Bloch vector",
                                "Beam polarizer direction times efficiency", Unit::unitless,
                                "polarizerBlochVector");
    m_analyzerBlochVector.init("Analyzer Bloch vector",
                               "Polarization analyzer direction times efficiency", Unit::unitless,
                               "analyzerBlochVector");
    m_background.init("Background", "");
}

InstrumentItem* InstrumentItem::createItemCopy() const
{
    const auto type = InstrumentItemCatalog::type(this);
    auto* copy = InstrumentItemCatalog::create(type);
    GUI::Util::copyContents(this, copy);
    return copy;
}

bool InstrumentItem::alignedWith(const RealItem* item) const
{
    return shape() == item->shape();
}

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

    // id
    w->writeStartElement(Tag::Id);
    XML::writeAttribute(w, XML::Attrib::value, m_id);
    w->writeEndElement();

    // name
    w->writeStartElement(Tag::Name);
    XML::writeAttribute(w, XML::Attrib::value, m_name);
    w->writeEndElement();

    // description
    w->writeStartElement(Tag::Description);
    XML::writeAttribute(w, XML::Attrib::value, m_description);
    w->writeEndElement();

    // with polarizer?
    w->writeStartElement(Tag::WithPolarizer);
    XML::writeAttribute(w, XML::Attrib::value, m_withPolarizer);
    w->writeEndElement();

    // beam polarizer
    w->writeStartElement(Tag::PolarizerBlochVector);
    m_polarizerBlochVector.writeTo(w);
    w->writeEndElement();

    // with analyzer?
    w->writeStartElement(Tag::WithAnalyzer);
    XML::writeAttribute(w, XML::Attrib::value, m_withAnalyzer);
    w->writeEndElement();

    // polarization analyzer
    w->writeStartElement(Tag::AnalyzerBlochVector);
    m_analyzerBlochVector.writeTo(w);
    w->writeEndElement();

    // background
    w->writeStartElement(Tag::Background);
    m_background.writeTo(w);
    w->writeEndElement();

    // info groupbox: is expanded?
    w->writeStartElement(Tag::ExpandInfoGroupbox);
    XML::writeAttribute(w, XML::Attrib::value, m_expandInfo);
    w->writeEndElement();

    // polarizer analyzer groupbox: is expanded?
    w->writeStartElement(Tag::ExpandPolarizerAlanyzerGroupbox);
    XML::writeAttribute(w, XML::Attrib::value, m_expandPolarizerAlanyzer);
    w->writeEndElement();

    // environment groupbox: is expanded?
    w->writeStartElement(Tag::ExpandEnvironmentGroupbox);
    XML::writeAttribute(w, XML::Attrib::value, m_expandEnvironment);
    w->writeEndElement();

    // detector groupbox: is expanded?
    w->writeStartElement(Tag::ExpandDetectorGroupbox);
    XML::writeAttribute(w, XML::Attrib::value, m_expandDetector);
    w->writeEndElement();
}

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

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

        // id
        if (tag == Tag::Id) {
            XML::readAttribute(r, XML::Attrib::value, &m_id);
            XML::gotoEndElementOfTag(r, tag);

            // name
        } else if (tag == Tag::Name) {
            XML::readAttribute(r, XML::Attrib::value, &m_name);
            XML::gotoEndElementOfTag(r, tag);

            // description
        } else if (tag == Tag::Description) {
            XML::readAttribute(r, XML::Attrib::value, &m_description);
            XML::gotoEndElementOfTag(r, tag);

            // with polarizer or analyzer? (OBSOLETE since v21 (version == 2))
        } else if (version == 1 && tag == Tag::WithPolarizerAnalyzer) {
            XML::readAttribute(r, XML::Attrib::value, &m_withPolarizer);
            m_withAnalyzer = m_withPolarizer;
            XML::gotoEndElementOfTag(r, tag);

            // with polarizer?
        } else if (tag == Tag::WithPolarizer) {
            XML::readAttribute(r, XML::Attrib::value, &m_withPolarizer);
            XML::gotoEndElementOfTag(r, tag);

            // polarization (alternate tag OBSOLETE since v21 (version == 2))
        } else if (tag == Tag::PolarizerBlochVector || (version == 1 && tag == Tag::Polarization)) {
            m_polarizerBlochVector.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // with analyzer?
        } else if (tag == Tag::WithAnalyzer) {
            XML::readAttribute(r, XML::Attrib::value, &m_withAnalyzer);
            XML::gotoEndElementOfTag(r, tag);

            // analyzer
        } else if (tag == Tag::AnalyzerBlochVector) {
            m_analyzerBlochVector.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // analyzer direction OBSOLETE since v21 (version == 2)
        } else if (version == 1 && tag == Tag::AnalyzerDirection) {
            m_analyzerBlochVector.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // analyzer efficiency OBSOLETE since v21 (version == 2)
        } else if (version == 1 && tag == Tag::AnalyzerEfficiency) {
            DoubleProperty analyzerEfficiency;
            analyzerEfficiency.readFrom(r);
            m_analyzerBlochVector.setR3(m_analyzerBlochVector.r3() * analyzerEfficiency.value());
            XML::gotoEndElementOfTag(r, tag);

            // background
        } else if (tag == Tag::Background) {
            m_background.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // info groupbox: is expanded?
        } else if (tag == Tag::ExpandInfoGroupbox) {
            XML::readAttribute(r, XML::Attrib::value, &m_expandInfo);
            XML::gotoEndElementOfTag(r, tag);

            // polarizer analyzer groupbox: is expanded?
        } else if (tag == Tag::ExpandPolarizerAlanyzerGroupbox) {
            XML::readAttribute(r, XML::Attrib::value, &m_expandPolarizerAlanyzer);
            XML::gotoEndElementOfTag(r, tag);

            // environment groupbox: is expanded?
        } else if (tag == Tag::ExpandEnvironmentGroupbox) {
            XML::readAttribute(r, XML::Attrib::value, &m_expandEnvironment);
            XML::gotoEndElementOfTag(r, tag);

            // detector groupbox: is expanded?
        } else if (tag == Tag::ExpandDetectorGroupbox) {
            XML::readAttribute(r, XML::Attrib::value, &m_expandDetector);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

//  ************************************************************************************************
//  class ScanningItem
//  ************************************************************************************************

ScanningFunctionality::ScanningFunctionality(double intensity)
    : m_scanItem(new ScanItem())
{
    m_scanItem->intensity().setValue(intensity); // overwrite default value set by BeamItem c'tor
}

//! Takes ownership of argument 'axis'.
std::unique_ptr<IBeamScan> ScanningFunctionality::createScan(const Scale& axis) const
{
    auto result = std::make_unique<AlphaScan>(axis);

    result->setIntensity(scanItem()->intensity());

    FootprintItemCatalog::CatalogedType* const footprint_item =
        scanItem()->footprintSelection().currentItem();
    result->setFootprint(footprint_item->createFootprint().get());

    {
        const BeamWavelengthItem* it = scanItem()->wavelengthItem();
        ASSERT(it);
        const auto* distr_item =
            dynamic_cast<const SymmetricResolutionItem*>(it->distributionItem());
        ASSERT(distr_item);

        const double scale = it->scaleFactor();
        if (std::unique_ptr<IDistribution1D> distr = distr_item->createDistribution(scale))
            result->setWavelengthDistribution(*distr);
        else
            result->setWavelength(scanItem()->wavelength());
    }

    {
        const GrazingScanItem* it = scanItem()->grazingScanItem();
        ASSERT(it);
        const auto* distr_item =
            dynamic_cast<const SymmetricResolutionItem*>(it->distributionItem());
        ASSERT(distr_item);

        const double scale = it->scaleFactor();
        if (std::unique_ptr<IDistribution1D> distr = distr_item->createDistribution(scale))
            result->setAngleDistribution(*distr);
    }

    return result;
}

void ScanningFunctionality::writeScanTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // scan
    w->writeStartElement(Tag::Scan);
    m_scanItem->writeTo(w);
    w->writeEndElement();
}

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

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

        // scan
        if (tag == Tag::Scan) {
            m_scanItem->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}


//  ************************************************************************************************
//  class SpecularInstrumentItem
//  ************************************************************************************************

SpecularInstrumentItem::SpecularInstrumentItem()
    : ScanningFunctionality(1e6)
{
}

std::vector<int> SpecularInstrumentItem::shape() const
{
    return {scanItem()->inclinationAxisItem()->binCount()};
}

void SpecularInstrumentItem::updateToRealData(const RealItem* item)
{
    if (shape().size() != item->shape().size())
        throw std::runtime_error("Specular instrument type is incompatible with passed data shape");

    const auto& data = item->nativeDatafield()->axis(0);
    std::unique_ptr<const ICoordSystem> converter = createCoordSystem();
    scanItem()->updateToData(data, item->nativeDataUnits(), *converter);
}

bool SpecularInstrumentItem::alignedWith(const RealItem* item) const
{
    const QString native_units = item->nativeDataUnits();
    if (native_units == "nbins")
        return scanItem()->grazingScanItem()->uniformAlphaAxisSelected()
               && shape() == item->shape();

    if (!scanItem()->grazingScanItem()->pointwiseAlphaAxisSelected())
        return false;

    const auto* axisItem =
        dynamic_cast<const PointwiseAxisItem*>(scanItem()->grazingScanItem()->alphaAxisItem());
    ASSERT(axisItem);

    if (axisItem->nativeAxisUnits() != native_units)
        return false;

    const auto* instrumentAxis = axisItem->axis();
    if (!instrumentAxis)
        return false;

    if (!item->hasNativeData())
        return false;

    // TODO remove native data from everywhere
    // https://jugit.fz-juelich.de/mlz/bornagain/-/issues/331
    const auto& native_axis = item->nativeDatafield()->axis(0);
    return *instrumentAxis == native_axis;
}

std::unique_ptr<const ICoordSystem> SpecularInstrumentItem::createCoordSystem() const
{
    auto* axis_item = scanItem()->inclinationAxisItem();

    if (auto* pointwise_axis = dynamic_cast<PointwiseAxisItem*>(axis_item)) {
        if (!pointwise_axis->axis()) // workaround for loading project
            return nullptr;
        Coords native_units =
            GUI::Util::CoordName::coordFromName(pointwise_axis->nativeAxisUnits());
        return std::make_unique<AngularReflectometryCoords>(scanItem()->wavelength(),
                                                            *pointwise_axis->axis(), native_units);
    }

    Scale axis = EquiDivision(axis_item->title().toStdString(), axis_item->binCount(),
                              axis_item->min(), axis_item->max());

    return std::make_unique<AngularReflectometryCoords>(scanItem()->wavelength(), axis,
                                                        Coords::DEGREES);
}

ISimulation* SpecularInstrumentItem::createSimulation(const MultiLayer& sample) const
{
    BasicAxisItem* const axis_item = scanItem()->inclinationAxisItem();

    const auto converter = createCoordSystem();
    std::unique_ptr<Scale> converted_axis(converter->convertedAxis(0, Coords::DEGREES));

    std::unique_ptr<Scale> axis = axis_item->itemToAxis(Units::deg, *converted_axis);
    std::unique_ptr<IBeamScan> scan = createScan(*axis);
    if (withPolarizer())
        scan->setPolarization(m_polarizerBlochVector);
    if (withAnalyzer())
        scan->setAnalyzer(m_analyzerBlochVector);
    auto* result = new SpecularSimulation(*scan, sample);

    if (const auto background = backgroundItem()->createBackground())
        result->setBackground(*background);
    return result;
}

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

    // instrument parameters from base class
    w->writeStartElement(Tag::BaseData);
    InstrumentItem::writeTo(w);
    w->writeEndElement();

    // scan parameters from base class
    w->writeStartElement(Tag::ScanParameters);
    ScanningFunctionality::writeScanTo(w);
    w->writeEndElement();
}

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

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

        // instrument parameters from base class
        if (tag == Tag::BaseData) {
            InstrumentItem::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // scan parameters from base class
        } else if (tag == Tag::ScanParameters) {
            ScanningFunctionality::readScanFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

//  ************************************************************************************************
//  class DepthprobeInstrumentItem
//  ************************************************************************************************

DepthprobeInstrumentItem::DepthprobeInstrumentItem()
    : ScanningFunctionality(1e8)
{
    auto* axisItem = scanItem()->inclinationAxisItem();
    axisItem->setMin(0.0);
    axisItem->setMax(1.0);
    axisItem->setBinCount(500);

    m_zAxis.initMin("Min", "Starting value below sample horizon", -100.0, Unit::nanometer,
                    RealLimits::limitless());
    m_zAxis.initMax("Max", "Ending value above sample horizon", 100.0, Unit::nanometer,
                    RealLimits::limitless());
}

std::vector<int> DepthprobeInstrumentItem::shape() const
{
    return {}; // no certain shape to avoid linking to real data
}

void DepthprobeInstrumentItem::updateToRealData(const RealItem*)
{
    ASSERT(false);
}

std::unique_ptr<const ICoordSystem> DepthprobeInstrumentItem::createCoordSystem() const
{
    // TODO the approach to changing units should be changed
    // https://jugit.fz-juelich.de/mlz/bornagain/-/issues/478
    // https://jugit.fz-juelich.de/mlz/bornagain/-/issues/505

    BasicAxisItem* const axis_item = scanItem()->inclinationAxisItem();
    std::unique_ptr<Scale> axis = axis_item->itemToRegularAxis(Units::deg);

    std::vector<const Scale*> axes({axis.release(), m_zAxis.createAxis(1.)->clone()});
    return std::make_unique<DepthprobeCoords>(std::move(axes), (2 * pi) / scanItem()->wavelength());
}

ISimulation* DepthprobeInstrumentItem::createSimulation(const MultiLayer& sample) const
{
    BasicAxisItem* const axis_item = scanItem()->inclinationAxisItem();
    std::unique_ptr<Scale> axis = axis_item->itemToRegularAxis(Units::deg);
    std::unique_ptr<IBeamScan> scan = createScan(*axis);
    // 'DepthprobeSimulation' works only with scalar flux so polarization is not passed there
    return new DepthprobeSimulation(*scan, sample, *m_zAxis.createAxis(1.));
}

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

    // instrument parameters from base class
    w->writeStartElement(Tag::BaseData);
    InstrumentItem::writeTo(w);
    w->writeEndElement();

    // scan parameters from base class
    w->writeStartElement(Tag::ScanParameters);
    ScanningFunctionality::writeScanTo(w);
    w->writeEndElement();

    // z axis
    w->writeStartElement(Tag::ZAxis);
    m_zAxis.writeTo(w);
    w->writeEndElement();
}

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

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

        // instrument parameters from base class
        if (tag == Tag::BaseData) {
            InstrumentItem::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // scan parameters from base class
        } else if (tag == Tag::ScanParameters) {
            ScanningFunctionality::readScanFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // z axis
        } else if (tag == Tag::ZAxis) {
            m_zAxis.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

//  ************************************************************************************************
//  class OffspecInstrumentItem
//  ************************************************************************************************

OffspecInstrumentItem::OffspecInstrumentItem()
    : ScanningFunctionality(1e8)
    , m_detector(new OffspecDetectorItem)
{
}

std::vector<int> OffspecInstrumentItem::shape() const
{
    return {scanItem()->grazingScanItem()->nBins(), detectorItem()->ySize()};
}

void OffspecInstrumentItem::updateToRealData(const RealItem* dataItem)
{
    if (!dataItem)
        return;

    const auto data_shape = dataItem->shape();
    if (shape().size() != data_shape.size())
        throw std::runtime_error("Offspec instrument type is incompatible with passed data shape");

    throw std::runtime_error("OffspecInstrumentItem::updateToRealData not yet implemented");
    // ... set to data_shape[0]

    detectorItem()->setYSize(data_shape[1]);
}

std::unique_ptr<const ICoordSystem> OffspecInstrumentItem::createCoordSystem() const
{
    BasicAxisItem* const axis_item = scanItem()->inclinationAxisItem();
    std::unique_ptr<Scale> axis = axis_item->itemToRegularAxis(Units::deg);
    return std::make_unique<OffspecCoords>(std::vector<const Scale*>{
        axis.release(), detectorItem()->createOffspecDetector()->axis(1).clone()});
}

ISimulation* OffspecInstrumentItem::createSimulation(const MultiLayer& sample) const
{
    const auto detector = detectorItem()->createOffspecDetector();
    detector->setAnalyzer(m_analyzerBlochVector);

    BasicAxisItem* const axis_item = scanItem()->inclinationAxisItem();
    std::unique_ptr<Scale> axis = axis_item->itemToRegularAxis(Units::deg);
    std::unique_ptr<IBeamScan> scan = createScan(*axis);
    if (withPolarizer())
        scan->setPolarization(m_polarizerBlochVector);
    if (withAnalyzer())
        scan->setAnalyzer(m_analyzerBlochVector);
    auto* result = new OffspecSimulation(*scan, sample, *detector);

    if (const auto background = backgroundItem()->createBackground())
        result->setBackground(*background);
    return result;
}

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

    // instrument parameters from base class
    w->writeStartElement(Tag::BaseData);
    InstrumentItem::writeTo(w);
    w->writeEndElement();

    // scan parameters from base class
    w->writeStartElement(Tag::ScanParameters);
    ScanningFunctionality::writeScanTo(w);
    w->writeEndElement();

    // detector
    w->writeStartElement(Tag::Detector);
    m_detector->writeTo(w);
    w->writeEndElement();
}

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

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

        // instrument parameters from base class
        if (tag == Tag::BaseData) {
            InstrumentItem::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // scan parameters from base class
        } else if (tag == Tag::ScanParameters) {
            ScanningFunctionality::readScanFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // detector
        } else if (tag == Tag::Detector) {
            m_detector->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

//  ************************************************************************************************
//  class GISASInstrumentItem
//  ************************************************************************************************

GISASInstrumentItem::GISASInstrumentItem()
{
    m_detector.init("Detector", "");
    m_beamItem.reset(new BeamItem());
}

std::vector<int> GISASInstrumentItem::shape() const
{
    auto* detector_item = detectorItem();
    return {detector_item->xSize(), detector_item->ySize()};
}

void GISASInstrumentItem::updateToRealData(const RealItem* item)
{
    if (!item)
        return;

    const auto data_shape = item->shape();
    if (shape().size() != data_shape.size())
        throw std::runtime_error("GISAS instrument type is incompatible with passed data shape.");
    detectorItem()->setXSize(data_shape[0]);
    detectorItem()->setYSize(data_shape[1]);
}

std::unique_ptr<const ICoordSystem> GISASInstrumentItem::createCoordSystem() const
{
    std::unique_ptr<const ICoordSystem> result;
    result.reset(normalDetector()->scatteringCoords(*beamItem()->createBeam()));
    return result;
}

ISimulation* GISASInstrumentItem::createSimulation(const MultiLayer& sample) const
{
    const auto beam = beamItem()->createBeam();
    if (withPolarizer())
        beam->setPolarization(m_polarizerBlochVector);
    const auto detector = detectorItem()->createDetector();
    detector->setDetectorNormal(beam->ki());
    if (withAnalyzer())
        detector->setAnalyzer(m_analyzerBlochVector);
    auto* result = new ScatteringSimulation(*beam, sample, *detector);

    setBeamDistribution(ParameterDistribution::BeamWavelength, beamItem()->wavelengthItem(),
                        result);
    setBeamDistribution(ParameterDistribution::BeamInclinationAngle,
                        beamItem()->beamDistributionItem(), result);
    setBeamDistribution(ParameterDistribution::BeamAzimuthalAngle, beamItem()->azimuthalAngleItem(),
                        result);

    if (const auto background = backgroundItem()->createBackground())
        result->setBackground(*background);
    return result;
}

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

    // parameters from base class
    w->writeStartElement(Tag::BaseData);
    InstrumentItem::writeTo(w);
    w->writeEndElement();

    // beam
    w->writeStartElement(Tag::Beam);
    m_beamItem->writeTo(w);
    w->writeEndElement();

    // detector
    w->writeStartElement(Tag::Detector);
    m_detector.writeTo(w);
    w->writeEndElement();
}

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

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

        // parameters from base class
        if (tag == Tag::BaseData) {
            InstrumentItem::readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // beam
        } else if (tag == Tag::Beam) {
            m_beamItem->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // detector
        } else if (tag == Tag::Detector) {
            m_detector.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

std::unique_ptr<IDetector> GISASInstrumentItem::normalDetector() const
{
    std::unique_ptr<IDetector> result = detectorItem()->createDetector();
    result->setDetectorNormal(beamItem()->createBeam()->ki());
    return result;
}

void GISASInstrumentItem::importMasks(const MaskContainerItem* maskContainer)
{
    detectorItem()->importMasks(maskContainer);
}
