//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/Coord/CoordSystem2D.h
//! @brief     Defines interface CoordSystem2D and its subclasses.
//!
//! @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)
//
//  ************************************************************************************************

#ifdef SWIG
#error no need to expose this header to Swig
#endif // SWIG
#ifndef BORNAGAIN_DEVICE_COORD_COORDSYSTEM2D_H
#define BORNAGAIN_DEVICE_COORD_COORDSYSTEM2D_H

#include "Device/Coord/ICoordSystem.h"
#include <heinz/Vectors3D.h>
#include <memory>

class Beam;
class RectangularPixel;

//! Interface for objects that provide axis translations to different units for IDetector objects

class CoordSystem2D : public ICoordSystem {
public:
    CoordSystem2D(std::vector<const Scale*>&& axes);
    ~CoordSystem2D() override = default;

    double calculateMin(size_t i_axis, Coords units) const override;
    double calculateMax(size_t i_axis, Coords units) const override;

    //! Returns list of units that are available for all 2D detectors.
    //! Further units may be added by child classes.
    std::vector<Coords> availableUnits() const override;

    Scale* convertedAxis(size_t i_axis, Coords units) const override;

protected:
    CoordSystem2D(const CoordSystem2D& other);

private:
    virtual double calculateValue(size_t i_axis, Coords units, double value) const = 0;
};


//! ICoordSystem class that handles the unit translations for spherical detectors
//! Its default units are radians for both axes

class SphericalCoords : public CoordSystem2D {
public:
    SphericalCoords(std::vector<const Scale*>&& axes, const R3& ki);
    ~SphericalCoords() override;

    SphericalCoords* clone() const override;

private:
    std::vector<Coords> availableUnits() const override;

    Coords defaultUnits() const override { return Coords::DEGREES; }

    std::string nameOfAxis(size_t i_axis, Coords units) const override;

    SphericalCoords(const SphericalCoords& other); //!< used by clone()
    double calculateValue(size_t i_axis, Coords units, double value) const override;
    const R3 m_ki;
};


//! ICoordSystem class that handles the unit translations for rectangular detectors
//! Its default units are mm for both axes

class ImageCoords : public CoordSystem2D {
public:
    ImageCoords(std::vector<const Scale*>&& axes, const R3& ki,
                const RectangularPixel* regionOfInterestPixel);
    ~ImageCoords() override;

    ImageCoords* clone() const override;

private:
    std::vector<Coords> availableUnits() const override;

    Coords defaultUnits() const override { return Coords::MM; }

    std::string nameOfAxis(size_t i_axis, Coords units) const override;

    ImageCoords(const ImageCoords& other); //!< used by clone()
    double calculateValue(size_t i_axis, Coords units, double value) const override;

    std::unique_ptr<const RectangularPixel> m_detector_pixel;
    const R3 m_ki;
};


//! ICoordSystem class that handles the unit translations for off-specular simulations
//! with a spherical detector
//! Its default units are radians for both axes

class OffspecCoords : public CoordSystem2D {
public:
    OffspecCoords(std::vector<const Scale*>&& axes);

    OffspecCoords* clone() const override;

private:
    Coords defaultUnits() const override { return Coords::DEGREES; }

    std::string nameOfAxis(size_t i_axis, Coords units) const override;

    OffspecCoords(const OffspecCoords& other); //!< used by clone()
    double calculateValue(size_t i_axis, Coords units, double value) const override;
};


//! DepthprobeCoords class handles the unit translations for depth probe simulations
//! Its default units are radians for x-axis and nm for y-axis

class DepthprobeCoords : public CoordSystem2D {
public:
    DepthprobeCoords(std::vector<const Scale*>&& axes, double ki0);
    ~DepthprobeCoords() override;

    DepthprobeCoords* clone() const override;

private:
    std::vector<Coords> availableUnits() const override;

    Coords defaultUnits() const override { return Coords::DEGREES; }

    std::string nameOfAxis(size_t i_axis, Coords units) const override;

    DepthprobeCoords(const DepthprobeCoords& other); //!< used by clone()
    double calculateValue(size_t, Coords units, double value) const override;
    const double m_ki0;
};

#endif // BORNAGAIN_DEVICE_COORD_COORDSYSTEM2D_H
