text:
// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// Repository at: https://github.com/djeedjay/DebugViewPP/

#include "stdafx.h"

#define BOOST_TEST_MODULE DebugView++Lib Unit Test

#include <boost/test/unit_test_gui.hpp>

#include <filesystem>
#include <random>
#include <fstream>
#include <iostream>

#include "Win32/Utilities.h"
#include "Win32/Win32Lib.h"
#include "CobaltFusion/stringbuilder.h"
#include "CobaltFusion/ExecutorClient.h"
#include "CobaltFusion/Executor.h"
#include "IndexedStorageLib/IndexedStorage.h"
#include "DebugView++Lib/ProcessInfo.h"
#include "DebugView++Lib/DBWinBuffer.h"
#include "DebugView++Lib/LogSources.h"
#include "DebugView++Lib/LogSource.h"
#include "DebugView++Lib/TestSource.h"
#include "DebugView++Lib/VectorLineBuffer.h"
#include "DebugView++Lib/LogFile.h"
#include "DebugView++Lib/FileIO.h"
#include "DebugView++Lib/Conversions.h"
#include "CobaltFusion/scope_guard.h"

namespace fusion {
namespace debugviewpp {

BOOST_AUTO_TEST_SUITE(DebugViewPlusPlusLib)

std::string GetTestString(size_t i)
{
	return stringbuilder() << "BB_TEST_ABCDEFGHI_EE_" << i;
}

class TestLineBuffer : public LineBuffer
{
public:
	TestLineBuffer(size_t size) : LineBuffer(size)
	{
	}

	virtual ~TestLineBuffer()
	{
	}

	virtual void WaitForReaderTimeout()
	{
		throw std::exception("WaitForReader timeout");
	}
};

class ScopedTimezoneBias
{
public:
	ScopedTimezoneBias(LONG Bias)
	{
		Win32::SetPrivilege(SE_TIME_ZONE_NAME, true);
		GetTimeZoneInformation(&m_tz);
		TIME_ZONE_INFORMATION tz = m_tz;
		tz.Bias = Bias;
		SetTimeZoneInformation(&tz);
	}

	~ScopedTimezoneBias()
	{
		SetTimeZoneInformation(&m_tz);
		Win32::SetPrivilege(SE_TIME_ZONE_NAME, false);
	}
private:
	TIME_ZONE_INFORMATION m_tz;
};

std::string GetTestFileName()
{
	using namespace std::experimental::filesystem;
	return absolute(path("SaveLoadLogFile_unique_test_filename")).string();
}

std::string SaveLogFile(const LogFile& logfile)
{
	auto filename = GetTestFileName();
	std::ofstream fs;
	OpenLogFile(fs, WStr(filename), OpenMode::Truncate);
	size_t count = logfile.Count();
	for (size_t i = 0; i < count; ++i)
	{
		auto msg = logfile[i];
		WriteLogFileMessage(fs, msg.time, msg.systemTime, msg.processId, msg.processName, msg.text);
	}
	fs.close();
	return filename;
}

std::string AppendLogFile(const LogFile& logfile)
{
	auto filename = GetTestFileName();
	std::ofstream fs;
	OpenLogFile(fs, WStr(filename), OpenMode::Append);
	size_t count = logfile.Count();
	for (size_t i = 0; i < count; ++i)
	{
		auto msg = logfile[i];
		WriteLogFileMessage(fs, msg.time, msg.systemTime, msg.processId, msg.processName, msg.text);
	}
	fs.close();
	return filename;
}

LogFile LoadLogFile(const std::string& filename)
{
	// load file test
	std::ifstream file(filename);
	Line line;
	line.processName = "process";
	line.systemTime = Win32::GetSystemTimeAsFileTime();
	LogFile result;
	ReadLogFileMessage(file, line);		// ignore header
	while (ReadLogFileMessage(file, line))
		result.Add(Message(line.time, line.systemTime, line.pid, line.processName, line.message));
	return result;
}

bool AreEqual(const Message& a, const Message& b)
{
	if (std::fabs(a.time - b.time) > 0.000001) return false;
	if (GetTimeText(a.systemTime) != GetTimeText(b.systemTime)) return false;
	if (a.processId != b.processId) return false;
	if (a.processName != b.processName) return false;
	if (a.text != b.text) return false;
	//if (a.color != b.color) return false;	// not stored in file
	return true;
}

bool AreEqual(const LogFile& a, const LogFile& b)
{
	if (a.Count() != b.Count())
		return false;
	size_t count = a.Count();
	for (size_t i = 0; i < count; ++i)
	{
		if (!AreEqual(a[i], b[i]))
			return false;
	}
	return true;
}

BOOST_AUTO_TEST_CASE(TimeZone)
{
	Timer timer;
	LogFile logFile;
	auto t1 = Win32::GetSystemTimeAsFileTime();
	logFile.Add(Message(timer.Get(), t1, 0, "processname", "test message 1"));
	auto t2 = Win32::GetSystemTimeAsFileTime();
	logFile.Add(Message(timer.Get(), t2, 0, "processname", "test message 2"));
	auto t3 = Win32::GetSystemTimeAsFileTime();
	logFile.Add(Message(timer.Get(), t3, 0, "processname", "test message 3"));
	auto t4 = Win32::GetSystemTimeAsFileTime();
	logFile.Add(Message(0, t4, 0, "processname", "message 4 (zero time message)"));

	BOOST_TEST_MESSAGE("UTC");
	{
		ScopedTimezoneBias(0);
		auto result = LoadLogFile(SaveLogFile(logFile));
		BOOST_TEST(AreEqual(result, logFile));
	}
	BOOST_TEST_MESSAGE("(UTC-8) Fremond");
	{
		ScopedTimezoneBias(-480);
		auto result = LoadLogFile(SaveLogFile(logFile));
		BOOST_TEST(AreEqual(result, logFile));
	}
	BOOST_TEST_MESSAGE("(UTC+8:30) Pyongyang Standard Time");
	{
		ScopedTimezoneBias(+510);
		auto result = LoadLogFile(SaveLogFile(logFile));
		BOOST_TEST(AreEqual(result, logFile));
	}
	BOOST_TEST_MESSAGE("test boundry case (UTC-12) Baker- / Howland Island (uninhabited islands belonging to the United States)");
	{
		ScopedTimezoneBias(-720);
		auto result = LoadLogFile(SaveLogFile(logFile));
		BOOST_TEST(AreEqual(result, logFile));
	}
	BOOST_TEST_MESSAGE("test boundry case (UTC+14) Kiritimati (Christmas Island, also uninhabited, part of the Kiribati Line Islands)");
	{
		ScopedTimezoneBias(+720);
		auto result = LoadLogFile(SaveLogFile(logFile));
		BOOST_TEST(AreEqual(result, logFile));
	}

	// test crossing timezones
	BOOST_TEST_MESSAGE("test sending a logfile into the past (UTC -> UTC-8)");
	{
		auto filename = SaveLogFile(logFile);
		ScopedTimezoneBias(-480);
		auto result = LoadLogFile(filename);
		BOOST_TEST(AreEqual(result, logFile));
	}

	// test crossing timezones
	BOOST_TEST_MESSAGE("test sending a logfile into the future (UTC-8 -> UTC)");
	{
		std::string filename;
		{
			ScopedTimezoneBias(-480);
			filename = SaveLogFile(logFile);
		}
		auto result = LoadLogFile(filename);
		BOOST_TEST(AreEqual(result, logFile));
	}
}

BOOST_AUTO_TEST_CASE(HandleTest)
{
	static constexpr HANDLE nullHandle = nullptr;

	HANDLE rawHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
	Win32::Handle handle(rawHandle);
	{
		BOOST_TEST(handle.get() == rawHandle);
		Win32::Handle handle1(std::move(handle));
		BOOST_TEST(handle.get() == nullHandle);
		BOOST_TEST(handle1.get() == rawHandle);
		{
			Win32::Handle handle2(std::move(handle1));
			handle2.release();
			BOOST_TEST(handle2.get() == nullHandle);
		}
		BOOST_TEST(handle1.get() == nullHandle);
	}

	{
		Win32::Handle handle1;
		{
			HANDLE zero = nullptr;
			Win32::Handle zeroHandle(zero);
			BOOST_TEST(zeroHandle.get() == nullHandle);
			handle1 = std::move(zeroHandle);
			BOOST_TEST(zeroHandle.get() == nullHandle);
			BOOST_TEST(handle1.get() == nullHandle);
		}
		// this scope ensures having a Handle leave scope that had its guts ripped out by std::move
		// will not cause nullpointers or exeptions
	}
}

BOOST_AUTO_TEST_CASE(LineBufferTest1)
{
	TestLineBuffer buffer(64);
	FILETIME ft;
	ft.dwLowDateTime = 42;
	ft.dwHighDateTime = 43;
	buffer.Add(42.0, ft, 0, "test", nullptr);

	auto lines = buffer.GetLines();
	auto line = lines[0];
	BOOST_TEST(line.time == 42.0);
	BOOST_TEST(line.systemTime.dwLowDateTime == 42);
	BOOST_TEST(line.systemTime.dwHighDateTime == 43);
	BOOST_TEST(buffer.Empty());
}

BOOST_AUTO_TEST_CASE(LineBufferTest2)	// test overflows boosttestui with log-output locking its GUI up temporarily
{
	TestLineBuffer buffer(600);
	Timer timer;
	std::cout << "LineBufferTest2 running..." << std::endl;
	for (int j = 0; j < 100; ++j)
	{
		//BOOST_TEST_MESSAGE("j: " << j << "\n");

		for (int i = 0; i < 17; ++i)
		{
			//BOOST_TEST_MESSAGE("i: " << i << "\n");
			FILETIME ft;
			ft.dwLowDateTime = 43;
			ft.dwHighDateTime = 44;
			buffer.Add(42.0, ft, 0, "test", nullptr);
		}

		auto lines = buffer.GetLines();
		BOOST_TEST(lines.size() == 17);
		for (auto& line : lines)
		{
			BOOST_TEST(line.time == 42.0);
			BOOST_TEST(line.systemTime.dwLowDateTime == 43);
			BOOST_TEST(line.systemTime.dwHighDateTime == 44);
		}
	}
}

BOOST_AUTO_TEST_CASE(IndexedStorageRandomAccess)
{
	using namespace indexedstorage;

	size_t testSize = 10000;
	auto testMax = testSize - 1;

	std::mt19937 generator;
	std::uniform_int_distribution<size_t> distribution(0, testMax);
	SnappyStorage s;
	for (size_t i = 0; i < testSize; ++i)
		s.Add(GetTestString(i));

	// random access can be somewhat slow in debug-mode, this is expected behaviour
	bool failed = false;
	for (size_t i = 0; i < testSize/10; ++i)
	{
		size_t j = distribution(generator);  // generates number in the range 0..testMax
		if (s[j] != GetTestString(j))
		{
			failed = true;
			break;
		};
	}
	BOOST_TEST(!failed);
}

BOOST_AUTO_TEST_CASE(IndexedStorageCompression)
{
	using namespace indexedstorage;

	// the memory allocator will mess up test results is < 64 kb is allocated
	// make sure SnappyStorage allocates at least ~500kb for reproducable results

	// this test is indicative only, on average the SnappyStorage should allocate at most 50% of memory compared to a normal vector.
	// since GetTestString returns an overly simpe-to-compress string, it will appear to perform insanely good.

	size_t testSize = 100000;
	VectorStorage v;
	SnappyStorage s;

	size_t m0 = ProcessInfo::GetPrivateBytes();

	for (size_t i = 0; i < testSize; ++i)
		v.Add(GetTestString(i));

	size_t m1 = ProcessInfo::GetPrivateBytes();
	size_t usedByVector = m1 - m0;
	BOOST_TEST_MESSAGE("VectorStorage requires: " << usedByVector/1024 << " kB");

	for (size_t i = 0; i < testSize; ++i)
		s.Add(GetTestString(i));

	size_t m2 = ProcessInfo::GetPrivateBytes();
	size_t usedBySnappy = m2 - m1;

	BOOST_TEST_MESSAGE("SnappyStorage requires: " << usedBySnappy/1024 << " kB (" << (100*usedBySnappy)/usedByVector << "%)");
	BOOST_TEST(0.50*usedByVector > usedBySnappy);
}

// execute as:
// "DebugView++Test.exe" --log_level=test_suite --run_test=*/LogSourcesReceiveMessages
BOOST_AUTO_TEST_CASE(LogSourcesReceiveMessages)
{
	ActiveExecutorClient executor;
	LogSources logsources(executor, false);
	LogSource* logsource;
	executor.Call([&] { logsource = logsources.AddTestSource();  });

	Timer timer;
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 1");
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 2");
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 3");
	BOOST_TEST_MESSAGE("3 lines added.");

	Lines lines;
	executor.Call([&] { lines = logsources.GetLines(); });
	BOOST_TEST_MESSAGE("received: " << lines.size() << " lines.");

	BOOST_TEST(lines.size() == 3);

	for (auto& line : lines)
	{
		BOOST_TEST_MESSAGE("line: " << line.message);
	}

	const int testsize = 1000;
	BOOST_TEST_MESSAGE("Write " << testsize << " lines...");
	for (int i = 0; i < testsize; ++i)
	{
		logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "TESTSTRING 1234\n");
	}

	Lines morelines;
	executor.Call([&] { morelines = logsources.GetLines(); });

	BOOST_TEST_MESSAGE("received: " << morelines.size() << " lines.");
	BOOST_TEST(morelines.size() == testsize);
}

BOOST_AUTO_TEST_CASE(LogSourcesCharacterPreservation)
{
	ActiveExecutorClient executor;
	LogSources logsources(executor, false);
	LogSource* logsource;
	executor.Call([&] { logsource = logsources.AddTestSource();  });

	Timer timer;
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "TrailingSpace ");
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "TrailingTab\t");

	Lines lines;
	executor.Call([&] { lines = logsources.GetLines(); });
	BOOST_TEST(lines.size() == 2);
	BOOST_TEST(lines[0].message == "TrailingSpace ");	// space preserved
	BOOST_TEST(lines[1].message == "TrailingTab\t");	// tab preserved
}

BOOST_AUTO_TEST_CASE(LogSourcesTabHandling)
{
	ActiveExecutorClient executor;
	LogSources logsources(executor, false);
	LogSource* logsource;
	executor.Call([&] { logsource = logsources.AddTestSource();  });

	Timer timer;
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "\tTabPrefix");
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "\t\tTwoTabsPrefixed");

	Lines lines;
	executor.Call([&] { lines = logsources.GetLines(); });
	BOOST_TEST(lines.size() == 2);
	BOOST_TEST(lines[0].message == "\tTabPrefix");	// space preserved
	BOOST_TEST(lines[1].message == "\t\tTwoTabsPrefixed");	// space preserved
}

BOOST_AUTO_TEST_CASE(LogSourcesNewLineHandling)
{
	ActiveExecutorClient executor;
	LogSources logsources(executor, false);
	LogSource* logsource;
	executor.Call([&] { logsource = logsources.AddTestSource();  });

	Timer timer;
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "NewLinePostfix\n");
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "NewLineCarriageReturnPostfix\n\r");
	logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "CarriageReturnNewLinePostfix\r\n");

	Lines lines;
	executor.Call([&] { lines = logsources.GetLines(); });
	BOOST_TEST(lines.size() == 3);
	BOOST_TEST(lines[0].message == "NewLinePostfix");
	BOOST_TEST(lines[1].message == "NewLineCarriageReturnPostfix");
	BOOST_TEST(lines[2].message == "CarriageReturnNewLinePostfix");
}

std::wstring GetExecutePath()
{
	auto path = system_complete(std::experimental::filesystem::path( Win32::GetCommandLineArguments()[0]));
	return path.remove_filename().c_str();
}

BOOST_AUTO_TEST_CASE(LogSourceLoopback)
{
	using namespace std::chrono_literals;

	std::string dbgMsgSrc = stringbuilder() << GetExecutePath() << "\\DbgMsgSrc.exe";
	BOOST_TEST(FileExists(dbgMsgSrc.c_str()));
	std::string cmd = stringbuilder() << "start \"\" " << dbgMsgSrc << " ";

	Lines lines;
	auto executor = std::make_unique<ActiveExecutorClient>();
	{
		LogSources logsources(*executor, true);
		executor->Call([&] { logsources.AddDBWinReader(false); });
		executor->Call([&] { logsources.SetAutoNewLine(true); });

		executor->Call([&] { logsources.AddMessage("Test message 1"); });
		executor->Call([&] { logsources.AddMessage("Test message 2"); });
		std::this_thread::sleep_for(200ms);
		executor->Call([&] { logsources.Abort(); });

		executor->Call([&] { lines = logsources.GetLines(); });
	}
	executor.reset();

	BOOST_TEST(lines.size() == 2);
}

BOOST_AUTO_TEST_CASE(LogSourceDBWinReader)
{
	using namespace std::chrono_literals;

	std::string dbgMsgSrc = stringbuilder() << GetExecutePath() << "\\DbgMsgSrc.exe";
	BOOST_TEST(FileExists(dbgMsgSrc.c_str()));
	std::string cmd = stringbuilder() << "start \"\" " << dbgMsgSrc << " ";

	auto executor = std::make_unique<ActiveExecutorClient>();
	Lines lines;
	{
		LogSources logsources(*executor, true);
		executor->Call([&] { logsources.SetAutoNewLine(true); });
		executor->Call([&] { logsources.AddDBWinReader(false); });

		BOOST_TEST_MESSAGE("cmd: " << cmd);
		system((cmd + "-n").c_str());
		BOOST_TEST_MESSAGE("done.");
		std::this_thread::sleep_for(200ms);
		executor->Call([&] { logsources.Abort(); });
		executor->Call([&] { lines = logsources.GetLines(); });
	}
	executor.reset();

	BOOST_TEST(lines.size() == 1);
}

std::string CreateTestFile()
{
	Timer timer;
	LogFile logFile;
	logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message 1"));
	logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message 2"));
	logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message 3"));
	logFile.Add(Message(0, Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 4 (zero time message)"));
	return SaveLogFile(logFile);
}

std::string AppendToTestFile()
{
	Timer timer;
	LogFile logFile;
	logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message appended"));
	return AppendLogFile(logFile);
}

std::vector<size_t> GetNewLineOffsets(const std::string& filename)
{
	std::vector<size_t> result;
	std::ifstream file(filename);
	Line line;
	while (ReadLogFileMessage(file, line))
		result.push_back(static_cast<size_t>(file.tellg()));
	return result;
}

void RemoveLinesFromFile(const std::string& filename, int linesToRemove)
{
	auto offsets = GetNewLineOffsets(filename);
	auto offset = offsets[offsets.size() - 1 - linesToRemove];
	Win32::HFile(GetTestFileName()).resize(offset);
}

BOOST_AUTO_TEST_CASE(LogSourceAnyFileReader)
{
	using namespace std::chrono_literals;
	auto executor = std::make_unique<ActiveExecutorClient>();
	LogSources logsources(*executor, true);
	auto filename = CreateTestFile();
	executor->Call([&] { logsources.SetAutoNewLine(true); });
	executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); });
	std::this_thread::sleep_for(200ms);

	Lines lines;
	executor->Call([&] { lines = logsources.GetLines(); });
	BOOST_TEST(lines.size() == 5);
}

BOOST_AUTO_TEST_CASE(LogSourceAnyFileReaderResychronizeShrinkingFile)
{
	using namespace std::chrono_literals;
	auto executor = std::make_unique<ActiveExecutorClient>();
	LogSources logsources(*executor, true);
	auto filename = CreateTestFile();
	executor->Call([&] { logsources.SetAutoNewLine(true); });
	executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); });
	std::this_thread::sleep_for(200ms);

	Lines lines;
	executor->Call([&] { lines = logsources.GetLines(); });
	BOOST_TEST(lines.size() == 5);

	RemoveLinesFromFile(GetTestFileName(), 2);
	AppendToTestFile();
	AppendToTestFile();
	AppendToTestFile();

	std::this_thread::sleep_for(200ms);
	executor->Call([&] { lines = logsources.GetLines(); });
	BOOST_TEST(lines.size() == 4);
}

std::string GetTestFileAsString()
{
	std::ifstream file(GetTestFileName());
	std::stringstream ss;
	ss << file.rdbuf();
	return ss.str();
}

BOOST_AUTO_TEST_CASE(LogSourceAnyFileReaderRewriteByteByByte)
{
	using namespace std::chrono_literals;
	auto executor = std::make_unique<ActiveExecutorClient>();
	LogSources logsources(*executor, true);
	auto filename = CreateTestFile();
	executor->Call([&] { logsources.SetAutoNewLine(true); });
	executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); });
	std::this_thread::sleep_for(200ms);

	{
		Lines lines;
		executor->Call([&] { lines = logsources.GetLines(); });
		BOOST_TEST(lines.size() == 5);
	}
	auto content = GetTestFileAsString();

	std::ofstream fs;
	fs.open(GetTestFileName(), std::ofstream::trunc);
	fs.close();
	std::this_thread::sleep_for(100ms);
	for (char c : content)
	{
		std::ofstream fs;
		fs.open(GetTestFileName(), std::ofstream::app);
		fs << c;
		// just flushing the fstream doesn't force a trigger of Find....ChangeNotification, but closing the fstream does the trick
		fs.flush();
		fs.close();
		Sleep(20);
	}

	std::this_thread::sleep_for(200ms);
	Lines lines;
	executor->Call([&] { lines = logsources.GetLines(); });
	for (auto& line : lines)
	{
		BOOST_TEST(line.message.find("xception") == std::string::npos);
		//std::cout << line.message << std::endl;
	}
	BOOST_TEST(lines.size() == 6);
	BOOST_TEST(lines.at(0).message.back() == '0'); // line ends in '...offset 0'
	BOOST_TEST(lines.at(1).message == "File Identification Header, DebugView++ Format Version 1");
	BOOST_TEST(lines.at(2).message == "test message 1");
	BOOST_TEST(lines.at(3).message == "test message 2");
	BOOST_TEST(lines.at(4).message == "test message 3");
	BOOST_TEST(lines.at(5).message == "message 4 (zero time message)");
}

std::string CreateAsciiTestFile()
{
	auto filename = GetTestFileName();
	std::ofstream fs;
	fs.open(filename, std::ofstream::trunc);
	fs << "This is the first line\n";
	fs << "\n"; // empty line
	fs << "This is the last line\n";
	return filename;
}

BOOST_AUTO_TEST_CASE(LogSourceAnyFileReaderEmptyLines)
{
	using namespace std::chrono_literals;
	auto executor = std::make_unique<ActiveExecutorClient>();
	LogSources logsources(*executor, true);
	auto filename = CreateAsciiTestFile();
	executor->Call([&] { logsources.SetAutoNewLine(true); });
	executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); });
	std::this_thread::sleep_for(200ms);

	{
		Lines lines;
		executor->Call([&] { lines = logsources.GetLines(); });
		BOOST_TEST(lines.size() == 4);
		BOOST_TEST(lines.at(0).message.find("tailing") != std::string::npos);
		BOOST_TEST(lines.at(1).message.find("first") != std::string::npos);
		BOOST_TEST(lines.at(2).message == "", "The expected newline is gone!");
		BOOST_TEST(lines.at(3).message.find("last") != std::string::npos);
		for (auto& line : lines)
		{
			BOOST_TEST(line.message.find("xception") == std::string::npos);
			std::cout << line.message << std::endl;
		}
	}
}

BOOST_AUTO_TEST_CASE(LogSourceLoopbackOrdering)
{
	using namespace std::chrono_literals;
	auto executor = std::make_unique<ActiveExecutorClient>();
	LogSources logsources(*executor, true);
	auto filename = CreateAsciiTestFile();
	executor->Call([&] { logsources.SetAutoNewLine(true); });
	executor->Call([&] { logsources.AddMessage("Loopback message 1"); });
	executor->Call([&] { logsources.AddMessage("Loopback message 2"); });
	executor->Call([&] { logsources.AddMessage("Loopback message 3"); });
	executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); });
	std::this_thread::sleep_for(200ms);
	Lines lines;
	executor->Call([&] { lines = logsources.GetLines(); });

	BOOST_TEST(lines.at(0).message == "Loopback message 1");
	BOOST_TEST(lines.at(1).message == "Loopback message 2");
	BOOST_TEST(lines.at(2).message == "Loopback message 3");
	BOOST_TEST(lines.at(3).message.find("tailing") != std::string::npos);
	BOOST_TEST(lines.at(4).message.find("first") != std::string::npos);
	BOOST_TEST(lines.at(5).message == "", "The expected newline is gone!");
	BOOST_TEST(lines.at(6).message.find("last") != std::string::npos);
	for (auto& line : lines)
	{
		std::cout << line.message << std::endl;
	}
}

std::string CreateUTF16LETestFile(int linecount)
{
	auto filename = GetTestFileName();
	std::wfstream fs;
	fs.imbue(std::locale(fs.getloc(), new std::codecvt_utf16<wchar_t, 0x10ffff, std::little_endian>));
	fs.open(filename, std::ios::binary | std::ios::trunc | std::ios::in | std::ios::out);
	for (int i = 0; i < linecount; ++i)
	{
		fs << "This is line number [" << i + 1 << "]\n";
	}
	fs.close();
	return filename;
}

BOOST_AUTO_TEST_CASE(LoadUTF16LE)
{
	using namespace std::chrono_literals;
	auto executor = std::make_unique<ActiveExecutorClient>();
	LogSources logsources(*executor, true);
	int lineCount = 65000;
	auto filename = CreateUTF16LETestFile(lineCount);
	executor->Call([&] { logsources.SetAutoNewLine(true); });
	executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); });

	// assumption: should be done in 4 seconds, even in debug mode
	int totalLines = 0;
	for (int i=0; i<20;++i)
	{
		std::this_thread::sleep_for(200ms);
		Lines lines;
		executor->Call([&] { lines = logsources.GetLines(); });
		totalLines += lines.size();
	}

	// (lineCount + 1) because the first line looks like: "Started tailing ....txt identified as '...'
	BOOST_TEST(totalLines == (lineCount + 1));
}

// add test simulating MFC application behaviour (pressing pause/unpause lots of times during significant incomming messages)

BOOST_AUTO_TEST_SUITE_END()

} // namespace debugviewpp
} // namespace fusion

max_stars_repo_name: LazyRoy/DebugViewPP

max_issues_repo_name: LazyRoy/DebugViewPP

max_forks_repo_name: LazyRoy/DebugViewPP


text:
#ifndef IDMAPPER_H_
#define IDMAPPER_H_

#include <types.h>
#include <utility>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <boost/unordered_map.hpp>
#include <3rdparty/json/json.h>
#include "SimpleSerialization.hpp"

namespace izenelib { namespace ir { namespace be_index {

class IDMapper {
public:
    IDMapper()
    {
    }

    ~IDMapper()
    {
    }

    std::pair<uint32_t, bool> insert(const std::string & attr)
    {
        typedef boost::unordered_map<std::string, uint32_t> dictT;

        dictT::iterator i = dict.find(attr);
        if (i != dict.end()) {
            return std::make_pair(i->second, false);
        } else {
            uint32_t ret = dict.size();
            dict.insert(std::make_pair(attr, ret));
            return std::make_pair(ret, true);
        }
    }

    std::pair<uint32_t, bool> has(const std::string & attr)
    {
        typedef boost::unordered_map<std::string, uint32_t> dictT;

        dictT::iterator i = dict.find(attr);
        if (i != dict.end()) {
            return std::make_pair(i->second, true);
        } else {
            return std::make_pair(0U, false);
        }
    }

    uint32_t size()
    {
        return dict.size();
    }

    void toJson(Json::Value & root)
    {
        dictToJson(root["dict"]);
    }

    void dictToJson(Json::Value & root)
    {
        for (boost::unordered_map<std::string, uint32_t>::iterator i = dict.begin(); i != dict.end(); ++i) {
            root[i->first] = Json::Value::UInt(i->second);
        }
    }

    void fromJson(Json::Value & root)
    {
        dictFromJson(root["dict"]);
    }

    void dictFromJson(Json::Value & root)
    {
        for (Json::ValueIterator i = root.begin(); i != root.end(); ++i) {
            dict[i.key().asString()] = (*i).asUInt();
        }
    }

    void save_binary(std::ostream & os)
    {
        serialize(dict.size(), os);
        std::vector<std::pair<std::string, uint32_t> > temp(dict.begin(), dict.end());
        std::sort(temp.begin(), temp.end());
        for (std::size_t i = 0; i != temp.size(); ++i) {
            serialize(temp[i].first, os);
            serialize(temp[i].second, os);
        }
    }

    void load_binary(std::istream & is)
    {
        dict.clear();

        std::size_t size;
        deserialize(is, size);
        for (std::size_t i = 0; i != size; ++i) {
            std::string s;
            deserialize(is, s);
            uint32_t id;
            deserialize(is, id);
            dict[s] = id;
        }
    }

private:
    boost::unordered_map<std::string, uint32_t> dict;
};

}}}

#endif

ext: hpp

max_stars_repo_name: izenecloud/izenelib

max_issues_repo_name: izenecloud/izenelib

max_forks_repo_name: izenecloud/izenelib


text:
////////////////////////////////////////////////////////////////////////////////
///
/// \file generic.hpp
/// -----------------
///
/// (c) Copyright Domagoj Saric 2016 - 2020.
///
///  Use, modification and distribution are subject to the
///  Boost Software License, Version 1.0. (See accompanying file
///  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
///
///  See http://www.boost.org for most recent version.
///
////////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------
#ifndef generic_hpp__99FE2034_248F_4C7D_8CD2_EB2BB8247377
#define generic_hpp__99FE2034_248F_4C7D_8CD2_EB2BB8247377
#pragma once
//------------------------------------------------------------------------------
#include "../hardware_concurrency.hpp"
#include "../queues/mpmc_moodycamel.hpp"

#include <boost/core/no_exceptions_support.hpp>
#include <boost/config_ex.hpp>
#include <boost/container/static_vector.hpp>
#include <boost/functionoid/functionoid.hpp>
#include <boost/range/iterator_range_core.hpp>

#include <atomic>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <future>
#include <iterator>
#ifdef BOOST_MSVC
#include <malloc.h>
#else
#include <alloca.h>
#endif // BOOST_MSVC
#include <memory>
#include <mutex>
#include <thread>
#include <type_traits>
#if 0 // sacrifice standard conformance to avoid the overhead of system_error
#include <system_error>
#endif // disabled

#ifdef BOOST_HAS_PTHREADS
#include <pthread.h>
#else
#include <windows.h> // for SetThreadPriority
#endif // BOOST_HAS_PTHREADS

#if defined( __linux )
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/syscall.h>

__attribute__(( const, weak ))
pid_t gettid() { return syscall( SYS_gettid ); }
#endif // __linux
//------------------------------------------------------------------------------
namespace boost
{
//------------------------------------------------------------------------------
namespace sweater
{
//------------------------------------------------------------------------------
namespace queues { template <typename Work> class mpmc_moodycamel; }
//------------------------------------------------------------------------------
namespace generic
{
//------------------------------------------------------------------------------

#if defined( BOOST_HAS_PTHREADS ) && !defined( __ANDROID__ )
namespace detail
{
#ifdef __EMSCRIPTEN__
    inline auto const default_policy_priority_min        ( 0 );
    inline auto const default_policy_priority_max        ( 0 );
#else
    inline auto const default_policy_priority_min        ( ::sched_get_priority_min( SCHED_OTHER ) );
    inline auto const default_policy_priority_max        ( ::sched_get_priority_max( SCHED_OTHER ) );
#endif
    inline auto const default_policy_priority_range      ( static_cast<std::uint8_t>( default_policy_priority_max - default_policy_priority_min ) );
    inline auto const default_policy_priority_unchangable( default_policy_priority_range == 0 );

    inline
    std::uint8_t round_divide( std::uint16_t const numerator, std::uint8_t const denominator ) noexcept
    {
        auto const integral_division      (   numerator / denominator                          );
        auto const at_least_half_remainder( ( numerator % denominator ) >= ( denominator / 2 ) );
        return integral_division + at_least_half_remainder;
    }
} // namespace detail
#endif // __linux && !__ANDROID__ || __APPLE__

// Resource saving option if jobs are always dispatched from a single thread
// (i.e. there is no fear of overcommiting the CPU)
#ifndef BOOST_SWEATER_USE_CALLER_THREAD
#   define BOOST_SWEATER_USE_CALLER_THREAD false
#endif

class shop
{
public:
    using iterations_t = std::uint32_t;

    enum struct priority : int
    {
    #ifdef BOOST_HAS_PTHREADS
        idle          =  19,
        background    =  10,
        low           =  5,
        normal        =  0,
        high          = -5,
        foreground    = -10,
        time_critical = -20
    #else
        idle          = THREAD_PRIORITY_LOWEST, // TODO THREAD_MODE_BACKGROUND_BEGIN
        background    = THREAD_PRIORITY_LOWEST,
        low           = THREAD_PRIORITY_BELOW_NORMAL,
        normal        = THREAD_PRIORITY_NORMAL,
        high          = THREAD_PRIORITY_ABOVE_NORMAL,
        foreground    = THREAD_PRIORITY_HIGHEST,
        time_critical = THREAD_PRIORITY_TIME_CRITICAL
    #endif // thread backend
    };

private:
#ifdef BOOST_SWEATER_SPIN_BEFORE_SUSPENSION
    // https://petewarden.com/2015/10/11/one-weird-trick-for-faster-android-multithreading
    static std::uint32_t spin_count;
#endif // BOOST_SWEATER_SPIN_BEFORE_SUSPENSION

    struct worker_traits : functionoid::std_traits
    {
        static constexpr auto copyable    = functionoid::support_level::na     ;
        static constexpr auto moveable    = functionoid::support_level::nofail ;
        static constexpr auto destructor  = functionoid::support_level::trivial;
        static constexpr auto is_noexcept = true;
        static constexpr auto rtti        = false;

#   ifdef __EMSCRIPTEN__
        // https://github.com/emscripten-core/emscripten/issues/5996
        static constexpr std::uint8_t sbo_alignment = 8;
#   else
        static constexpr std::uint8_t sbo_alignment = 16;
#   endif

        using empty_handler = functionoid::assert_on_empty;
    }; // struct worker_traits

    using worker_counter = std::atomic<hardware_concurrency_t>;

    //...mrmlj...lighterweight alternatives to std::thread...to be moved to a separate lib...
#ifdef BOOST_HAS_PTHREADS
    class pthread_condition_variable;
    class pthread_mutex
    {
    public:
        pthread_mutex() noexcept BOOST_NOTHROW_LITE
#       if !defined( NDEBUG ) && defined( PTHREAD_ERRORCHECK_MUTEX_INITIALIZER )
            : mutex_( PTHREAD_ERRORCHECK_MUTEX_INITIALIZER ) {}
#       else
            : mutex_( PTHREAD_MUTEX_INITIALIZER ) {}
#       endif // NDEBUG
        ~pthread_mutex() noexcept BOOST_NOTHROW_LITE { BOOST_VERIFY( pthread_mutex_destroy( &mutex_ ) == 0 ); }

        pthread_mutex( pthread_mutex && other ) noexcept : mutex_( other.mutex_ ) { other.mutex_ = PTHREAD_MUTEX_INITIALIZER; }
        pthread_mutex( pthread_mutex const & ) = delete;

        void   lock() noexcept BOOST_NOTHROW_LITE { BOOST_VERIFY( pthread_mutex_lock  ( &mutex_ ) == 0 ); }
        void unlock() noexcept BOOST_NOTHROW_LITE { BOOST_VERIFY( pthread_mutex_unlock( &mutex_ ) == 0 ); }

        bool try_lock() noexcept BOOST_NOTHROW_LITE { return pthread_mutex_trylock( &mutex_ ) == 0; }

    private: friend class pthread_condition_variable;
        pthread_mutex_t mutex_;
    }; // class pthread_mutex

    class pthread_condition_variable
    {
    public:
        pthread_condition_variable() noexcept BOOST_NOTHROW_LITE : cv_( PTHREAD_COND_INITIALIZER ) {}
       ~pthread_condition_variable() noexcept BOOST_NOTHROW_LITE { BOOST_VERIFY( pthread_cond_destroy( &cv_ ) == 0 ); }

        pthread_condition_variable( pthread_condition_variable && other ) noexcept : cv_( other.cv_ ) { other.cv_ = PTHREAD_COND_INITIALIZER; }
        pthread_condition_variable( pthread_condition_variable const & ) = delete;

        void notify_all() noexcept BOOST_NOTHROW_LITE { BOOST_VERIFY( pthread_cond_broadcast( &cv_ ) == 0 ); }
        void notify_one() noexcept BOOST_NOTHROW_LITE { BOOST_VERIFY( pthread_cond_signal   ( &cv_ ) == 0 ); }

        void wait( std::unique_lock<pthread_mutex> & lock   ) noexcept BOOST_NOTHROW_LITE { wait( *lock.mutex() ); }
        void wait( pthread_mutex & mutex /*must be locked*/ ) noexcept BOOST_NOTHROW_LITE { BOOST_VERIFY( pthread_cond_wait( &cv_, &mutex.mutex_ ) == 0 ); }

    private:
        pthread_cond_t cv_;
    }; // class pthread_condition_variable

    class thread_impl
    {
    public:
        using native_handle_type = pthread_t;
        using id                 = pthread_t;

        void join  () noexcept BOOST_NOTHROW_LITE { BOOST_VERIFY( pthread_join  ( handle_, nullptr ) == 0 ); handle_ = {}; }
        void detach() noexcept BOOST_NOTHROW_LITE { BOOST_VERIFY( pthread_detach( handle_          ) == 0 ); handle_ = {}; }
        auto get_id() const noexcept { return handle_; }

        static auto get_active_thread_id() noexcept BOOST_NOTHROW_LITE { return pthread_self(); }

    protected:
       // https://stackoverflow.com/questions/43819314/default-member-initializer-needed-within-definition-of-enclosing-class-outside
       constexpr  thread_impl() noexcept {};
                 ~thread_impl() noexcept = default;

        using thread_procedure = void * (*) ( void * );

        auto create( thread_procedure const start_routine, void * const arg ) noexcept BOOST_NOTHROW_LITE
        {
            auto const error( pthread_create( &handle_, nullptr, start_routine, arg ) );
            if ( BOOST_UNLIKELY( error ) )
            {
            #ifndef __ANDROID__
                /// \note NDK 15c Clang miscompiles this function to
                /// allways assume that error == EAGAIN (even if it is 0) if the
                /// line below is included.
                ///                           (29.09.2017.) (Domagoj Saric)
                BOOST_ASSUME( error == EAGAIN ); // any other error indicates a programmer error
            #endif // !__ANDROID__
                handle_ = {};
            }
            return error;
        }

    protected:
        native_handle_type handle_{};
    }; // class thread_impl

    using condition_variable = pthread_condition_variable;
    using mutex              = pthread_mutex;
#else // Win32
    // Strategies for Implementing POSIX Condition Variables on Win32 http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
    // http://developers.slashdot.org/story/07/02/26/1211220/pthreads-vs-win32-threads
    // http://nasutechtips.blogspot.com/2010/11/slim-read-write-srw-locks.html

    class condition_variable;
    class mutex
    {
    public:
        mutex(                ) noexcept : lock_{ SRWLOCK_INIT } {}
        mutex( mutex && other ) noexcept : lock_( other.lock_ ) { other.lock_ = { SRWLOCK_INIT }; }
        mutex( mutex const &  ) = delete ;
       ~mutex(                ) = default;

        void   lock() noexcept { ::AcquireSRWLockExclusive( &lock_ ); }
        void unlock() noexcept { ::ReleaseSRWLockExclusive( &lock_ ); }

        bool try_lock() noexcept { return ::TryAcquireSRWLockExclusive( &lock_ ) != false; }

    private: friend class condition_variable;
        ::SRWLOCK lock_;
    }; // class mutex

    class condition_variable
    {
    public:
        using lock_t = std::unique_lock<mutex>;

        condition_variable(                             ) noexcept : cv_{ CONDITION_VARIABLE_INIT } {}
        condition_variable( condition_variable && other ) noexcept : cv_( other.cv_ ) { other.cv_ = { CONDITION_VARIABLE_INIT }; }
        condition_variable( condition_variable const &  ) = delete ;
       ~condition_variable(                             ) = default;

        void notify_all() noexcept { ::WakeAllConditionVariable( &cv_ ); }
        void notify_one() noexcept { ::WakeConditionVariable   ( &cv_ ); }

        void wait( lock_t & lock ) noexcept { BOOST_VERIFY( wait( lock, INFINITE ) ); }
        void wait( mutex  & m    ) noexcept { BOOST_VERIFY( wait( m   , INFINITE ) ); }

        bool wait( lock_t & lock                , std::uint32_t const milliseconds ) noexcept { return wait( *lock.mutex(), milliseconds ); }
        bool wait( mutex  & m /*must be locked*/, std::uint32_t const milliseconds ) noexcept
        {
            auto const result( ::SleepConditionVariableSRW( &cv_, &m.lock_, milliseconds, 0/*CONDITION_VARIABLE_LOCKMODE_SHARED*/ ) );
            BOOST_ASSERT( result || ::GetLastError() == ERROR_TIMEOUT );
            return result != false;
        }

    private:
        ::CONDITION_VARIABLE cv_;
    }; // class condition_variable

    class thread_impl
    {
    public:
        using native_handle_type = ::HANDLE;
        using id                 = ::DWORD ;

        BOOST_NOTHROW_LITE void join() noexcept
        {
            BOOST_VERIFY( ::WaitForSingleObjectEx( handle_, INFINITE, false ) == WAIT_OBJECT_0 );
        #ifndef NDEBUG
            DWORD exitCode;
            BOOST_VERIFY( ::GetExitCodeThread( handle_, &exitCode ) );
            BOOST_ASSERT( exitCode == 0 );
        #endif // NDEBUG
            detach();
        }

        BOOST_NOTHROW_LITE void detach() noexcept
        {
            BOOST_VERIFY( ::CloseHandle( handle_ ) );
            handle_ = {};
        }

        auto get_id    () const noexcept { return ::GetThreadId( handle_ ); }
        auto get_handle() const noexcept { return handle_; }

        static auto get_active_thread_id() noexcept { return ::GetCurrentThreadId(); }

    protected:
        thread_impl() = default;
       ~thread_impl() = default;

        using thread_procedure = PTHREAD_START_ROUTINE;

        BOOST_NOTHROW_LITE auto create( thread_procedure const start_routine, void * const arg ) noexcept
        {
            handle_ = ::CreateThread( nullptr, 0, start_routine, arg, 0, nullptr );
            if ( BOOST_UNLIKELY( handle_ == nullptr ) )
            {
                BOOST_ASSERT( ::GetLastError() == ERROR_NOT_ENOUGH_MEMORY ); // any other error indicates a programmer error
                return ERROR_NOT_ENOUGH_MEMORY;
            }
            return ERROR_SUCCESS;
        }

    protected:
        native_handle_type handle_{};
    }; // class thread_impl
#endif // BOOST_HAS_PTHREADS

    class thread : public thread_impl
    {
    private:
        class synchronized_invocation
        {
        public:
            synchronized_invocation( void const * const p_functor ) noexcept
                :
                p_functor_     ( const_cast<void *>( p_functor ) ),
                lock_          ( mutex_                          ),
                functor_copied_( false                           )
            {}

            ~synchronized_invocation() noexcept
            {
                while ( BOOST_UNLIKELY( !functor_copied_ ) )
                    event_.wait( lock_ );
            }

            template <typename Functor>
            auto & functor() const noexcept { return *static_cast<Functor *>( p_functor_ ); }

            auto notify() noexcept
            {
                functor_copied_ = true;
                event_.notify_one();
            }

        private:
            void *                  const p_functor_;
            mutex                         mutex_;
            condition_variable            event_;
            std::unique_lock<mutex>       lock_;
            bool volatile                 functor_copied_;
        }; // struct synchronized_invocation

    public:
        thread() noexcept = default;
       ~thread() noexcept { BOOST_ASSERT_MSG( !joinable(), "Abandoning a thread!" ); }

        thread( thread && other ) noexcept { swap( other ); }
        thread( thread const & ) = delete;

        thread & operator=( thread && other ) noexcept
        {
            this->handle_ = other.handle_;
            other.handle_ = {};
            return *this;
        }

        template <class F>
        thread & operator=( F && functor )
        {
            using ret_t   = std::invoke_result_t<thread_procedure, void *>;
            using Functor = std::decay_t<F>;

            if constexpr
            (
                ( sizeof ( functor ) <= sizeof ( void * ) ) &&
                ( alignof( Functor ) <= alignof( void * ) ) &&
                std::is_trivially_copy_constructible_v<Functor> &&
                std::is_trivially_destructible_v      <Functor>
            )
            {
                void * context;
                new ( &context ) Functor( std::forward<F>( functor ) );
                create
                (
                    []( void * context ) noexcept -> ret_t
                    {
                        auto & tiny_functor( reinterpret_cast<Functor &>( context ) );
                        tiny_functor();
                        return 0;
                    },
                    context
                );
            }
            else
            if constexpr ( noexcept( Functor( std::forward<F>( functor ) ) ) )
            {
                synchronized_invocation context( &functor );

                create
                (
                    []( void * const context ) noexcept -> ret_t
                    {
                        auto & synchronized_context( *static_cast<synchronized_invocation *>( context ) );
                        Functor functor( std::forward<F>( synchronized_context.functor<Functor>() ) );
                        synchronized_context.notify();
                        functor();
                        return 0;
                    },
                    &context
                );
            }
            else
            {
                auto p_functor( std::make_unique<Functor>( std::forward<F>( functor ) ) );
                create
                (
                    []( void * const context ) noexcept -> ret_t
                    {
                        std::unique_ptr<Functor> const p_functor( static_cast<Functor *>( context ) );
                        (*p_functor)();
                        return 0;
                    },
                    p_functor.get()
                );
                p_functor.release();
            }
            return *this;
        }

        auto native_handle() const noexcept { return handle_; }

        bool joinable() const noexcept { return native_handle() != native_handle_type{}; }

        void join() noexcept
        {
            BOOST_ASSERT_MSG( joinable(), "No thread to join" );
            BOOST_ASSERT_MSG( get_id() != get_active_thread_id(), "Waiting on this_thread: deadlock!" );

            thread_impl::join();
        }

        void swap( thread & other ) noexcept { std::swap( this->handle_, other.handle_ ); }

        static auto hardware_concurrency() noexcept { return hardware_concurrency_max; }

    private:
        BOOST_ATTRIBUTES( BOOST_COLD )
        void create( thread_procedure const start_routine, void * const arg )
        {
            BOOST_ASSERT_MSG( !joinable(), "A thread already created" );
            auto const error( thread_impl::create( start_routine, arg ) );
            if ( BOOST_UNLIKELY( error ) )
            {
#           ifdef BOOST_NO_EXCEPTIONS
                std::terminate();
#           elif 0 // disabled - avoid the overhead of (at least) <system_error>
                throw std::system_error( std::error_code( error, std::system_category() ), "Thread creation failed" );
#           else
                throw std::runtime_error( "Not enough resources to create a new thread" );
#           endif
            }
        }
    }; // class thread

    class barrier
    {
    public:
        barrier( hardware_concurrency_t const initial_value ) noexcept
            :
            counter_( initial_value )
        {
            BOOST_VERIFY( mutex_.try_lock() );
        }
#   ifndef _WIN32 // not needed for trivial SRWs
        ~barrier() noexcept { mutex_.unlock(); }
#   endif

        BOOST_NOINLINE
        void arrive() noexcept
        {
            std::unique_lock<mutex> lock( mutex_ );
            if ( counter_.fetch_sub( 1, std::memory_order_relaxed ) == 1 )
                event_.notify_one();
        }

        BOOST_NOINLINE
        void wait() noexcept
        {
            while ( BOOST_UNLIKELY( counter_.load( std::memory_order_relaxed ) != 0 ) )
                event_.wait( mutex_ );
        }

        void mark_as_arrived_to_by_all() noexcept { counter_.store( 0, std::memory_order_relaxed ); }

    private:
        // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html#gen_cond_var
        mutex              mutex_  ;
        condition_variable event_  ;
        worker_counter     counter_;
    }; // class barrier

    class spread_setup
    {
    public:
        spread_setup( iterations_t const iterations, hardware_concurrency_t const actual_number_of_workers ) noexcept
            :
            iterations_per_worker          ( iterations / actual_number_of_workers ),
            threads_with_extra_iteration   ( iterations % actual_number_of_workers - leave_one_for_the_calling_thread() ),
            number_of_dispatched_work_parts( number_of_work_parts( iterations, actual_number_of_workers ) - BOOST_SWEATER_USE_CALLER_THREAD ),
            completion_barrier             ( number_of_dispatched_work_parts )
        {
            BOOST_ASSERT( ( leave_one_for_the_calling_thread() == false ) || ( iterations < iterations_t( actual_number_of_workers ) ) );
#       if BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY
            BOOST_ASSUME( number_of_dispatched_work_parts <= ( BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY - BOOST_SWEATER_USE_CALLER_THREAD ) );
#       endif
        }

        iterations_t           const iterations_per_worker;
        hardware_concurrency_t const threads_with_extra_iteration;
        hardware_concurrency_t const number_of_dispatched_work_parts;
        barrier                      completion_barrier;

    private:
        // If iterations < workers prefer using the caller thread instead of waking up a worker thread...
        bool leave_one_for_the_calling_thread() const noexcept { return ( iterations_per_worker == 0 ) && BOOST_SWEATER_USE_CALLER_THREAD; }
        static hardware_concurrency_t number_of_work_parts( iterations_t const iterations, hardware_concurrency_t const actual_number_of_workers ) noexcept
        {
            return static_cast<hardware_concurrency_t>( std::min<iterations_t>( actual_number_of_workers, iterations ) );
        }
    }; // class spread_setup

    using work_t = functionoid::callable<void(), worker_traits>;

    using my_queue = queues::mpmc_moodycamel<work_t>;

    auto worker_loop( [[ maybe_unused ]] hardware_concurrency_t const worker_index ) noexcept
    {
        auto worker_loop_impl
        {
            [=, this]() noexcept
            {
#           if BOOST_SWEATER_EXACT_WORKER_SELECTION
                auto       & __restrict work_event{ pool_[ worker_index ].event };
#           else
                auto       & __restrict work_event{ work_event_ };
#           endif
                auto       & __restrict queue     { queue_      };
                auto       & __restrict work_mutex{ mutex_      };
                auto const & __restrict exit      { brexit_     };

                auto token( queue.consumer_token() );

                work_t work;

                for ( ; ; )
                {
#               ifdef BOOST_SWEATER_SPIN_BEFORE_SUSPENSION
                    auto const dequeue_cost( 2048 );
                    for ( auto try_count( 0U ); try_count < (spin_count / dequeue_cost); ++try_count )
                    {
                        if ( BOOST_LIKELY( queue.dequeue( work, token ) ) )
                        {
                            work();
                            try_count = 0; // restart the spin-wait
                        }
                    }
#               endif // BOOST_SWEATER_SPIN_BEFORE_SUSPENSION

                    bool have_work;
                    {
                        std::unique_lock<mutex> lock( work_mutex );
                        if ( BOOST_UNLIKELY( exit.load( std::memory_order_relaxed ) ) )
                            return;
                        /// \note No need for another loop here as a
                        /// spurious-wakeup would be handled by the check in
                        /// the loop above.
                        ///               (08.11.2016.) (Domagoj Saric)
                        have_work = queue.dequeue( work, token );
                        if ( BOOST_UNLIKELY( !have_work ) )
                            work_event.wait( lock );
                    }
                    if ( BOOST_LIKELY( have_work ) )
                        work();
                }
            }
        }; // worker_loop_impl
        static_assert( std::is_trivially_copy_constructible_v< decltype( worker_loop_impl ) > && std::is_trivially_destructible_v< decltype( worker_loop_impl ) > );
        return worker_loop_impl;
    }

public:
    shop()
    {
#   ifdef __GNUC__ // compilers with init_priority attribute (see hardware_concurency.hpp)
        auto const local_hardware_concurrency( hardware_concurrency_max );
#   else
        /// \note Avoid the static-initialization-order-fiasco (for compilers
        /// not supporting the init_priority attribute) by not using the
        /// global hardware_concurrency_max variable (i.e. allow users to
        /// safely create plain global-variable sweat_shop singletons).
        ///                                   (01.05.2017.) (Domagoj Saric)
        auto const local_hardware_concurrency( detail::get_hardware_concurrency_max() );
#   endif // __GNUC__
        create_pool( local_hardware_concurrency );
    }

    ~shop() noexcept { stop_and_destroy_pool(); }

    auto number_of_workers() const noexcept
    {
        auto const actual_number_of_workers( static_cast< hardware_concurrency_t >( pool_.size() ) );
#   if BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY
        BOOST_ASSUME( actual_number_of_workers <= BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY );
#   endif
        return actual_number_of_workers;
    }

    /// For GCD dispatch_apply/OMP-like parallel loops.
    /// \details Guarantees that <VAR>work</VAR> will not be called more than
    /// <VAR>iterations</VAR> times (even if number_of_workers() > iterations).
    template <typename F>
    bool spread_the_sweat( iterations_t const iterations, F && __restrict work ) noexcept
    {
        static_assert( noexcept( work( iterations, iterations ) ), "F must be noexcept" );

#   if 0 // worth it...not worth it...
        if ( BOOST_UNLIKELY( iterations == 0 ) )
            return true;
#   endif
#   if 0 // worth it...not worth it...
        if ( BOOST_UNLIKELY( iterations == 1 ) )
        {
            work( 0, 1 );
            return true;
        }
#   endif

        spread_setup setup( iterations, number_of_workers() );

        /// \note MSVC does not support VLAs but has an alloca that returns (16
        /// byte) aligned memory. Clang's alloca is unaligned and it does not
        /// support VLAs of non-POD types. GCC has a (16 byte) aligned alloca
        /// and supports VLAs of non-POD types
        /// (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19131).
        /// Regardless of any of this a work_t VLA is not used to avoid needless
        /// default construction of its members.
        /// The code below is safe with noexcept enqueue and noexcept
        /// destructible and constructible work_ts.
        ///                                   (21.01.2017.) (Domagoj Saric)
#   ifdef BOOST_MSVC
        auto const dispatched_work_parts{ static_cast<work_t *>( alloca( setup.number_of_dispatched_work_parts * sizeof( work_t ) ) ) };
#   else
        alignas( work_t ) char dispatched_work_parts_storage[ setup.number_of_dispatched_work_parts * sizeof( work_t ) ];
        auto * const BOOST_MAY_ALIAS dispatched_work_parts{ reinterpret_cast<work_t *>( dispatched_work_parts_storage ) };
#   endif // BOOST_MSVC

        iterations_t iteration{ 0 };
        for ( hardware_concurrency_t work_part{ 0 }; work_part < setup.number_of_dispatched_work_parts; ++work_part )
        {
            auto const start_iteration{ iteration };
            auto const extra_iteration{ work_part < setup.threads_with_extra_iteration };
            auto const end_iteration  { static_cast<iterations_t>( start_iteration + setup.iterations_per_worker + extra_iteration ) };
            auto const placeholder{ &dispatched_work_parts[ work_part ] };
#       ifdef BOOST_MSVC
            // MSVC14.1 still generates a branch w/o this (GCC issues a warning that it knows that &placeholder cannot be null so this has to be ifdef-guarded).
            BOOST_ASSUME( placeholder );
#       endif // BOOST_MSVC
            new ( placeholder ) work_t
            (
                [&completion_barrier = setup.completion_barrier, &work, start_iteration = iteration, end_iteration]() noexcept
                {
                    work( start_iteration, end_iteration );
                    completion_barrier.arrive();
                }
            );
            iteration = end_iteration;
        }

        auto const enqueue_failure_iteration_mask
        {
            enqueue( dispatched_work_parts, setup.number_of_dispatched_work_parts, setup.completion_barrier )
        };
        iteration &= enqueue_failure_iteration_mask;

        auto const caller_thread_start_iteration{ iteration };
#   if BOOST_SWEATER_USE_CALLER_THREAD
        BOOST_ASSERT( caller_thread_start_iteration < iterations );
        work( caller_thread_start_iteration, iterations );
#   else
        BOOST_VERIFY( caller_thread_start_iteration == iterations );
#   endif
        setup.completion_barrier.wait();

        return enqueue_failure_iteration_mask != 0;
    }

    template <typename F>
    bool fire_and_forget( F && work ) noexcept( noexcept( std::is_nothrow_constructible_v<std::remove_reference_t<F>, F &&> ) )
    {
        using Functor = std::remove_reference_t<F>;
        return create_fire_and_destroy<Functor>( std::forward<F>( work ) );
    }

    template <typename F>
    auto dispatch( F && work )
    {
        // http://scottmeyers.blogspot.hr/2013/03/stdfutures-from-stdasync-arent-special.html
        using Functor = std::remove_reference_t<F>;
        struct future_wrapper
        {
            // Note: Clang v8 and clang v9 think that result_t is unused
        #ifdef __clang__
        #   pragma clang diagnostic push
        #   pragma clang diagnostic ignored "-Wunused-local-typedef"
        #endif
            using result_t  = decltype( std::declval<Functor &>()() );
        #ifdef __clang__
        #   pragma clang diagnostic pop
        #endif
            using promise_t = std::promise<result_t>;
            using future_t  = std::future <result_t>;

            future_wrapper( F && work_source, future_t & future )
                :
                work( std::forward<F>( work_source ) )
            {
                future = promise.get_future();
            }

            void operator()() noexcept
            {
                BOOST_TRY
                {
                    if constexpr ( std::is_same_v<result_t, void> )
                    {
                        work();
                        promise.set_value();
                    }
                    else
                    {
                        promise.set_value( work() );
                    }
                }
                BOOST_CATCH( ... )
                {
                    promise.set_exception( std::current_exception() );
                }
                BOOST_CATCH_END
            }

            Functor   work   ;
            promise_t promise;
        }; // struct future_wrapper

        typename future_wrapper::future_t future;
        auto const dispatch_succeeded( this->create_fire_and_destroy<future_wrapper>( std::forward<F>( work ), future ) );
        if ( BOOST_UNLIKELY( !dispatch_succeeded ) )
        {
            typename future_wrapper::promise_t failed_promise;
            failed_promise.set_exception( std::make_exception_ptr( std::bad_alloc() ) );
            future = failed_promise.get_future();
        }
        return future;
    }

#ifdef BOOST_SWEATER_SPIN_BEFORE_SUSPENSION
    static void set_idle_suspend_spin_count( std::uint32_t const new_spin_count ) noexcept
    {
        spin_count = new_spin_count;
    }
#endif // BOOST_SWEATER_SPIN_BEFORE_SUSPENSION

    BOOST_ATTRIBUTES( BOOST_MINSIZE )
    bool set_priority( priority const new_priority ) noexcept
    {
#   ifdef __EMSCRIPTEN__
        if constexpr ( true )
            return ( new_priority == priority::normal );
#   endif
#   ifdef __ANDROID__
        /// \note Android's pthread_setschedparam() does not actually work so we
        /// have to abuse the general Linux' setpriority() non-POSIX compliance
        /// (i.e. that it sets the calling thread's priority).
        /// http://stackoverflow.com/questions/17398075/change-native-thread-priority-on-android-in-c-c
        /// https://android.googlesource.com/platform/dalvik/+/gingerbread/vm/alloc/Heap.c
        /// https://developer.android.com/topic/performance/threads.html
        /// _or_
        /// try the undocumented things the Java Process.setThreadPriority()
        /// function seems to be doing:
        /// https://github.com/android/platform_frameworks_base/blob/master/core/java/android/os/Process.java#L634
        /// https://github.com/android/platform_frameworks_base/blob/master/core/jni/android_util_Process.cpp#L475
        /// https://android.googlesource.com/platform/frameworks/native/+/jb-dev/libs/utils/Threads.cpp#329
        ///                                   (03.05.2017.) (Domagoj Saric)
#   endif
        auto const nice_value( static_cast<int>( new_priority ) );
        bool success( true );
        for ( auto & thread : pool_ )
        {
#   ifdef BOOST_HAS_PTHREADS
#       if defined( __ANDROID__ )
            success &= ( ::setpriority( PRIO_PROCESS, thread.native_handle(), nice_value ) == 0 );
#       else
            std::uint8_t const api_range            ( static_cast<std::int8_t>( priority::idle ) - static_cast<std::int8_t>( priority::time_critical ) );
            auto         const platform_range       ( detail::default_policy_priority_range );
            auto         const uninverted_nice_value( static_cast<std::uint8_t>( - ( nice_value - static_cast<std::int8_t>( priority::idle ) ) ) );
            int          const priority_value       ( detail::default_policy_priority_min + detail::round_divide( uninverted_nice_value * platform_range, api_range ) ); // surely it will be hoisted
#           if defined( __APPLE__ )
            BOOST_ASSERT( !detail::default_policy_priority_unchangable );
            ::sched_param scheduling_parameters;
            int           policy;
            auto const handle( thread.native_handle() );
            BOOST_VERIFY( pthread_getschedparam( handle, &policy, &scheduling_parameters ) == 0 );
            scheduling_parameters.sched_priority = priority_value;
            success &= ( pthread_setschedparam( handle, policy, &scheduling_parameters ) == 0 );
#           else
            success &= !detail::default_policy_priority_unchangable && ( pthread_setschedprio( thread.native_handle(), priority_value ) == 0 );
#           endif
#       endif // __ANDROID__
#   else // thread backend
            /// \note SetThreadPriority() silently falls back to the highest
            /// priority level available to the caller based on its privileges
            /// (instead of failing).
            ///                               (23.06.2017.) (Domagoj Saric)
            BOOST_VERIFY( ::SetThreadPriority( thread.native_handle(), nice_value ) != false );
            success &= true;
#   endif // thread backend
        }

#   if defined( __linux ) // also on Android
        if ( !success )
        {
            success = true;
            spread_the_sweat
            (
                hardware_concurrency_max,
                [ &success, nice_value ]( iterations_t, [[ maybe_unused ]] iterations_t const thread_index ) noexcept
                {
#               if BOOST_SWEATER_USE_CALLER_THREAD
                    /// \note Do not change the caller thread's priority.
                    ///                       (05.05.2017.) (Domagoj Saric)
                    if ( thread_index != hardware_concurrency_max )
#               endif
                    {
                        auto const result( ::setpriority( PRIO_PROCESS, 0, nice_value ) );
                        BOOST_ASSERT( ( result == 0 ) || ( errno == EACCES ) );
                        success &= ( result == 0 );
                    }
                }
            );
        }
#   endif // __linux
        return success;
    }

    BOOST_ATTRIBUTES( BOOST_COLD )
    void bind_worker_to_cpu( hardware_concurrency_t const worker_index, unsigned const cpu_id ) noexcept
    {
        auto & thread{ pool_[ worker_index ] };
#   ifdef _WIN32
        BOOST_VERIFY( ::SetThreadAffinityMask( thread.get_handle(), DWORD_PTR( 1 ) << cpu_id ) != 0 );
#   else // platform
        cpu_set_t cpuset;
        CPU_ZERO( &cpuset );
        CPU_SET( cpu_id, &cpuset );
#       if 0 // Android does not have pthread_setaffinity_np or pthread_attr_setaffinity_np
         // and there seems to be no way of detecting its presence.
        pthread_setaffinity_np( thread.get_id(), sizeof( cpuset ), &cpuset );
#       elif defined( __ANDROID__ ) && ( __ANDROID_API__ >= 21 )
        BOOST_VERIFY( sched_setaffinity( pthread_gettid_np( thread.get_id() ), sizeof( cpuset ), &cpuset ) == 0 );
#       else
        // TODO can be done more efficiently for the BOOST_SWEATER_EXACT_WORKER_SELECTION case
        spread_the_sweat
        (
            number_of_workers(),
            [ &, target_handle = thread.get_id() ]( iterations_t, iterations_t ) noexcept
            {
                if ( pthread_self() == target_handle )
                {
                    BOOST_VERIFY( sched_setaffinity( ::gettid(), sizeof( cpuset ), &cpuset ) == 0 );
                }
            }
        );
#       endif // platform
#   endif // platform
    }

    BOOST_ATTRIBUTES( BOOST_COLD )
    void set_max_allowed_threads( hardware_concurrency_t const max_threads )
    {
        BOOST_ASSERT_MSG( queue_.empty(), "Cannot change parallelism level while items are in queue." );
        stop_and_destroy_pool();
        brexit_.store( false, std::memory_order_relaxed );
        create_pool( max_threads - BOOST_SWEATER_USE_CALLER_THREAD );
    }

private:
    BOOST_ATTRIBUTES( BOOST_COLD )
    void create_pool( hardware_concurrency_t const size )
    {
        BOOST_ASSERT_MSG( size <= sweater::detail::get_hardware_concurrency_max(), "Requested parallelism level not offered in hardware." );
        auto const current_size( pool_.size() );
        BOOST_ASSUME( current_size == 0 );
#   if BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY
        pool_.resize( size );
#   else
        auto p_workers( std::make_unique<worker_thread[]>( size ) );
        pool_ = make_iterator_range_n( p_workers.get(), size );
#   endif // !BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY

        for ( hardware_concurrency_t worker_index{ 0 }; worker_index < size; ++worker_index )
            pool_[ worker_index ] = worker_loop( worker_index );
#   if !BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY
        p_workers.release();
#   endif // !BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY
    }

    BOOST_ATTRIBUTES( BOOST_COLD )
    void stop_and_destroy_pool() noexcept
    {
        {
            std::unique_lock<mutex> lock( mutex_ );
            brexit_.store( true, std::memory_order_relaxed );
            wake_all_workers();
        }
        for ( auto & worker : pool_ )
            worker.join();
#   if BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY
        pool_.clear();
#   else
        delete[] pool_.begin();
        pool_ = {};
#   endif // BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY
    }

    BOOST_NOINLINE
    iterations_t // mask for the current iteration count (for branchless setting to zero)
    BOOST_CC_REG enqueue
    (
        work_t                 * __restrict const dispatched_work_parts,
        hardware_concurrency_t              const number_of_dispatched_work_parts,
        barrier                & __restrict       completion_barrier
    ) noexcept
    {
        auto const enqueue_succeeded( queue_.enqueue_bulk( std::make_move_iterator( dispatched_work_parts ), number_of_dispatched_work_parts ) );
        for ( hardware_concurrency_t work_part( 0 ); work_part < number_of_dispatched_work_parts; ++work_part )
            dispatched_work_parts[ work_part ].~work_t();
        if ( BOOST_LIKELY( enqueue_succeeded ) )
        {
            std::unique_lock<mutex> lock( mutex_ );
            if ( BOOST_LIKELY( number_of_dispatched_work_parts == number_of_workers() ) )
            {
                wake_all_workers();
            }
            else
            {
                for ( auto part( 0U ); part < number_of_dispatched_work_parts; ++part )
                {
#               if BOOST_SWEATER_EXACT_WORKER_SELECTION
                    pool_[ part ].event.notify_one();
#               else
                    work_event_.notify_one();
#               endif
                }
            }
            return static_cast<iterations_t>( -1 );
        }
        else
        {
            /// \note If enqueue failed perform everything on the caller's
            /// thread.
            ///                               (21.01.2017.) (Domagoj Saric)
            completion_barrier.mark_as_arrived_to_by_all();
            return static_cast<iterations_t>( 0 );
        }
    }

    template <typename Functor, typename ... Args>
    bool create_fire_and_destroy( Args && ... args ) noexcept
    (
        std::is_nothrow_constructible_v<Functor, Args && ...> &&
        !work_t::requiresAllocation<Functor>
    )
    {
        static_assert( noexcept( std::declval<Functor &>()() ), "Fire and forget work has to be noexcept" );

        bool enqueue_succeeded;
        auto const requiresAllocation( work_t::requiresAllocation<Functor> );
        if constexpr( requiresAllocation )
        {
            struct self_destructed_work
            {
                self_destructed_work( Args && ... args ) : p_functor( new Functor{ std::forward<Args>( args )... } ) {}
                self_destructed_work( self_destructed_work && other ) noexcept : p_functor( other.p_functor ) { other.p_functor = nullptr; BOOST_ASSERT( p_functor ); }
                self_destructed_work( self_destructed_work const & ) = delete;
                void operator()() noexcept( noexcept( std::declval<Functor &>()() ) )
                {
                    BOOST_ASSERT( p_functor );
                    struct destructor
                    {
                        Functor * const p_work;
                        ~destructor() noexcept { delete p_work; }
                    } const eh_safe_destructor{ p_functor };
                    (*p_functor)();
                #ifndef NDEBUG
                    p_functor = nullptr;
                #endif // NDEBUG
                } // void operator()
                Functor * __restrict p_functor = nullptr;
            }; // struct self_destructed_work
            static_assert( std::is_trivially_destructible_v<self_destructed_work> );
            enqueue_succeeded = this->queue_.enqueue( self_destructed_work( std::forward<Args>( args )... ) );
        }
        else
        {
            struct self_destructed_work
            {
                self_destructed_work( Args && ... args ) { new ( storage ) Functor{ std::forward<Args>( args )... }; }
                self_destructed_work( self_destructed_work && other ) noexcept( std::is_nothrow_move_constructible_v<Functor> )
                {
                    auto & source( reinterpret_cast<Functor &>( other.storage ) );
                    new ( storage ) Functor( std::move( source ) );
                    source.~Functor();
                }
                self_destructed_work( self_destructed_work const & ) = delete;
                void operator()() noexcept( noexcept( std::declval<Functor &>()() ) )
                {
                    auto & work( reinterpret_cast<Functor &>( storage ) );
                    struct destructor
                    {
                        Functor & work;
                        ~destructor() noexcept { work.~Functor(); }
                    } eh_safe_destructor{ work };
                    work();
                } // void operator()
                alignas( alignof( Functor ) ) char storage[ sizeof( Functor ) ];
            }; // struct self_destructed_work
            enqueue_succeeded = this->queue_.enqueue( self_destructed_work( std::forward<Args>( args )... ) );
        }
        this->wake_one_worker();
        return BOOST_LIKELY( enqueue_succeeded );
    }

    void wake_one_worker() noexcept
    {
        /// No need for a branch here as the worker thread has to handle
        /// spurious wakeups anyway.
#   if BOOST_SWEATER_EXACT_WORKER_SELECTION
        //...mrmlj...TODO find a idle worker...
        pool_.front().event.notify_one();
#   else
        work_event_.notify_one();
#   endif
    }

    void wake_all_workers() noexcept
    {
        /// No need for a branch here as the worker thread has to handle
        /// spurious wakeups anyway.
#   if BOOST_SWEATER_EXACT_WORKER_SELECTION
        for ( auto & worker : pool_ )
            worker.event.notify_one();
#   else
        work_event_.notify_one();
#   endif
    }

private:
    std::atomic<bool>  brexit_ = ATOMIC_FLAG_INIT;
    mutex              mutex_;
#if BOOST_SWEATER_EXACT_WORKER_SELECTION
    struct alignas( 64 ) worker_thread : thread
    {
        using thread::operator=;
        condition_variable event;
    };
#else
    using worker_thread = thread;
    condition_variable work_event_;
#endif

    /// \todo Further queue refinements.
    /// https://en.wikipedia.org/wiki/Work_stealing
    /// http://www.drdobbs.com/parallel/writing-lock-free-code-a-corrected-queue/210604448
    /// https://github.com/cameron314/readerwriterqueue
    /// http://moodycamel.com/blog/2013/a-fast-lock-free-queue-for-c++
    /// http://stackoverflow.com/questions/1164023/is-there-a-production-ready-lock-free-queue-or-hash-implementation-in-c#14936831
    /// https://github.com/facebook/folly/blob/master/folly/docs/ProducerConsumerQueue.md
    /// https://github.com/facebook/folly/blob/master/folly/MPMCQueue.h
    /// http://landenlabs.com/code/ring/ring.html
    /// https://github.com/Qarterd/Honeycomb/blob/master/src/common/Honey/Thread/Pool.cpp
    ///                                       (12.10.2016.) (Domagoj Saric)
    my_queue queue_;

#if BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY
    using pool_threads_t = container::static_vector<worker_thread, BOOST_SWEATER_MAX_HARDWARE_CONCURRENCY - BOOST_SWEATER_USE_CALLER_THREAD>;
#else
    using pool_threads_t = iterator_range<worker_thread *>;
#endif
    pool_threads_t pool_;
}; // class shop

#ifdef BOOST_SWEATER_SPIN_BEFORE_SUSPENSION
BOOST_OVERRIDABLE_MEMBER_SYMBOL
std::uint32_t shop::spin_count = 1 * 1000 * 1000;
#endif // BOOST_SWEATER_SPIN_BEFORE_SUSPENSION

//------------------------------------------------------------------------------
} // namespace generic
//------------------------------------------------------------------------------
} // namespace sweater
//------------------------------------------------------------------------------
} // namespace boost
//------------------------------------------------------------------------------
#endif // generic_hpp

max_stars_repo_name: psiha/sweater

max_issues_repo_name: psiha/sweater

max_forks_repo_name: psiha/sweater


text:
/*
 * Main File For testing
 * Added by Mohamed TarekIbnZiad
 */

#include <assert.h>
#include <vector>
#include <crypto/paillier.hh>
#include <Img.hh>
#include <crypto/gm.hh>
#include <NTL/ZZ.h>
#include <gmpxx.h>
#include <math/util_gmp_rand.h>

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <ctime>

#include<iostream>

using namespace cv;
using namespace std;
using namespace NTL;

/*
 * Apply average filter in Plain domain using open cv ready made function (blur)
 */
Mat
AverageFilterOpenCV(Mat src)
{
    //enter the kernel size
    int kerSize;
    cout << "Please enter the kernel size: \n";
    cin >> kerSize;

    Mat dst;
    //smooth the image in the "src" and save it to "dst"
    blur( src, dst, Size( kerSize, kerSize ) );

    return dst;
}

/*
 * Apply average filter in Plain domain
 */
Mat
AverageFilter(Mat src, vector < vector<mpz_class> > &filter)
{
    Mat dst = src.clone(); //Copy of src as initial value
    Scalar s;
    //Filter size
    int ro = filter.size();
    int co = filter.data()->size();

    //get summation of the filter elements to divide by
    mpz_class nFilter = 0;
    for (int i=0; i < ro; i++){
	     for (int j=0; j < co; j++){
            nFilter += filter[i][j];
       }
    }

    for (int i=1; i < src.rows - 2; i++){
	     for (int j=1; j < src.cols - 2; j++){
            mpz_class sum=0;
            for(int m=-1; m<=1; m++){
		            for(int n=-1; n<=1; n++){
                    s = src.at<uchar>(i+m,j+n);
                    sum += s.val[0] * filter[m+1][n+1];
                }
            }
            s.val[0] = sum.get_d()/nFilter.get_d();
            dst.at<uchar>(i,j) = s.val[0];
        }
    }
    return dst;
}

/*
 * Apply Sobel operator in Plain domain using open cv ready made function (Sobel)
 */
Mat
SobelFilterOpenCV(Mat src)
{
    int scale = 1;
    int delta = 0;
    int ddepth = CV_16S;

    Mat dst;

    // Generate grad_x and grad_y
    Mat grad_x, grad_y;
    Mat abs_grad_x, abs_grad_y;

    // Gradient X
    Sobel( src, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
    convertScaleAbs( grad_x, abs_grad_x );

    // Gradient Y
    Sobel( src, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
    convertScaleAbs( grad_y, abs_grad_y );

    // Total Gradient (approximate)
    addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst );

    return dst;
}

/*
 * Apply Sobel operator in Plain domain using indirect access
 */
Mat
SobelFilter(Mat src)
{
    int dx[3][3] = {{1,0,-1},{2,0,-2},{1,0,-1}};
    int dy[3][3] = {{1,2,1},{0,0,0},{-1,-2,-1}};

    Mat dst = src.clone(); //Copy of src as initial value
    //Vec3b s; // for colored images
    Scalar s;

    for (int i=1; i < src.rows - 2; i++){
	     for (int j=1; j < src.cols - 2; j++){
            // apply kernel in X and Y directions
            int sum_x=0;
            int sum_y=0;
            for(int m=-1; m<=1; m++){
		            for(int n=-1; n<=1; n++){
                    //s=cvGet2D(img,i+m,j+n); // get the (i,j) pixel value
                    s = src.at<uchar>(i+m,j+n);
                    sum_x += s.val[0] * dx[m+1][n+1];
                    sum_y += s.val[0] * dy[m+1][n+1];
                }
            }

            int sum=abs(sum_x)+abs(sum_y);
            s.val[0] = (sum>255)? 255:sum;
            dst.at<uchar>(i,j) = s.val[0];
            //dst.at<Vec2d>(i,j)[0] = s;
            //cvSet2D(dst,i,j,s); // set the (i,j) pixel value
        }
    }
    return dst;
}

/*
 * Apply convolution in Plain domain
 */
Mat
convolution(Mat src, vector < vector<mpz_class> > &filter)
{
    Mat dst = src.clone(); //Copy of src as initial value
    Scalar s;

    for (int i=1; i < src.rows - 1; i++){
	     for (int j=1; j < src.cols - 1; j++){
            mpz_class sum=0;
            for(int m=-1; m<=1; m++){
		            for(int n=-1; n<=1; n++){
                    if (filter[m+1][n+1] == 0) { //Not encrypted value
                        continue;
                    }
                    s = src.at<uchar>(i+m,j+n);
                    sum += s.val[0] * filter[m+1][n+1];
                }
            }
            s.val[0] = abs(sum.get_d());
            dst.at<uchar>(i,j) = s.val[0];
        }
    }
    return dst;
}

/*
 * used to Add/Sub constant value to the image brightness in plain domain
 */
Mat
AdjustBrightness(Mat src, int value)
{
    Mat dst = src.clone(); //Copy of src as initial value
    Scalar s;
    int newValue;
    for (int i=0; i < src.rows; i++){
	     for (int j=0; j < src.cols; j++){
            s = src.at<uchar>(i,j);
            newValue = s.val[0] + value;
            newValue = (newValue>255)? 255:
                       (newValue<0)?     0:newValue;
            dst.at<uchar>(i,j) = newValue;
        }
    }
    return dst;
}

/*
 * used to fix the image colors range after return from the homo domain
 */
vector < vector<mpz_class> >
FixRange(vector < vector<mpz_class> > &in)
{

    int rows = in.size();
    int cols = in.data()->size();
    int newValue;

    vector < vector<mpz_class> > out(rows, vector<mpz_class>(cols));
    for (int i=0; i < rows; i++){
	     for (int j=0; j < cols; j++){
            newValue = (int)in[i][j].get_d();
            newValue = (newValue>255)? 255:
                       (newValue<0)?     0:newValue;
            out[i][j] = (int)newValue;
        }
    }
    return out;
}

/*
 * used to get the negative image in plain domain
 */
Mat
NegativeImage(Mat src)
{
    Mat dst = src.clone(); //Copy of src as initial value
    Scalar s;
    int newValue;
    for (int i=0; i < src.rows; i++){
	     for (int j=0; j < src.cols; j++){
            s = src.at<uchar>(i,j);
            newValue = 255 - s.val[0];
            dst.at<uchar>(i,j) = newValue;
        }
    }
    return dst;
}

/*
 * used to add salt and pepper noise to the image
 */
Mat
AddSaltPepperNoise(Mat src)
{
    Mat saltpepper_noise = Mat::zeros(src.rows, src.cols,CV_8U);
    randu(saltpepper_noise,0,255);

    Mat black = saltpepper_noise < 10;
    Mat white = saltpepper_noise > 245;

    Mat saltpepper_img = src.clone();
    saltpepper_img.setTo(255,white);
    saltpepper_img.setTo(0,black);

    return saltpepper_img;
}

/*
 * convert an image from Mat type to mpz_class type used in encryption
 */
vector < vector<mpz_class> >
Mat2mpz(Mat src)
{
    vector < vector<mpz_class> > A(src.rows, vector<mpz_class>(src.cols));
    Scalar s;
    for (int i = 0; i < src.rows; i++) {
        for (int j = 0; j < src.cols; j++){
            s = src.at<uchar>(i,j);
            A[i][j] = (int)s[0];
        }
    }
    return A;
}

/*
 * convert an image from mpz_class type used in encryption to Mat type (opencv)
 */
Mat
mpz2Mat(vector < vector<mpz_class> > &A)
{
    int rows = A.size();
    int cols = A.data()->size();
    Mat dst(rows,cols,CV_8UC1);
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++){
            dst.at<uchar>(i,j) = (int)A[i][j].get_d(); //double to int
        }
    }
    return dst;
}

/*
 * used to divide by n in the average filter in time domain
 * would be removed later after implementing paillier encoding
 */
vector < vector<mpz_class> >
divideP (const vector < vector<mpz_class> > &src, const vector < vector<mpz_class> > &filter)
{
    //Src image size
    int rows = src.size();
    int cols = src.data()->size();
    //Filter size
    int ro = filter.size();
    int co = filter.data()->size();

    vector < vector<mpz_class> > dst (rows, vector<mpz_class>(cols));

    //get summation of the filter elements to divide by
    mpz_class sum = 0;
    for (int i=0; i < ro; i++){
	     for (int j=0; j < co; j++){
            sum += filter[i][j];
        }
    }

    for (int i=0; i < rows; i++){
	     for (int j=0; j < cols; j++){
            dst[i][j] = src[i][j]/sum;
        }
    }
    return dst;
}

static void
test_image()
{
    //create 2 empty windows
    //namedWindow( "Original Image" , CV_WINDOW_AUTOSIZE );
    //namedWindow( "Smoothed Image" , CV_WINDOW_AUTOSIZE );

    // Load an image from file
    Mat src = imread( "cameraman.JPG", 1 ); //0 --> gray, 1 --> RGB image

    //show the loaded image
    imshow( "Original Image", src );

    Mat dst;

    // Test Average filter by Opencv
    dst = AverageFilterOpenCV(src);
    imshow( "Opencv Smoothed Image", dst );

    //Add Salt and pepper noise:
    Mat saltpepper_img = AddSaltPepperNoise(src);
    imshow( "Noisy Image", saltpepper_img );

    dst = AverageFilterOpenCV(saltpepper_img);
    imshow( "Opencv Smoothed Image", dst );

    // Test Sobel filter by Me
    Mat src_gray;
    cvtColor(src, src_gray, CV_BGR2GRAY); //change the color image to grayscale image
    imshow( "Gray Image", src_gray );

    dst = SobelFilter(src_gray);
    imshow( "My Sobel Image", dst );

    // Test Sobel filter by Opencv
    dst = SobelFilterOpenCV(src_gray);
    imshow( "Opencv Sobel Image", dst );
}

static void
test_paillier()
{
    cout << "Test Paillier ...\n" << flush;

    gmp_randstate_t randstate;
    gmp_randinit_default(randstate);
    gmp_randseed_ui(randstate,time(NULL));

    auto sk = Paillier_priv::keygen(randstate,16,2); //600 ,256
    Paillier_priv pp(sk,randstate);

    auto pk = pp.pubkey();
    mpz_class n = pk[0];
    Paillier p(pk,randstate);

    //mpz_class pt0, pt1,m;
    //mpz_urandomm(pt0.get_mpz_t(),randstate,n.get_mpz_t());
    //mpz_urandomm(pt1.get_mpz_t(),randstate,n.get_mpz_t());
    //mpz_urandomm(m.get_mpz_t(),randstate,n.get_mpz_t());
    mpz_class pt0 = 2, pt1 = 3, m = 5; //instead of the random values
    cout << "pt0: "<< pt0 << endl;
    cout << "pt1: "<< pt1 << endl;

    mpz_class ct0 = p.encrypt(pt0);         cout << "ct0: "<< ct0 << endl;
    mpz_class ct1 = p.encrypt(pt1);         cout << "ct1: "<< ct1 << endl;
    mpz_class sum = p.add(ct0, ct1);        cout << "sum_enc: "<< sum << endl;
    mpz_class prod = p.constMult(m,ct0);    cout << "prod_enc: "<< prod << endl;
    mpz_class diff = p.sub(ct0, ct1);       cout << "diff_enc: "<< diff << endl;

    mpz_class ct0_dec = pp.decrypt(ct0);
    cout << "ct0_dec "<< ct0_dec.get_d() << endl;
    cout << "n " <<  n.get_d() <<endl;
    assert(pp.decrypt(ct0) == pt0);
    assert(pp.decrypt(ct1) == pt1);
    assert(pp.decrypt(sum) == (pt0+pt1)%n);
    mpz_class d = pt0 - pt1;
    if (d < 0) {
        d += n;
    }
    assert(pp.decrypt(diff) == d);
    assert(pp.decrypt(prod) == (m*pt0)%n);

    cout << "Test Paillier passed" << endl;

    cout << "Test Matrix Paillier ...\n" << flush;
    vector < vector<mpz_class> > A = {{1,2,3},{1,1,1},{4,5,6}};
    int Arows = A.size();
    int Acols = A.data()->size();
    vector < vector<mpz_class> > A_enc(Arows, vector<mpz_class>(Acols));
    A_enc = p.encryptMatrix(A);
    vector < vector<mpz_class> > A_dec(Arows, vector<mpz_class>(Acols));
    A_dec = pp.decryptMatrix(A_enc);
    cout << "Test Matrix Paillier passed" << endl;
}

double
getRMSE(const Mat& I1, const Mat& I2)
{
    Mat s1;
    absdiff(I1, I2, s1);       // |I1 - I2|
    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits
    s1 = s1.mul(s1);           // |I1 - I2|^2

    Scalar s = sum(s1);        // sum elements per channel
    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels
    double mse  = sse / (double)(I1.channels() * I1.total());
    double rmse = sqrt(mse);

    return rmse;
 }

void
testImagePaillierTime()
{
    struct timespec t0,t1;
    int n_iteration = 10;
    uint64_t t;

    //Generate Paillier key
    gmp_randstate_t randstate;
    gmp_randinit_default(randstate);
    gmp_randseed_ui(randstate,time(NULL));

    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        auto sk = Paillier_priv::keygen(randstate,32,2); //600 ,256
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Key Generation: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    auto sk = Paillier_priv::keygen(randstate,32,2); //600 ,256
    Paillier_priv pp(sk,randstate);

    auto pk = pp.pubkey();
    mpz_class n = pk[0];
    Paillier p(pk,randstate);

    //read image
    Mat src = imread( "cameraman.JPG", 0);
    imshow( "Original Image", src );

    int rows = src.rows;
    int cols = src.cols;
    vector < vector<mpz_class> > A(rows, vector<mpz_class>(cols));
    A = Mat2mpz(src);

    //Encrypt image
    vector < vector<mpz_class> > A_enc = p.encryptMatrix(A);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        A_enc = p.encryptMatrix(A);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    //imshow( "Encrypted Image", mpz2Mat(A_enc) );
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "public encryption: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;

    //Decrypt image
    vector < vector<mpz_class> > A_dec = pp.decryptMatrix(A_enc);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        A_dec = pp.decryptMatrix(A_enc);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "private decryption: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    Mat dst;
    dst = mpz2Mat(A_dec);
    //imshow( "Decrypted Image", dst );

    //Negate image in plain domain
    dst = NegativeImage(src);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = NegativeImage(src);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Negative Image PD: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    //imshow( "Negative Image", dst );

    //Negate image in encrypted domain
    vector < vector<mpz_class> > Neg_enc = p.NegativeImageH(A_enc);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        vector < vector<mpz_class> > Neg_enc = p.NegativeImageH(A_enc);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Negative Image ED: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    //imshow( "Encrypted Negative Image", mpz2Mat(Neg_enc) );

    vector < vector<mpz_class> > Neg_dec = pp.decryptMatrix(Neg_enc);
    //imshow( "Decrypted Negative Image", mpz2Mat(Neg_dec) );

    //Test Brightness
    int value = 50;
    //Increase brightness in plain domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = AdjustBrightness(src, value);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Adjust Brightness PD: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    dst = AdjustBrightness(src, value);
    //imshow( "Brightness Image", dst );

    //Increase brightness in encrypted domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        mpz_class value_enc = p.encrypt(value);
        vector < vector<mpz_class> > Bright_enc = p.AdjustBrightnessH(A_enc, value_enc);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Adjust Brightness ED: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    mpz_class value_enc = p.encrypt(value);
    vector < vector<mpz_class> > Bright_enc = p.AdjustBrightnessH(A_enc, value_enc);
    //imshow( "Encrypted Brightness Image", mpz2Mat(Bright_enc) );

    vector < vector<mpz_class> > Bright_dec = pp.decryptMatrix(Bright_enc);
    //imshow( "Decrypted Brightness Image", mpz2Mat(Bright_dec) );

    //Test convolution
    vector < vector<mpz_class> > filter;
    //filter = {{1,0,-1},{2,0,-2},{1,0,-1}};//Vertical edges
    filter = {{1,2,1},{0,0,0},{-1,-2,-1}};  //Horizontal edges
    vector < vector<mpz_class> > conv_enc = p.convolutionH(A_enc, filter);
    //imshow( "Encrypted convoluted Image", mpz2Mat(conv_enc) );
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        vector < vector<mpz_class> > conv_enc = p.convolutionH(A_enc, filter);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Convolution (3x3) Sobel ED: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;

    vector < vector<mpz_class> > conv_dec = pp.decryptMatrix(conv_enc);
    //imshow( "Decrypted convoluted Image", mpz2Mat(conv_dec) );

    //Test convolution in Plain domain
    dst = convolution(src, filter);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = convolution(src, filter);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Convolution (3x3) Sobel PD: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    //imshow( "convoluted Image", dst );

    //Test Average filter
    filter = {{1,1,1},{1,1,1},{1,1,1}};
    vector < vector<mpz_class> > Avg_enc = p.convolutionH(A_enc, filter);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        Avg_enc = p.convolutionH(A_enc, filter);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Average (3x3) ED: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    //imshow( "Encrypted averaged Image", mpz2Mat(Avg_enc) );
    vector < vector<mpz_class> > Avg_dec = pp.decryptMatrix(Avg_enc);
    //Divide by filter summation in plain domain
    vector < vector<mpz_class> > Avg_decP = divideP(Avg_dec, filter);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        Avg_decP = divideP(Avg_dec, filter);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Average (3x3) ED Post Processing: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    //imshow( "Decrypted averaged Image", mpz2Mat(Avg_decP) );

    //Test average filter in plain domain
    dst = AverageFilter(src,filter);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = AverageFilter(src,filter);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Average (3x3) PD: "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    //imshow( "Averaged Image", dst );

}

void
testImagePaillier()
{
    //Generate Paillier key
    gmp_randstate_t randstate;
    gmp_randinit_default(randstate);
    gmp_randseed_ui(randstate,time(NULL));

    auto sk = Paillier_priv::keygen(randstate,32,2); //600 ,256
    Paillier_priv pp(sk,randstate);

    auto pk = pp.pubkey();
    mpz_class n = pk[0];
    Paillier p(pk,randstate);
    cout << "key  ready ...\n" << endl;

    //read image
    Mat src = imread( "cameraman.JPG", 0);
    imshow( "Original Image", src );

    int rows = src.rows;
    int cols = src.cols;
    vector < vector<mpz_class> > A(rows, vector<mpz_class>(cols));
    A = Mat2mpz(src);

    Img img1(A);
    img1.setPbKey(p);

    //Encrypt image
    vector < vector<mpz_class> > A_enc = img1.encryptMatrix(A);
    imshow( "Encrypted Image", mpz2Mat(A_enc) );

    //The client only can decrypt a matrix
    vector < vector<mpz_class> > A_dec = pp.decryptMatrix(A_enc);

    //Decrypt image
    Mat dst;
    dst = mpz2Mat(A_dec);
    imshow( "Decrypted Image", dst );
    //cout << "Root Mean Square Error in decryption: "<<getRMSE(src,dst)<<endl;

    //Negate image in plain domain
    dst = NegativeImage(src);
    imshow( "Negative Image", dst );

    //Negate image in encrypted domain
    vector < vector<mpz_class> > Neg_enc = img1.NegativeImageH(A_enc);
    imshow( "Encrypted Negative Image", mpz2Mat(Neg_enc) );

    vector < vector<mpz_class> > Neg_dec = pp.decryptMatrix(Neg_enc);
    imshow( "Decrypted Negative Image", mpz2Mat(Neg_dec) );
    //cout << "Root Mean Square Error in Negative image: "<<getRMSE(dst,mpz2Mat(Neg_dec))<<endl;

    //Test Brightness
    int value = 50;
    //Increase brightness in plain domain
    dst = AdjustBrightness(src, value);
    imshow( "Brightness Image", dst );

    //Increase brightness in encrypted domain
    mpz_class value_enc = p.encrypt(value);
    vector < vector<mpz_class> > Bright_enc = img1.AdjustBrightnessH(A_enc, value_enc);
    imshow( "Encrypted Brightness Image", mpz2Mat(Bright_enc) );

    vector < vector<mpz_class> > Bright_dec = pp.decryptMatrix(Bright_enc);
    imshow( "Decrypted Brightness Image", mpz2Mat(Bright_dec) );
    //cout << "Root Mean Square Error in Brightness image: "<<getRMSE(dst,mpz2Mat(Bright_dec))<<endl;
    //post-processing to fix the image
    //Bright_dec = FixRange(Bright_dec);
    //imshow( "Decrypted Brightness Image", mpz2Mat(Bright_dec) );

    //Test convolution
    vector < vector<mpz_class> > filter;
    //filter = {{1,0,-1},{2,0,-2},{1,0,-1}};//Vertical edges
    filter = {{1,2,1},{0,0,0},{-1,-2,-1}};  //Horizontal edges

    vector < vector<mpz_class> > conv_enc = img1.convolutionH(A_enc, filter);
    imshow( "Encrypted convoluted Image", mpz2Mat(conv_enc) );
    vector < vector<mpz_class> > conv_dec = pp.decryptMatrix(conv_enc);
    imshow( "Decrypted convoluted Image", mpz2Mat(conv_dec) );

    //Test convolution in Plain domain
    dst = convolution(src, filter);
    imshow( "convoluted Image", dst );
    //cout << "Root Mean Square Error in convoluted image: "<<getRMSE(dst,mpz2Mat(conv_dec))<<endl;

    //Test Average filter
    filter = {{1,1,1},{1,1,1},{1,1,1}};
    vector < vector<mpz_class> > Avg_enc = img1.convolutionH(A_enc, filter);
    imshow( "Encrypted averaged Image", mpz2Mat(Avg_enc) );
    vector < vector<mpz_class> > Avg_dec = pp.decryptMatrix(Avg_enc);
    //Divide by filter summation in plain domain
    vector < vector<mpz_class> > Avg_decP = divideP(Avg_dec, filter);
    imshow( "Decrypted averaged Image", mpz2Mat(Avg_decP) );

    //Test average filter in plain domain
    dst = AverageFilter(src,filter);
    imshow( "Averaged Image", dst );
    //cout << "Root Mean Square Error in Averaged image: "<<getRMSE(dst,mpz2Mat(Avg_decP))<<endl;

    //wait for a key press infinitely
    waitKey(0);
}

int
main ( int argc, char **argv )
{
    //SetSeed(to_ZZ(time(NULL)));
    //test_paillier();
    //test_image();

    testImagePaillier(); /*For generating images*/
    //testImagePaillierTime(); /*For calculating time*/

    return 0;
}

ext: cc

max_stars_repo_name: TarekIbnZiad/CryptoImg

max_issues_repo_name: TarekIbnZiad/CryptoImg

max_forks_repo_name: TarekIbnZiad/CryptoImg