//  ************************************************************************************************
//
//  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/PolyItem.h"
#include "GUI/Model/Descriptor/VectorProperty.h"
#include "GUI/Model/Sim/BackgroundItemCatalog.h"
#include "GUI/Model/Type/NamedItem.h"
#include <memory>

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

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

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

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

    virtual std::vector<int> axdims() const = 0;

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

    virtual bool alignedWith(const DatafileItem* dfileItem) const;

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

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

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

    InstrumentItem* clone() const;

    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; }

    template <typename T> T* setBackgroundItemType();
    PolyItem<BackgroundItemCatalog>& 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 expandPolarizerAlanyzer = true;
    bool expandEnvironment = true;

protected:
    explicit InstrumentItem(const QString& modelType);

    QString m_id;
    bool m_with_polarizer;
    bool m_with_analyzer;
    PolyItem<BackgroundItemCatalog> m_background;

    VectorProperty m_polarizer_bloch_vector;
    VectorProperty m_analyzer_bloch_vector;
};


//! Mix-in class, to equip an instrument class with a scan item

class ScanningFunctionality {
public:
    ScanningFunctionality(double intensity);
    ScanItem* scanItem() const { return m_scan_item.get(); }

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

    void writeScanTo(QXmlStreamWriter* w) const;
    void readScanFrom(QXmlStreamReader* r);

private:
    std::unique_ptr<ScanItem> m_scan_item;
};


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

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

    BeamItem* beamItem() const { return m_beam_item.get(); }
    DetectorItem* detectorItem() const { return m_detector_item.get(); }

    std::unique_ptr<IDetector> normalDetector() const;

private:
    std::unique_ptr<BeamItem> m_beam_item;
    std::unique_ptr<DetectorItem> m_detector_item;
};


class OffspecInstrumentItem : public InstrumentItem, public ScanningFunctionality {
public:
    OffspecInstrumentItem();

    QString instrumentType() const override { return "Offspec"; }
    int detectorRank() const override { return 1; }
    std::vector<int> axdims() const override;
    void updateToRealData(const DatafileItem* dfileItem) override;
    std::unique_ptr<Frame> createFrame() const override;
    ISimulation* createSimulation(const MultiLayer& 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 InstrumentItem, public ScanningFunctionality {
public:
    SpecularInstrumentItem();
    QString instrumentType() const override { return "Specular"; }
    int detectorRank() const override { return 1; }
    std::vector<int> axdims() const override;
    void updateToRealData(const DatafileItem* dfileItem) override;
    bool alignedWith(const DatafileItem* dfileItem) const override;
    std::unique_ptr<Frame> createFrame() const override;
    ISimulation* createSimulation(const MultiLayer& sample) const override;

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


class DepthprobeInstrumentItem : public InstrumentItem, public ScanningFunctionality {
public:
    DepthprobeInstrumentItem();

    QString instrumentType() const override { return "Depthprobe"; }
    int detectorRank() const override { return 1; }
    std::vector<int> axdims() const override;
    void updateToRealData(const DatafileItem* dfileItem) override;
    std::unique_ptr<Frame> createFrame() const override;
    ISimulation* createSimulation(const MultiLayer& 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;
};


//  ************************************************************************************************
//  templated functions implementation
//  ************************************************************************************************

template <typename T> T* InstrumentItem::setBackgroundItemType()
{
    m_background.createCertainItem<T>();
    return dynamic_cast<T*>(m_background.certainItem());
}

#endif // BORNAGAIN_GUI_MODEL_SIM_INSTRUMENTITEMS_H
