#ifndef PARSE_DISTANCE_MATRIX_HPP_
#define PARSE_DISTANCE_MATRIX_HPP_

#include <boost/spirit/core.hpp>
#include <boost/spirit/iterator.hpp>
#include <boost/spirit/utility/chset.hpp>
#include <boost/spirit/actor/assign_actor.hpp>
#include <boost/spirit/actor/increment_actor.hpp>
#include <boost/spirit/actor/push_back_actor.hpp>
#include <boost/spirit/actor/clear_actor.hpp>


struct MatrixResizer
{
	profdist::distance_matrix& _matrix;
	MatrixResizer(profdist::distance_matrix& matrix) : _matrix(matrix) {}
	void operator()(int val) const
	{
		_matrix.resize(val, val, 0);
	}
};

struct VectorResizer
{
	std::vector<std::string>& _v;
	VectorResizer(std::vector<std::string>& v) : _v(v) {}
	void operator()(int val) const
	{
		_v.resize(val);
	}
};

template<typename Iterator>
struct EntryAdder
{
	std::list<std::pair<std::string, std::list<int> > >& _all_rows;
	std::list<int>& _actual_row;
	std::string& _actual_name;
	EntryAdder(std::list<std::pair<std::string, std::list<int> > >& all_rows,
			   std::list<int>& actual_row,
			   std::string& actual_name)
	: _all_rows(all_rows), _actual_row(actual_row), _actual_name(actual_name) {}
	void operator()(Iterator, Iterator) const
	{
		_all_rows.push_back(std::make_pair(_actual_name, _actual_row));
	}
};

void parse_distance_matrix( const std::string& file, profdist::distance_matrix & matrix, std::vector<std::string>& sequenceNames)
{
	typedef boost::spirit::position_iterator<boost::spirit::file_iterator<char> > iterator_t;
	boost::spirit::file_iterator<char> fileStart(file.c_str());
	iterator_t first(fileStart, fileStart.make_end(), file);
	iterator_t last(fileStart.make_end(), fileStart.make_end(), file);
	
	using boost::spirit::ch_p;
	using boost::spirit::alpha_p;
	using boost::spirit::alnum_p;
	using boost::spirit::int_p;
	using boost::spirit::chset;
	using boost::spirit::assign_a;
	using boost::spirit::increment_a;
	using boost::spirit::rule;
	using boost::spirit::push_back_a;
	using boost::spirit::eol_p;
	using boost::spirit::space_p;
	using boost::spirit::blank_p;
	using boost::spirit::anychar_p;
	using boost::spirit::clear_a;
	using boost::spirit::digit_p;
	
	int count = 0;
	profdist::distance_matrix::iterator iter;
	std::string actual_name;
	std::list<int> actual_row;
	std::list<std::pair<std::string, std::list<int> > > all_rows;
	
	chset<> ws(" \t");
	chset<> nl("\n\r");
	
	
	boost::spirit::parse_info<iterator_t> info =
		boost::spirit::parse(
			first,
			last,
			*blank_p >> int_p[MatrixResizer(matrix)][VectorResizer(sequenceNames)] >> *blank_p >> eol_p >>
				+(
					*blank_p >>
					(
						(*digit_p >> (alpha_p | "_") >> *(alnum_p | "_")) % (+blank_p)
						//(*alnum_p >> +(alpha_p | "_") >> *(alnum_p | "_" )) % +blank_p
						//(+alpha_p >> *alnum_p) % (+blank_p)
					)[assign_a(actual_name)] >> +blank_p >>
					(
						int_p[push_back_a(actual_row)] % +blank_p
					) >> *blank_p >> eol_p >> *blank_p
				)[EntryAdder<iterator_t>(all_rows, actual_row, actual_name)][increment_a(count)][clear_a(actual_row)]
		);
	
	if(!info.full)
	{
		boost::spirit::file_position position = first.get_position();
		std::ostringstream out;
		out << "Error: Could not parse newick file '" << position.file 
			<< "'. Syntax error happend in line " << position.line
			<< " column " << position.column << " :" << std::endl
			<< std::string(std::max(first, info.stop - 20), info.stop)
			<< std::endl
			<< std::string(static_cast<unsigned int>(info.stop - std::max(first, info.stop - 20)), '-') << '^' << std::endl;
		throw std::runtime_error(out.str().c_str());
	}
	if(count != matrix.nRows())
		throw std::runtime_error("Invalid number of rows");
	
	typedef std::list<std::pair<std::string, std::list<int> > >::const_iterator const_iter_t;
	size_t row = 0;
	size_t actual_col_size = (*(all_rows.begin())).second.size();
	for(const_iter_t i1 = all_rows.begin(), e1 = all_rows.end(); i1 != e1; i1++, row++)
	{
		sequenceNames[row] = (*i1).first;
		size_t col = 0;
		if((*i1).second.size() != actual_col_size)
			throw std::runtime_error("invalid number of columns");
		for(std::list<int>::const_iterator i2 = (*i1).second.begin(), e2 = (*i1).second.end(); i2 != e2; i2++, col++)
		{
			matrix(row, col) = *i2;
		}
	}
}

#endif // PARSE_DISTANCE_MATRIX_HPP_
