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

namespace clustering
{
	namespace som
	{
		std::string CSOMNeuron::END_OF_WEIGHTS("-=END_OF_WEIGHTS=-");
		std::string CSOMNeuron::END_OF_DOCUMENTS("-=END_OF_DOCUMENTS=-");
		size_t CSOMNeuron::m_IdGen = 1;

		CSOMNeuron::CSOMNeuron(size_t cWeights, double x, double y,
			boost::shared_ptr<smartcomparator::simfunctions::LocalSimilarityFunction> simfun)
			: m_pDataIndices( new std::set<size_t>() ), m_SimFunction(simfun)

		{
			m_neuronDistance = EUCLIDEAN;
			double len = sqrt(x*x+y*y);
			m_dNormalizedX = x/len;
			m_dNormalizedY = y/len;
			m_dX = x;
			m_dY = y;
			RandomizeWeights(cWeights);
			m_dMQE = 0;

			m_Id = m_IdGen++;
		}

		size_t CSOMNeuron::GetId() const
		{
			return m_Id;
		}

		void CSOMNeuron::SetXY(double x, double y)
		{
			double len = sqrt(x*x+y*y);
			m_dNormalizedX = x/len;
			m_dNormalizedY = y/len;
			m_dX = x;
			m_dY = y;
		}

		CSOMNeuron::~CSOMNeuron(void)
		{
		}

		void CSOMNeuron::SaveState(std::ostream &out)
		{
			out << m_dX << " " << m_dY << " "  << m_neuronDistance << " "
				<< m_dMQE <<std::endl;
			for(std::set<size_t>::iterator i = m_pDataIndices->begin(); i != m_pDataIndices->end(); i++)
				out << *i << std::endl;

			out << CSOMNeuron::END_OF_DOCUMENTS << std::endl;

			for(std::vector<double>::iterator i = m_vWeights.begin(); i != m_vWeights.end(); i++)
				out << *i << std::endl;

			out << CSOMNeuron::END_OF_WEIGHTS << std::endl;
		}

		void CSOMNeuron::LoadState(std::istream &in)
		{
			int distType;
			in >> m_dX >> m_dY >> distType >> m_dMQE;
			m_neuronDistance = (CSOMNeuron::DistanceType) distType;

			double len = sqrt(m_dY*m_dY+m_dX*m_dX);
			m_dNormalizedX = m_dX/len;
			m_dNormalizedY = m_dY/len;

			const size_t BUF_LEN = 128;
			char buf[BUF_LEN];
			std::string line = "";
			m_pDataIndices->clear();
			m_vWeights.clear();

			while(in)
			{
				in.getline(buf, BUF_LEN);
				line = buf;
				if(line.empty() )
					continue;
				boost::algorithm::trim(line);
				if(line == CSOMNeuron::END_OF_DOCUMENTS)
					break;
				m_pDataIndices->insert( boost::lexical_cast<size_t>(line) );
			};

			while(in)
			{
				in.getline(buf, BUF_LEN);
				line = buf;
				if(line.empty() )
					continue;
				boost::algorithm::trim(line);
				if(line == CSOMNeuron::END_OF_WEIGHTS)
					break;
				m_vWeights.push_back( boost::lexical_cast<double>(line) );
			};

		}

		double CSOMNeuron::Length(const std::vector<double> &v)
		{
			double len = 0.;
			std::vector<double>::const_iterator iter = v.begin();

			do
			{
				len += (*iter) * (*iter) ;
				iter++;
			}while(iter != v.end());

			return sqrt(len);
		}

		void CSOMNeuron::Normalize(std::vector<double> &v)
		{
			std::vector<double>::iterator iter = v.begin();

			double len = Length(v);
			do
			{
				(*iter) /= len;
				iter++;
			}while(iter != v.end());
		}


		double CSOMNeuron::DistanceTo(const CSOMNeuron& neuron)
		{
			double distance = 0.;
			switch(m_neuronDistance)
			{
			case COSINE:
				// -2, bo cos daje podobienstwo
				distance = 2 - m_dNormalizedX*neuron.m_dNormalizedX + m_dNormalizedY * neuron.m_dNormalizedY;
				break;
			case EUCLIDEAN:
				distance = (neuron.m_dX-m_dX)*(neuron.m_dX-m_dX) + (neuron.m_dY-m_dY)*(neuron.m_dY-m_dY);
				break;
			default:
				throw std::runtime_error(std::string("CSOMNeuron::DistanceTo : zly sposob liczenia odleglosci pomiedzy neuronami"));
			}
			return distance;
		}

		double CSOMNeuron::SimilarityTo(Matrices::CompressedVectorPtr inputVector)
		{
			Matrices::CompressedVector weights(m_vWeights);

			return m_SimFunction->CountSimilarity(*inputVector, weights);
		}

		void CSOMNeuron::SetNeuronDistanceType(DistanceType dt)
		{
			m_neuronDistance = dt;
		}

		void CSOMNeuron::UpdateWeights(Matrices::CompressedVectorPtr inputCompressedVector,
			double learningRate, double theta)
		{
			//TODO perhabs it can be done without decompressing of vector?
			double *inputVector = inputCompressedVector->DecompressData();

			for (size_t i = 0; i < m_vWeights.size(); i++)
			{
				m_vWeights[i] += learningRate * theta * (inputVector[i] - m_vWeights[i]);
			}
			inputCompressedVector->FreeBuffer();
			Normalize(m_vWeights);
		}

		void CSOMNeuron::RandomizeWeights(size_t cWeights)
		{
			for(size_t i = 0; i < cWeights; i++)
			{
				// FIXME better random weights initalization
				// a) rand returns integers (so it is not very good, but after normalization it is little better)
				// b) better to have random numbers from <min_value_in_data, max_value_in_data>
				m_vWeights.push_back(rand());
			}
			Normalize(m_vWeights);
		}

		void CSOMNeuron::ClearData()
		{
			m_pDataIndices->clear();
		}
		double CSOMNeuron::GetMQE() const
		{
			return m_dMQE;
		}

		void CSOMNeuron::SetMQE(double mqe)
		{
			m_dMQE = mqe;
		}

		double CSOMNeuron::GetX() const
		{
			return m_dX;
		}
		double CSOMNeuron::GetY() const
		{
			return m_dY;
		}
	}//namespace SOM
}//namespace Clustering

