//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Sim/InstrumentItems.h
//! @brief     Defines 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)
//
//  ************************************************************************************************

#ifndef BORNAGAIN_GUI_MODEL_SIM_INSTRUMENTITEMS_H
#define BORNAGAIN_GUI_MODEL_SIM_INSTRUMENTITEMS_H

#include "GUI/Model/Descriptor/AxisProperty.h"
#include "GUI/Model/Descriptor/PolyPtr.h"
#include "GUI/Model/Descriptor/VectorProperty.h"
#include "GUI/Model/Sim/BackgroundCatalog.h"
#include "GUI/Model/Sim/BackgroundItems.h"
#include "GUI/Model/Type/NamedItem.h"
#include <memory>

class BeamItem;
class DatafileItem;
class DetectorItem;
class Frame;
class IDetector;
class ISimulation;
class OffspecDetectorItem;
class PhysicalScan;
class Sample;
class Scale;
class ScanItem;

//! Abstract base class for instrument-specific item classes.

class InstrumentItem : public NamedItem {
public:
    InstrumentItem();
    virtual ~InstrumentItem();

    InstrumentItem* clone() const;

    //! The type as how to show it on the UI. Do not use for type checking or similar!
    virtual QString instrumentType() const = 0;
    virtual size_t detectorRank() const = 0;
    virtual size_t axdim(int i) const = 0;

    virtual void updateToRealData(const DatafileItem* dfi) = 0;

    virtual bool alignedWith(const DatafileItem* dfi) const;

    virtual std::unique_ptr<Frame> createFrame() const = 0;
    virtual Frame makeFrame() const;

    virtual ISimulation* createSimulation(const Sample& sample) const = 0;

    virtual void writeTo(QXmlStreamWriter* w) const;
    virtual void readFrom(QXmlStreamReader* r);

    QString id() const { return m_id; }
    void setId(const QString& id) { m_id = id; }

    template <typename T> bool is() const { return dynamic_cast<const T*>(this) != nullptr; }

    PolyPtr<BackgroundItem, BackgroundCatalog>& backgroundSelection() { return m_background; }
    BackgroundItem* backgroundItem() const { return m_background.certainItem(); }

    bool withPolarizer() const { return m_with_polarizer; }
    void setWithPolarizer(bool with) { m_with_polarizer = with; }
    bool withAnalyzer() const { return m_with_analyzer; }
    void setWithAnalyzer(bool with) { m_with_analyzer = with; }

    VectorProperty& polarizerBlochVector() { return m_polarizer_bloch_vector; }
    const VectorProperty& polarizerBlochVector() const { return m_polarizer_bloch_vector; }
    void setPolarizerBlochVector(const R3& v) { m_polarizer_bloch_vector.setR3(v); }

    VectorProperty& analyzerBlochVector() { return m_analyzer_bloch_vector; }
    const VectorProperty& analyzerBlochVector() const { return m_analyzer_bloch_vector; }
    void setAnalyzerBlochVector(const R3& v) { m_analyzer_bloch_vector.setR3(v); }

    bool expandDetector = true;
    bool expandInfo = true;
    bool expandPolarizerAnalyzer = true;
    bool expandEnvironment = true;

protected:
    explicit InstrumentItem(const QString& modelType);

    QString m_id;
    bool m_with_polarizer;
    bool m_with_analyzer;
    PolyPtr<BackgroundItem, BackgroundCatalog> m_background;

    VectorProperty m_polarizer_bloch_vector;
    VectorProperty m_analyzer_bloch_vector;
};


//! Instrument with a scan.

class ScanningInstrumentItem : public InstrumentItem {
public:
    ScanningInstrumentItem(double intensity);
    ScanItem* scanItem() const { return m_scan.get(); }

    std::unique_ptr<PhysicalScan> createPhysicalScan(const Scale& axis) const;

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    void setupScan(PhysicalScan* scan) const;

    std::unique_ptr<ScanItem> m_scan;
};


class Scatter2DInstrumentItem : public InstrumentItem {
public:
    Scatter2DInstrumentItem();

    QString instrumentType() const override { return "Scatter2D"; }
    size_t detectorRank() const override { return 2; }
    size_t axdim(int i) const override;
    void updateToRealData(const DatafileItem* dfi) override;
    std::unique_ptr<Frame> createFrame() const override;
    ISimulation* createSimulation(const Sample& sample) const override;
    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

    BeamItem* beamItem() const { return m_beam.get(); }
    DetectorItem* detectorItem() const { return m_detector.get(); }

private:
    std::unique_ptr<BeamItem> m_beam;
    std::unique_ptr<DetectorItem> m_detector;
};


class OffspecInstrumentItem : public ScanningInstrumentItem {
public:
    OffspecInstrumentItem();

    QString instrumentType() const override { return "Offspec"; }
    size_t detectorRank() const override { return 2; }
    size_t axdim(int i) const override;
    void updateToRealData(const DatafileItem* dfi) override;
    std::unique_ptr<Frame> createFrame() const override;
    ISimulation* createSimulation(const Sample& sample) const override;
    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

    OffspecDetectorItem* detectorItem() const { return m_detector.get(); }

private:
    std::unique_ptr<OffspecDetectorItem> m_detector;
};


class SpecularInstrumentItem : public ScanningInstrumentItem {
public:
    SpecularInstrumentItem();
    QString instrumentType() const override { return "Specular"; }
    size_t detectorRank() const override { return 1; }
    size_t axdim(int i) const override;
    void updateToRealData(const DatafileItem* dfi) override;
    bool alignedWith(const DatafileItem* dfi) const override;
    std::unique_ptr<Frame> createFrame() const override;
    ISimulation* createSimulation(const Sample& sample) const override;

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;
};


class DepthprobeInstrumentItem : public ScanningInstrumentItem {
public:
    DepthprobeInstrumentItem();

    QString instrumentType() const override { return "Depthprobe"; }
    size_t detectorRank() const override { return 2; }
    size_t axdim(int i) const override;
    void updateToRealData(const DatafileItem* dfi) override;
    std::unique_ptr<Frame> createFrame() const override;
    ISimulation* createSimulation(const Sample& sample) const override;
    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

    AxisProperty& zAxis() { return m_z_axis; }

private:
    AxisProperty m_z_axis;
};

#endif // BORNAGAIN_GUI_MODEL_SIM_INSTRUMENTITEMS_H
