#include "Sample/Sample/Sample.h"

#include "Base/Const/Units.h"
#include "Sample/Interface/Roughness.h"
#include "Sample/Material/RefractiveMaterial.h"
#include "Sample/Multilayer/Layer.h"
#include "Sample/Multilayer/LayerStack.h"
#include "Tests/GTestWrapper/google_test.h"

constexpr double inf = std::numeric_limits<double>::infinity();

class MultilayerTest : public ::testing::Test {
protected:
    MultilayerTest()
        // The following delta, beta are all unphysical. Values don't matter here.
        : topLayer(air)
        , layer1(iron, 20 * Units::nm)
        , layer2(chromium, 40 * Units::nm)
        , substrate(stone)
    {
    }
    void set_four()
    {
        mSample.addLayer(topLayer);
        mSample.addLayer(layer1);
        mSample.addLayer(layer2);
        mSample.addLayer(substrate);
        mSample.validate();
    }

    Sample mSample;
    const RefractiveMaterial air = {"air", MaterialColor(0.5, 0.5, 0.5), 1e-6, 9e-4};
    const RefractiveMaterial iron = {"iron", MaterialColor(0.5, 0.5, 0.5), 2e-5, 8e-5};
    const RefractiveMaterial chromium = {"chromium", MaterialColor(0.5, 0.5, 0.5), 3e-7, 7e-6};
    const RefractiveMaterial stone = {"stone", MaterialColor(0.5, 0.5, 0.5), 4e-4, 8e-7};
    Layer topLayer, layer1, layer2, substrate;
};

TEST_F(MultilayerTest, BasicProperty)
{
    // check default properties
    EXPECT_THROW(mSample.nUnwrapped(), std::runtime_error);

    // adding layers
    mSample.addLayer(topLayer);
    mSample.addLayer(layer1);
    mSample.addLayer(layer2);
    mSample.addLayer(substrate);
    EXPECT_EQ(size_t(4), mSample.nUnwrapped());
}

TEST_F(MultilayerTest, LayerThicknesses)
{
    set_four();

    // check layer thickness
    EXPECT_EQ(mSample.unwrappedLayer(0)->thickness(), inf);
    EXPECT_EQ(mSample.unwrappedLayer(1)->thickness(), 20);
    EXPECT_EQ(mSample.unwrappedLayer(2)->thickness(), 40);
    EXPECT_EQ(mSample.unwrappedLayer(3)->thickness(), inf);
}

TEST_F(MultilayerTest, CheckAllLayers)
{
    set_four();

    // check individual layer
    const Layer* got0 = mSample.unwrappedLayer(0);
    EXPECT_FALSE(std::isfinite(got0->thickness()));

    const Layer* got1 = mSample.unwrappedLayer(1);
    EXPECT_EQ(20, got1->thickness());

    const Layer* got2 = mSample.unwrappedLayer(2);
    EXPECT_EQ(40, got2->thickness());

    const Layer* got3 = mSample.unwrappedLayer(3);
    EXPECT_FALSE(std::isfinite(got3->thickness()));
}

TEST_F(MultilayerTest, LayerInterfaces)
{
    set_four();

    // check interfaces
    EXPECT_EQ(mSample.unwrappedLayer(1)->rms(), 0.0);
    EXPECT_EQ(mSample.unwrappedLayer(2)->rms(), 0.0);
    EXPECT_EQ(mSample.unwrappedLayer(3)->rms(), 0.0);
}

TEST_F(MultilayerTest, Clone)
{
    set_four();

    Sample* mSampleClone = mSample.clone();

    // check properties
    EXPECT_EQ(size_t(4), mSampleClone->nUnwrapped());

    // check layer thickness
    EXPECT_EQ(topLayer.thickness(), mSampleClone->unwrappedLayer(0)->thickness());
    EXPECT_EQ(layer1.thickness(), mSampleClone->unwrappedLayer(1)->thickness());
    EXPECT_EQ(layer2.thickness(), mSampleClone->unwrappedLayer(2)->thickness());
    EXPECT_EQ(substrate.thickness(), mSampleClone->unwrappedLayer(3)->thickness());

    // check interfaces
    EXPECT_EQ(mSampleClone->unwrappedLayer(1)->rms(), 0.0);
    EXPECT_EQ(mSampleClone->unwrappedLayer(2)->rms(), 0.0);
    EXPECT_EQ(mSampleClone->unwrappedLayer(3)->rms(), 0.0);

    delete mSampleClone;
}

TEST_F(MultilayerTest, WithRoughness)
{
    SelfAffineFractalModel autocorrelation(1.1, .3, 0.1, 1e100);
    ErfTransient transient;
    Roughness roughness(&autocorrelation, &transient);

    mSample.addLayer(topLayer);
    mSample.addLayer(Layer(*layer1.material(), layer1.thickness(), &roughness));
    mSample.addLayer(substrate);
    mSample.validate();

    const Roughness* roughness1 = mSample.unwrappedLayer(1)->roughness();
    const Roughness* roughness2 = mSample.unwrappedLayer(2)->roughness();

    EXPECT_TRUE(roughness1);
    EXPECT_TRUE(!roughness2);

    EXPECT_EQ(1.1, mSample.unwrappedLayer(1)->rms());
    EXPECT_TRUE(roughness1);
    EXPECT_EQ(mSample.unwrappedLayer(2)->rms(), 0.0);
}

TEST_F(MultilayerTest, CloneWithRoughness)
{
    ErfTransient transient;

    SelfAffineFractalModel autocorrelation1(2.1, .3, 12.1, 1e100);
    Roughness lr1(&autocorrelation1, &transient);
    SelfAffineFractalModel autocorrelation2(1.1, .3, 0.1, 1e100);
    Roughness lr2(&autocorrelation2, &transient);

    auto magnetization = R3{0., 1e8, 0.};
    auto magneticLayer =
        Layer(RefractiveMaterial("iron", MaterialColor(0.45, 0.32, 0.8), 2e-5, 8e-5, magnetization),
              20 * Units::nm);

    mSample.addLayer(topLayer);
    mSample.addLayer(Layer(*magneticLayer.material(), magneticLayer.thickness(), &lr1));
    mSample.addLayer(Layer(*substrate.material(), substrate.thickness(), &lr2));

    Sample* mSampleClone = mSample.clone();
    mSampleClone->validate();

    const Roughness* roughness1 = mSampleClone->unwrappedLayer(1)->roughness();
    const Roughness* roughness2 = mSampleClone->unwrappedLayer(2)->roughness();

    EXPECT_TRUE(roughness1);
    EXPECT_TRUE(roughness2);

    const auto* roughness1_AC =
        dynamic_cast<const SelfAffineFractalModel*>(roughness1->autocorrelationModel());
    EXPECT_TRUE(roughness1_AC);

    EXPECT_EQ(2.1, mSampleClone->unwrappedLayer(1)->rms());
    EXPECT_EQ(.3, roughness1_AC->hurst());
    EXPECT_EQ(12.1, roughness1_AC->lateralCorrLength());

    const auto* roughness2_AC =
        dynamic_cast<const SelfAffineFractalModel*>(roughness2->autocorrelationModel());
    EXPECT_TRUE(roughness2_AC);

    EXPECT_EQ(1.1, mSampleClone->unwrappedLayer(2)->rms());
    EXPECT_EQ(.3, roughness2_AC->hurst());
    EXPECT_EQ(0.1, roughness2_AC->lateralCorrLength());

    EXPECT_EQ(mSampleClone->unwrappedLayer(1)->material()->magnetized(), true);
    EXPECT_EQ(mSampleClone->unwrappedLayer(1)->material()->magnetization(), magnetization);

    delete mSampleClone;
}

TEST_F(MultilayerTest, Composite)
{
    Sample mSample;
    R3 magnetic_field(0.0, 0.0, 0.0);
    const RefractiveMaterial magMaterial0("MagMat0", MaterialColor(0.5, 0.5, 0.5), 6e-4, 2e-8,
                                          magnetic_field);
    const RefractiveMaterial magMaterial1("MagMat1", MaterialColor(0.5, 0.5, 0.5), -5.6, 10,
                                          magnetic_field);

    Layer layer1(iron, 10 * Units::nm);
    Layer layer2(magMaterial0, 20 * Units::nm);
    Layer layer3(magMaterial1, 30 * Units::nm);
    Layer layer4(stone, 40 * Units::nm);

    mSample.addLayer(topLayer);
    mSample.addLayer(layer1);
    mSample.addLayer(layer2);
    mSample.addLayer(layer3);
    mSample.addLayer(layer4);

    const auto& sublayers = mSample.outerStack().sublayers();
    EXPECT_EQ(sublayers.size(), 5u);
    std::vector<const Layer*> layer_buffer;
    for (const auto& sub : sublayers) {
        const auto* layer = dynamic_cast<const Layer*>(sub.get());
        EXPECT_TRUE(nullptr != layer);
        layer_buffer.push_back(layer);
    }
    EXPECT_EQ(size_t(5), layer_buffer.size());
    EXPECT_EQ(inf, layer_buffer[0]->thickness());
    EXPECT_EQ(10 * Units::nm, layer_buffer[1]->thickness());
    EXPECT_EQ(20 * Units::nm, layer_buffer[2]->thickness());
    EXPECT_EQ(30 * Units::nm, layer_buffer[3]->thickness());
    EXPECT_EQ(40 * Units::nm, layer_buffer[4]->thickness());
}
