/*
    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 "../comparator_stdheaders.h"
#include "SVDTransformation.h"

static log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("SuperMatrix.comparator.transformations.SVDTransformation"));

namespace smartcomparator
{
	namespace transformations
	{
		SVDTransformation::SVDTransformation(Matrices::SuperMatrixPtr matrix, int dimensions, bool testing_mode)
							:Transformation(matrix),
							m_pMatrix(matrix),
							m_dimensions(dimensions),
							m_testing(testing_mode)
		{
			/* Checking SVDLIBC; SVD is defined in CMakeLists.txt */
			if(!atoi(SVD))
				throw std::runtime_error("SVDLIBC is not installed");
		}

		void SVDTransformation::TransformMatrix()
		{
			LOG4CXX_INFO(logger, "SVDTransformation start");
			SVDRun();
		}

		void SVDTransformation::SVDRun()
		{
			workDir= open_temp("/tmp")+"/";
			matrixPath=workDir+"matrix.st";


			STFormat=m_pMatrix->GetCCS();
			Matrices::IO::WriteCCSToST(*(STFormat),matrixPath);

			SVDCompute();
			LoadAndMultiplyMatrices();
			crs2sm();
			boost::filesystem::remove_all(boost::filesystem::path(workDir));

		}

		std::string SVDTransformation::open_temp(std::string path)
		{
			path += "/supermatrix_XXXXXX";
			std::vector<char> dst_path(path.begin(), path.end());
			dst_path.push_back('\0');


			mkdtemp(&dst_path[0]);
			path.assign(dst_path.begin(), dst_path.end() - 1);

			return path;
		}

		void SVDTransformation::SVDCompute()
		{
			size_t resultsDimensions=0;
			if(m_dimensions>m_pMatrix->NumberOfColumns())
				throw std::runtime_error("Dimensios value is bigger than matrix");

			std::stringstream command;
			if(m_dimensions==0)
				command<<"tcsh -c 'svd -o "<<workDir<<" -r st "<<matrixPath<<"'";
			else
				command<<"tcsh -c 'svd -d "<<m_dimensions<<" -o "<<workDir<<" -r st "<<matrixPath<<"'";
			int returnValue = system(command.str().c_str());
			if(returnValue!=0)
				LOG4CXX_ERROR(logger,"Return value from system: "<<returnValue);
			std::ifstream Sfile((workDir+"-S").c_str());
			if(!Sfile)
				throw std::runtime_error("Cannot open results file from SVD\n");
			std::string line;
			std::getline(Sfile,line);
			resultsDimensions = atoi(line.c_str());
			Sfile.close();
			if(resultsDimensions>m_dimensions && m_dimensions!=0)
				throw std::runtime_error("Unknown error - resultsDimensions>m_dimensions");


		}

		void SVDTransformation::LoadAndMultiplyMatrices()
		{
			Matrices::CRSMatrixPtr UCRS;
			Matrices::CRSMatrixPtr SCRS;
			Matrices::CRSMatrixPtr VCRS;
			std::string line;
			std::vector<std::string> tokens;

			std::ifstream Ufile((workDir+"-Ut").c_str());
			if(!Ufile)
				throw std::runtime_error("Cannot open results file from SVD\n");
			std::getline(Ufile,line);
			boost::split(tokens,line,boost::is_any_of(" "));
			size_t URows = atoi(tokens[1].c_str());
			size_t UCols = atoi(tokens[0].c_str());
			UCRS = Matrices::CRSMatrixPtr(new Matrices::CRSMatrix("U matrix",URows,UCols));
			for(size_t i=0;i<UCols;i++)
			{
				if(!(std::getline(Ufile,line)))
					throw std::runtime_error("Unexpected end of -Ut file\n");
				boost::split(tokens,line,boost::is_any_of(" "));
				if(tokens.size()!=URows)
					throw std::runtime_error("Invalid number of columns in -Ut file");
				for(size_t j=0;j<URows;j++)
					UCRS->SetCell(atof(tokens[j].c_str()),j,i);
			}
			Ufile.close();
			std::ifstream Sfile((workDir+"-S").c_str());
			if(!Sfile)
				throw std::runtime_error("Cannot open results file from SVD\n");

			std::getline(Sfile,line);
			int Sdimensions = atoi(line.c_str());
			SCRS = Matrices::CRSMatrixPtr(new Matrices::CRSMatrix("S matrix",Sdimensions,Sdimensions));
			for(int i=0;i<Sdimensions;i++)
			{
				if(!(std::getline(Sfile,line)))
					throw std::runtime_error("Unexpected end of -S file\n");
				SCRS->SetCell(atof(line.c_str()),i,i);
			}
			Sfile.close();


			LOG4CXX_INFO(logger,"Multiplying matrices U x S..");
			UxS = multiplyMatrices(UCRS,SCRS);
			if(m_testing)
			{
			    test_SCRS=SCRS;
			    test_UCRS=UCRS;
			}
		}

		Matrices::CRSMatrixPtr SVDTransformation::multiplyMatrices(Matrices::CRSMatrixPtr matrix1, Matrices::CRSMatrixPtr matrix2)
		{
			Matrices::CRSMatrixPtr outputMatrix(new Matrices::CRSMatrix("multiplied_matrix",matrix1->NumberOfRows(),matrix2->NumberOfColumns()));
			for(size_t row=0;row<outputMatrix->NumberOfRows();row++)
			{
				for(size_t col=0;col<outputMatrix->NumberOfColumns();col++)
				{
					double sum=0;
					for(size_t iterator=0;iterator<matrix1->NumberOfColumns();iterator++)
						sum+=matrix1->GetCell(row,iterator)*matrix2->GetCell(iterator,col);
					outputMatrix->SetCell(sum,row,col);
				}
				LOG4CXX_INFO(logger,"Multiplying matrices "<<matrix1->GetName()<<" x "<<matrix2->GetName()<<"...row "<<row<<" / "<<outputMatrix->NumberOfRows());
			}
			return outputMatrix;
		}

		void SVDTransformation::crs2sm()
		{
			Matrices::CCSMatrixPtr ccsMatrix (new Matrices::CCSMatrix("ccs",1,1));
			UxS->Clone(ccsMatrix.get());

			Matrices::FeatureVectorPtr colFeatures(new Matrices::FeatureVector());
			for(size_t i =0;i<UxS->NumberOfColumns();i++)
			{
				std::stringstream ss;
				ss<<i;
				colFeatures->push_back(Matrices::Feature(ss.str(),0,0,0,-1));

			}
			std::map<size_t,std::string> bound;
			m_pMatrix->SetCCS(ccsMatrix);
			m_pMatrix->SetCRS(UxS);
			m_pMatrix->SetColumnFeatures(colFeatures);
			m_dimensions=m_pMatrix->NumberOfRows();

		}

		Matrices::CRSMatrixPtr SVDTransformation::getTestUCRS()
		{
		    if(m_testing)
			return test_UCRS;
		    else
			throw std::runtime_error("SVDWeight: Create class in testing mode");
		}

		Matrices::CRSMatrixPtr SVDTransformation::getTestSCRS()
		{
		    if(m_testing)
			return test_SCRS;
		    else
			throw std::runtime_error("SVDWeight: Create class in testing mode");
		}
	}
}
