/*
    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 "groupedboundary.h"

BoundaryGrouper::BoundaryGrouper(std::string grfile, std::string mdir,std::string mname,std::string outdir,std::string outname, std::string method)
{
	this->groupFilename = grfile;
	this->matrixDir = mdir;
	this->matrixName = mname;
	this->outMatrixDir = outdir;
	this->outMatrixName = outname;
	this->method = method;

	skipOutliers = true;
}

void BoundaryGrouper::loadGroups()
{
	std::ifstream groupFile;
	groupFile.open(groupFilename.c_str());
	size_t group;
	size_t wordIndex = 0;

	groups.clear();
	groupFile>>group;
	while(!groupFile.eof())
	{
		
		if (groups.find(group) != groups.end())
		{
			groups[group]->push_back(wordIndex);
		}
		else
		{
			groups[group] = boost::shared_ptr<std::vector<size_t> >(new std::vector<size_t>());
			groups[group]->push_back(wordIndex);
		}
		wordIndex ++;
		groupFile>>group;
	}
}

void BoundaryGrouper::loadSourceMatrix()
{
	this->sourceMatrix = Matrices::IO::LoadSuperMatrix(matrixDir, matrixName, true, true);
}

void BoundaryGrouper::processRowAverage(std::set<size_t>::iterator rowIt, std::map<size_t, size_t_vector_ptr >::iterator it, size_t colNo)
{
	const size_t& i = (*rowIt);
	double avg = 0;
	size_t nz = 0;
	for (size_t j = 0; j<(*it).second->size(); j++)
	{
		double cellVal = sourceMatrix->GetCRS()->GetCell(i,(*(*it).second)[j]);
		if (true)
		{
			avg+=cellVal;
			nz++;
		}
	}
	if (avg > 0)
	{
		avg /= nz;
	}
	newMatrix->GetCRS()->SetCell(avg,i,colNo);
	newMatrix->GetCCS()->SetCell(avg,i,colNo);

}

void BoundaryGrouper::processRowWeightedAverage(std::set<size_t>::iterator rowIt, std::map<size_t, size_t_vector_ptr >::iterator it, size_t colNo)
{
	const size_t& i = (*rowIt);
	double avg = 0;
	size_t nz = 0;
	for (size_t j = 0; j<(*it).second->size(); j++)
	{
		double cellVal = sourceMatrix->GetCRS()->GetCell(i,(*(*it).second)[j]);
		if (true)
		{
			avg+=cellVal;
			nz++;
		}
	}
	if (avg > 0)
	{
		avg /= nz;
		avg *= log((*it).second->size());
	}
	newMatrix->GetCRS()->SetCell(avg,i,colNo);
	newMatrix->GetCCS()->SetCell(avg,i,colNo);

}

void BoundaryGrouper::processRowSum(std::set<size_t>::iterator rowIt, std::map<size_t, size_t_vector_ptr >::iterator it, size_t colNo)
{
	const size_t& i = (*rowIt);
	double sum = 0;
	for (size_t j = 0; j<(*it).second->size(); j++)
	{
		sum+= sourceMatrix->GetCRS()->GetCell(i,(*(*it).second)[j]);
	}
	newMatrix->GetCRS()->SetCell(sum,i,colNo);
	newMatrix->GetCCS()->SetCell(sum,i,colNo);
}
void BoundaryGrouper::processRowMedian(std::set<size_t>::iterator rowIt, std::map<size_t, size_t_vector_ptr >::iterator it, size_t colNo)
{
	const size_t& i = (*rowIt);
	double val = 0;
	std::set<double> rowVals;
	for (size_t j = 0; j<(*it).second->size(); j++)
	{
		val = sourceMatrix->GetCRS()->GetCell(i,(*(*it).second)[j]);
		if (val > 0)
		{
			rowVals.insert(val);
		}
	}
	size_t setSize = rowVals.size();
	if (setSize % 2)
	{
		size_t x = 0;
		for (std::set<double>::iterator sortedIt = rowVals.begin(); sortedIt != rowVals.end() && x <= setSize/2; ++sortedIt)
		{
			x++;
			val = (*sortedIt);
		}
	}
	else
	{
		size_t x = 0;
		for (std::set<double>::iterator sortedIt = rowVals.begin(); sortedIt != rowVals.end() && x <= setSize/2; ++sortedIt)
		{
			if(x == setSize/2 - 1)
			{
				val = (*sortedIt);
			}
			if(x == setSize/2)
			{
				val = (val + (*sortedIt)) / 2;
			}
			x++;
		}
	}
	newMatrix->GetCRS()->SetCell(val,i,colNo);
	newMatrix->GetCCS()->SetCell(val,i,colNo);
}

void BoundaryGrouper::createResultMatrix()
{
	Matrices::CRSMatrixPtr crsM(new Matrices::CRSMatrix("crs",sourceMatrix->NumberOfRows(),groups.size()));
	Matrices::CCSMatrixPtr ccsM(new Matrices::CCSMatrix("ccs",sourceMatrix->NumberOfRows(),groups.size()));
	Matrices::FeatureVectorPtr rowFeatures(new Matrices::FeatureVector());
	Matrices::FeatureVectorPtr columnFeatures(new Matrices::FeatureVector());
	std::map<size_t, std::string> boundaries;
	boundaries[sourceMatrix->NumberOfColumns()-1] = "all";

	newMatrix = Matrices::SuperMatrixPtr (new Matrices::SuperMatrix(outMatrixName, ccsM, crsM, rowFeatures, columnFeatures, 666, "HOW DO I EVEN", boundaries));

	if (method == "bounds")
	{
		processMatrixBounds(newMatrix);
		return;
	}

	size_t colNo = 0;
	for(std::map<size_t, size_t_vector_ptr >::iterator it = groups.begin(); it!=groups.end(); ++it)
	{
		if ((*it).first == size_t(-1))
		{
			std::cout<<"Skipping outlier column\n";
			continue;
		}
		std::cout<<"Processing column "<<colNo<<" of "<<groups.size()<<"\n";
		std::set<size_t> cellsToCheck;
		for (size_t j = 0; j<(*it).second->size(); j++)
		{
			Matrices::CompressedVectorPtr colCells = sourceMatrix->GetColumn((*(*it).second)[j]);
			std::map<size_t,double> colvals = *(colCells->GetValues());
			for (std::map<size_t,double>::iterator colit = colvals.begin(); colit!=colvals.end(); ++colit)
			{
				cellsToCheck.insert((*colit).first);
			}
		}
		for (std::set<size_t>::iterator rowIt=cellsToCheck.begin(); rowIt!=cellsToCheck.end(); ++rowIt)
		{
			if (method == "sum")
				processRowSum(rowIt, it, colNo);
			if (method == "avg")
				processRowAverage(rowIt, it, colNo);
			if (method == "weighted_avg")
				processRowWeightedAverage(rowIt, it, colNo);
			if (method == "median")
				processRowMedian(rowIt, it, colNo);
		}
		colNo ++;
	}

	for(size_t row = 0; row<newMatrix->NumberOfRows(); row++)
	{
		Matrices::CompressedVectorPtr v = crsM->GetCompressedRow(row);

		Matrices::FeatureVector f = *(sourceMatrix->GetRowFeatures());
		std::string word =f[row].GetLabel();
		size_t TF =f[row].GetGlobalTermFrequency();
		size_t tf =f[row].GetTermFrequency();

		size_t nz = v->GetValues()->size();
		double entropy = Measures::CountVectorEntropy( *v );

		rowFeatures->push_back( Matrices::Feature(word, nz, tf, TF, entropy));
	}
	for(size_t column = 0; column<newMatrix->NumberOfColumns(); column++)
	{
		Matrices::CompressedVectorPtr v = ccsM->GetCompressedColumn(column);

		std::string word = boost::lexical_cast<std::string>(column);;
		size_t TF = 42;
		size_t tf = (size_t) Measures::CountVectorSum( *v );

		size_t nz = v->GetValues()->size();
		double entropy = Measures::CountVectorEntropy( *v );

		columnFeatures->push_back( Matrices::Feature(word, nz, tf, TF, entropy));
	}
}

void BoundaryGrouper::processMatrixBounds(Matrices::SuperMatrixPtr outMatrix)
{
	size_t colNo = 0;
	std::stringstream ss;
	for(std::map<size_t, size_t_vector_ptr >::iterator it = groups.begin(); it!=groups.end(); ++it)
	{
		if ((*it).first == size_t(-1) && skipOutliers)
		{
			std::cout<<"Skipping outlier column\n";
			continue;
		}
		for (size_t x=0; x<(*it).second->size();x++)
		{
			Matrices::CompressedVectorPtr column = sourceMatrix->GetCCS()->GetCompressedColumn((*(*it).second)[x]);
			outMatrix->GetCRS()->SetCompressedColumn(colNo, column);
			colNo++;
		}
		ss.flush();
		ss<<"Group " << (*it).first;
		outMatrix->Boundaries()[colNo] = ss.str();
	}
}

void BoundaryGrouper::saveResultMatrix()
{
	Matrices::IO::WriteSuperMatrix(newMatrix,outMatrixDir);
}
