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

namespace smartcomparator
{
	namespace simfunctions
	{
		LocalCosineSimilarity::LocalCosineSimilarity(bool isNormalized, bool doFilter, 
			double percentInter, size_t minInter)
			: m_dPrecentIntersec(percentInter),
			m_stMinIntersec(minInter),
			m_bIsNormalized(isNormalized),
			m_bDoFilter(doFilter)

		{

		}

		LocalCosineSimilarity::~LocalCosineSimilarity(void)
		{
		}


		double LocalCosineSimilarity::CountSimilarity(Matrices::CompressedVector& v1, Matrices::CompressedVector& v2,
			std::set<size_t> &v1Ind, std::set<size_t> &v2Ind)
		{
			if(m_bDoFilter)
				return CountFilteredSimilarity(v1, v2, v1Ind, v2Ind);

			return CountSimilarityWithoutFilter(v1, v2, v1Ind, v2Ind);
		}

		double LocalCosineSimilarity::CountFilteredSimilarity(Matrices::CompressedVector& v1, Matrices::CompressedVector& v2,
			const std::set<size_t> &v1Ind, const std::set<size_t> &v2Ind)
		{
			double dot = 0;


			std::set<size_t> filteredIndices;

			// tutaj filtrujemy

			std::set<size_t> itersec;

			std::insert_iterator< std::set<size_t> > inserter( itersec, itersec.begin() );

			std::set_intersection(v1Ind.begin(), v1Ind.end(), v2Ind.begin(), v2Ind.end(), inserter);

			if(itersec.size() < m_stMinIntersec)
				return 0;

			double precent = (double)(itersec.size()) / (double)(filteredIndices.size());

			if( precent < m_dPrecentIntersec )
				return 0;

			// END: tutaj filtrujemy

			if(! m_bIsNormalized)
			{
				double v1len = 0;
				double v2len = 0;
				for(std::set<size_t>::const_iterator iter = filteredIndices.begin(); iter != filteredIndices.end();iter++)
				{
					double v1Val = (v1)[(*iter)];
					double v2Val = (v2)[(*iter)];
					v1len += v1Val*v1Val;
					v2len += v2Val*v2Val;
					dot += v1Val*v2Val;
				}

				double d =  sqrt(v1len)*sqrt(v2len);

				if(d == 0)
					return 0;
				double cos = dot/d;

				if( cos < 0 )
					return 0;

				return cos;
			}

			// znormalizowane:
			for(std::set<size_t>::const_iterator iter = filteredIndices.begin(); iter != filteredIndices.end();iter++)
			{
				double v1Val = (v1)[(*iter)];
				double v2Val = (v2)[(*iter)];
				dot += v1Val*v2Val;
			}

			return dot < 0? 0 : dot;
		}


		double LocalCosineSimilarity::CountSimilarityWithoutFilter(Matrices::CompressedVector& v1, 
			Matrices::CompressedVector& v2,
			const std::set<size_t> &v1Ind, const std::set<size_t> &v2Ind)
		{
			double dot = 0;

			if(! m_bIsNormalized)
			{
				double v1len = 0;
				double v2len = 0;
				std::set<size_t> used;
				for(std::set<size_t>::const_iterator iter = v1Ind.begin(); iter != v1Ind.end(); iter++)
				{
					used.insert(*iter);
					double x1 = (v1)[(*iter)];
					double x2 = (v2)[(*iter)];
					v1len += x1*x1;
					v2len += x2*x2;
					dot += x1*x2;
				}
				for(std::set<size_t>::const_iterator iter = v2Ind.begin(); iter != v2Ind.end(); iter++)
				{
					if( used.find(*iter) != v2Ind.end())
						continue;

					double x2 = (v2)[(*iter)];
					v2len += x2*x2;
				}

				if( !v1len || !v2len)
					return -1;

				double d =  sqrt(v1len)*sqrt(v2len);

				double cos = dot/d;

				if( cos < 0 )
					return 0;

				return cos;
			}

			// znormalizowane
			for(std::set<size_t>::const_iterator iter = v1Ind.begin(); iter != v1Ind.end(); iter++)
			{
				
				double x1 = (v1)[(*iter)];
				double x2 = (v2)[(*iter)];
				if(x2 == 0)
					continue;

				dot +=x1*x2;
			}
			return dot < 0? 0 : dot;
		}

		double LocalCosineSimilarity::CountSimilarity(Matrices::CompressedVector& v1, Matrices::CompressedVector& v2)
		{
			if(m_bDoFilter)
				return CountFilteredSimilarity(v1, v2);

			return CountSimilarityWithoutFilter(v1, v2);
		}

		double LocalCosineSimilarity::CountFilteredSimilarity(Matrices::CompressedVector& v1, 
			Matrices::CompressedVector& v2)
		{
			double dot = 0;


			std::set<size_t> filteredIndices;

			// tutaj filtrujemy
			std::set<size_t> itersec;

			std::map<size_t,double> *smaller = v1.GetValues();
			std::map<size_t,double> *other = v2.GetValues();
			if(smaller->size() > v2.GetValues()->size() )
			{
				other = smaller;
				smaller = v2.GetValues();				
			}

			for(std::map<size_t, double>::iterator i = smaller->begin();
				i != smaller->end(); i++)
				if( other->find(i->first) != other->end() )
					itersec.insert(i->first);


			if(itersec.size() < m_stMinIntersec)
				return 0;

			double precent = (double)(itersec.size()) / (double)(filteredIndices.size());

			if( precent < m_dPrecentIntersec )
				return 0;

			// END: tutaj filtrujemy

			if(! m_bIsNormalized)
			{
				double v1len = 0;
				double v2len = 0;
				for(std::set<size_t>::const_iterator iter = filteredIndices.begin(); iter != filteredIndices.end();iter++)
				{
					double v1Val = (v1)[(*iter)];
					double v2Val = (v2)[(*iter)];
					v1len += v1Val*v1Val;
					v2len += v2Val*v2Val;
					dot += v1Val*v2Val;
				}
			
				if( !v1len || !v2len)
					return 0;

				double d =  sqrt(v1len)*sqrt(v2len);
				
				double cos = dot/d;

				if( cos < 0 )
					return 0;

				return cos;
			}

			// znormalizowane:
			for(std::set<size_t>::const_iterator iter = filteredIndices.begin(); iter != filteredIndices.end();iter++)
			{
				double v1Val = (v1)[(*iter)];
				double v2Val = (v2)[(*iter)];
				dot += v1Val*v2Val;
			}

			return dot < 0? 0 : dot;
		}


		double LocalCosineSimilarity::CountSimilarityWithoutFilter(Matrices::CompressedVector& v1, 
			Matrices::CompressedVector& v2)
		{
			double dot = 0;

			if(! m_bIsNormalized)
			{
				double v1len = 0;
				double v2len = 0;
				std::set<size_t> used;

				for(std::map<size_t, double>::const_iterator iter = v1.GetValues()->begin(); 
					iter != v1.GetValues()->end(); iter++)
				{
					used.insert(iter->first);
					double v1val = iter->second;
					v1len += v1val*v1val;
					double v2val = v2[iter->first];
					if( v2val == 0)
						continue;
					v2len += v2val*v2val;

					dot += v1val*v2val;
				}

				for(std::map<size_t, double>::const_iterator iter = v2.GetValues()->begin(); 
					iter != v2.GetValues()->end(); iter++)
				{
					if(used.find(iter->first) == used.end()  )
					{
						double v2val = iter->second;
						v2len += v2val*v2val;
					}
				}

				if( !v1len || !v2len)
					return 0;

				double d =  sqrt(v1len)*sqrt(v2len);


				double cos = dot/d;
				if(cos < 0 )
					return 0;

				return cos;
			}

			// znormalizowane
			for(std::map<size_t, double>::const_iterator iter = v1.GetValues()->begin(); 
				iter != v1.GetValues()->end(); iter++)
			{
				double v1val = iter->second;
				double v2val = v2[iter->first];
				if( v2val == 0)
					continue;

				dot +=v1val*v2val;
			}

			return dot < 0 ? 0 : dot;
		}


	}
}
