/*
    Copyright (C) 2012 Bartosz Broda, Jacek Bochenek, Łukasz Biłeńkij, 
    Damian Jaworski, Wojciech Mazur, Dominik Piasecki, Maciej Piasecki, 
    Radosław Ramocki
    
    This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser 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 LICENSE and COPYING files for more details.
*/

#define BOOST_TEST_MODULE master
#include <boost/test/included/unit_test.hpp>
#include <boost/math/special_functions/round.hpp>
#include "Evaluator.h"

using namespace evaluator;

double round(double number, int places)
{
    double temp, mult;
    mult = pow(10.0, places);
    temp = floor(number * mult + 0.5);
    temp = temp / mult;
    return temp;
}

BOOST_AUTO_TEST_CASE(test_homogeneity1)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFile = (data_dir / "homo1.clust").string();
    std::string classFile = (data_dir / "homo.classes").string();

    eval.InitEvaluator(clustFile, classFile, true);
    double precision, recall;
    BOOST_CHECK(round(eval.CountPurity(), 2) == 0.71);
    BOOST_CHECK(round(eval.CountNMI(), 2) == 0.50);
    BOOST_CHECK(round(eval.CountRandIndex(), 2) == 0.68);
    BOOST_CHECK(round(eval.CountFmeasure(1), 2) == 0.47);
    BOOST_CHECK(round(eval.CountFmeasure(5), 2) == 0.52);
    BOOST_CHECK(round(eval.CountBCubed(0.5, precision, recall), 2) == 0.64);
}

BOOST_AUTO_TEST_CASE(test_homogeneity2)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFile = (data_dir / "homo2.clust").string();
    std::string classFile = (data_dir / "homo.classes").string();

    eval.InitEvaluator(clustFile, classFile, true);
    double precision, recall;
    BOOST_CHECK(round(eval.CountPurity(), 2) == 0.79);
    BOOST_CHECK(round(eval.CountNMI(), 2) == 0.58);
    BOOST_CHECK(round(eval.CountRandIndex(), 2) == 0.70);
    BOOST_CHECK(round(eval.CountFmeasure(1), 2) == 0.49);
    BOOST_CHECK(round(eval.CountFmeasure(5), 2) == 0.52);
    BOOST_CHECK(round(eval.CountBCubed(0.5, precision, recall), 2) == 0.69);
}

BOOST_AUTO_TEST_CASE(test_completeness1)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFile = (data_dir / "compl1.clust").string();
    std::string classFile = (data_dir / "compl.classes").string();

    eval.InitEvaluator(clustFile, classFile, true);
    double precision, recall;
    BOOST_CHECK(round(eval.CountPurity(), 2) == 0.79);
    BOOST_CHECK(round(eval.CountNMI(), 2) == 0.57);
    BOOST_CHECK(round(eval.CountRandIndex(), 2) == 0.68);
    BOOST_CHECK(round(eval.CountFmeasure(1), 2) == 0.47);
    BOOST_CHECK(round(eval.CountFmeasure(5), 2) == 0.48);
    BOOST_CHECK(round(eval.CountBCubed(0.5, precision, recall), 2) == 0.70);
}

BOOST_AUTO_TEST_CASE(test_completeness2)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFile = (data_dir / "compl2.clust").string();
    std::string classFile = (data_dir / "compl.classes").string();

    eval.InitEvaluator(clustFile, classFile, true);
    double precision, recall;
    BOOST_CHECK(round(eval.CountPurity(), 2) == 0.79);
    BOOST_CHECK(round(eval.CountNMI(), 2) == 0.60);
    BOOST_CHECK(round(eval.CountRandIndex(), 2) == 0.70);
    BOOST_CHECK(round(eval.CountFmeasure(1), 2) == 0.53);
    BOOST_CHECK(round(eval.CountFmeasure(5), 2) == 0.55);
    BOOST_CHECK(round(eval.CountBCubed(0.5, precision, recall), 2) == 0.72);
}

BOOST_AUTO_TEST_CASE(test_ragbag1)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFile = (data_dir / "rag1.clust").string();
    std::string classFile = (data_dir / "rag.classes").string();

    eval.InitEvaluator(clustFile, classFile, true);
    double precision, recall;
    BOOST_CHECK(round(eval.CountPurity(), 2) == 0.56);
    BOOST_CHECK(round(eval.CountNMI(), 2) == 0.61);
    BOOST_CHECK(round(eval.CountRandIndex(), 2) == 0.72);
    BOOST_CHECK(round(eval.CountFmeasure(1), 2) == 0.55);
    BOOST_CHECK(round(eval.CountFmeasure(5), 2) == 0.94);
    BOOST_CHECK(round(eval.CountBCubed(0.5, precision, recall), 2) == 0.66);
}

BOOST_AUTO_TEST_CASE(test_ragbag2)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFile = (data_dir / "rag2.clust").string();
    std::string classFile = (data_dir / "rag.classes").string();

    eval.InitEvaluator(clustFile, classFile, true);
    double precision, recall;
    BOOST_CHECK(round(eval.CountPurity(), 2) == 0.56);
    BOOST_CHECK(round(eval.CountNMI(), 2) == 0.61);
    BOOST_CHECK(round(eval.CountRandIndex(), 2) == 0.72);
    BOOST_CHECK(round(eval.CountFmeasure(1), 2) == 0.55);
    BOOST_CHECK(round(eval.CountFmeasure(5), 2) == 0.94);
    BOOST_CHECK(round(eval.CountBCubed(0.5, precision, recall), 2) == 0.71);
}

BOOST_AUTO_TEST_CASE(test_sizeVsQuantity1)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFile = (data_dir / "size1.clust").string();
    std::string classFile = (data_dir / "size.classes").string();

    eval.InitEvaluator(clustFile, classFile, true);
    double precision, recall;
    BOOST_CHECK(round(eval.CountPurity(), 2) == 1.00);
    BOOST_CHECK(round(eval.CountNMI(), 2) == 0.88);
    BOOST_CHECK(round(eval.CountRandIndex(), 2) == 0.95);
    BOOST_CHECK(round(eval.CountFmeasure(1), 2) == 0.83);
    BOOST_CHECK(round(eval.CountFmeasure(5), 2) == 0.72);
    BOOST_CHECK(round(eval.CountBCubed(0.5, precision, recall), 2) == 0.82);
}

BOOST_AUTO_TEST_CASE(test_sizeVsQuantity2)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFile = (data_dir / "size2.clust").string();
    std::string classFile = (data_dir / "size.classes").string();

    eval.InitEvaluator(clustFile, classFile, true);
    double precision, recall;
    BOOST_CHECK(round(eval.CountPurity(), 2) == 1.00);
    BOOST_CHECK(round(eval.CountNMI(), 2) == 0.94);
    BOOST_CHECK(round(eval.CountRandIndex(), 2) == 0.95);
    BOOST_CHECK(round(eval.CountFmeasure(1), 2) == 0.83);
    BOOST_CHECK(round(eval.CountFmeasure(5), 2) == 0.72);
    BOOST_CHECK(round(eval.CountBCubed(0.5, precision, recall), 2) == 0.93);
}

BOOST_AUTO_TEST_CASE(test_custom)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFile = (data_dir / "test.clust").string();
    std::string classFile = (data_dir / "test.classes").string();

    eval.InitEvaluator(clustFile, classFile, true);
    double precision, recall;
    BOOST_CHECK(round(eval.CountPurity(), 2) == 0.75);
    BOOST_CHECK(round(eval.CountNMI(), 2) == 0.17);
    BOOST_CHECK(round(eval.CountRandIndex(), 2) == 0.57);
    BOOST_CHECK(round(eval.CountFmeasure(1), 2) == 0.54);
    BOOST_CHECK(round(eval.CountFmeasure(5), 2) == 0.54);
    BOOST_CHECK(round(eval.CountBCubed(0.5, precision, recall), 2) == 0.63);
}

BOOST_AUTO_TEST_CASE(test_good_outlierA)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFileBetter = (data_dir / "out1a.clust").string();
    std::string clustFileWorse = (data_dir / "out2a.clust").string();
    std::string classFile = (data_dir / "outa.classes").string();

    eval.InitEvaluator(clustFileBetter, classFile, true);
    double precision, recall;
    double BCubedBetter = eval.CountBCubed(0.5, precision, recall);

    eval.InitEvaluator(clustFileWorse, classFile, true);

    double BCubedWorse = eval.CountBCubed(0.5, precision, recall);
//    std::cout << "Better: " << BCubedBetter << std::endl;
//    std::cout << "Worse: " << BCubedWorse << std::endl;

    BOOST_CHECK(BCubedBetter > BCubedWorse);
}

BOOST_AUTO_TEST_CASE(test_bad_outlierB)
{
    Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

    std::string clustFileWorse = (data_dir / "out1b.clust").string();
    std::string clustFileBetter = (data_dir / "out2b.clust").string();
    std::string classFile = (data_dir / "outb.classes").string();

    eval.InitEvaluator(clustFileWorse, classFile, true);
    double precision, recall;
    double BCubedWorse = eval.CountBCubed(0.5, precision, recall);

    eval.InitEvaluator(clustFileBetter, classFile, true);

    double BCubedBetter = eval.CountBCubed(0.5, precision, recall);

//    std::cout << "Worse: " << BCubedWorse << std::endl;
//    std::cout << "Better: " << BCubedBetter << std::endl;

    BOOST_CHECK(BCubedBetter > BCubedWorse);
}

BOOST_AUTO_TEST_CASE(test_DunnIndex)
{
	using namespace smartcomparator::compare;
	Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

	std::string matrix_dir = data_dir.string();
	std::string matrix_name = "matrix";
	std::streambuf* oldbuff = std::cout.rdbuf();
	std::stringstream s;
	std::cout.rdbuf(s.rdbuf());
	Matrices::SuperMatrixPtr matrix = Matrices::IO::LoadSuperMatrix(matrix_dir, matrix_name, false, true);
	CompareFunctionPtr cmp_fun = FunctionFactory::CreateFunction("cmp(simfunction=cosine())", matrix);
	eval.SetCompareFunction(cmp_fun);
	std::cout.rdbuf(oldbuff);

	std::string clustFile = (data_dir / "dunn.clusters").string();

	eval.InitEvaluator(clustFile, "", true);
	BOOST_CHECK_CLOSE_FRACTION(eval.CountDunnIndex(cmp_fun, false), 0.333333, 0.00001);
}

BOOST_AUTO_TEST_CASE(test_DunnIndex2)
{
	using namespace smartcomparator::compare;
	Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

	std::string matrix_dir = data_dir.string();
	std::string matrix_name = "matrix";
	std::streambuf* oldbuff = std::cout.rdbuf();
	std::stringstream s;
	std::cout.rdbuf(s.rdbuf());
	Matrices::SuperMatrixPtr matrix = Matrices::IO::LoadSuperMatrix(matrix_dir, matrix_name, false, true);
	CompareFunctionPtr cmp_fun = FunctionFactory::CreateFunction("cmp(simfunction=cosine())", matrix);
	eval.SetCompareFunction(cmp_fun);
	std::cout.rdbuf(oldbuff);

	std::string clustFile = (data_dir / "dunn.clusters").string();

	eval.InitEvaluator(clustFile, "", true);
	BOOST_CHECK_CLOSE_FRACTION(eval.CountDunnIndex(cmp_fun, true), 0.849991, 0.00001);
}

BOOST_AUTO_TEST_CASE(test_DunnIndex_withSimMatrix)
{
	using namespace smartcomparator::compare;
	Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

	std::string matrix_dir = data_dir.string();
	std::streambuf* oldbuff = std::cout.rdbuf();
	std::stringstream s;
	std::cout.rdbuf(s.rdbuf());
	Matrices::SuperMatrixPtr matrix = Matrices::IO::LoadSuperMatrix(matrix_dir, "matrix", false, true);
	CompareFunctionPtr cmp_fun = FunctionFactory::CreateFunction("cmp(simfunction=cosine())", matrix);
	eval.SetCompareFunction(cmp_fun);
	Matrices::SuperMatrixPtr sim_matrix = Matrices::IO::LoadSuperMatrix(matrix_dir, "sim_matrix", false, true);
	eval.SetSimilarityMatrix(sim_matrix);
	std::cout.rdbuf(oldbuff);

	std::string clustFile = (data_dir / "dunn.clusters").string();

	eval.InitEvaluator(clustFile, "", true);
	BOOST_CHECK_CLOSE_FRACTION(eval.CountDunnIndex(cmp_fun, false), 0.333333, 0.00001);
}

BOOST_AUTO_TEST_CASE(test_DunnIndex2_withSimMatrix)
{
	using namespace smartcomparator::compare;
	Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

	std::string matrix_dir = data_dir.string();
	std::streambuf* oldbuff = std::cout.rdbuf();
	std::stringstream s;
	std::cout.rdbuf(s.rdbuf());
	Matrices::SuperMatrixPtr matrix = Matrices::IO::LoadSuperMatrix(matrix_dir, "matrix", false, true);
	CompareFunctionPtr cmp_fun = FunctionFactory::CreateFunction("cmp(simfunction=cosine())", matrix);
	eval.SetCompareFunction(cmp_fun);
	Matrices::SuperMatrixPtr sim_matrix = Matrices::IO::LoadSuperMatrix(matrix_dir, "sim_matrix", false, true);
	eval.SetSimilarityMatrix(sim_matrix);
	std::cout.rdbuf(oldbuff);

	std::string clustFile = (data_dir / "dunn.clusters").string();

	eval.InitEvaluator(clustFile, "", true);
	BOOST_CHECK_CLOSE_FRACTION(eval.CountDunnIndex(cmp_fun, true), 0.849991, 0.00001);
}

BOOST_AUTO_TEST_CASE(test_ExpectedDensity)
{
	using namespace smartcomparator::compare;
	Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

	std::string matrix_dir = data_dir.string();
	std::string matrix_name = "matrix";
	std::streambuf* oldbuff = std::cout.rdbuf();
	std::stringstream s;
	std::cout.rdbuf(s.rdbuf());
	Matrices::SuperMatrixPtr matrix = Matrices::IO::LoadSuperMatrix(matrix_dir, matrix_name, false, true);
	CompareFunctionPtr cmp_fun = FunctionFactory::CreateFunction("cmp(simfunction=cosine())", matrix);
	eval.SetCompareFunction(cmp_fun);
	std::cout.rdbuf(oldbuff);

	std::string clustFile = (data_dir / "dunn.clusters").string();

	eval.InitEvaluator(clustFile, "", true);
	BOOST_CHECK_CLOSE_FRACTION(eval.CountExpectedDensity(), 0.875069, 0.00001);
}

BOOST_AUTO_TEST_CASE(test_ExpectedDensity_withSimMatrix)
{
	using namespace smartcomparator::compare;
	Evaluator eval;

#ifdef EVALUATOR_TEST_DATA_DIR
	boost::filesystem::path data_dir(EVALUATOR_TEST_DATA_DIR);
#else
	boost::filesystem::path data_dir("./");
#endif

	std::string matrix_dir = data_dir.string();
	std::streambuf* oldbuff = std::cout.rdbuf();
	std::stringstream s;
	std::cout.rdbuf(s.rdbuf());
	Matrices::SuperMatrixPtr matrix = Matrices::IO::LoadSuperMatrix(matrix_dir, "matrix", false, true);
	CompareFunctionPtr cmp_fun = FunctionFactory::CreateFunction("cmp(simfunction=cosine())", matrix);
	eval.SetCompareFunction(cmp_fun);
	Matrices::SuperMatrixPtr sim_matrix = Matrices::IO::LoadSuperMatrix(matrix_dir, "sim_matrix", false, true);
	eval.SetSimilarityMatrix(sim_matrix);
	std::cout.rdbuf(oldbuff);

	std::string clustFile = (data_dir / "dunn.clusters").string();

	eval.InitEvaluator(clustFile, "", true);
	BOOST_CHECK_CLOSE_FRACTION(eval.CountExpectedDensity(), 0.875069, 0.00001);
}
