#include "Base/Axis/MakeScale.h"
#include "Base/Axis/Scale.h"
#include "Base/Const/Units.h"
#include "Device/Coord/CoordSystem2D.h"
#include "Tests/GTestWrapper/google_test.h"
#include <numbers>

using std::numbers::pi;

class DepthprobeCoordsTest : public ::testing::Test {
protected:
    DepthprobeCoordsTest();

    void checkMainFunctionality(const DepthprobeCoords& test_object);
    void checkAlphaAxis(Coords units, const DepthprobeCoords& test_object);
    void checkZAxis(Coords units, const DepthprobeCoords& test_object);
    const double m_alpha_start = 0.5; // first axis value in rads
    const double m_alpha_end = 1.0;   // last axis value in rads
    const double m_z_start = -30.0;
    const double m_z_end = 10.0;
    const size_t m_nbins = 100;
    double m_wavenumber = (2 * pi) / 1.0;
    std::vector<const Scale*> m_axes;
};

DepthprobeCoordsTest::DepthprobeCoordsTest()
    : m_axes({newEquiDivision("Angles", m_nbins, m_alpha_start, m_alpha_end), // angles in radians
              newEquiDivision("Positions", m_nbins, m_z_start, m_z_end)})     // z positions in nm
{
}

void DepthprobeCoordsTest::checkMainFunctionality(const DepthprobeCoords& test_object)
{
    EXPECT_EQ(test_object.rank(), 2u);

    EXPECT_NEAR(test_object.calculateMin(0, Coords::UNDEFINED), 2.8647889757e1,
                2.8647889757e1 * 1e-10);
    EXPECT_NEAR(test_object.calculateMin(0, Coords::DEGREES), 2.8647889757e1,
                2.8647889757e1 * 1e-10);
    EXPECT_NEAR(test_object.calculateMin(0, Coords::QSPACE), 6.0246390001, 6.0246390001 * 1e-10);
    EXPECT_EQ(test_object.calculateMin(0, Coords::RADIANS), m_alpha_start);
    EXPECT_EQ(test_object.calculateMin(0, Coords::NBINS), 0.0);

    EXPECT_NEAR(test_object.calculateMax(0, Coords::UNDEFINED), 5.7295779513e1,
                5.7295779513e1 * 1e-10);
    EXPECT_NEAR(test_object.calculateMax(0, Coords::DEGREES), 5.7295779513e1,
                5.7295779513e1 * 1e-10);
    EXPECT_NEAR(test_object.calculateMax(0, Coords::QSPACE), 1.0574236256e1,
                1.0574236256e1 * 1e-10);
    EXPECT_EQ(test_object.calculateMax(0, Coords::RADIANS), m_alpha_end);
    const auto n_bins = static_cast<double>(m_nbins);
    EXPECT_NEAR(test_object.calculateMax(0, Coords::NBINS), n_bins, n_bins * 1e-10);

    checkAlphaAxis(Coords::UNDEFINED, test_object);
    checkAlphaAxis(Coords::DEGREES, test_object);
    checkAlphaAxis(Coords::RADIANS, test_object);
    checkAlphaAxis(Coords::QSPACE, test_object);
    checkAlphaAxis(Coords::NBINS, test_object);

    checkZAxis(Coords::UNDEFINED, test_object);
    checkZAxis(Coords::DEGREES, test_object);
    checkZAxis(Coords::RADIANS, test_object);
    checkZAxis(Coords::QSPACE, test_object);
    checkZAxis(Coords::NBINS, test_object);
}

void DepthprobeCoordsTest::checkAlphaAxis(Coords units, const DepthprobeCoords& test_object)
{
    std::unique_ptr<Scale> axis(test_object.convertedAxis(0, units));
    EXPECT_EQ(axis->size(), m_nbins);
    EXPECT_EQ(axis->min(), test_object.calculateMin(0, units));
    EXPECT_EQ(axis->max(), test_object.calculateMax(0, units));
}

void DepthprobeCoordsTest::checkZAxis(Coords units, const DepthprobeCoords& test_object)
{
    std::unique_ptr<Scale> axis(test_object.convertedAxis(1, units));
    EXPECT_EQ(axis->size(), m_nbins);

    EXPECT_EQ(axis->min(), test_object.calculateMin(1, units));
    const double test_min = units == Coords::NBINS ? 0 : m_z_start;
    EXPECT_NEAR(axis->min(), test_min, std::abs(test_min) * 1e-10);

    EXPECT_EQ(axis->max(), test_object.calculateMax(1, units));
    const double test_max = units == Coords::NBINS ? m_nbins : m_z_end;
    EXPECT_NEAR(axis->max(), test_max, std::abs(test_max) * 1e-10);
}

TEST_F(DepthprobeCoordsTest, DepthprobeCoords)
{
    DepthprobeCoords converter(std::move(m_axes), m_wavenumber);
    checkMainFunctionality(converter);
}

TEST_F(DepthprobeCoordsTest, DepthprobeCoordsClone)
{
    DepthprobeCoords converter(std::move(m_axes), m_wavenumber);
    std::unique_ptr<DepthprobeCoords> converter_clone(converter.clone());
    checkMainFunctionality(*converter_clone);
}
