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

using namespace Matrices;
using namespace clustering::som;
using namespace smartcomparator::simfunctions;
using namespace smartcomparator::compare;
using namespace std;

void SaveClutoFile(const ClusterVectorPtr & clusters, const string & filename, bool firstLayerOnly);
void SaveResultFile(HierarchicalSOM & som, const string & filename, const string & matrixPath, bool saveContexts);
boost::shared_ptr<vector<string> > ReadVector(const string & filename);
ClusterVectorPtr Renumerate(std::map<size_t, size_t> & numbers, HierarchicalSOM & som, bool firstLayerOnly);

int main(int ac, char **av)
{
	Options opt(ac, av);

	Params param = opt.GetParams();

	//string mdir = "/home/wojtas/Mgr/Macierze/ann_agent/output";
	//string mname = "subst:agent";

	Matrices::SuperMatrixPtr matrix = Matrices::IO::LoadSuperMatrix(param.matrix_dir, param.matrix_name, false, true);

	//weight matrix
	CompareFunctionPtr fun = FunctionFactory::CreateFunction(param.cmp_function, matrix);
	//<zmiany wprowadzone przez Jacka przy zmianie metody TransformMatrix>
	fun->WeightMatrixCopy();
	matrix = fun->GetMatrix();
	//</zmiany wprowadzone przez Jacka przy zmianie metody TransformMatrix>

	using namespace smartcomparator::weights;

	//normalize matrix
	WeightFeaturePtr weight = WeightFeaturePtr(new NormalizeWeight(matrix));
	MatrixTransformer transformer(weight);
	transformer.Transform();

	//create local comparision function
	//LocalCosineSimilarity *cosinePtr = new LocalCosineSimilarity(true, false);
	// WM: really needed?
	//boost::shared_ptr<LocalSimilarityFunction> cosine ((LocalSimilarityFunction*)cosinePtr) ;

	boost::shared_ptr<LocalSimilarityFunction> cosine(new LocalCosineSimilarity(true, false));

	//crete hierarchical som
	HierarchicalSOM som(matrix, cosine, param.t1, param.t2, 2, 10);
	som.Start();

//	for (ClusteringBase::ClusterSet::iterator iter = som.GetClusters()->begin(); iter != som.GetClusters()->end(); ++iter)
//	{
//		ClusteringBase::Cluster & cluster = (**iter);
//		cout << "ID: " << cluster.GetID() << " size: " << cluster.GetPointsID().size() << endl;
//		for (ClusteringBase::IdSet::iterator id = cluster.GetPointsID().begin(); id != cluster.GetPointsID().end(); ++id)
//			cout << *id << " ";
//		cout << endl;
//	}

	if (param.cluto_file != "")
	{
		std::map<size_t, size_t> numbers;
		ClusterVectorPtr clusters = Renumerate(numbers, som, false);
		SaveClutoFile(clusters, param.cluto_file, false);
		clusters = Renumerate(numbers, som, true);
		SaveClutoFile(clusters, param.cluto_file + ".first", true);
	}

	if (param.output_file != "")
		SaveResultFile(som, param.output_file, param.matrix_dir + "/" + param.matrix_name, true);
}

void SaveClutoFile(const ClusterVectorPtr & clusters, const string & filename, bool firstLayerOnly)
{
	//renumerate clusters
	ofstream out_stream(filename.c_str());
	if(!out_stream)
		throw runtime_error("Error writing to file " + filename);

	//ClusterVectorPtr clusters = som.GetClustersIndices(firstLayerOnly);
	copy(clusters->begin(), clusters->end(), ostream_iterator<size_t>(out_stream, "\n"));

	out_stream.close();
}

void SaveResultFile(HierarchicalSOM & som, const string & filename, const string & matrixPath, bool saveContexts)
{
	ofstream out_stream(filename.c_str());
	if(!out_stream)
		throw runtime_error("Error writing to file " + filename);

	//does vector<string> = *boost::shared_ptr<vector<string> > make a local copy?
	boost::shared_ptr<vector<string> > rowLabels, contexts;
	rowLabels = ReadVector(matrixPath + "/class_labels.txt");
	if (saveContexts)
		contexts = ReadVector(matrixPath + "/contexts.txt");

	multimap<size_t, boost::shared_ptr<CSOMNet> > maps = som.GetMaps();
	multimap<size_t, boost::shared_ptr<CSOMNet> >::iterator map = maps.begin();
	for (; map != maps.end(); ++map)
	{
		string tabs = string(map->first, '\t'); //indent depending on layer no

		boost::shared_ptr<CSOMNet> &net = map->second;

		for(size_t row = 0; row < net->GetNumberOfNeurons(); row++)
		{
			for(size_t column = 0; column < net->GetNumberOfNeurons(); column++)
			{
				CSOMNeuron & neuron = *net->GetNeurons()[row][column];
				set<size_t> indices = *neuron.GetDataIndices(); //change to shared pointer?
				out_stream << tabs << "Neuron: " << neuron.GetId() << " (" << row << "," << column << ") " /*<< " size: " << indices.size()*/ << endl;
				for (set<size_t>::iterator iter = indices.begin(); iter != indices.end(); ++iter)
				{
					out_stream << tabs << *iter << " - " << (*rowLabels)[*iter];
					if (saveContexts)
						out_stream << " - " << (*contexts)[*iter];
					out_stream << endl;
				}
			}
		}
	}
	out_stream.close();
}

boost::shared_ptr<vector<string> > ReadVector(const string & filename)
{
	boost::shared_ptr<vector<string> > result(new vector<string>());

	ifstream in_stream(filename.c_str());
	if(!in_stream)
		throw runtime_error("Error reading from file " + filename);

	string line;

	while (getline(in_stream, line, '\n'))
		result->push_back(line);

	return result;
}

ClusterVectorPtr Renumerate(std::map<size_t, size_t> & numbers, HierarchicalSOM & som, bool firstLayerOnly)
{
	size_t current = 0, i = 0;
	numbers.clear();
	ClusterVectorPtr clusters = som.GetClustersIndices(firstLayerOnly);
	ClusterVectorPtr result(new std::vector<size_t>(clusters->size()));

	for (std::vector<size_t>::iterator iter = clusters->begin(); iter != clusters->end(); ++iter, ++i)
	{
		std::map<size_t, size_t>::iterator number = numbers.find(*iter);
		if (number != numbers.end())
			(*result)[i] = number->second;
		else
		{
			(*result)[i] = current;
			numbers[*iter] = current;
			current++;
		}
	}
	return result;
}
