#include "Device/Detector/SphericalDetector.h"
#include "Base/Axis/MakeScale.h"
#include "Base/Axis/Scale.h"
#include "Base/Const/Units.h"
#include "Device/Beam/Beam.h"
#include "Device/Detector/SimulationAreaIterator.h"
#include "Device/Mask/DetectorMask.h"
#include "Device/Mask/Polygon.h"
#include "Device/Mask/Rectangle.h"
#include "Device/Resolution/ConvolutionDetectorResolution.h"
#include "Device/Resolution/ResolutionFunction2DGaussian.h"
#include "Tests/GTestWrapper/google_test.h"
#include <memory>

// Construction of the detector with axes.
TEST(SphericalDetectorTest, constructionWithAxes)
{
    auto axis0 = sharedEquiDivision("axis0", 10, 0.0, 10.0);
    auto axis1 = sharedEquiDivision("axis1", 20, 0.0, 20.0);
    SphericalDetector detector(std::array<std::shared_ptr<Scale>, 2>{axis0, axis1});

    // checking dimension and axes
    EXPECT_EQ(axis0->min(), detector.axis(0).min());
    EXPECT_EQ(axis0->max(), detector.axis(0).max());
    EXPECT_EQ(axis1->min(), detector.axis(1).min());
    EXPECT_EQ(axis1->max(), detector.axis(1).max());
}

// Construction of the detector via classical constructor.
TEST(SphericalDetectorTest, constructionWithParameters)
{
    SphericalDetector detector(10, -1.0, 1.0, 20, 0.0, 2.0);
    EXPECT_EQ(10u, detector.axis(0).size());
    EXPECT_EQ(-1.0, detector.axis(0).min());
    EXPECT_EQ(1.0, detector.axis(0).max());
    EXPECT_EQ(20u, detector.axis(1).size());
    EXPECT_EQ(0.0, detector.axis(1).min());
    EXPECT_EQ(2.0, detector.axis(1).max());
}

// Creation of the detector map with axes in given units
TEST(SphericalDetectorTest, createDetectorMap)
{
    SphericalDetector detector(10, -1.0 * Units::deg, 1.0 * Units::deg, 20, 0.0 * Units::deg,
                               2.0 * Units::deg);

    // creating map in default units, which are radians and checking axes
    auto data = detector.createDetectorMap();
    EXPECT_EQ(data.axis(0).size(), 10u);
    EXPECT_EQ(data.axis(0).min(), -1.0 * Units::deg);
    EXPECT_EQ(data.axis(0).max(), 1.0 * Units::deg);
    EXPECT_EQ(data.axis(1).size(), 20u);
    EXPECT_EQ(data.axis(1).min(), 0.0 * Units::deg);
    EXPECT_EQ(data.axis(1).max(), 2.0 * Units::deg);
}

// Testing region of interest.
TEST(SphericalDetectorTest, regionOfInterest)
{
    SphericalDetector detector(std::array<std::shared_ptr<Scale>, 2>{
        sharedEquiDivision("axis0", 8, -3.0, 5.0), sharedEquiDivision("axis1", 4, 0.0, 4.0)});

    // creating region of interest
    double xlow(-2.0), ylow(1.0), xup(4.0), yup(3.0);
    detector.setRegionOfInterest(xlow, ylow, xup, yup);
    EXPECT_TRUE(detector.hasExplicitRegionOfInterest());
    EXPECT_EQ(detector.regionOfInterestBounds(0).first, xlow);
    EXPECT_EQ(detector.regionOfInterestBounds(0).second, xup);
    EXPECT_EQ(detector.regionOfInterestBounds(1).first, ylow);
    EXPECT_EQ(detector.regionOfInterestBounds(1).second, yup);

    // replacing region of interest with a new one
    double xlow2(-2.1), ylow2(1.1), xup2(4.1), yup2(3.1);
    detector.setRegionOfInterest(xlow2, ylow2, xup2, yup2);
    EXPECT_EQ(detector.regionOfInterestBounds(0).first, xlow2);
    EXPECT_EQ(detector.regionOfInterestBounds(0).second, xup2);
    EXPECT_EQ(detector.regionOfInterestBounds(1).first, ylow2);
    EXPECT_EQ(detector.regionOfInterestBounds(1).second, yup2);
}

// Create detector map in the presence of region of interest.
TEST(SphericalDetectorTest, regionOfInterestAndDetectorMap)
{
    SphericalDetector detector(6, -1.0 * Units::deg, 5.0 * Units::deg, 4, 0.0 * Units::deg,
                               4.0 * Units::deg);

    detector.setRegionOfInterest(0.1 * Units::deg, 1.1 * Units::deg, 3.0 * Units::deg,
                                 2.9 * Units::deg);
    // Creating map in default units, which are radians and checking that axes are clipped
    // to region of interest.
    auto data = detector.createDetectorMap();
    EXPECT_EQ(data.axis(0).size(), 4u);
    EXPECT_EQ(data.axis(0).min(), 0.1 * Units::deg);
    EXPECT_EQ(data.axis(0).max(), 3.0 * Units::deg);
    EXPECT_EQ(data.axis(1).size(), 2u);
    EXPECT_EQ(data.axis(1).min(), 1.1 * Units::deg);
    EXPECT_EQ(data.axis(1).max(), 2.9 * Units::deg);
}

// Checking clone in the presence of ROI and masks.
TEST(SphericalDetectorTest, Clone)
{
    SphericalDetector detector(6, -1.0 * Units::deg, 5.0 * Units::deg, 4, 0.0 * Units::deg,
                               4.0 * Units::deg);
    detector.setRegionOfInterest(0.1 * Units::deg, 1.1 * Units::deg, 3.0 * Units::deg,
                                 2.9 * Units::deg);
    detector.addMask(
        Rectangle(-0.9 * Units::deg, 0.1 * Units::deg, 0.9 * Units::deg, 1.9 * Units::deg), true);
    detector.addMask(
        Rectangle(3.1 * Units::deg, 2.1 * Units::deg, 4.9 * Units::deg, 3.9 * Units::deg), true);
    detector.setDetectorResolution(
        ConvolutionDetectorResolution(ResolutionFunction2DGaussian(1, 1)));

    std::unique_ptr<SphericalDetector> clone(detector.clone());

    auto data = clone->createDetectorMap();
    EXPECT_EQ(data.axis(0).size(), 4u);
    EXPECT_EQ(data.axis(0).min(), 0.1 * Units::deg);
    EXPECT_EQ(data.axis(0).max(), 3.0 * Units::deg);
    EXPECT_EQ(data.axis(1).size(), 2u);
    EXPECT_EQ(data.axis(1).min(), 1.1 * Units::deg);
    EXPECT_EQ(data.axis(1).max(), 2.9 * Units::deg);

    EXPECT_EQ(clone->detectorMask()->numberOfMaskedChannels(), 8);

    // checking iteration over the map of cloned detector
    std::vector<size_t> expectedDetectorIndexes = {6, 9, 10, 13, 14, 17};
    std::vector<size_t> detectorIndexes;
    clone->iterateOverNonMaskedPoints(
        [&](const auto it) { detectorIndexes.push_back(it.detectorIndex()); });
    EXPECT_EQ(detectorIndexes, expectedDetectorIndexes);
}
