/* -*- 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/>.
 *
 */



#include <mia/internal/autotest.hh>

#include <cmath>
#include <cstdint>
#include <mia/core/cmeans.hh>


using namespace mia;

using std::vector;

double gauss(double x, double c, double sigma)
{
       const double f = sqrt(2 * 3.1415926);
       double d = x - c;
       return 100000 * exp(- d * d / (2 * sigma * sigma)) / (sigma * f);
}

class CTestFixedInitializer: public CMeans::Initializer
{
public:
       CTestFixedInitializer(const CMeans::DVector& init);
       CMeans::DVector run(const CMeans::NormalizedHistogram& nh) const;
private:
       CMeans::DVector m_init;
};

CTestFixedInitializer::CTestFixedInitializer(const CMeans::DVector& init):
       m_init(init)
{
}

CMeans::DVector CTestFixedInitializer::run(const CMeans::NormalizedHistogram& MIA_PARAM_UNUSED(nh)) const
{
       return m_init;
}


BOOST_AUTO_TEST_CASE( test_even_initialized )
{
       CMeans::SparseHistogram sh(250);
       vector<double> x = {
              2,    4,  5,  7,  8, 10, 12, 14, 18, 20,
              22,  24, 25, 30, 31, 34, 37, 38, 39, 40,
              41, 42, 43, 45, 46, 48, 49, 50, 53, 55,
              57, 58, 60, 61, 63, 64, 67, 70, 72, 75,
              78, 79, 80, 82, 85, 86, 88, 90, 92, 93,

              95, 99, 100, 102, 103, 104, 106, 107, 109, 110,
              112, 114, 116, 118, 121, 122, 123, 124, 128, 130,
              132, 134, 136, 138, 141, 142, 143, 144, 148, 149,

              150, 152, 156, 158, 159, 160, 163, 164, 165, 167,
              168, 169, 170, 171, 172, 173, 175, 177, 178, 179,

              180, 182, 186, 188, 189, 190, 193, 194, 195, 197,
              198, 199, 200, 201, 202, 203, 205, 207, 208, 209,
              212, 214, 216, 218, 221, 222, 223, 224, 228, 230,
              232, 234, 236, 238, 241, 242, 243, 244, 248, 249,
              250, 252, 256, 258, 259, 260, 263, 264, 265, 267,

              268, 269, 270, 271, 272, 273, 275, 277, 278, 279,
              280, 282, 286, 288, 289, 290, 293, 294, 295, 297,
              312, 314, 316, 318, 321, 322, 323, 324, 328, 330,
              332, 334, 336, 338, 341, 342, 343, 344, 348, 349,
              350, 352, 356, 358, 359, 360, 363, 364, 365, 367,

              368, 369, 370, 371, 372, 373, 375, 377, 378, 379,
              380, 382, 386, 388, 389, 390, 393, 394, 395, 397,
              400, 401, 402, 403, 405, 407, 408, 409, 412, 414,
              416, 418, 421, 422, 423, 424, 428, 430, 432, 434,
              436, 438, 441, 442, 443, 444, 448, 449, 450, 452
       };
       vector<double> centers{30, 120, 220, 300, 390};
       vector<double> weights{2.0, 0.9, 1.3, 1.1, 0.9};
       vector<double> expect{34.438498496123287,
                             118.45642796465776,
                             215.64776588197637,
                             290.79720889114071,
                             385.55101288097893};
       double k = 20;

       for (int i = 0; i < 250; ++i) {
              sh[i].first = x[i];

              for (int j = 0; j < 5; ++j) {
                     double v = gauss(x[i], centers[j], k) * weights[j];
                     sh[i].second += v;
              }

              cvdebug() << "h[" << sh[i].first << "]=" << sh[i].second << "\n";
       }

       CMeans::PInitializer cci(new CTestFixedInitializer({0, 0.25, 0.5, 0.75, 1}));
       CMeans cm(0.0001, cci);
       CMeans::DVector result_cci(5);
       auto sp = cm.run(sh,  result_cci);

       for (int i = 0; i < 5; ++i) {
              BOOST_CHECK_CLOSE(result_cci[i], expect[i], 0.1);
       }
}


BOOST_AUTO_TEST_CASE( test_store_load_probmap )
{
       CMeans::SparseProbmap my_map(2);
       my_map[0].first = 1.0;
       my_map[1].first = 2.0;
       my_map[0].second = CMeans::DVector({1, 2, 3});
       my_map[1].second = CMeans::DVector({4, 5, 6});
       my_map.save("/tmp/tempmap.mia");
       CMeans::SparseProbmap loaded_map("/tmp/tempmap.mia");
       BOOST_CHECK_EQUAL(loaded_map.size(), my_map.size());
       BOOST_CHECK_EQUAL(loaded_map[0].first, my_map[0].first);
       BOOST_CHECK_EQUAL(loaded_map[1].first, my_map[1].first);
       BOOST_CHECK_EQUAL(loaded_map[0].second.size(), my_map[0].second.size());
       BOOST_CHECK_EQUAL(loaded_map[1].second.size(), my_map[1].second.size());

       for (int i = 0; i < 3; ++i) {
              BOOST_CHECK_EQUAL(loaded_map[0].second[i], my_map[0].second[i]);
              BOOST_CHECK_EQUAL(loaded_map[1].second[i], my_map[1].second[i]);
       }
}


BOOST_AUTO_TEST_CASE( test_get_fuzzy )
{
       CMeans::SparseProbmap my_map(3);
       my_map[0].first = 1.0;
       my_map[1].first = 2.0;
       my_map[2].first = 4.0;
       my_map[0].second = CMeans::DVector({1, 2, 3});
       my_map[1].second = CMeans::DVector({4, 5, 6});
       my_map[2].second = CMeans::DVector({6, 9, 9});
       auto fuzzy0 = my_map.get_fuzzy(0.0);
       BOOST_CHECK_EQUAL(fuzzy0[0], 1.0);
       BOOST_CHECK_EQUAL(fuzzy0[1], 2.0);
       BOOST_CHECK_EQUAL(fuzzy0[2], 3.0);
       fuzzy0 = my_map.get_fuzzy(1.0);
       BOOST_CHECK_EQUAL(fuzzy0[0], 1.0);
       BOOST_CHECK_EQUAL(fuzzy0[1], 2.0);
       BOOST_CHECK_EQUAL(fuzzy0[2], 3.0);
       fuzzy0 = my_map.get_fuzzy(1.2);
       BOOST_CHECK_CLOSE(fuzzy0[0], 1.6, 0.01);
       BOOST_CHECK_CLOSE(fuzzy0[1], 2.6, 0.01);
       BOOST_CHECK_CLOSE(fuzzy0[2], 3.6, 0.01);
       fuzzy0 = my_map.get_fuzzy(2.0);
       BOOST_CHECK_EQUAL(fuzzy0[0], 4.0);
       BOOST_CHECK_EQUAL(fuzzy0[1], 5.0);
       BOOST_CHECK_EQUAL(fuzzy0[2], 6.0);
       fuzzy0 = my_map.get_fuzzy(3.4);
       BOOST_CHECK_CLOSE(fuzzy0[0], 5.4, 0.01);
       BOOST_CHECK_CLOSE(fuzzy0[1], 7.8, 0.01);
       BOOST_CHECK_CLOSE(fuzzy0[2], 8.1, 0.01);
       fuzzy0 = my_map.get_fuzzy(4.0);
       BOOST_CHECK_CLOSE(fuzzy0[0], 6, 0.01);
       BOOST_CHECK_CLOSE(fuzzy0[1], 9, 0.01);
       BOOST_CHECK_CLOSE(fuzzy0[2], 9, 0.01);
       fuzzy0 = my_map.get_fuzzy(4.4);
       BOOST_CHECK_CLOSE(fuzzy0[0], 6, 0.01);
       BOOST_CHECK_CLOSE(fuzzy0[1], 9, 0.01);
       BOOST_CHECK_CLOSE(fuzzy0[2], 9, 0.01);
}

// Helper class neede to get a container with only one template parameter
// that is needed by the cmeans templates
// template alias doesn't cut it.

template <typename T>
class Vec: public std::vector<T>
{
public:
       using vector<T>::vector;
};


BOOST_AUTO_TEST_CASE( test_cmeans_evaluate_probabilities_IF )
{
       Vec<uint16_t> image = {1,     2,   3,   4,   5};
       Vec<float>    gain =  {1.0, 2.0, 1.0, 2.0, 0.5};
       vector<double>   class_centers = {1.5, 3.5};
       Vec<Vec<float>> pv(2, Vec<float>(5));
       cmeans_evaluate_probabilities(image, gain, class_centers, pv);
       BOOST_CHECK_EQUAL(pv[0][0], 1.0);
       BOOST_CHECK_EQUAL(pv[1][0], 0.0);
       BOOST_CHECK_EQUAL(pv[0][1], 1.0);
       BOOST_CHECK_EQUAL(pv[1][1], 0.0);
       BOOST_CHECK_EQUAL(pv[0][2], 0.25f / 2.5f); // 1.5 - 3 - 3.5    (2.25   0.25)  / 2.5
       BOOST_CHECK_EQUAL(pv[1][2], 2.25f / 2.5f);
       BOOST_CHECK_EQUAL(pv[0][3], .9f);
       BOOST_CHECK_EQUAL(pv[1][3], .1f);
       BOOST_CHECK_EQUAL(pv[0][4], 0.0);
       BOOST_CHECK_EQUAL(pv[1][4], 1.0);
}

BOOST_AUTO_TEST_CASE( test_cmeans_update_class_centers )
{
       Vec<uint16_t> image = {1,    2,   3,   4,   5};
       Vec<float>    gain =  {1.0, 2.0, 1.0, 2.0, 0.5};
       vector<double>   class_centers = {1.0, 3.0};
       Vec<Vec<float>> pv(2, Vec<float>(5));
       pv[0][0] = 1.0;
       pv[0][1] = 1.0;
       pv[0][2] = 0.25f / 2.5f; // 1.5 - 3 - 3.5    (2.25   0.25)  / 2.5
       pv[0][3] = .9f;
       pv[0][4] = 0.0;
       pv[1][0] = 0.0;
       pv[1][1] = 0.0;
       pv[1][2] = 2.25f / 2.5f;
       pv[1][3] = .1f;
       pv[1][4] = 1.0;
       //double test_class_center1_w = 1 + 4  + 0.25 * 0.25 / 2.5 / 2.5 * 3 + 0.9 * 0.9 * 8;
       //double test_class_center1_n = 1 + 4  + 0.25 * 0.25 / 2.5 / 2.5 * 1 + 0.9 * 0.9 * 4;
       //double test_class_center2_w = 2.25 * 2.25 / 2.5 / 2.5 * 3 + 0.1 * 0.1 * 8 + 2,5;
       //double test_class_center2_n = 2.25 * 2.25 / 2.5 / 2.5 * 1 + 0.1 * 0.1 * 4 + 0.25;
       double r =  cmeans_update_class_centers(image, gain, pv, class_centers);
       BOOST_CHECK_CLOSE(class_centers[0], 1.197575757, 0.001);
       BOOST_CHECK_CLOSE(class_centers[1], 3.777272727, 0.001);
       BOOST_CHECK_CLOSE(r, sqrt(.197575757  * .197575757 + .777272727 * .777272727), 0.001);
}
