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

#include "vector_test.h"
#include "vector_definitions.h"

#include "matrices_all.h"

#include <boost/generator_iterator.hpp>
#include <boost/random.hpp>
#include <boost/foreach.hpp>
#define foreach         BOOST_FOREACH

#include <ctime>

using namespace boost::numeric::ublas;


////////////////// / declarations /////////////////

typedef boost::mt19937 base_generator_type;
double DIVIDE_FACTOR = 100.0;
double g_nz_ratio = 0.0;
boost::uniform_real<> uni_dist(0,100);

base_generator_type generator(42u);
boost::variate_generator<base_generator_type&, boost::uniform_real<> > uni(generator, uni_dist);

void generate_vector_data(size_t v_len, std::map<size_t, double> &values);
void generate_matrices(size_t rows, size_t v_len, std::vector< std::vector<BaseVector*> > & matrices);
void kbest_test(std::vector<BaseVector*> &matrix);

typedef std::pair<double, size_t> DivColsPair;
typedef std::vector<time_duration> TimeVector;
typedef std::pair<TimeVector, TimeVector> TimeVectorPair;
typedef std::map<DivColsPair, TimeVectorPair> ResultsMap;
typedef std::map<DivColsPair, std::vector<double> > NZRatioMap;

void PrintResults(ResultsMap & results, NZRatioMap &nzmap);

void crs_to_vectors(std::vector<BaseVector*> &matrix, Matrices::CRSMatrixPtr crs);


///////////////// implementation /////////////////

void test_on_matrices(std::vector<std::string> paths) {
	std::map<std::string, std::vector<time_duration> > results;
	for (size_t i = 0; i < paths.size(); i++) {
		std::string path = paths[i];
		std::vector<time_duration> t = test_on_matrix(path);
		results[path] = t;
	}
	std::cout << "path;              map matirx     da matrix      boost map (std) matrix     boost map array boost compressed\n";
	for (std::map<std::string, std::vector<time_duration> >::iterator iter = results.begin(); iter != results.end(); iter++) {
		std::vector<time_duration> t = iter->second;

		std::cout << iter->first.substr(46, iter->first.length() - 46 - 15) << " \t ";
		std::copy(t.begin(), t.end(), std::ostream_iterator<time_duration>(std::cout, " "));
		std::cout << std::endl;
	}
}

std::vector<time_duration> test_on_matrix(std::string path)
{
	std::vector<time_duration> times;
	Matrices::CRSMatrixPtr crs = Matrices::IO::ReadCRSFromSingleFile(path, "test_matrix");
	std::vector< std::vector<BaseVector*> > matrices;
	matrices.push_back(std::vector<BaseVector*>() );
	matrices.push_back(std::vector<BaseVector*>() );
	matrices.push_back(std::vector<BaseVector*>() );
	matrices.push_back(std::vector<BaseVector*>() );
	matrices.push_back(std::vector<BaseVector*>() );
	std::vector<BaseVector*> & map_matrix = matrices[0];
	std::vector<BaseVector*> & da_matrix = matrices[1];
	std::vector<BaseVector*> & boost_mapped_std_matrix = matrices[2];
	std::vector<BaseVector*> & boost_mapped_array_matrix = matrices[3];
	std::vector<BaseVector*> & boost_compressed_matrix = matrices[4];

	size_t rows = crs->NumberOfRows();
	for(size_t r = 0; r < rows; r++)
	{
		map_matrix.push_back( new MapVector() );
		da_matrix.push_back( new DoubleArrayVector() );
		boost_mapped_std_matrix.push_back( new (BoostMappedVector<map_std<size_t,double> >)() );
		boost_mapped_array_matrix.push_back( new (BoostMappedVector<map_array<size_t,double> >)() );
		boost_compressed_matrix.push_back( new BoostCompressedVector()  );
	}

	std::cout << "Filling map matrix with data" << std::endl;
	crs_to_vectors(map_matrix, crs);
	std::cout << "Testing map matrix" << std::endl;
	ptime start = microsec_clock::local_time();
	//kbest_test(map_matrix);
	ptime end = microsec_clock::local_time();
	time_duration len = end-start;
	times.push_back(len);
	std::cout << "Test of map matrix in " << len << std::endl;
	map_matrix.clear();

	std::cout << "Filing da matrix with data" << std::endl;
	crs_to_vectors(da_matrix, crs);
	std::cout << "Testing da matrix" << std::endl;
	start = microsec_clock::local_time();
	kbest_test(da_matrix);
	end = microsec_clock::local_time();
	time_duration len2 = end-start;
	times.push_back(len2);
	std::cout << "Test of da matrix in " << len2 << std::endl;

	std::cout << "Filing boost mapped (std) matrix with data" << std::endl;
	crs_to_vectors(boost_mapped_std_matrix, crs);
	start = microsec_clock::local_time();
	//kbest_test(boost_mapped_std_matrix);
	end = microsec_clock::local_time();
	time_duration len3 = end - start;
	times.push_back(len3);
	std::cout << "Test of boost mapped (std) matrix in " << len3 << std::endl;

	std::cout << "Filing boost mapped (array) matrix with data" << std::endl;
	crs_to_vectors(boost_mapped_array_matrix, crs);
	start = microsec_clock::local_time();
	kbest_test(boost_mapped_array_matrix);
	end = microsec_clock::local_time();
	time_duration len4 = end - start;
	times.push_back(len4);
	std::cout << "Test of boost mapped (array) matrix in " << len4  << std::endl;

	std::cout << "Filing boost compressed matrix with data" << std::endl;
	crs_to_vectors(boost_compressed_matrix, crs);
	start = microsec_clock::local_time();
	kbest_test(boost_compressed_matrix);
	end = microsec_clock::local_time();
	time_duration len5 = end - start;
	times.push_back(len5);
	std::cout << "Test of boost compressed matrix in " << len5  << std::endl;

	std::cout << "Free mem.." << std::endl;
	BOOST_FOREACH (std::vector<BaseVector*> & matrix, matrices)
		BOOST_FOREACH (BaseVector* vector, matrix)
			delete vector;
	return times;
}


void crs_to_vectors(std::vector<BaseVector*> &matrix, Matrices::CRSMatrixPtr crs)
{
	size_t rows = crs->NumberOfRows();
	for(size_t row_no = 0; row_no < rows; row_no++)
	{
		std::map<size_t, double> *vals = crs->GetCompressedRow(row_no)->GetValues();
		matrix[row_no]->SetValues(*vals);
	}
}

void full_vector_test(size_t rows, size_t no_of_repeats)
{
	std::vector<double> div_factors;
	div_factors.push_back(1);
	div_factors.push_back(10);
	div_factors.push_back(15);
	div_factors.push_back(100);

	std::vector<size_t> cols;
	cols.push_back(1000);
	cols.push_back(10000);
	cols.push_back(50000);
	cols.push_back(100000);
	cols.push_back(250000);
	cols.push_back(300000);
	cols.push_back(500000);// */

	ResultsMap results;
	NZRatioMap nzmap;

	for(std::vector<size_t>::iterator cols_iter = cols.begin(); cols_iter != cols.end(); cols_iter++)
	{
		for(std::vector<double>::iterator div_iter = div_factors.begin(); div_iter != div_factors.end(); div_iter++)
		{
			DIVIDE_FACTOR = *div_iter;
			TimeVectorPair tvp;
			std::vector<double> nzs;
			for(size_t repeat = 0; repeat < no_of_repeats; repeat++)
			{
				std::cout << "Div factor: " << DIVIDE_FACTOR << ", cols: " << *cols_iter << " rep no " << repeat << " / " << no_of_repeats <<std::endl;
				// map, da
				std::pair<time_duration, time_duration> result = vector_test(rows, *cols_iter);
				tvp.first.push_back( result.first );
				tvp.second.push_back( result.second );
				nzs.push_back(g_nz_ratio);
			}

			results[ DivColsPair(*div_iter, *cols_iter) ] = tvp;
			nzmap[DivColsPair(*div_iter, *cols_iter)] = nzs;
			PrintResults(results, nzmap);

		}
	}
	PrintResults(results, nzmap);
}
void PrintResults(ResultsMap & results, NZRatioMap &nzmap)
{
	std::ofstream out("results.txt");

	for(ResultsMap::iterator iter = results.begin(); iter != results.end(); iter++)
	{
		DivColsPair dcp = iter->first;
		std::cout << "Div: " << dcp.first << " cols: " << dcp.second << std::endl;
		out << "Div: " << dcp.first << " cols: " << dcp.second << std::endl;
		TimeVectorPair &tvp = iter->second;
		size_t len = tvp.first.size();

		time_duration avg1,avg2;
		std::cout << "no \t" << "map" << "  \t  " << "da"<< std::endl;
		out << "no \t" << "map" << "  \t  " << "da"<< std::endl;
		double nzavg = 0.0;
		for(size_t result_no = 0; result_no < len; result_no++)
		{
			std::cout << result_no << ".\t" << tvp.first[result_no] << "  \t  " << tvp.second[result_no]  << "    nz: " << nzmap[dcp][result_no] <<  std::endl;
			out << result_no << ".\t" << tvp.first[result_no] << "  \t  " << tvp.second[result_no]  << nzmap[dcp][result_no] <<std::endl;
			avg1 += tvp.first[result_no];
			avg2 += tvp.second[result_no];
			nzavg += nzmap[dcp][result_no];
		}
		std::cout << "avg \t" << avg1 / len << "  \t  " << avg2 / len <<"    nz: " << nzavg / len <<std::endl;
		out << "avg \t" << avg1 / len << "  \t  " << avg2 / len << "    nz: " << nzavg / len <<std::endl;
	}
	out.close();
}

std::pair<time_duration, time_duration> vector_test(size_t rows, size_t cols)
{
	generator.seed(static_cast<unsigned int>(std::time(0)));
	std::cout << "Vectors test...\n";
	std::vector< std::vector<BaseVector*> > matrices;
	matrices.push_back(std::vector<BaseVector*>() );
	matrices.push_back(std::vector<BaseVector*>() );
	std::vector<BaseVector*> & map_matrix = matrices[0];
	std::vector<BaseVector*> & da_matrix = matrices[1];

	for(size_t r = 0; r < rows; r++)
	{
		map_matrix.push_back( new MapVector() );
		da_matrix.push_back( new DoubleArrayVector() );
	}

	ptime start = microsec_clock::local_time();
	generate_matrices( rows, cols, matrices);
	ptime end = microsec_clock::local_time();
	time_duration len = end-start;
	std::cout << "Generated data in " << len.total_milliseconds() << " ms." << std::endl;

	start = microsec_clock::local_time();
	kbest_test(map_matrix);
	end = microsec_clock::local_time();
	time_duration len1 = end-start;
	std::cout << "Test of map matrix in " << len1.total_milliseconds() << " ms." << std::endl;

	start = microsec_clock::local_time();
	kbest_test(da_matrix);
	end = microsec_clock::local_time();
	time_duration len2 = end-start;
	std::cout << "Test of array matrix in " << len2.total_milliseconds() << " ms." << std::endl;

	for(size_t r = 0; r < rows; r++)
	{
		delete map_matrix[r];
		delete da_matrix[r];
	}

	return std::pair<time_duration, time_duration> (len1, len2);
}

void kbest_test(std::vector<BaseVector*> &matrix)
{
	// for each row
	for(size_t row_no = 0; row_no < matrix.size(); row_no++)
	{
		BaseVector* v1 = matrix[row_no];
		// compare to all others
		double sim = 0;
		for(size_t second_row = 0; second_row < matrix.size(); second_row++)
		{
			if (second_row == row_no)
				continue;
			BaseVector* v2 = matrix[second_row];
			sim = v1->Cosine(v2);
		}

		if (row_no % 1000 == 0)
			std::cout << "Comparing vectors " << row_no << " / " << matrix.size() << " | "<< sim <<std::endl;
	}
}

void generate_matrices(size_t rows, size_t v_len, std::vector< std::vector<BaseVector*> > & matrices)
{
	size_t nz = 0;
	for(size_t row_no = 0; row_no < rows; row_no++)
	{
		std::map<size_t, double> vals;
		generate_vector_data(v_len, vals);
		nz+= vals.size();
		for(size_t matrix_no = 0; matrix_no < matrices.size(); matrix_no++)
			matrices[matrix_no][row_no]->SetValues(vals);
		if (row_no % 1000 == 0)
			std::cout << "Generating data " << row_no << " / " << rows << std::endl;
	}

	g_nz_ratio = double(nz) / double(rows*v_len);
	std::cout << "Generate matrix with "<< nz << " nz cells (" << g_nz_ratio << ")" << std::endl;
}

void generate_vector_data(size_t v_len, std::map<size_t, double> &values)
{
	boost::uniform_int<> uni_int(0,v_len);
	boost::variate_generator<base_generator_type&, boost::uniform_int<> > gen_ind(generator, uni_int);

	size_t nz = 0;
	while(nz == 0)
		nz = gen_ind() / DIVIDE_FACTOR;
	values.clear();
	while(values.size() < nz)
	{
		size_t ind = gen_ind();
		double val = uni();
		values[ind] = val;
	}
}
