//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Sim/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/Sim/InstrumentItems.h"
#include "Base/Axis/Frame.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/Data/Datafield.h"
#include "Device/Detector/IDetector.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/DetectorItem.h"
#include "GUI/Model/Detector/OffspecDetectorItem.h"
#include "GUI/Model/File/DatafileItem.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "GUI/Model/Sim/BackgroundItems.h"
#include "GUI/Model/Sim/InstrumentItems.h"
#include "GUI/Model/Sim/InstrumentsCatalog.h"
#include "GUI/Model/Util/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 <QUuid>
#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_with_polarizer(false)
    , m_with_analyzer(false)
{
    m_id = QUuid::createUuid().toString();
    m_polarizer_bloch_vector.init("Polarizer Bloch vector",
                                  "Beam polarizer direction times efficiency",
                                  "polarizerBlochVector");
    m_analyzer_bloch_vector.init("Analyzer Bloch vector",
                                 "Polarization analyzer direction times efficiency",
                                 "analyzerBlochVector");
    m_background.initWithArgs("Background", "", BackgroundItemCatalog::Type::Constant);
}

InstrumentItem::~InstrumentItem() = default;

InstrumentItem* InstrumentItem::clone() const
{
    const auto type = InstrumentsCatalog::type(this);
    auto* copy = InstrumentsCatalog::create(type);
    GUI::Util::copyContents(this, copy);
    copy->setId(QUuid::createUuid().toString());
    return copy;
}

bool InstrumentItem::alignedWith(const DatafileItem* dfileItem) const
{
    return axdims() == dfileItem->axdims();
}

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, name());
    w->writeEndElement();

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

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

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

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

    // polarization analyzer
    w->writeStartElement(Tag::AnalyzerBlochVector);
    m_analyzer_bloch_vector.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, expandInfo);
    w->writeEndElement();

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

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

    // detector groupbox: is expanded?
    w->writeStartElement(Tag::ExpandDetectorGroupbox);
    XML::writeAttribute(w, XML::Attrib::value, 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) {
            setName(XML::readAttributeString(r, XML::Attrib::value));
            XML::gotoEndElementOfTag(r, tag);

            // description
        } else if (tag == Tag::Description) {
            setDescription(XML::readAttributeString(r, XML::Attrib::value));
            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_with_polarizer);
            m_with_analyzer = m_with_polarizer;
            XML::gotoEndElementOfTag(r, tag);

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

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

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

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

            // analyzer direction OBSOLETE since v21 (version == 2)
        } else if (version == 1 && tag == Tag::AnalyzerDirection) {
            m_analyzer_bloch_vector.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_analyzer_bloch_vector.setR3(m_analyzer_bloch_vector.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, &expandInfo);
            XML::gotoEndElementOfTag(r, tag);

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

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

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

        } else
            r->skipCurrentElement();
    }
}

Frame InstrumentItem::makeFrame() const
{
    return *createFrame().release();
}

//  ************************************************************************************************
//  class ScanningFunctionality
//  ************************************************************************************************

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

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

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

    FootprintItemCatalog::BaseType* const footprint_item =
        scanItem()->footprintSelection().certainItem();
    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);
    }

    {
        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_scan_item->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_scan_item->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

//  ************************************************************************************************
//  class Scatter2DInstrumentItem
//  ************************************************************************************************

Scatter2DInstrumentItem::Scatter2DInstrumentItem()
    : m_beam_item(std::make_unique<BeamItem>())
    , m_detector_item(std::make_unique<DetectorItem>())
{
    setName("GISAS");
}

std::vector<int> Scatter2DInstrumentItem::axdims() const
{
    ASSERT(detectorItem());
    return {(int)detectorItem()->phiAxis().nbins(), (int)detectorItem()->alphaAxis().nbins()};
}

void Scatter2DInstrumentItem::updateToRealData(const DatafileItem* dfileItem)
{
    if (!dfileItem)
        return;

    const auto data_shape = dfileItem->axdims();
    if (axdims().size() != data_shape.size())
        throw std::runtime_error("GISAS instrument type is incompatible with passed data shape.");
    ASSERT(detectorItem());
    detectorItem()->phiAxis().setNbins(data_shape[0]);
    detectorItem()->alphaAxis().setNbins(data_shape[1]);
}

std::unique_ptr<Frame> Scatter2DInstrumentItem::createFrame() const
{
    return std::make_unique<Frame>(normalDetector()->clippedFrame());
}

ISimulation* Scatter2DInstrumentItem::createSimulation(const MultiLayer& sample) const
{
    const auto beam = beamItem()->createBeam();
    if (withPolarizer())
        beam->setPolarization(m_polarizer_bloch_vector);
    const auto detector = detectorItem()->createDetector();
    if (withAnalyzer())
        detector->setAnalyzer(m_analyzer_bloch_vector);
    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 Scatter2DInstrumentItem::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_beam_item->writeTo(w);
    w->writeEndElement();

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

void Scatter2DInstrumentItem::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_beam_item->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

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

        } else
            r->skipCurrentElement();
    }
}

std::unique_ptr<IDetector> Scatter2DInstrumentItem::normalDetector() const
{
    return detectorItem()->createDetector();
}

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

OffspecInstrumentItem::OffspecInstrumentItem()
    : ScanningFunctionality(1e8)
    , m_detector(new OffspecDetectorItem)
{
    setName("Offspec");
}

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

void OffspecInstrumentItem::updateToRealData(const DatafileItem* dfileItem)
{
    if (!dfileItem)
        return;

    const auto data_shape = dfileItem->axdims();
    if (axdims().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<Frame> OffspecInstrumentItem::createFrame() const
{
    BasicAxisItem* const axis_item = scanItem()->inclinationAxisItem();
    Scale xAxis = axis_item->makeScale("alpha_i (rad)");
    return std::make_unique<Frame>(xAxis.clone(),
                                   detectorItem()->createOffspecDetector()->axis(1).clone());
}

ISimulation* OffspecInstrumentItem::createSimulation(const MultiLayer& sample) const
{
    const Frame frame = makeFrame();
    const auto detector = detectorItem()->createOffspecDetector();
    std::unique_ptr<PhysicalScan> scan = createScan(frame.axis(0));
    if (withPolarizer())
        scan->setPolarization(m_polarizer_bloch_vector);
    if (withAnalyzer())
        detector->setAnalyzer(m_analyzer_bloch_vector); // offspec uses detector analyzer, not scan
    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 SpecularInstrumentItem
//  ************************************************************************************************

SpecularInstrumentItem::SpecularInstrumentItem()
    : ScanningFunctionality(1e6)
{
    setName("Specular");
}

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

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

    const auto& dataAxis = dfileItem->dataItem()->c_field()->axis(0);
    scanItem()->updateToData(dataAxis);
    scanItem()->grazingScanItem()->updateAxIndicators(makeFrame());
}

bool SpecularInstrumentItem::alignedWith(const DatafileItem* dfileItem) const
{
    if (!dfileItem->holdsDimensionalData())
        return scanItem()->grazingScanItem()->uniformAlphaAxisSelected()
               && axdims() == dfileItem->axdims();

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

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

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

    const auto& native_axis = dfileItem->dataItem()->c_field()->axis(0);
    return *instrumentAxis == native_axis;
}

std::unique_ptr<Frame> SpecularInstrumentItem::createFrame() const
{
    auto* axis_item = scanItem()->inclinationAxisItem();
    ASSERT(axis_item);

    if (auto* pointwise_axis = dynamic_cast<PointwiseAxisItem*>(axis_item)) {
        const Scale* pAxis = pointwise_axis->axis();
        if (!pAxis) // workaround for loading project
            return {};

        if (pAxis->unit() == "1/nm") {
            double lambda = scanItem()->wavelength();
            Scale ax =
                pAxis->transformedScale(Coordinate("alpha_i", "rad").label(), [lambda](double qz) {
                    return std::asin(qz * lambda / 4 / pi);
                });
            return std::make_unique<Frame>(ax.clone());
        }
        return std::make_unique<Frame>(pAxis->clone());
    }

    Scale axis = axis_item->makeScale("alpha_i (rad)");
    return std::make_unique<Frame>(axis.clone());
}

ISimulation* SpecularInstrumentItem::createSimulation(const MultiLayer& sample) const
{
    auto frame = createFrame();
    std::unique_ptr<BeamScan> scan = createScan(frame->axis(0));
    if (withPolarizer())
        scan->setPolarization(m_polarizer_bloch_vector);
    if (withAnalyzer())
        scan->setAnalyzer(m_analyzer_bloch_vector);
    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)
{
    setName("Depthprobe");

    auto* axisItem = scanItem()->inclinationAxisItem();
    axisItem->setMin(0.0);
    axisItem->setMax(1.0);
    axisItem->resize(500);

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

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

void DepthprobeInstrumentItem::updateToRealData(const DatafileItem*)
{
    ASSERT_NEVER;
}

std::unique_ptr<Frame> DepthprobeInstrumentItem::createFrame() const
{
    BasicAxisItem* const axis_item = scanItem()->inclinationAxisItem();
    Scale xAxis = axis_item->makeScale("alpha_i (rad)");
    Scale zAxis = m_z_axis.createAxis("z (nm)");
    std::vector<const Scale*> axes{xAxis.clone(), zAxis.clone()};
    return std::make_unique<Frame>(axes);
}

ISimulation* DepthprobeInstrumentItem::createSimulation(const MultiLayer& sample) const
{
    const Frame frame = makeFrame();
    std::unique_ptr<BeamScan> scan = createScan(frame.axis(0));
    return new DepthprobeSimulation(*scan, sample, frame.axis(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_z_axis.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_z_axis.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}
