#include "Resample/Slice/ComputeKz.h"

#include "Base/Const/Units.h"
#include "Base/Vector/GisasDirection.h"
#include "Resample/Sample/ReSample.h"
#include "Sample/Material/SLDMaterial.h"
#include "Sample/Material/Vacuum.h"
#include "Sample/Multilayer/Layer.h"
#include "Sample/Option/SimulationOptions.h"
#include "Sample/Sample/Sample.h"
#include "Tests/GTestWrapper/google_test.h"

namespace {

const MaterialColor TEST_COLOR(0.5, 0.5, 0.5);

SLDMaterial testSLDMaterial(const std::string& name, const complex_t& sld)
{
    return {name, TEST_COLOR, sld.real(), sld.imag()};
}

SLDMaterial testSLDMaterial(const std::string& name, double sld_real, double sld_imag)
{
    return {name, TEST_COLOR, sld_real, sld_imag};
}

} // namespace


TEST(KzComputation, initial)
{
    const complex_t sld_0(0.0, 0.0);
    const complex_t sld_1(8.0241e-06, 6.0448e-8);
    const complex_t sld_2(4.0241e-06, 2.0448e-8);
    const complex_t sld_3(1.0241e-06, 2.0448e-8);
    const complex_t sld_4(0.0, 2.0448e-6);

    Sample sample;

    Layer layer0(testSLDMaterial("m0", sld_0));
    Layer layer1(testSLDMaterial("m1", sld_1), 10);
    Layer layer2(testSLDMaterial("m2", sld_2), 10);
    Layer layer3(testSLDMaterial("m3", sld_3), 10);
    Layer layer4(testSLDMaterial("m4", sld_4));
    sample.addLayer(layer0);
    sample.addLayer(layer1);
    sample.addLayer(layer2);
    sample.addLayer(layer3);
    sample.addLayer(layer4);

    R3 k = vecOfLambdaAlphaPhi(1.0, -1.0 * Units::deg);

    const auto re_sample = ReSample::processedSample(sample, {});

    const std::vector<std::unique_ptr<const Slice>>& slices = re_sample.slices();
    auto res_ref = Compute::reducedKz(slices, k);
    auto res_ri = Compute::kzFromRefractivity(slices, k);
    auto res_sld = Compute::kzFromSLDs(slices, k.z());

    EXPECT_EQ(res_ref.size(), res_sld.size());
    EXPECT_EQ(res_ref.size(), res_ri.size());
    for (size_t i = 0; i < res_ref.size(); ++i) {
        EXPECT_NEAR(res_ref[i].real(), res_sld[i].real(), 1e-10);
        EXPECT_NEAR(res_ref[i].imag(), res_sld[i].imag(), 1e-10);
        EXPECT_NEAR(res_ref[i].real(), res_ri[i].real(), 1e-10);
        EXPECT_NEAR(res_ref[i].imag(), res_ri[i].imag(), 1e-10);
    }
}

TEST(KzComputation, negativeKz)
{
    const complex_t sld_0(0.0, 0.0);
    const complex_t sld_1(8.0241e-06, 6.0448e-8);
    const complex_t sld_2(4.0241e-06, 2.0448e-8);
    const complex_t sld_3(1.0241e-06, 2.0448e-8);
    const complex_t sld_4(0.0, 2.0448e-6);

    Sample sample;

    Layer layer0(testSLDMaterial("m0", sld_0));
    Layer layer1(testSLDMaterial("m1", sld_1), 10);
    Layer layer2(testSLDMaterial("m2", sld_2), 10);
    Layer layer3(testSLDMaterial("m3", sld_3), 10);
    Layer layer4(testSLDMaterial("m4", sld_4));
    sample.addLayer(layer0);
    sample.addLayer(layer1);
    sample.addLayer(layer2);
    sample.addLayer(layer3);
    sample.addLayer(layer4);

    R3 k = vecOfLambdaAlphaPhi(1.0, -1.0 * Units::deg);

    ReSample re_sample = ReSample::processedSample(sample, {});

    const std::vector<std::unique_ptr<const Slice>>& slices = re_sample.slices();
    auto res_ref = Compute::reducedKz(slices, k);
    auto res_ri = Compute::kzFromRefractivity(slices, k);
    auto res_sld = Compute::kzFromSLDs(slices, k.z());

    EXPECT_EQ(res_ref.size(), res_sld.size());
    EXPECT_EQ(res_ref.size(), res_ri.size());
    for (size_t i = 0; i < res_ref.size(); ++i) {
        EXPECT_NEAR(res_ref[i].real(), res_sld[i].real(), 1e-10);
        EXPECT_NEAR(res_ref[i].imag(), res_sld[i].imag(), 1e-10);
        EXPECT_NEAR(res_ref[i].real(), res_ri[i].real(), 1e-10);
        EXPECT_NEAR(res_ref[i].imag(), res_ri[i].imag(), 1e-10);
    }
}

TEST(KzComputation, absorptiveAmbience)
{
    const complex_t sld_0(8.0241e-06, 6.0448e-5);
    const complex_t sld_1(8.0241e-06, 6.0448e-8);
    const complex_t sld_2(4.0241e-06, 2.0448e-8);
    const complex_t sld_3(1.0241e-06, 2.0448e-8);
    const complex_t sld_4(0.0, 2.0448e-6);

    Sample sample;

    Layer layer0(testSLDMaterial("m0", sld_0));
    Layer layer1(testSLDMaterial("m1", sld_1), 10);
    Layer layer2(testSLDMaterial("m2", sld_2), 10);
    Layer layer3(testSLDMaterial("m3", sld_3), 10);
    Layer layer4(testSLDMaterial("m4", sld_4));
    sample.addLayer(layer0);
    sample.addLayer(layer1);
    sample.addLayer(layer2);
    sample.addLayer(layer3);
    sample.addLayer(layer4);

    R3 k = vecOfLambdaAlphaPhi(1.0, 1.0 * Units::deg);

    ReSample re_sample(ReSample::processedSample(sample, {}));

    const std::vector<std::unique_ptr<const Slice>>& slices = re_sample.slices();
    auto res_ri = Compute::kzFromRefractivity(slices, k);
    auto res_sld = Compute::kzFromSLDs(slices, k.z());

    EXPECT_EQ(res_ri.size(), res_sld.size());
    for (size_t i = 0; i < res_ri.size(); ++i) {
        EXPECT_NEAR(res_ri[i].real(), res_sld[i].real(), 1e-10);
        EXPECT_NEAR(res_ri[i].imag(), res_sld[i].imag(), 1e-10);
    }
}

TEST(KzComputation, TiNiSampleWithRoughness)
{
    Sample sample;
    sample.addLayer(Layer(Vacuum{}));
    for (int i = 0; i < 10; ++i) {
        sample.addLayer(Layer(testSLDMaterial("Ti", -1.9493e-06, 9.6013e-10), 3));
        sample.addLayer(Layer(testSLDMaterial("Ni", 9.4245e-06, 1.1423e-09), 7));
    }
    sample.addLayer(Layer(testSLDMaterial("Si", 2.0704e-06, 2.3726e-11)));

    R3 k = vecOfLambdaAlphaPhi(1.0, 0.0001 * Units::deg);

    const auto re_sample = ReSample::processedSample(sample, {});

    const std::vector<std::unique_ptr<const Slice>>& slices = re_sample.slices();
    auto res_ri = Compute::kzFromRefractivity(slices, k);
    auto res_sld = Compute::kzFromSLDs(slices, k.z());

    EXPECT_EQ(res_ri.size(), res_sld.size());
    for (size_t i = 0; i < res_ri.size(); ++i) {
        EXPECT_NEAR(res_ri[i].real(), res_sld[i].real(), 1e-10);
        EXPECT_NEAR(res_ri[i].imag(), res_sld[i].imag(), 1e-10);
    }
}
