#ifndef DISTRIBUTIONSTEST_H
#define DISTRIBUTIONSTEST_H

#include "Units.h"
#include "BornAgainNamespace.h"
#include "Distributions.h"
#include "ParameterSample.h"

#include <cmath>
#include <memory>

class DistributionsTest : public ::testing::Test
{
protected:
   DistributionsTest() {}
};

// -------------------------------------------------------------------------- //

TEST_F(DistributionsTest, DistributionGateDefaultConstructor)
{
    std::unique_ptr<DistributionGate> P_distr_gate { new DistributionGate() };
    EXPECT_EQ(0.5, P_distr_gate->getMean());
    EXPECT_EQ(0.0, P_distr_gate->getMin());
    EXPECT_EQ(1.0, P_distr_gate->getMax());
    EXPECT_EQ(1.0, P_distr_gate->probabilityDensity(1.0));
    EXPECT_EQ(0, P_distr_gate->probabilityDensity(3.0));
    EXPECT_EQ(BornAgain::DistributionGateType, P_distr_gate->getName());

    std::vector<double> list1 = P_distr_gate->generateValueList(1, 0.0);
    EXPECT_EQ(list1.size(), size_t(1));
    EXPECT_EQ(P_distr_gate->getMean(), list1[0]);

    std::vector<double> list2 = P_distr_gate->generateValueList(2, 0.0);
    EXPECT_EQ(list2.size(), size_t(2));
    EXPECT_EQ(0, list2[0]);
    EXPECT_EQ(1, list2[1]);
}

TEST_F(DistributionsTest, DistributionGateConstructor)
{
    // Throw error when m_min > m_max:
    EXPECT_THROW(DistributionGate(1.1, 1.0), ClassInitializationException);

    // When m_min == m_max, only one sample is generated (the mean):
    DistributionGate distr1(1.0, 1.0);
    std::vector<double> list1 = distr1.generateValueList(5, 0.0);
    EXPECT_EQ(size_t(1), list1.size());
    EXPECT_EQ(1.0, list1[0]);

    // Test distribution with m_min < m_max:
    DistributionGate distr2(1.0, 2.0);
    EXPECT_EQ(1.5, distr2.getMean());
    EXPECT_EQ(1.0, distr2.getMin());
    EXPECT_EQ(2.0, distr2.getMax());
    EXPECT_EQ(1.0, distr2.probabilityDensity(1));
    EXPECT_EQ(0, distr2.probabilityDensity(3));
    EXPECT_EQ(BornAgain::DistributionGateType, distr2.getName());

    std::vector<double> list2 = distr2.generateValueList(1, 0.0);
    EXPECT_EQ(list2.size(), size_t(1));
    EXPECT_EQ(distr2.getMean(), list2[0]);

    list2 = distr2.generateValueList(2, 0.0);
    EXPECT_EQ(list2.size(), size_t(2));
    EXPECT_EQ(1.0, list2[0]);
    EXPECT_EQ(2.0, list2[1]);

    std::vector<ParameterSample> samples = distr2.generateSamples(3);
    EXPECT_EQ(samples.size(), size_t(3));
    EXPECT_EQ(samples[0].value, 1.0);
    EXPECT_EQ(samples[0].weight, 1./3.);
    EXPECT_EQ(samples[1].value, 1.5);
    EXPECT_EQ(samples[1].weight, 1./3.);
    EXPECT_EQ(samples[2].value, 2.0);
    EXPECT_EQ(samples[2].weight, 1./3.);
}

TEST_F(DistributionsTest, DistributionGateParameters)
{
    DistributionGate gate(2.0, 3.0);
    /* TEMPORARILY DISABLED getParameterPool() 
    EXPECT_EQ(gate.getMin(), gate.getParameterPool()->getParameter(BornAgain::Minimum).getValue());
    EXPECT_EQ(gate.getMax(), gate.getParameterPool()->getParameter(BornAgain::Maximum).getValue());
    */
}

TEST_F(DistributionsTest, DistributionGateClone)
{
    DistributionGate gate(2.0, 3.0);
    DistributionGate *clone = gate.clone();
    EXPECT_EQ(gate.getName(), clone->getName());
    EXPECT_EQ(gate.getMean(), clone->getMean());
    EXPECT_EQ(gate.getMin(), clone->getMin());
    EXPECT_EQ(gate.getMax(), clone->getMax());
    delete clone;
}

// -------------------------------------------------------------------------- //

TEST_F(DistributionsTest, DistributionLorentzDefaultConstructor)
{
    std::unique_ptr<DistributionLorentz> P_distr_lorentz { new DistributionLorentz() };
    EXPECT_EQ(0.0, P_distr_lorentz->getMean());
    EXPECT_EQ(1.0, P_distr_lorentz->getHWHM());
    EXPECT_EQ(BornAgain::DistributionLorentzType, P_distr_lorentz->getName());
    EXPECT_EQ(1/(2*Units::PI), P_distr_lorentz->probabilityDensity(1.0));

    std::vector<double> list1 = P_distr_lorentz->generateValueList(1, 0.0);
    EXPECT_EQ(P_distr_lorentz->getMean(), list1[0]);

    std::vector<double> list2 = P_distr_lorentz->generateValueList(2, 0.0);
    EXPECT_EQ(-2, list2[0]);
    EXPECT_EQ(2, list2[1]);
}

TEST_F(DistributionsTest, DistributionLorentzConstructor)
{
    // When HWHM == 0.0, only one sample is generated (the mean):
    DistributionLorentz distr1(1.0, 0.0);
    std::vector<double> list1 = distr1.generateValueList(5, 0.0);
    EXPECT_EQ(size_t(1), list1.size());
    EXPECT_EQ(1.0, list1[0]);

    // Test distribution with HWHM > 0.0:
    DistributionLorentz distr2(1.0, 1.0);
    EXPECT_EQ(1.0, distr2.getMean());
    EXPECT_EQ(1.0, distr2.getHWHM());
    EXPECT_EQ(BornAgain::DistributionLorentzType, distr2.getName());
    EXPECT_EQ(1.0/Units::PI, distr2.probabilityDensity(1.0));

    std::vector<double> list2 = distr2.generateValueList(1, 0.0);
    EXPECT_EQ(distr2.getMean(), list2[0]);

    std::vector<double> list3 = distr2.generateValueList(2, 0.0);
    EXPECT_EQ(-1, list3[0]);
    EXPECT_EQ(3, list3[1]);
}

TEST_F(DistributionsTest, DistributionLorentzParameters)
{
    DistributionLorentz lorentz(2.0, 3.0);
    /* TEMPORARILY DISABLED getParameterPool() 
    EXPECT_EQ(lorentz.getMean(),
              lorentz.getParameterPool()->getParameter(BornAgain::Mean).getValue());
    EXPECT_EQ(lorentz.getHWHM(),
              lorentz.getParameterPool()->getParameter(BornAgain::HWHM).getValue());
    */
}

TEST_F(DistributionsTest, DistributionLorentzClone)
{
    std::unique_ptr<DistributionLorentz> P_distr_lorentz { new DistributionLorentz(1.0, 2.0) };
    std::unique_ptr<DistributionLorentz> P_clone { P_distr_lorentz->clone() };
    EXPECT_EQ(1.0, P_clone->getMean());
    EXPECT_EQ(2.0, P_clone->getHWHM());
    EXPECT_EQ(BornAgain::DistributionLorentzType, P_clone->getName());
}

TEST_F(DistributionsTest, DistributionLorentzSamples)
{
    DistributionLorentz distr(1.0, 0.1);

    const int nbr_samples(3);

    // with sigma factor
    const double sigma_factor(2.0);
    std::vector<ParameterSample> samples = distr.generateSamples(nbr_samples, sigma_factor);

    EXPECT_EQ(samples.size(), size_t(nbr_samples));
    EXPECT_EQ(samples[0].value, 1.0 - sigma_factor*0.1);
    EXPECT_EQ(samples[1].value, 1.0);
    EXPECT_EQ(samples[2].value, 1.0 + sigma_factor*0.1);
    double d1 = distr.probabilityDensity(samples[0].value);
    double d2 = distr.probabilityDensity(samples[1].value);
    double d3 = distr.probabilityDensity(samples[2].value);
    EXPECT_EQ(samples[0].weight, d1/(d1+d2+d3));
    EXPECT_EQ(samples[1].weight, d2/(d1+d2+d3));
    EXPECT_EQ(samples[2].weight, d3/(d1+d2+d3));

    // with AttLimits
    samples = distr.generateSamples(nbr_samples, sigma_factor, AttLimits::lowerLimited(0.99));
    EXPECT_EQ(samples[0].value, 0.99);
    EXPECT_EQ(samples[1].value, samples[0].value + (samples[2].value - samples[0].value)/2.0);
    EXPECT_EQ(samples[2].value, 1.0 + sigma_factor*0.1);

    // with xmin, xmax
    samples = distr.generateSamples(nbr_samples, 0.8, 1.2);
    EXPECT_EQ(samples[0].value, 0.8);
    EXPECT_EQ(samples[1].value, 1.0);
    EXPECT_EQ(samples[2].value, 1.2);
}

// -------------------------------------------------------------------------- //

TEST_F(DistributionsTest, DistributionGaussianDefaultConstructor)
{
    std::unique_ptr<DistributionGaussian> P_distr_gauss { new DistributionGaussian() };
    EXPECT_EQ(0.0, P_distr_gauss->getMean());
    EXPECT_EQ(1.0, P_distr_gauss->getStdDev());
    EXPECT_EQ(std::exp(-1.0/2.0)/std::sqrt(2.0*Units::PI), P_distr_gauss->probabilityDensity(1.0));
    EXPECT_EQ(BornAgain::DistributionGaussianType, P_distr_gauss->getName());

    std::vector<double> list1 = P_distr_gauss->generateValueList(1, 0.0);
    EXPECT_EQ(P_distr_gauss->getMean(), list1[0]);

    std::vector<double> list2 = P_distr_gauss->generateValueList(2, 0.0);
    EXPECT_EQ(-2, list2[0]);
    EXPECT_EQ(2, list2[1]);
}

TEST_F(DistributionsTest, DistributionGaussianConstructor)
{
    // When std_dev == 0.0, only one sample is generated (the mean):
    DistributionGaussian distr1(1.0, 0.0);
    std::vector<double> list1 = distr1.generateValueList(5, 0.0);
    EXPECT_EQ(size_t(1), list1.size());
    EXPECT_EQ(1.0, list1[0]);

    // Test distribution with std_dev > 0.0:
    DistributionGaussian distr2(1.0, 1.0);
    EXPECT_EQ(1.0, distr2.getMean());
    EXPECT_EQ(1.0, distr2.getStdDev());
    EXPECT_EQ(1/std::sqrt(2.0*Units::PI), distr2.probabilityDensity(1.0));
    EXPECT_EQ(BornAgain::DistributionGaussianType, distr2.getName());

    std::vector<double> list2 = distr2.generateValueList(1, 0.0);
    EXPECT_EQ(distr2.getMean(), list2[0]);

    std::vector<double> list3 = distr2.generateValueList(2, 0.0);
    EXPECT_EQ(-1, list3[0]);
    EXPECT_EQ(3, list3[1]);
}

TEST_F(DistributionsTest, DistributionGaussianParameters)
{
    DistributionGaussian gaussian(2.0, 3.0);
    /* TEMPORARILY DISABLED getParameterPool() 
    EXPECT_EQ(gaussian.getMean(),
              gaussian.getParameterPool()->getParameter(BornAgain::Mean).getValue());
    EXPECT_EQ(gaussian.getStdDev(),
              gaussian.getParameterPool()->getParameter(BornAgain::StdDeviation).getValue());
    */
}

TEST_F(DistributionsTest, DistributionGaussianClone)
{
    std::unique_ptr<DistributionGaussian> P_distr_gauss { new DistributionGaussian(1.0, 1.0) };
    std::unique_ptr<DistributionGaussian> P_clone { P_distr_gauss->clone() };
    EXPECT_EQ(1.0, P_clone->getMean());
    EXPECT_EQ(1.0, P_clone->getStdDev());
    EXPECT_EQ(1/std::sqrt(2.0*Units::PI), P_clone->probabilityDensity(1.0));
    EXPECT_EQ(BornAgain::DistributionGaussianType, P_clone->getName());

    std::vector<double> list1 = P_clone->generateValueList(1, 0.0);
    EXPECT_EQ(P_distr_gauss->getMean(), list1[0]);

    std::vector<double> list2 = P_clone->generateValueList(2, 0.0);
    EXPECT_EQ(-1, list2[0]);
    EXPECT_EQ(3, list2[1]);
}

// -------------------------------------------------------------------------- //

TEST_F(DistributionsTest, DistributionLogNormalConstructorWithOneParameter)
{
    // When scale_par == 0.0, only one sample is generated (the mean):
    DistributionLogNormal distr1(1.0, 0.0);
    std::vector<double> list1 = distr1.generateValueList(5, 0.0);
    EXPECT_EQ(size_t(1), list1.size());
    EXPECT_EQ(1.0, list1[0]);

    // Test distribution with scale_par > 0.0:
    DistributionLogNormal distr2(1.0);
    EXPECT_EQ(1.0, distr2.getMedian());
    EXPECT_EQ(1.0, distr2.getScalePar());
    EXPECT_EQ(std::exp(0.5), distr2.getMean());
    EXPECT_EQ(1.0/std::sqrt(2.0*Units::PI), distr2.probabilityDensity(1.0));
    EXPECT_EQ(BornAgain::DistributionLogNormalType, distr2.getName());

    std::vector<double> list2 = distr2.generateValueList(1, 0.0);
    EXPECT_EQ(distr2.getMedian(), list2[0]);

    std::vector<double> list3 = distr2.generateValueList(2, 0.0);
    EXPECT_EQ(std::exp(-2), list3[0]);
    EXPECT_EQ(std::exp(-2) + std::exp(2) - std::exp(-2), list3[1]);
}

TEST_F(DistributionsTest, DistributionLogNormalConstructorWithTwoParameter)
{
    std::unique_ptr<DistributionLogNormal> P_distr_lognormal { new DistributionLogNormal(1.0,1.0) };
    EXPECT_EQ(1.0, P_distr_lognormal->getMedian());
    EXPECT_EQ(1.0, P_distr_lognormal->getScalePar());
    EXPECT_EQ(std::exp(0.5), P_distr_lognormal->getMean());
    EXPECT_EQ(1.0/std::sqrt(2.0*Units::PI), P_distr_lognormal->probabilityDensity(1.0));
    EXPECT_EQ(BornAgain::DistributionLogNormalType, P_distr_lognormal->getName());

    std::vector<double> list1 = P_distr_lognormal->generateValueList(1, 0.0);
    EXPECT_EQ(P_distr_lognormal->getMedian(), list1[0]);

    std::vector<double> list2 = P_distr_lognormal->generateValueList(2, 0.0);
    EXPECT_EQ(std::exp(-2), list2[0]);
    EXPECT_EQ(std::exp(-2) + std::exp(2) - std::exp(-2), list2[1]);
}

TEST_F(DistributionsTest, DistributionLogNormalParameters)
{
    DistributionLogNormal logNormal(2.0, 3.0);
    /* TEMPORARILY DISABLED getParameterPool() 
    EXPECT_EQ(logNormal.getMedian(),
              logNormal.getParameterPool()->getParameter(BornAgain::Median).getValue());
    EXPECT_EQ(logNormal.getScalePar(),
              logNormal.getParameterPool()->getParameter(BornAgain::ScaleParameter).getValue());
    */
}

TEST_F(DistributionsTest, DistributionLogNormalClone)
{
    std::unique_ptr<DistributionLogNormal> P_distr_lognormal { new DistributionLogNormal(1.0, 1.0) };
    std::unique_ptr<DistributionLogNormal> P_clone { P_distr_lognormal->clone() };
    EXPECT_EQ(1.0, P_distr_lognormal->getMedian());
    EXPECT_EQ(1.0, P_distr_lognormal->getScalePar());
    EXPECT_EQ(std::exp(0.5), P_distr_lognormal->getMean());
    EXPECT_EQ(1/std::sqrt(2.0*Units::PI), P_distr_lognormal->probabilityDensity(1.0));
    EXPECT_EQ(BornAgain::DistributionLogNormalType, P_distr_lognormal->getName());

    std::vector<double> list1 = P_distr_lognormal->generateValueList(1, 0.0);
    EXPECT_EQ(P_distr_lognormal->getMedian(), list1[0]);

    std::vector<double> list2 = P_distr_lognormal->generateValueList(2,0.0);
    EXPECT_EQ(std::exp(-2), list2[0]);
    EXPECT_EQ(std::exp(-2) + std::exp(2) - std::exp(-2), list2[1]);
}

// -------------------------------------------------------------------------- //

TEST_F(DistributionsTest, DistributionCosineDefaultConstructor)
{
    std::unique_ptr<DistributionCosine> P_distr_cosine { new DistributionCosine() };
    EXPECT_EQ(0.0, P_distr_cosine->getMean());
    EXPECT_EQ(1.0, P_distr_cosine->getSigma());
    EXPECT_DOUBLE_EQ((1.0+std::cos(1.0))/(2.0*Units::PI), P_distr_cosine->probabilityDensity(1.0));
    EXPECT_EQ(0, P_distr_cosine->probabilityDensity(100.0));
    EXPECT_EQ(BornAgain::DistributionCosineType, P_distr_cosine->getName());

    std::vector<double> list1 = P_distr_cosine->generateValueList(1, 0.0);
    EXPECT_EQ(P_distr_cosine->getMean(), list1[0]);

    std::vector<double> list2 = P_distr_cosine->generateValueList(2, 0.0);
    EXPECT_EQ(-Units::PI, list2[0]);
    EXPECT_EQ(Units::PI, list2[1]);
}

TEST_F(DistributionsTest, DistributionCosineConstructor)
{
    // When sigma == 0.0, only one sample is generated (the mean):
    DistributionCosine distr1(1.0, 0.0);
    std::vector<double> list1 = distr1.generateValueList(5, 0.0);
    EXPECT_EQ(size_t(1), list1.size());
    EXPECT_EQ(1.0, list1[0]);

    // Test distribution with sigma > 0.0:
    DistributionCosine distr2(1.0,1.0);
    EXPECT_EQ(1.0, distr2.getMean());
    EXPECT_EQ(1.0, distr2.getSigma());
    EXPECT_EQ(2.0/(2.0*Units::PI), distr2.probabilityDensity(1.0));
    EXPECT_EQ(0, distr2.probabilityDensity(100.0));
    EXPECT_EQ(BornAgain::DistributionCosineType, distr2.getName());

    std::vector<double> list2 = distr2.generateValueList(1, 0.0);
    EXPECT_EQ(distr2.getMean(), list2[0]);

    std::vector<double> list3 = distr2.generateValueList(2, 0.0);
    EXPECT_EQ(1-Units::PI, list3[0]);
    EXPECT_EQ(1+Units::PI, list3[1]);
}

TEST_F(DistributionsTest, DistributionCosineParameters)
{
    DistributionCosine cosine(2.0, 3.0);
    /* TEMPORARILY DISABLED getParameterPool() 
    EXPECT_EQ(cosine.getMean(), cosine.getParameterPool()->getParameter(BornAgain::Mean).getValue());
    EXPECT_EQ(cosine.getSigma(), cosine.getParameterPool()->getParameter(BornAgain::Sigma).getValue());
    */
}

TEST_F(DistributionsTest, DistributionCosineClone)
{
    std::unique_ptr<DistributionCosine> P_distr_cosine { new DistributionCosine(1.0,1.0) };
    std::unique_ptr<DistributionCosine> P_clone { P_distr_cosine->clone() };
    EXPECT_EQ(1.0, P_clone->getMean());
    EXPECT_EQ(1.0, P_clone->getSigma());
    EXPECT_EQ(2.0/(2.0*Units::PI), P_clone->probabilityDensity(1.0));
    EXPECT_EQ(0, P_distr_cosine->probabilityDensity(100.0));
    EXPECT_EQ(BornAgain::DistributionCosineType, P_clone->getName());

    std::vector<double> list1 = P_clone->generateValueList(1, 0.0);
    EXPECT_EQ(P_clone->getMean(), list1[0]);

    std::vector<double> list2 = P_clone->generateValueList(2, 0.0);
    EXPECT_EQ(1-Units::PI, list2[0]);
    EXPECT_EQ(1+Units::PI, list2[1]);
}

#endif