/*
    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 "zscores.h"
#include <iostream>

using namespace smartcomparator::compare;

namespace clustering
{
	namespace zscores
	{
		ZScores::ZScores(std::vector<int> cluster_ids, CompareFunctionPtr cmp_fun)
		{
			this->cmp_fun = cmp_fun;
			CreateClusterMap(cluster_ids);
			CountInternalZScores();
			CountExternalZScores();
		}

		void ZScores::CreateClusterMap(std::vector<int> cluster_ids)
		{
			for (size_t i = 0; i < cluster_ids.size(); ++i)
			{
				ClusterMap::iterator iter = _clusterMap.find(cluster_ids[i]);
				if (iter == _clusterMap.end())
					_clusterMap.insert(std::make_pair(cluster_ids[i], std::vector<size_t>()));
				_clusterMap[cluster_ids[i]].push_back(i);
			}
		}

		ClusterMap & ZScores::GetClusterMap()
		{
			return _clusterMap;
		}

		void ZScores::CountInternalZScores()
		{
			std::map<size_t, double> average_sims; // s_j - average sim for each row to other elements in the SAME cluster
			internals.clear();

			for (ClusterMap::iterator iter = _clusterMap.begin(); iter != _clusterMap.end(); ++iter)
			{
				double cluster_mean = 0;
				std::vector<size_t> & cluster = iter->second;
				if (cluster.size() == 0)
					continue;
				for (size_t i = 0; i < cluster.size(); ++i)
				{
					double sum = 0.0;
					for (size_t j = 0; j < cluster.size(); ++j)
					{
						if (cluster[i] == cluster[j])
							continue;
						double sim = cmp_fun->Compare(cluster[i], cluster[j]);
						sum += sim;
					}
					sum /= (cluster.size() - 1);
					average_sims[cluster[i]] = sum;
					cluster_mean += sum;
				}
				cluster_mean /= cluster.size();

				double cluster_std_dev = 0;
				for (size_t i = 0; i < cluster.size(); ++i)
				{
					double diff = average_sims[cluster[i]] - cluster_mean;
					cluster_std_dev += (diff * diff);
				}
				cluster_std_dev /= (cluster.size() - 1);
				cluster_std_dev = sqrt(cluster_std_dev);

				for (size_t i = 0; i < cluster.size(); ++i)
					internals[cluster[i]] = (average_sims[cluster[i]] - cluster_mean) / cluster_std_dev;
			}
		}

		void ZScores::CountExternalZScores()
		{
			std::map<size_t, double> average_sims; // s_j - average sim for each row to other elements in DIFFERENT clusters
			externals.clear();

			for (ClusterMap::iterator iter = _clusterMap.begin(); iter != _clusterMap.end(); ++iter)
			{
				double cluster_mean = 0;
				std::vector<size_t> & cluster = iter->second;
				for (size_t i = 0; i < cluster.size(); ++i)
				{
					double sum = 0.0;
					size_t number = 0;
					for (ClusterMap::iterator iter2 = _clusterMap.begin(); iter2 != _clusterMap.end(); ++iter2)
					{
						if (iter->first == iter2->first)
							continue;
						std::vector<size_t> & cluster2 = iter2->second;
						for (size_t j = 0; j < cluster2.size(); ++j)
						{
							double sim = cmp_fun->Compare(cluster[i], cluster2[j]);
							sum += sim;
							number++;
						}
					}
					sum /= number;
					average_sims[cluster[i]] = sum;
					cluster_mean += sum;
				}
				cluster_mean /= cluster.size();

				double cluster_std_dev = 0;
				for (size_t i = 0; i < cluster.size(); ++i)
				{
					double diff = average_sims[cluster[i]] - cluster_mean;
					cluster_std_dev += (diff * diff);
				}
				cluster_std_dev /= (cluster.size() - 1);
				cluster_std_dev = sqrt(cluster_std_dev);

				for (size_t i = 0; i < cluster.size(); ++i)
					externals[cluster[i]] = (average_sims[cluster[i]] - cluster_mean) / cluster_std_dev;
			}
		}

		double ZScores::GetInternalZScore(size_t row_index)
		{
			return internals[row_index];
		}

		double ZScores::GetExternalZScore(size_t row_index)
		{
			return externals[row_index];
		}
	}
}
