#include "Sample/Multilayer/Sample.h"

#include "Base/Const/Units.h"
#include "Sample/Aggregate/ParticleLayout.h"
#include "Sample/Interface/LayerRoughness.h"
#include "Sample/Material/MaterialFactoryFuncs.h"
#include "Sample/Multilayer/Layer.h"
#include "Tests/GTestWrapper/google_test.h"

class MultiLayerTest : public ::testing::Test {
protected:
    MultiLayerTest()
        // The following delta, beta are all unphysical. Values don't matter here.
        : air(RefractiveMaterial("Vacuum", 1e-6, 9e-4))
        , iron(RefractiveMaterial("iron", 2e-5, 8e-5))
        , chromium(RefractiveMaterial("chromium", 3e-7, 7e-6))
        , stone(RefractiveMaterial("stone", 4e-4, 8e-7))
        , topLayer(air, 0 * Units::nm)
        , layer1(iron, 20 * Units::nm)
        , layer2(chromium, 40 * Units::nm)
        , substrate(stone, 0 * Units::nm)
    {
    }
    void set_four()
    {
        mLayer.addLayer(topLayer);
        mLayer.addLayer(layer1);
        mLayer.addLayer(layer2);
        mLayer.addLayer(substrate);
    }

    Sample mLayer;
    const Material air, iron, chromium, stone;
    Layer topLayer, layer1, layer2, substrate;
};

TEST_F(MultiLayerTest, BasicProperty)
{
    // check default properties
    EXPECT_EQ(size_t(0), mLayer.numberOfLayers());

    // adding layers
    mLayer.addLayer(topLayer);
    EXPECT_EQ(size_t(1), mLayer.numberOfLayers());

    mLayer.addLayer(layer1);
    mLayer.addLayer(layer2);
    mLayer.addLayer(substrate);
    EXPECT_EQ(size_t(4), mLayer.numberOfLayers());
}

TEST_F(MultiLayerTest, LayerThicknesses)
{
    set_four();

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

TEST_F(MultiLayerTest, CheckAllLayers)
{
    set_four();

    // check individual layer
    const Layer* got0 = mLayer.layer(0);
    EXPECT_EQ(0, got0->thickness());

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

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

    const Layer* got3 = mLayer.layer(3);
    EXPECT_EQ(0, got3->thickness());
}

TEST_F(MultiLayerTest, LayerInterfaces)
{
    set_four();

    // check interfaces
    const LayerRoughness* roughness1 = mLayer.layer(1)->roughness();
    EXPECT_TRUE(nullptr != roughness1);
    EXPECT_EQ(mLayer.layerRoughnessRMS(1), 0.0);

    const LayerRoughness* roughness2 = mLayer.layer(2)->roughness();
    EXPECT_TRUE(nullptr != roughness2);
    EXPECT_EQ(mLayer.layerRoughnessRMS(2), 0.0);

    const LayerRoughness* roughness3 = mLayer.layer(3)->roughness();
    EXPECT_TRUE(nullptr != roughness3);
    EXPECT_EQ(mLayer.layerRoughnessRMS(3), 0.0);
}

TEST_F(MultiLayerTest, Clone)
{
    set_four();

    Sample* mLayerClone = mLayer.clone();

    // check properties
    EXPECT_EQ(size_t(4), mLayerClone->numberOfLayers());

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

    // check interfaces
    const LayerRoughness* roughness0 = mLayerClone->layer(1)->roughness();
    EXPECT_TRUE(nullptr != roughness0);
    EXPECT_EQ(mLayerClone->layerRoughnessRMS(1), 0.0);
    EXPECT_TRUE(nullptr == roughness0->crosscorrelationModel());

    const LayerRoughness* roughness1 = mLayerClone->layer(2)->roughness();
    EXPECT_TRUE(nullptr != roughness1);
    EXPECT_EQ(mLayerClone->layerRoughnessRMS(2), 0.0);
    EXPECT_TRUE(nullptr == roughness1->crosscorrelationModel());

    const LayerRoughness* roughness2 = mLayerClone->layer(3)->roughness();
    EXPECT_TRUE(nullptr != roughness2);
    EXPECT_EQ(mLayerClone->layerRoughnessRMS(3), 0.0);
    EXPECT_TRUE(nullptr == roughness2->crosscorrelationModel());

    delete mLayerClone;
}

TEST_F(MultiLayerTest, WithRoughness)
{
    K_CorrelationModel autocorrelation(1.1, .3, 0.1, 1e100);
    ErfInterlayer interlayer;
    LayerRoughness lr(&autocorrelation, &interlayer);

    mLayer.addLayer(topLayer);
    mLayer.addLayer(Layer(*layer1.material(), layer1.thickness(), &lr));
    mLayer.addLayer(substrate);

    const LayerRoughness* roughness0 = mLayer.layer(1)->roughness();
    const LayerRoughness* roughness1 = mLayer.layer(2)->roughness();

    EXPECT_TRUE(roughness0);
    auto* roughness0_AC =
        dynamic_cast<const K_CorrelationModel*>(roughness0->autocorrelationModel());
    EXPECT_TRUE(roughness0_AC);

    EXPECT_EQ(1.1, mLayer.layerRoughnessRMS(1));
    EXPECT_EQ(.3, roughness0_AC->hurst());
    EXPECT_EQ(0.1, roughness0_AC->lateralCorrLength());

    EXPECT_TRUE(roughness1);
    EXPECT_EQ(mLayer.layerRoughnessRMS(2), 0.0);
}

TEST_F(MultiLayerTest, CloneWithRoughness)
{
    ErfInterlayer interlayer;

    K_CorrelationModel autocorrelation1(2.1, .3, 12.1, 1e100);
    LayerRoughness lr1(&autocorrelation1, &interlayer);
    K_CorrelationModel autocorrelation2(1.1, .3, 0.1, 1e100);
    LayerRoughness lr2(&autocorrelation2, &interlayer);

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

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

    Sample* mLayerClone = mLayer.clone();

    const LayerRoughness* roughness1 = mLayerClone->layer(1)->roughness();
    const LayerRoughness* roughness2 = mLayerClone->layer(2)->roughness();

    EXPECT_TRUE(roughness1);
    EXPECT_TRUE(roughness2);

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

    EXPECT_EQ(2.1, mLayerClone->layerRoughnessRMS(1));
    EXPECT_EQ(.3, roughness1_AC->hurst());
    EXPECT_EQ(12.1, roughness1_AC->lateralCorrLength());

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

    EXPECT_EQ(1.1, mLayerClone->layerRoughnessRMS(2));
    EXPECT_EQ(.3, roughness2_AC->hurst());
    EXPECT_EQ(0.1, roughness2_AC->lateralCorrLength());

    EXPECT_EQ(mLayerClone->layer(1)->material()->isMagneticMaterial(), true);
    EXPECT_EQ(mLayerClone->layer(1)->material()->magnetization(), magnetization);

    delete mLayerClone;
}

TEST_F(MultiLayerTest, MultiLayerComposite)
{
    Sample mLayer;
    R3 magnetic_field(0.0, 0.0, 0.0);
    Material magMaterial0 = RefractiveMaterial("MagMat0", 6e-4, 2e-8, magnetic_field);
    Material magMaterial1 = RefractiveMaterial("MagMat1", -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);

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

    std::vector<const Layer*> layer_buffer;
    int counter(0);

    std::vector<const INode*> grandChildren = mLayer.nodeChildren();
    EXPECT_EQ(grandChildren.size(), 5);
    for (const auto* sample : grandChildren) {
        if (counter % 2 == 0) {
            const auto* layer = dynamic_cast<const Layer*>(sample);
            EXPECT_TRUE(nullptr != layer);
            layer_buffer.push_back(layer);
        }
        counter++;
    }
    EXPECT_EQ(size_t(3), layer_buffer.size());
    for (size_t i = 0; i < layer_buffer.size(); ++i)
        EXPECT_EQ(double(i * 20), layer_buffer[i]->thickness());
}
