/*
 * ExceptionHandler.h
 *
 * Created on: Mar 7, 2019
 * Author: Jan Travnicek
 */

#pragma once

#include <exception>
#include <iostream>
#include <functional>
#include <optional>
#include <sstream>
#include <vector>

namespace alib {

class ExceptionHandler {
private:
	struct NestedException {
		std::string type;
		std::optional < std::string > desc;

		explicit NestedException ( std::string type_, std::string desc_ );
		explicit NestedException ( std::string type_ );
		operator std::string ( ) const;
	};

public:
	class NestedExceptionContainer {
		public:
			template < typename ... Params >
			void push_back ( Params && ... params ) {
				exceptions.emplace_back ( std::forward < std::string > ( params ) ... );
			}

			const std::vector < NestedException > & getExceptions ( ) const { return exceptions; }

		private:
			std::vector < NestedException > exceptions;
	};

private:
	static int handlerRec ( NestedExceptionContainer & output, const std::vector < std::function < int ( NestedExceptionContainer & ) > >::const_reverse_iterator & iter );

	static std::vector < std::function < int ( NestedExceptionContainer & ) > > & handlers ( );

	template < class ExceptionType >
	static void addHandlerDeduce ( std::function < void ( NestedExceptionContainer &, const ExceptionType & ) > handler, int result ) {
		handlers ( ).push_back ( [ = ] ( NestedExceptionContainer & output ) {
			try {
				throw;
			} catch ( const ExceptionType & exception ) {
				handler ( output, exception );
				return ExceptionHandler::rethrow_if_nested ( output, exception, result );
			}
		} );
	}

	static int rethrow_if_nested ( NestedExceptionContainer &, const std::exception & e, int result );

public:
	static int handle ( NestedExceptionContainer & output );
	static int handle ( std::ostream & output );

	template < int Result, class Callable >
	static void addHandler ( Callable handler ) {
		addHandlerDeduce ( std::function ( handler ), Result );
	}
};

} /* namespace alib */

