/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <log4cxx/logger.h>
#include <log4cxx/xml/domconfigurator.h>
#include <log4cxx/patternlayout.h>
#include <log4cxx/consoleappender.h>
#include <log4cxx/helpers/transcoder.h>

#include <string>
#include <thread>

#if LOG4CXX_USING_STD_FORMAT
#include <format>
#else
#include <fmt/core.h>
#include <fmt/chrono.h>
#include <fmt/ostream.h>
#endif

#include "log4cxxbenchmarker.h"
using log4cxx::LogString;

static log4cxx::LoggerPtr console = log4cxx::Logger::getLogger( "console" );
static std::vector<uint64_t> results;
static std::mutex results_mutex;

static void benchmark_function( const std::string& name, void (*fn)(int), int howmany )
{
	using std::chrono::duration;
	using std::chrono::duration_cast;
	using std::chrono::high_resolution_clock;

	auto start = high_resolution_clock::now();
	fn(howmany);
	auto delta = high_resolution_clock::now() - start;
	auto delta_d = duration_cast<duration<double>>(delta).count();

	std::lock_guard<std::mutex> lk(results_mutex);
	results.push_back( uint64_t(howmany / delta_d) );
	LOG4CXX_INFO_FMT( console, "Log4cxx {} Elapsed: {:.4} secs {:L}/sec",
		name,
		delta_d,
		results.back());
}

static void benchmark_conversion_pattern( const std::string& name,
	const std::string& conversion_pattern,
	void(*fn)(const LogString&, int),
	int howmany)
{
	using std::chrono::duration;
	using std::chrono::duration_cast;
	using std::chrono::high_resolution_clock;

	auto start = high_resolution_clock::now();
	LOG4CXX_DECODE_CHAR(conversion_patternLS, conversion_pattern);
	fn(conversion_patternLS, howmany);
	auto delta = high_resolution_clock::now() - start;
	auto delta_d = duration_cast<duration<double>>(delta).count();

	std::lock_guard<std::mutex> lk(results_mutex);
	results.push_back( uint64_t(howmany / delta_d) );
	LOG4CXX_INFO_FMT( console, "Log4cxx {} pattern: {} Elapsed: {:.4} secs {:L}/sec",
		name,
		conversion_pattern,
		delta_d,
		results.back());
}

static void bench_log4cxx_single_threaded(int iters)
{
	LOG4CXX_INFO(console, LOG4CXX_STR("**************************************************************"));
	LOG4CXX_INFO_FMT(console, "Benchmarking Single threaded: {} messages", iters );
	LOG4CXX_INFO(console, LOG4CXX_STR("**************************************************************"));

	benchmark_conversion_pattern( "NoFormat", "%m%n", &log4cxxbenchmarker::logWithConversionPattern, iters );
	benchmark_conversion_pattern( "DateOnly", "[%d] %m%n", &log4cxxbenchmarker::logWithConversionPattern, iters );
	benchmark_conversion_pattern( "DateClassLevel", "[%d] [%c] [%p] %m%n", &log4cxxbenchmarker::logWithConversionPattern, iters );

	benchmark_function( "Logging with FMT", &log4cxxbenchmarker::logWithFMT, iters );
	benchmark_function( "Logging static string", &log4cxxbenchmarker::logStaticString, iters );
	benchmark_function( "Logging static string with FMT", &log4cxxbenchmarker::logStaticStringFMT, iters );
	benchmark_function( "Logging disabled debug", &log4cxxbenchmarker::logDisabledDebug, iters );
	benchmark_function( "Logging disabled trace", &log4cxxbenchmarker::logDisabledTrace, iters );
	benchmark_function( "Logging enabled debug", &log4cxxbenchmarker::logEnabledDebug, iters );
	benchmark_function( "Logging enabled trace", &log4cxxbenchmarker::logEnabledTrace, iters );
}

static void bench_log4cxx_multi_threaded(size_t threads, int iters)
{
	LOG4CXX_INFO(console, LOG4CXX_STR("**************************************************************"));
	LOG4CXX_INFO_FMT(console, "Benchmarking multithreaded threaded: {} messages/thread, {} threads", iters, threads );
	LOG4CXX_INFO(console, LOG4CXX_STR("**************************************************************"));

	std::vector<std::thread> runningThreads;

	auto logger = log4cxxbenchmarker::logSetupMultithreaded();

	for ( size_t x = 0; x < threads; x++ )
	{
		runningThreads.push_back( std::thread( [iters]()
		{
			benchmark_function( "Logging with FMT MT", &log4cxxbenchmarker::logWithFMTMultithreaded, iters );
		}) );
	}

	for ( std::thread& th : runningThreads )
	{
		th.join();
	}
}

static void bench_log4cxx_multi_threaded_disabled(size_t threads, int iters)
{
	LOG4CXX_INFO(console, LOG4CXX_STR("**************************************************************"));
	LOG4CXX_INFO_FMT(console, "Benchmarking multithreaded disabled: {} messages/thread, {} threads", iters, threads );
	LOG4CXX_INFO(console, LOG4CXX_STR("**************************************************************"));

	std::vector<std::thread> runningThreads;

	auto logger = log4cxxbenchmarker::logSetupMultithreaded();

	for ( size_t x = 0; x < threads; x++ )
	{
		runningThreads.push_back( std::thread( [iters]()
		{
			benchmark_function( "Logging disabled MT", &log4cxxbenchmarker::logDisabledMultithreaded, iters );
		}) );
	}

	for ( std::thread& th : runningThreads )
	{
		th.join();
	}
}

int main(int argc, char* argv[])
{
	int iters = 1000000;
	size_t threads = 4;
	size_t max_threads = 32;

	std::setlocale( LC_ALL, "" ); /* Set locale for C functions */
	std::locale::global(std::locale("")); /* set locale for C++ functions */

	console->setAdditivity( false );
	log4cxx::PatternLayoutPtr pattern( new log4cxx::PatternLayout() );
	pattern->setConversionPattern( LOG4CXX_STR("%m%n") );

	log4cxx::ConsoleAppenderPtr consoleWriter( new log4cxx::ConsoleAppender );
	consoleWriter->setLayout( pattern );
	consoleWriter->setTarget( LOG4CXX_STR("System.out") );
	log4cxx::helpers::Pool p;
	consoleWriter->activateOptions(p);

	console->addAppender( consoleWriter );

	if (argc > 1)
	{
		iters = std::stoi(argv[1]);
	}

	if (argc > 2)
	{
		threads = std::stoul(argv[2]);
	}

	if (threads > max_threads)
	{
		LOG4CXX_ERROR_FMT(console, "Too many threads specified(max: {})", max_threads);
		return 1;
	}

	LOG4CXX_INFO_FMT(console, "Benchmarking library only(no writing out):", 0);
	bench_log4cxx_single_threaded(iters);
	bench_log4cxx_multi_threaded(threads, iters);
	bench_log4cxx_multi_threaded_disabled(threads, iters);

	LOG4CXX_INFO_FMT(console, "Results for use in spreadsheet:", 0);

	for ( uint64_t result : results )
	{
		LOG4CXX_INFO_FMT(console, "{}", result );
	}

	return 0;
}
