#include <catch2/catch.hpp>
#include <sstream>
#include <thread>
#include <cmath>
#include <iostream>
#include <alib/measure>

TEST_CASE ( "Measurements", "[unit][measure][measurements][!mayfail]" ) {
	measurements::reset ( );

	SECTION ( "Time Measurements" ) {
		measurements::start ( "global", measurements::Type::OVERALL );
		measurements::start ( "init", measurements::Type::INIT );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 100 ) );
		measurements::end ( );
		measurements::start ( "main", measurements::Type::MAIN );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 20 ) );
		measurements::start ( "aux", measurements::Type::AUXILIARY );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 40 ) );
		measurements::end ( );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 80 ) );
		measurements::start ( "aux", measurements::Type::AUXILIARY );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 40 ) );
		measurements::end ( );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 80 ) );
		measurements::end ( );
		measurements::start ( "fin", measurements::Type::FINALIZE );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 30 ) );
		measurements::start ( "aux", measurements::Type::AUXILIARY );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 40 ) );
		measurements::end ( );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 80 ) );
		measurements::end ( );
		measurements::end ( );
		measurements::start ( "global2", measurements::Type::OVERALL );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 100 ) );
		measurements::end ( );
		measurements::start ( "global3", measurements::Type::OVERALL );
		measurements::start ( "init", measurements::Type::INIT );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 100 ) );
		measurements::end ( );
		measurements::start ( "main", measurements::Type::MAIN );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 20 ) );
		measurements::start ( "aux", measurements::Type::AUXILIARY );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 40 ) );
		measurements::end ( );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 80 ) );
		measurements::start ( "aux", measurements::Type::AUXILIARY );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 40 ) );
		measurements::end ( );
		std::this_thread::sleep_for ( std::chrono::milliseconds ( 80 ) );
		measurements::end ( );
		measurements::end ( );


		//root, global1, init, main, aux, aux, fin,aux,...,global2,...,global3, init, main, aux, aux
		int expectedResults [ ] = {0, 510000, 100000, 260000, 40000, 40000, 150000, 40000, 100000, 360000, 100000, 260000, 40000, 40000 };
		const int DEVIATION = 25000;

		auto results = measurements::results ( );

		int i = 0;
		for(const measurements::MeasurementFrame & frame : results.frames) {
			CHECK ( std::abs ( expectedResults[i] - frame.time.duration.count ( ) ) <= DEVIATION ); // unstable on build machines
			i++;
		}

		// std::cout << measurements::MeasurementFormat::LIST << measurements::results ( ) << std::endl;
		// std::cout << measurements::MeasurementFormat::TREE << measurements::results ( ) << std::endl;
	}

#ifdef __clang__
#define __NO_OPTIMIZE_ATTRIBUTE__ __attribute__((optnone))
#else
#define __NO_OPTIMIZE_ATTRIBUTE__ __attribute__((optimize("O0")))
#endif

	//void __NO_OPTIMIZE_ATTRIBUTE__ MeasurementsTest::testMemoryMeasurements ( ) {
	SECTION ( "Memory Measurements" ) {
		measurements::start ( "chunk1", measurements::Type::MAIN );
		int * baz = new int[500];
		measurements::end ( );
		delete[] baz;

		int * foo = new int[1000];
		measurements::start ( "chunk2", measurements::Type::MAIN );
		measurements::start ( "chunk21", measurements::Type::MAIN );
		int * bar = new int[2000];
		measurements::end ( );
		delete[] foo;
		delete[] bar;
		measurements::end ( );

		measurements::start ( "chunk3", measurements::Type::MAIN );
		measurements::start ( "chunk31", measurements::Type::MAIN );
		bar = new int[1000];
		delete[] bar;
		measurements::end ( );
		measurements::end ( );

		auto results = measurements::results ( );



		//std::cout << measurements::MeasurementFormat::LIST << measurements::results ( ) << std::endl;
		//std::cout << measurements::MeasurementFormat::TREE << measurements::results ( ) << std::endl;

#if 0
		int expectedResultsHW [ ]  = { 12000, 2000, 12000 , 12000, 4000, 4000 };
		int expectedResultsSHU [ ] = { 0, 0   , 4000  , 4000 , 0   , 0    };
		int expectedResultsEHU [ ] = { 0, 2000, 0     , 12000, 0   , 0    };

		int i = 0;
		for(const measurements::MeasurementFrame & frame : results.frames) {
			CHECK ( expectedResultsHW[i] ==  frame.memory.highWatermark );
			CHECK ( expectedResultsSHU[i] == frame.memory.startHeapUsage );
			CHECK ( expectedResultsEHU[i] == frame.memory.endHeapUsage );
			i++;
		}
#endif
	}

	SECTION ( "Counter" ) {
		measurements::start ( "chunk1", measurements::Type::MAIN );
		measurements::counterInc ( "test1", 5 );
		measurements::counterInc ( "test2" );
		measurements::start ( "chunk11", measurements::Type::MAIN );
		measurements::counterDec ( "test3" );
		measurements::start ( "chunk111", measurements::Type::MAIN );
		measurements::counterDec ( "test1" );
		measurements::counterDec ( "test2", 10 );
		measurements::counterDec ( "test3" );
		measurements::counterDec ( "test4" );
		measurements::end ( );
		measurements::start ( "chunk112", measurements::Type::MAIN );
		measurements::counterDec ( "test1" );
		measurements::counterDec ( "test2", 10 );
		measurements::counterDec ( "test3" );
		measurements::counterDec ( "test4" );
		measurements::end ( );
		measurements::end ( );
		measurements::end ( );

		std::map < std::string, int > expectedResults [ ] = {
			{ } ,
			{ { "test1", 3 } , { "test2", -19 } , { "test3", -3 } , { "test4", -2 } } ,
			{ { "test1", 3 } , { "test2", -19 } , { "test3", -3 } , { "test4", -2 } } ,
			{ { "test1", 4 } , { "test2", -9  } , { "test3", -2 } , { "test4", -1 } } ,
			{ { "test1", 3 } , { "test2", -19 } , { "test3", -3 } , { "test4", -2 } } ,
		};

		auto results = measurements::results ( );

		int i = 0;
		for(const measurements::MeasurementFrame & frame : results.frames) {

			auto erit = expectedResults[i].begin ( );
			auto it = frame.counter.counters.begin ( );

			while ( erit != expectedResults[i].end ( ) ) {
				REQUIRE ( erit->first == measurements::to_string ( it->first ) );
				CHECK ( erit->second == it->second );
				++erit;
				++it;
			}

			i++;
		}

		// std::cout << measurements::MeasurementFormat::LIST << measurements::results ( ) << std::endl;
		// std::cout << measurements::MeasurementFormat::TREE << measurements::results ( ) << std::endl;
	}
}
