/*
	Simple example using restinio::transforms::zlib facility.
*/

#include <iostream>
#include <random>

#include <restinio/all.hpp>
#include <restinio/transforms/zlib.hpp>

#include <clara.hpp>
#include <fmt/format.h>

//
// app_args_t
//

struct app_args_t
{
	bool m_help{ false };
	std::string m_address{ "localhost" };
	std::uint16_t m_port{ 8080 };
	std::size_t m_pool_size{ 1 };
	bool m_trace_server{ false };

	static app_args_t
	parse( int argc, const char * argv[] )
	{
		using namespace clara;

		app_args_t result;

		auto cli =
			Opt( result.m_address, "address" )
					["-a"]["--address"]
					( fmt::format(
							RESTINIO_FMT_FORMAT_STRING(
								"address to listen (default: {})" ),
							result.m_address ) )
			| Opt( result.m_port, "port" )
					["-p"]["--port"]
					( fmt::format(
							RESTINIO_FMT_FORMAT_STRING(
								"port to listen (default: {})" ), result.m_port ) )
			| Opt( result.m_pool_size, "thread-pool size" )
					[ "-n" ][ "--thread-pool-size" ]
					( fmt::format(
							RESTINIO_FMT_FORMAT_STRING(
								"The size of a thread pool to run server (default: {})" ),
						result.m_pool_size ) )
			| Opt( result.m_trace_server )
					[ "-t" ][ "--trace" ]
					( "Enable trace server" )
			| Help(result.m_help);

		auto parse_result = cli.parse( Args(argc, argv) );
		if( !parse_result )
		{
			throw std::runtime_error{
				fmt::format(
					RESTINIO_FMT_FORMAT_STRING(
						"Invalid command-line arguments: {}" ),
					parse_result.errorMessage() ) };
		}

		if( result.m_help )
		{
			std::cout << cli << std::endl;
		}

		return result;
	}
};

using router_t = restinio::router::express_router_t<>;

namespace rtz = restinio::transforms::zlib;

auto make_router()
{
	auto router = std::make_unique< router_t >();

	router->http_post(
		R"-(/)-",
		[ & ]( const restinio::request_handle_t& req, auto ){

			return req->create_response()
				.append_header( "Server", "RESTinio" )
				.append_header_date_field()
				.append_header(
					// Inherit content-type from request (if it is set).
					restinio::http_field::content_type,
					req->header().get_field_or(
						restinio::http_field::content_type,
						"text/plain" ) )
				.set_body(
					// Decompress request body if necessary.
					rtz::handle_body(
						*req,
						// NOTE: the specification of the return type is necessary
						// for Visual Studio 2019 (16.11.5) and C++20.
						[]( std::string body_data ) -> std::string
						{
							return body_data;
						} ) )
				.done();

		} );

	return router;
}

template < typename Server_Traits >
void run_server( const app_args_t & args )
{
	restinio::run(
		restinio::on_thread_pool< Server_Traits >( args.m_pool_size )
			.port( args.m_port )
			.address( args.m_address )
			.concurrent_accepts_count( args.m_pool_size )
			.request_handler( make_router() ) );
}

int main( int argc, const char * argv[] )
{
	try
	{
		const auto args = app_args_t::parse( argc, argv );

		if( !args.m_help )
		{
			if( args.m_trace_server )
			{
				using traits_t =
					restinio::traits_t<
						restinio::asio_timer_manager_t,
						restinio::shared_ostream_logger_t,
						router_t >;

				run_server< traits_t >( args );
			}
			else
			{
				using traits_t =
					restinio::traits_t<
						restinio::asio_timer_manager_t,
						restinio::null_logger_t,
						router_t >;

				run_server< traits_t >( args );
			}
		}
	}
	catch( const std::exception & ex )
	{
		std::cerr << "Error: " << ex.what() << std::endl;
		return 1;
	}

	return 0;
}
