/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#define VSTREAM_DOMAIN "TEST_HISTOGRAM"
#define BOOST_TEST_DYN_LINK
#include <stdexcept>
#include <vector>
#include <climits>

#include <mia/internal/autotest.hh>
#include <mia/core/noisegen.hh>

#include <mia/core/histogram.hh>
#include <mia/core/msgstream.hh>

NS_MIA_USE
using namespace std;
using namespace boost::unit_test;
namespace bfs =::boost::filesystem;


BOOST_AUTO_TEST_CASE( test_empty_histogram)
{
       THistogram<CUBHistogramFeeder> h(CUBHistogramFeeder(0, 0, 0));
       BOOST_CHECK_EQUAL(h.average(), 0);
       BOOST_CHECK_EQUAL(h.deviation(), 0);
       BOOST_CHECK_EQUAL(h.median(), 0);
       BOOST_CHECK_EQUAL(h.MAD(), 0);
}

BOOST_AUTO_TEST_CASE( test_histogram)
{
       unsigned char init_data[10] = { 1, 2, 3, 4, 5, 5, 5, 5, 4, 3 };
       size_t test_result[256];
       fill( test_result, test_result + 256, 0);
       test_result[1] = 1;
       test_result[2] = 1;
       test_result[3] = 2;
       test_result[4] = 2;
       test_result[5] = 4;
       THistogram<CUBHistogramFeeder> h = THistogram<CUBHistogramFeeder>(CUBHistogramFeeder(0, 0, 0));
       BOOST_CHECK(h.size() == 256);

       for (int i = 0; i < 10; ++i)
              h.push(init_data[i]);

       BOOST_CHECK(equal(h.begin(), h.end(), test_result));
       BOOST_CHECK_EQUAL(h.median(), 4);
       h.push(6, 2);
       BOOST_CHECK_EQUAL(h[6], 2u);
       BOOST_CHECK_EQUAL(h.median(), 4);
       BOOST_CHECK_EQUAL(h.MAD(), 1);
}

BOOST_AUTO_TEST_CASE( test_float_histogram)
{
       float init_data[10] = { 1, 2, 3, 4, 5, 5, 5, 5, 4, 3 };
       THistogram<THistogramFeeder<float>> h(THistogramFeeder<float>(1, 5, 5));
       h.push_range(init_data, init_data + 10);
       THistogram<THistogramFeeder<float>>::value_type value = h.at(0);
       BOOST_CHECK_EQUAL(value.first, 1);
       BOOST_CHECK_EQUAL(value.second, 1u);
       value = h.at(4);
       BOOST_CHECK_EQUAL(value.first, 5);
       BOOST_CHECK_EQUAL(value.second, 4u);
       BOOST_CHECK_CLOSE(h.average(), 3.7, 0.1);
       BOOST_CHECK_CLOSE(h.deviation(), sqrt((155.0 - 37.0 * 3.7) / 9.0), 0.1);
       THistogram<THistogramFeeder<float>> h2(h, 0.0);
       BOOST_CHECK_CLOSE(h2.average(), 3.7, 0.1);
       BOOST_CHECK_CLOSE(h2.deviation(), sqrt((155.0 - 37.0 * 3.7) / 9.0), 0.1);
}

BOOST_AUTO_TEST_CASE( test_histogram_excess_kurtosis_1 )
{
       vector<double> input{1, 2, 3, 2, 5, 6, 1, 2, 1, 5};
       THistogram<THistogramFeeder<float>> h(THistogramFeeder<float>(1, 6, 6));
       h.push_range(input.begin(), input.end());
       // test values have been evaluated by using octave 3.8.2
       BOOST_CHECK_CLOSE(h.average(),  2.8, 0.1);
       BOOST_CHECK_CLOSE(h.deviation(), sqrt(3.5111), 0.1);
       BOOST_CHECK_CLOSE(h.skewness(), 0.62378, 0.1);
       BOOST_CHECK_CLOSE(h.excess_kurtosis(),  -1.1530, 0.1);
}

BOOST_AUTO_TEST_CASE( test_histogram2)
{
       const size_t nsamples = 31;
       const float init_data[nsamples] = {
              1, 1, 2, 3, 1, 6, 8, 1,
              1, 4, 6, 8, 5, 1, 1, 2,
              2, 2, 7, 3, 1,
              3, 3, 7, 7, 2,
              2, 4, 1, 8, 2
       };
       size_t test_result[256];
       fill( test_result, test_result + 256, 0);
       test_result[0] = 9;
       test_result[1] = 7;
       test_result[2] = 4;
       test_result[3] = 2;
       test_result[4] = 1;
       test_result[5] = 2;
       test_result[6] = 3;
       test_result[7] = 3;
       THistogram<THistogramFeeder<float>> h(THistogramFeeder<float>(1, 256, 256));
       BOOST_CHECK(h.size() == 256);

       for (size_t i = 0; i < nsamples; ++i)
              h.push(init_data[i]);

       for (size_t i = 0; i < 256; ++i) {
              cvdebug() << "i (" << h.at(i).first << ") = " << h.at(i).second << " vs. " << test_result[i] << "\n";
              BOOST_CHECK_EQUAL(h.at(i).second, test_result[i]);
       }

       BOOST_CHECK_EQUAL(h.median(), 2);
       BOOST_CHECK_EQUAL(h.MAD(), 1);
}

BOOST_AUTO_TEST_CASE( test_histogram3)
{
       const size_t nsamples = 31;
       const float init_data[nsamples] = {
              1, 1, 2, 3, 1, 6, 9, 1,
              1, 4, 6, 9, 5, 1, 1, 2,
              2, 2, 7, 3, 1,
              3, 3, 7, 7, 2,
              2, 4, 1, 9, 2
       };
       THistogram<THistogramFeeder<double>> h(THistogramFeeder<double>(1, 9, 4096));
       BOOST_CHECK(h.size() == 4096);

       for (size_t i = 0; i < nsamples; ++i)
              h.push(init_data[i]);

       BOOST_CHECK_CLOSE(h.median(), 2.0, 0.1);
       BOOST_CHECK_CLOSE(h.MAD(), 1.0, 0.1);
}

BOOST_AUTO_TEST_CASE( test_histogram_cut_5)
{
       const size_t nsamples = 100;
       THistogram<THistogramFeeder<double>> h(THistogramFeeder<double>(1, 100, 100));

       for (size_t i = 0; i < nsamples; ++i)
              h.push(i + 1);

       auto range = h.get_reduced_range(5);
       BOOST_CHECK_EQUAL(range.first, 5);
       BOOST_CHECK_EQUAL(range.second, 95);
}


struct FNormalDistribution {
       FNormalDistribution(float mean, float sigma) :
              m_mean(mean),
              m_sigma(sigma),
              m_w1(1.0 / (2.0 * m_sigma * m_sigma)),
              m_w2(sqrt( m_w1 / M_PI))

       {
       }

       float operator()(float x) const
       {
              const float h = x - m_mean;
              return m_w2 * exp( - m_w1 * h * h);
       }
       float m_mean;
       float m_sigma;
       float m_w1;
       float m_w2;
};

BOOST_AUTO_TEST_CASE ( test_histogram_gauss_noise )
{
       const size_t size = 32000;
       vector<double> data;
       const CNoiseGeneratorPluginHandler::Instance&  ngp = CNoiseGeneratorPluginHandler::instance();
       auto ng = ngp.produce("gauss:mu=127,sigma=16,seed=1");
       FNormalDistribution n1(127, 16);
       typedef THistogram<THistogramFeeder<double>> CDoubleHistogram;
       CDoubleHistogram histo(THistogramFeeder<double>(0, 256, 64));

       for (size_t k = 0; k < 1; ++k) {
              const CNoiseGenerator& g = *ng;
              size_t i = 0;

              while ( i < size ) {
                     double h = g();

                     if ( h >= 0.0 && h < 256.0) {
                            histo.push(h);
                            ++i;
                     }
              }
       }

       BOOST_CHECK_CLOSE(histo.average(), 127.0, 1);
       BOOST_CHECK_CLOSE(histo.deviation(), 16.0, 1);
}
