//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/Detector/Detector.h
//! @brief     Defines common detector interface.
//!
//! @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_DEVICE_DETECTOR_DETECTOR_H
#define BORNAGAIN_DEVICE_DETECTOR_DETECTOR_H

#include "Base/Axis/Frame.h"
#include "Base/Type/Cloneable.h"
#include <heinz/Vectors3D.h>

class Beam;
class Datafield;
class DetectorResolution;
class MaskStack;
class Pixel;
class PolFilter;
class ResolutionFunction2D;
class Scale;
class Shape2D;
class SpinMatrix;
struct RoiOfAxis;

//! Abstract base for 2D detectors, realized by SphericalDetector.
//!
//! Handles also "region of interest" (ROI). In general, the ROI is the whole
//! detector, and all methods related to ROI work on the whole detector.
//! If a ROI different to the detector size is explicitly set, then ROI-related
//! methods work on this reduced ROI.
//! Therefore, when calling ROI related methods, the distinction between "explicit
//! ROI exists: yes/no" does not have to be made by the caller, but it is handled in here.
//! For access to the whole detector, even if a smaller ROI is explicitly defined, use the
//! non-ROI-related methods like totalSize() or axes().
//! Any method which is not speaking of "explicit ROI" handles the "implicit ROI", i.e. uses
//! an explicitly set ROI or the whole detector if no explicit ROI exists.

class Detector : public Cloneable {
public:
    //! Constructor that takes ownership of Frame.
    Detector(Frame* frame);

    ~Detector() override;

    //! Sets the polarization analyzer characteristics of the detector
    void setAnalyzer(const R3& direction, double transmission_selected = 1,
                     double transmission_suppressed = 0);

    void setResolutionFunction(const ResolutionFunction2D& resFunc);

    const Frame& frame() const;
    const MaskStack* detectorMask() const;

    //! Adds mask of given shape to the stack of detector masks. The mask value 'true' means
    //! that the channel will be excluded from the simulation. The mask which is added last
    //! has priority.
    //! @param shape The shape of mask (Rectangle, Polygon, Line, Ellipse)
    //! @param mask_value The value of mask
    void addMask(const Shape2D& shape, bool mask_value = true);

    //! Put the mask for all detector channels (i.e. exclude whole detector from the analysis)
    void maskAll();

    //! One axis of the complete detector.
    //! Any region of interest is not taken into account.
    const Scale& axis(size_t i) const;

    //! Sets rectangular region of interest with lower left and upper right corners defined.
    void setRegionOfInterest(double xlow, double ylow, double xup, double yup);

    //! Returns a Frame clipped to the region of interest. If no region of interest is explicitly
    //! defined, then the whole detector is taken as "region of interest".
    Frame clippedFrame() const;

    //! Convert an index of the region of interest to an index of the detector.
    //! If no region of interest is set, then the index stays unmodified (since ROI == detector
    //! area).
    size_t roiToFullIndex(size_t i) const;

#ifndef SWIG
    Detector* clone() const override = 0;

    //! Returns total number of pixels.
    //! Any region of interest is not taken into account.
    size_t totalSize() const;

    //! Returns a pointer to detector resolution object
    const DetectorResolution* detectorResolution() const { return m_resolution.get(); }

    //! Sets the detector resolution
    void setDetectorResolution(const DetectorResolution& detector_resolution);

    //! Creates an Pixel for the given Datafield object and index
    virtual std::unique_ptr<Pixel> createPixel(size_t i) const = 0;

    //! Returns vector of unmasked detector indices.
    std::vector<size_t> activeIndices() const;

    //! Returns index of pixel that contains the specular wavevector.
    //! If no pixel contains this specular wavevector, the number of pixels is
    //! returned. This corresponds to an overflow index.
    virtual size_t indexOfSpecular(const Beam& beam) const = 0;

    //! Applies the detector resolution to the given intensity maps
    void applyDetectorResolution(Datafield* df) const;

    //! True if a region of interest is explicitly set.
    bool hasExplicitRegionOfInterest() const;

    //! The size of the "Region of Interest". Same as totalSize()
    //! if no region of interest has been explicitly set.
    size_t sizeOfRegionOfInterest() const;

    //! The lower and upper bound of the region of interest. If no region of interest is explicitly
    //! defined, then the whole detector is taken as "region of interest".
    std::pair<double, double> regionOfInterestBounds(size_t iAxis) const;

    //! Returns empty detector map in given axes units.
    //! This map is a data array limited to the size of the "Region of interest"
    Datafield createDetectorMap() const;

    //! Returns detection properties
    const PolFilter* analyzer() const { return m_pol_analyzer.get(); }
    SpinMatrix analyzerMatrix() const;

protected:
    //! Copies ROI, mask, resolution, and analyzer settings to 'other'.
    void copySettings(Detector* other) const;

    //! Returns flattened index computed from two axis indices.
    size_t globalIndex(size_t x, size_t y) const;

    //! Calculate axis index for given global index
    size_t axisBinIndex(size_t i, size_t k_axis) const;

private:
    std::vector<RoiOfAxis> m_explicitROI; //!< an explicitly defined region of interest.
                                          //!< Empty if no ROI has been defined.
                                          //!< Vector index corresponds to axis index in m_frame

    std::unique_ptr<Frame> m_frame;
    std::unique_ptr<PolFilter> m_pol_analyzer;
    std::unique_ptr<DetectorResolution> m_resolution;
    std::unique_ptr<MaskStack> m_mask;
#endif // SWIG
};

#endif // BORNAGAIN_DEVICE_DETECTOR_DETECTOR_H
