/*
    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 "CompareFunction.h"
#include <boost/date_time/posix_time/posix_time.hpp>

static log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("SuperMatrix.comparator.compare.CompareFunction"));

namespace smartcomparator
{

	namespace compare
	{
		CompareFunction::CompareFunction(Matrices::SuperMatrixPtr matrix,
						 filters::FilterFeaturePtr filter,
						 selectors::SelectorPtr selector ,
						 weights::WeightFeaturePtr weight,
						 simfunctions::SimilarityFunctionPtr simfun ,
						 selectors::RowRelativeSelectorPtr relativeSelector,
						 transformations::TransformationPtr transformation)
							 : m_pMatrix(matrix),
							 m_pFilter(filter),
							 m_pSelector(selector),
							 m_pWeight(weight),
							 m_pSimfun(simfun),
							 m_pRelativeSelector(relativeSelector),
							 m_pTransform(transformation)
		{
			m_bTransformed = false;
		}

		bool CompareFunction::IsSymmetrical() const
		{
			return m_pSimfun->IsSymmetrical();
		}

		bool CompareFunction::IsTransformingMatrix() const
		{
			return false;
		}

		CompareFunction::~CompareFunction(void)
		{
		}

		double CompareFunction::Compare(const std::string &w1, const std::string &w2)
		{
			size_t n1 = m_pMatrix->GetRowIndex( w1 );
			size_t n2 = m_pMatrix->GetRowIndex( w2 );
			if( n1 == (size_t) -1 || n2 == (size_t) -1 )
				return -1;

			return Compare(n1, n2);
		}
		void CompareFunction::TransformRow(size_t row_index, Matrices::SuperMatrixPtr sourceMatrix)
		{

			Matrices::CompressedVectorPtr rowN1 = sourceMatrix->GetCRS()->GetCompressedRow(row_index);
			Matrices::CompressedVectorPtr rowN1Copy = Matrices::CompressedVectorPtr(new Matrices::CompressedVector(*rowN1));

			std::set<size_t> filterN1;

			for(std::map<size_t, double>::const_iterator iter = rowN1Copy->GetValues()->begin(); iter != rowN1Copy->GetValues()->end(); iter++)
				filterN1.insert( iter->first );

			// Filtering
			if(m_pFilter)
			{
				std::set<size_t> out1;
				m_pFilter->Filter(out1, &filterN1);

				for(std::set<size_t>::iterator iter = filterN1.begin(); iter != filterN1.end(); iter++)
					if( out1.find(*iter) == out1.end())
						rowN1Copy->SetElement(*iter, 0);
				filterN1.swap(out1);
			}

			// Selection
			if(m_pSelector)
			{
				// copy selected indcies
				std::set<size_t> in1;
				// clear them
				filterN1.swap(in1);
				// fill them up again
				m_pSelector->SelectBestFeatures(row_index, in1, filterN1);
				for(std::set<size_t>::iterator iter = in1.begin(); iter != in1.end(); iter++)
					if( filterN1.find(*iter) == filterN1.end())
						rowN1Copy->SetElement(*iter, 0);
				filterN1.swap(in1);
			}

			//Weighting
			if(m_pWeight)
				m_pWeight->Weight(row_index, rowN1Copy.get(), filterN1);


			// remove indices after weighting
			std::set<size_t> remove;

			for(Matrices::STDoubleMapIter iter = rowN1Copy->GetValues()->begin(); iter != rowN1Copy->GetValues()->end(); ++iter)
				if( filterN1.find(iter->first) == filterN1.end())
					remove.insert(iter->first);
			for(std::set<size_t>::iterator iter = remove.begin(); iter != remove.end(); iter++)
				rowN1Copy->SetElement(*iter, 0);
			m_pMatrix->GetCRS()->SetCompressedRow(row_index, rowN1Copy);

		}

		void CompareFunction::WeightMatrix()
		{
			if(m_pRelativeSelector)
			{
				LOG4CXX_ERROR(logger,"Cannot transorm matrix with relative selector!");
				return;
			}
			if(!m_bTransformed)
			{
				smartcomparator::transformations::GenericTransformer transformer(m_pMatrix, m_pWeight, m_pFilter,m_pSelector, m_pTransform);
				transformer.TransformMatrix();
				m_bTransformed = true;
			}
		}

		void CompareFunction::WeightMatrixCopy()
		{
			if(m_pRelativeSelector)
			{
				LOG4CXX_ERROR(logger,"Cannot transorm matrix with relative selector!");
				return;
			}
			if(!m_bTransformed)
			{
				Matrices::SuperMatrixPtr smCopy;
				m_pMatrix->Clone(smCopy);
				Matrices::SuperMatrixPtr smSource = m_pMatrix;
				m_pMatrix = smCopy;

				smartcomparator::transformations::GenericTransformer transformer(m_pMatrix, m_pWeight, m_pFilter,m_pSelector, m_pTransform);
				transformer.TransformMatrix();
				m_bTransformed = true;

			}
		}

		double CompareFunction::PreWeightCompare(size_t n1, size_t n2)
		{
			Matrices::CompressedVectorPtr rowN1 = m_pMatrix->GetCRS()->GetCompressedRow(n1);
			Matrices::CompressedVectorPtr rowN2 = m_pMatrix->GetCRS()->GetCompressedRow(n2);

			std::set<size_t> sum_nz;
			std::set<size_t> filterN1, filterN2;

			for(std::map<size_t, double>::const_iterator iter = rowN1->GetValues()->begin(); iter != rowN1->GetValues()->end(); iter++)
				filterN1.insert( iter->first );

			for(std::map<size_t, double>::const_iterator iter = rowN2->GetValues()->begin(); iter != rowN2->GetValues()->end(); iter++)
				filterN2.insert( iter->first);

			return m_pSimfun->CountSimilarity(*rowN1, *rowN2, n1, n2, filterN1, filterN2);
		}
		double CompareFunction::Compare(size_t n1, size_t n2)
		{
			if(m_bTransformed)
			{
				return PreWeightCompare(n1,n2);
			}
			if(m_pTransform)
			{
				WeightMatrix();
				return PreWeightCompare(n1,n2);
			}
			else
			{
				return StandardCompare(n1, n2);
			}
		}

		double CompareFunction::StandardCompare(size_t n1, size_t n2)
		{

			Matrices::CompressedVectorPtr rowN1 = m_pMatrix->GetCRS()->GetCompressedRow(n1);
			Matrices::CompressedVectorPtr rowN2 = m_pMatrix->GetCRS()->GetCompressedRow(n2);
			Matrices::CompressedVector rowN1Copy(*rowN1);
			Matrices::CompressedVector rowN2Copy(*rowN2);

			std::set<size_t> sum_nz;
			std::set<size_t> filterN1, filterN2;

			for(std::map<size_t, double>::const_iterator iter = rowN1->GetValues()->begin(); iter != rowN1->GetValues()->end(); iter++)
			{
				sum_nz.insert( iter->first );
				filterN1.insert( iter->first );
			}
			for(std::map<size_t, double>::const_iterator iter = rowN2->GetValues()->begin(); iter != rowN2->GetValues()->end(); iter++)
			{
				sum_nz.insert( iter->first );
				filterN2.insert( iter->first);
			}

			// Filtering
			if(m_pFilter)
			{
				std::set<size_t> out1, out2;
				m_pFilter->Filter(out1, &filterN1);
				m_pFilter->Filter(out2, &filterN2);



				if(out1.size() == 0 && out2.size() == 0)
					return m_pSimfun->MinValue();

				for(std::set<size_t>::iterator iter = filterN1.begin(); iter != filterN1.end(); iter++)
					if( out1.find(*iter) == out1.end())
						rowN1Copy.SetElement(*iter, 0);

				for(std::set<size_t>::iterator iter = filterN2.begin(); iter != filterN2.end(); iter++)
					if( out2.find(*iter) == out2.end())
						rowN2Copy.SetElement(*iter, 0);

				filterN1.swap(out1);
				filterN2.swap(out2);
			}

			// Selection
			if(m_pSelector)
			{
				// copy selected indcies
				std::set<size_t> in1, in2;
				// clear them
				filterN1.swap(in1);
				filterN2.swap(in2);
				// fill them up again
				m_pSelector->SelectBestFeatures(n1, in1, filterN1);
				m_pSelector->SelectBestFeatures(n2, in2, filterN2);
			}

			//Relative selection
			if(m_pRelativeSelector)
			{
				m_pRelativeSelector->SelectBestFeatures(n1, n2, filterN1, filterN2);
			}

			//Weighting
			if(m_pWeight)
			{
				m_pWeight->Weight(n1,&rowN1Copy, filterN1);
				m_pWeight->Weight(n2,&rowN2Copy, filterN2);
			}

			return m_pSimfun->CountSimilarity(rowN1Copy, rowN2Copy, n1, n2, filterN1, filterN2);
		}

		double CompareFunction::ExternalCompare(size_t n1, Matrices::CompressedVectorPtr extVector)
		//This method compares a matrix row with the index n1 with an arbitrary external vector, using the similarity function only.
		//Whether this method will yield valid results or not depends strongly on the comparator being used. Extremely volatile, triple-check in the desired context before using.
		{
			std::set<size_t> sum_nz;
			std::set<size_t> filterN1;
			std::set<size_t> filterN2;

			Matrices::CompressedVectorPtr rowN1 = m_pMatrix->GetCRS()->GetCompressedRow(n1);
			Matrices::CompressedVector rowN1Copy(*rowN1);

			for(std::map<size_t, double>::const_iterator iter = rowN1Copy.GetValues()->begin(); iter != rowN1Copy.GetValues()->end(); iter++)
			{
				sum_nz.insert( iter->first );
				filterN1.insert( iter->first );
			}
			for(std::map<size_t, double>::const_iterator iter = extVector->GetValues()->begin(); iter != extVector->GetValues()->end(); iter++)
			{
				sum_nz.insert( iter->first );
				filterN2.insert( iter->first);
			}

			return m_pSimfun->CountSimilarity(rowN1Copy, (*extVector), n1, -1, filterN1, filterN2);

		}

		double CompareFunction::ExternalCompare(std::string w1, Matrices::CompressedVectorPtr extVector)
		{
			size_t n1 = m_pMatrix->GetRowIndex( w1 );
			if (n1 == (size_t) -1)
				return -1;
			return ExternalCompare(n1,extVector);
		}

		Matrices::SuperMatrixPtr  CompareFunction::GetMatrix()
		{
			return m_pMatrix;
		}


		double CompareFunction::MinValue() const
		{
			return m_pSimfun->MinValue();
		}
		weights::WeightFeaturePtr CompareFunction::GetWeightFeature()
		{
			return m_pWeight;
		}
	}
}
