//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Device/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_DEVICE_INSTRUMENTITEMS_H
#define BORNAGAIN_GUI_MODEL_DEVICE_INSTRUMENTITEMS_H

#include "GUI/Model/Descriptor/AxisProperty.h"
#include "GUI/Model/Descriptor/SelectionProperty.h"
#include "GUI/Model/Descriptor/VectorProperty.h"
#include "GUI/Model/Detector/DetectorItem.h"
#include "GUI/Model/Detector/DetectorItemCatalog.h"
#include "GUI/Model/Device/BackgroundItemCatalog.h"
#include <functional>
#include <memory>

class BackgroundItem;
class BeamItem;
class DataItem;
class DepthprobeSimulation;
class Scale;
class IBeamScan;
class ICoordSystem;
class ISimulation;
class MaskContainerItem;
class MultiLayer;
class OffspecDetectorItem;
class OffspecSimulation;
class RealItem;
class ScanItem;
class ScatteringSimulation;
class SphericalDetectorItem;

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

class InstrumentItem {
public:
    InstrumentItem();
    virtual ~InstrumentItem() = default;

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

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

    virtual void updateToRealData(const RealItem* item) = 0;

    virtual bool alignedWith(const RealItem* item) const;

    virtual std::unique_ptr<const ICoordSystem> createCoordSystem() const = 0;

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

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

    //! Creates an exact copy; also ID is the same!
    InstrumentItem* createItemCopy() const;

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

    QString instrumentName() const { return m_name; }
    void setInstrumentName(const QString& instrumentName) { m_name = instrumentName; }

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

    QString description() const { return m_description; }
    void setDescription(const QString& description) { m_description = description; }

    template <typename T>
    T* setBackgroundItemType();
    SelectionProperty<BackgroundItemCatalog>& backgroundSelection() { return m_background; }
    BackgroundItem* backgroundItem() const { return m_background.currentItem(); }

    bool withPolarizer() const { return m_withPolarizer; }
    void setWithPolarizer(bool with) { m_withPolarizer = with; }
    bool withAnalyzer() const { return m_withAnalyzer; }
    void setWithAnalyzer(bool with) { m_withAnalyzer = with; }

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

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

    bool isExpandInfo() const { return m_expandInfo; }
    void setExpandInfo(bool b) { m_expandInfo = b; }

    bool isExpandPolarizerAlanyzer() const { return m_expandPolarizerAlanyzer; }
    void setExpandPolarizerAlanyzer(bool b) { m_expandPolarizerAlanyzer = b; }

    bool isExpandEnvironment() const { return m_expandEnvironment; }
    void setExpandEnvironment(bool b) { m_expandEnvironment = b; }

    bool isExpandDetector() const { return m_expandDetector; }
    void setExpandDetector(bool b) { m_expandDetector = b; }

protected:
    explicit InstrumentItem(const QString& modelType);

    QString m_id;
    QString m_name;
    QString m_description;
    bool m_withPolarizer;
    bool m_withAnalyzer;
    SelectionProperty<BackgroundItemCatalog> m_background;

    VectorProperty m_polarizerBlochVector;
    VectorProperty m_analyzerBlochVector;

    bool m_expandInfo = true;
    bool m_expandPolarizerAlanyzer = true;
    bool m_expandEnvironment = true;
    bool m_expandDetector = true;
};


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

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

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

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

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


class SpecularInstrumentItem : public InstrumentItem, public ScanningFunctionality {
public:
    SpecularInstrumentItem();
    QString instrumentType() const override { return "Specular"; }

    std::vector<int> shape() const override;
    void updateToRealData(const RealItem* item) override;
    bool alignedWith(const RealItem* item) const override;
    std::unique_ptr<const ICoordSystem> createCoordSystem() 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 "Depth probe"; }
    std::vector<int> shape() const override;
    void updateToRealData(const RealItem* item) override;
    std::unique_ptr<const ICoordSystem> createCoordSystem() const override;
    ISimulation* createSimulation(const MultiLayer& sample) const override;
    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

    AxisProperty& zAxis() { return m_zAxis; }

private:
    AxisProperty m_zAxis;
};


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

    QString instrumentType() const override { return "Off specular"; }
    std::vector<int> shape() const override;
    void updateToRealData(const RealItem* item) override;
    std::unique_ptr<const ICoordSystem> createCoordSystem() 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 GISASInstrumentItem : public InstrumentItem {
public:
    GISASInstrumentItem();

    QString instrumentType() const override { return "GISAS"; }
    std::vector<int> shape() const override;
    void updateToRealData(const RealItem* item) override;
    std::unique_ptr<const ICoordSystem> createCoordSystem() 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_beamItem.get(); }
    DetectorItem* detectorItem() const { return m_detector.currentItem(); }

    std::unique_ptr<IDetector> normalDetector() const;
    template <typename T>
    T* setDetectorItemType();
    SelectionProperty<DetectorItemCatalog>& detectorSelection() { return m_detector; }
    void importMasks(const MaskContainerItem* maskContainer);

private:
    SelectionProperty<DetectorItemCatalog> m_detector;
    std::unique_ptr<BeamItem> m_beamItem;
};


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

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

template <typename T>
T* GISASInstrumentItem::setDetectorItemType()
{
    m_detector.setCurrentItem<T>();
    return dynamic_cast<T*>(m_detector.currentItem());
}

#endif // BORNAGAIN_GUI_MODEL_DEVICE_INSTRUMENTITEMS_H
