/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

/*---------------------------------------------------------------
	APPLICATION: mrpt-performance
	PURPOSE: Do an exhaustive test of performance for various algos of MRPT
  ---------------------------------------------------------------*/

#include <mrpt/3rdparty/tclap/CmdLine.h>
#include <mrpt/io/CFileInputStream.h>
#include <mrpt/io/CFileOutputStream.h>
#include <mrpt/io/CMemoryStream.h>
#include <mrpt/obs/CObservation2DRangeScan.h>
#include <mrpt/obs/stock_observations.h>
#include <mrpt/serialization/CArchive.h>
#include <mrpt/serialization/stl_serialization.h>
#include <mrpt/version.h>

#include "common.h"

using namespace mrpt;
using namespace mrpt::system;
using namespace mrpt::math;
using namespace std;

std::list<TestData> lstTests;

// Data for performance comparatives between different versions:
#ifdef MRPT_DOC_PERF_DIR
const std::string PERF_DATA_DIR = std::string(MRPT_DOC_PERF_DIR);
#else
const std::string PERF_DATA_DIR;
#endif

vector<pair<string, double>> all_perf_data;	 // pair: description, time

// All this mess is to avoid the smart compiler optimizer to remove "code with
// no effect" in some test functions...
void dummy_do_nothing_with_string(const std::string& s)
{
	static std::string S;
	S = s;
}

void getTestImage(unsigned int img_index, mrpt::img::CImage& out_img)
{
	mrpt::obs::stock_observations::exampleImage(out_img, img_index);
}

#include "run_build_tables.h"

// ------------------------------------------------------
//						MAIN
// ------------------------------------------------------
int main(int argc, char** argv)
{
	try
	{
		TCLAP::CmdLine cmd("mrpt-performance", ' ', MRPT_getVersion().c_str());

		TCLAP::ValueArg<std::string> arg_contains(
			"c", "match-contains",
			"Run only the tests containing the given substring", false, "NAME",
			"NAME", cmd);

		TCLAP::SwitchArg arg_build_tables(
			"t", "tables",
			"Don't run any test, instead build the tables of compared "
			"performances in SOURCE_DIR/doc/",
			cmd, false);
		TCLAP::SwitchArg arg_release(
			"r", "release",
			"Don't use the postfix 'dev' in the performance stats file", cmd,
			false);

		// Parse arguments:
		if (!cmd.parse(argc, argv))
			throw std::runtime_error("");  // should exit.

		if (arg_build_tables.isSet()) return run_build_tables();

		const std::string filName = "./mrpt-performance.html";

		std::string match_contains;
		if (arg_contains.isSet())
		{
			cout << "Using match filter: " << match_contains << endl;
			match_contains = arg_contains.getValue();
		}

		bool doLog = true;
		bool HAVE_PERF_DATA_DIR = !PERF_DATA_DIR.empty() &&
			mrpt::system::directoryExists(PERF_DATA_DIR);
		if (HAVE_PERF_DATA_DIR)
			cout << "Using perf-data dir: " << PERF_DATA_DIR << endl;

		CTicTac globalTime;
		globalTime.Tic();

		CFileOutputStream fo;
		doLog = fo.open(filName);
		if (doLog) cout << "Saving log to: " << filName << endl;
		else
			cout << "Cannot save log, error opening " << filName
				 << " for writing..." << endl;

		cout << endl;

		if (doLog)
		{
			fo.printf(
				"<html><head><title>mrpt-performance "
				"results</title></head><body>\n");
			fo.printf("\n");
		}

		// Start tests:
		// --------------------
		register_tests_icpslam();
		register_tests_poses();
		register_tests_pose_interp();
		register_tests_matrices();
		register_tests_grids();
		register_tests_grid3D();
		register_tests_pointmaps();
		register_tests_random();
		register_tests_math();
		register_tests_image();
		register_tests_scan_matching();
		register_tests_feature_extraction();
		register_tests_feature_matching();
		register_tests_graph();
		register_tests_graphslam();
		register_tests_CObservation3DRangeScan();
		register_tests_atan2lut();
		register_tests_strings();
		register_tests_octomaps();
		register_tests_system();
		register_tests_yaml();

		if (doLog)
		{
			fo.printf("<div align=\"center\"><h3>Results</h3></div><br>");
			fo.printf("<div align=\"center\"><table border=\"1\">\n");
			fo.printf(
				"<tr> <td align=\"center\"><b>Test description</b></td> "
				"<td align=\"center\"><b>Execution time</b></td>"
				"<td align=\"center\"><b>Execution rate (Hz)</b></td> </tr>\n");
		}

		for (auto it = lstTests.begin(); it != lstTests.end(); it++)
		{
			// Filter tests?
			if (!match_contains.empty())
				if (string::npos == string(it->name).find(match_contains))
					continue;  // doesn't have the substring

			printf("%-60s", it->name);
			cout.flush();

			try
			{
				const double t = it->func(it->arg1, it->arg2);	// Run it.

				mrpt::system::consoleColorAndStyle(
					mrpt::system::ConsoleForegroundColor::GREEN);

				cout << mrpt::system::intervalFormat(t);

				mrpt::system::consoleColorAndStyle(
					mrpt::system::ConsoleForegroundColor::DEFAULT);
				cout << endl;

				// Make list of all data:
				all_perf_data.emplace_back(it->name, t);

				if (doLog)
				{
					fo.printf(
						"<tr> <td>%s</td> <td align=\"right\">%s</td> <td "
						"align=\"right\">%sHz</td>  </tr>\n",
						it->name, mrpt::system::intervalFormat(t).c_str(),
						mrpt::system::unitsFormat(1.0 / t).c_str());
				}
			}
			catch (const std::exception& e)
			{
				cerr << "Skipped due to exception:\n"
					 << mrpt::exception_to_str(e) << endl;
			}
		}

		// Finish log:
		if (doLog)
		{
			fo.printf("</table></div>\n");
			fo.printf("<p> &nbsp; </p>\n");

			if (mrpt::system::fileExists("/proc/cpuinfo"))
			{
				fo.printf("<div align=\"center\"><h3>cpuinfo</h3></div>");
				fo.printf("<p> &nbsp; </p>\n");

				fo.printf("<verbatim><small>");
				ifstream cpuFil("/proc/cpuinfo");
				if (cpuFil.good())
				{
					string s;
					while (!cpuFil.eof())
					{
						getline(cpuFil, s);
						fo.printf("%s <br>\n", s.c_str());
					}
				}
				fo.printf("</small></verbatim>");
				fo.printf("<p> &nbsp; </p>\n");
			}

			fo.printf(
				"<hr><small>Automated test run at %s with %s using program "
				"'mrpt-performance'.<br>Overall run took %s</small>\n",
				mrpt::system::dateTimeLocalToString(now()).c_str(),
				MRPT_getVersion().c_str(),
				mrpt::system::intervalFormat(globalTime.Tac()).c_str());

			fo.printf("</body></html>\n");
			cout << endl << "Checkout the logfile: " << filName << endl;
		}

		// Save to perf-data dir?
		if (HAVE_PERF_DATA_DIR)
		{
			const char* version_postfix = arg_release.isSet() ? "" : "dev";

// Macros to create strings with the compiler version:
#define ___STR2__(x) #x
#define ___STR1__(x) ___STR2__(x)
#define COMP_VER(NAME, MAJ, MIN, PATCH)                                        \
	NAME ___STR1__(MAJ) ___STR1__(MIN) ___STR1__(PATCH)

#if defined(_MSC_VER)
#if _MSC_VER == 1700
			const char* compiler_name = "MSVC2012";
#elif _MSC_VER == 1800
			const char* compiler_name = "MSVC2013";
#elif _MSC_VER == 1900
			const char* compiler_name = "MSVC2015";
#elif (_MSC_VER >= 1910) && (_MSC_VER <= 1919)
			const char* compiler_name = "MSVC2017";
#else
			const char* compiler_name = "MSVC";
#endif
#elif defined(__clang__)
			const char* compiler_name = COMP_VER(
				"CLANG", __clang_major__, __clang_minor__,
				__clang_patchlevel__);
#elif defined(__GNUC__)
			const char* compiler_name =
				COMP_VER("GCC", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
#else
			const char* compiler_name = "unknowncompiler";
#endif

			const string fil_name = PERF_DATA_DIR +
				mrpt::format("/perf-results-%i.%i.%i%s-%s-%ibit.dat",
							 int((MRPT_VERSION >> 16) & 0xFF),
							 int((MRPT_VERSION >> 8) & 0xFF),
							 int((MRPT_VERSION >> 0) & 0xFF), version_postfix,
							 compiler_name, int(MRPT_WORD_SIZE));
			cout << "Saving perf-data to: " << fil_name << endl;
			CFileOutputStream f(fil_name);
			auto arch = archiveFrom(f);
			arch << all_perf_data;
		}

		return 0;
	}
	catch (const std::exception& e)
	{
		if (::strlen(e.what()))
		{
			mrpt::system::consoleColorAndStyle(
				mrpt::system::ConsoleForegroundColor::RED);

			std::cerr << "Program finished for an exception!!" << std::endl;

			mrpt::system::consoleColorAndStyle(
				mrpt::system::ConsoleForegroundColor::DEFAULT);

			std::cerr << mrpt::exception_to_str(e) << std::endl;

			mrpt::system::pause();
		}
		return -1;
	}
}
