/*
 * StringApi.hpp
 *
 * Created on: Apr 1, 2013
 * Author: Jan Travnicek
 */

#ifndef STRING_API_HPP_
#define STRING_API_HPP_

#include <alib/functional>
#include <alib/deque>
#include <alib/map>
#include <alib/string>
#include <alib/memory>
#include <alib/algorithm>

#include <object/Object.h>

#include "exception/CommonException.h"

namespace core {

template < typename T, typename Enable = void >
struct stringApi { };

template < >
struct stringApi < object::Object > {
private:
	class GroupReader {
	public:
		virtual object::Object parse ( std::istream & input ) = 0;
		virtual ~GroupReader ( ) {
		}
	};

	static ext::deque < std::pair < std::function < bool ( std::istream & ) >, std::unique_ptr < GroupReader > > > & parseFunctions ( ) {
		static ext::deque < std::pair < std::function < bool ( std::istream & ) >, std::unique_ptr < GroupReader > > > res;

		return res;
	}

	template < class Type >
	class ReaderRegister : public GroupReader {
		std::function < Type ( std::istream & ) > parseFunction;

	public:
		ReaderRegister( ) : parseFunction ( stringApi < Type >::parse ) {
		}

		virtual ~ReaderRegister ( ) {
		}

		virtual object::Object parse ( std::istream & input ) {
			return object::Object ( parseFunction ( input ) );
		}
	};

	class GroupWriter {
	public:
		virtual void compose ( std::ostream & output, const object::Object & group ) = 0;

		virtual ~GroupWriter ( ) {
		}
	};

	static ext::map < std::string, std::unique_ptr < GroupWriter > > & composeFunctions ( ) {
		static ext::map < std::string, std::unique_ptr < GroupWriter > > res;

		return res;
	}

	template < class Type >
	class WriterRegister : public GroupWriter {
		std::function < void ( std::ostream &, const Type & ) > composeFunction;

	public:
		WriterRegister( ) : composeFunction ( stringApi < Type >::compose ) {
		}

		virtual ~WriterRegister ( ) {
		}

		virtual void compose ( std::ostream & output, const object::Object & group ) {
			composeFunction ( output, static_cast < const Type & > ( group.getData ( ) ) );
		}
	};

public:
	template < class Type >
	static void registerStringReader ( ) {
		parseFunctions ( ).push_back ( std::make_pair ( stringApi < Type >::first, std::unique_ptr < GroupReader > ( new ReaderRegister < Type > ( ) ) ) );
	}

	template < class Type >
	static void registerStringWriter ( ) {
		bool res = composeFunctions ( ).insert ( std::make_pair ( ext::to_string < Type > ( ), std::unique_ptr < GroupWriter > ( new WriterRegister < Type > ( ) ) ) ).second;
		if ( ! res ) {
			std::string groupName = ext::to_string < object::Object > ( );
			std::string typeName = ext::to_string < Type > ( );

			throw::exception::CommonException ( "Parse callback of " + typeName + " already registered in group " + groupName + "." );
		}
	}

	static object::Object parse ( std::istream & input ) {
		auto lambda = [ & ] ( const std::pair < std::function < bool ( std::istream & ) >, std::unique_ptr < GroupReader > > & entry ) {

			return entry.first ( input );
		};

		int pos = input.tellg();

		auto callback = find_if ( parseFunctions ( ).begin ( ), parseFunctions ( ).end ( ), lambda );
		if ( callback == parseFunctions ( ).end ( ) )
			throw exception::CommonException ( "Parse callback not registered." );

		if ( pos != input.tellg ( ) )
			throw exception::CommonException ( "First function of registered callback moved the stream" );

		return callback->second->parse ( input );
	}

	static bool first ( std::istream & input ) {
		auto lambda = [ & ] ( const std::pair < std::function < bool ( std::istream & ) >, std::unique_ptr < GroupReader > > & entry ) {
			return entry.first ( input );
		};

		return std::any_of ( parseFunctions ( ).begin ( ), parseFunctions ( ).end ( ), lambda );
	}

	static void compose ( std::ostream & output, const object::Object & data ) {
		const auto & content = data.getData ( );
		std::string type = ext::to_string ( ext::type_index ( typeid ( content ) ) );
		auto callback = composeFunctions ( ).find ( type );

		if ( callback == composeFunctions ( ).end ( ) ) throw exception::CommonException ( "Compose callback for " + type + " tag not registered." );

		callback->second->compose ( output, data );
	}

};

} /* namespace core */

#endif /* STRING_API_HPP_ */