/*
 * batchRunner.cpp
 *
 *  Created on: November 15, 2019
 *      Author: vadims
 */

#include "batchRunner.hpp"
#include "easylogging++/easylogging++.h"
#include "Timer.hpp"
#include <future>

int batchRunner::countBatchRunners = 0;

//Fix for CP-1511
std::wstring s2ws(const std::string& s)
{
	int len;
	int slength = (int)s.length() + 1;
	len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
	wchar_t* buf = new wchar_t[len];
	MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
	std::wstring r(buf);
	delete[] buf;
	return r;
}

batchRunner::batchRunner(string dir, string cmd, string args) : wDir(dir), exec(cmd), arguments(args)
{
	countBatchRunners++;
	stopRequired = false;
}

batchRunner::~batchRunner()
{
	countBatchRunners--;
}

messagedata batchRunner::run(std::shared_ptr<OutputAnalyzer> analyzer)
{

//***************************** Job Objects stuff **********************************************************
	//We will use JobObjects
	BOOL we_are_in_job;
	bool ok = IsProcessInJob(GetCurrentProcess(), NULL, &we_are_in_job);
	if (ok == FALSE)
	{
		LOG(ERROR) << "Failed to determine if we are in a Job already: error " << GetLastError();
		return messagedata(messagedata::error, messagedata::response, "Failed to determine if we are in a Job already :(");
	}

	if (we_are_in_job)
	{
		LOG(INFO) << "Process is already in Job";
	}

	HANDLE hJob = CreateJobObject(NULL, NULL);
	if (hJob == NULL) 
	{
		LOG(ERROR) << "Cannot create JOB Object: error " << GetLastError();
		return messagedata(messagedata::error, messagedata::response, "Cannot create JOB Object :(");
	}

	JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
	jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
	ok = SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));
	if (ok == FALSE)
	{
		LOG(ERROR) << "Cannot set JOB Object Info: error " << GetLastError();
		return messagedata(messagedata::error, messagedata::response, "Cannot set JOB Object Info :(");
	}


	//we have to ghave different cereation flags, depending on if we are in a Job already or not.
	DWORD dwCreationFlags = we_are_in_job ? CREATE_BREAKAWAY_FROM_JOB : 0;

//***************************** Job Objects stuff **********************************************************


	Timer t;
	t.setMessage("batch runner execution time: ");
	
	HANDLE hStdInPipeRead = NULL;
	HANDLE hStdInPipeWrite = NULL;
	HANDLE hStdOutPipeRead = NULL;
	HANDLE hStdOutPipeWrite = NULL;

	// Create two pipes.
	SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
	ok = CreatePipe(&hStdInPipeRead, &hStdInPipeWrite, &sa, 0);
	if (ok == FALSE) 
		return messagedata(messagedata::error, messagedata::response, "Could not start the pipe for running getdp.");
	ok = CreatePipe(&hStdOutPipeRead, &hStdOutPipeWrite, &sa, 0);
	if (ok == FALSE) 
		return messagedata(messagedata::error, messagedata::response, "Could not start the pipe for running getdp.");

	//Fix for CP-1511
	std::wstring command = s2ws(exec + " " + arguments);
	std::wstring wdir_w = s2ws(wDir);
	STARTUPINFO si = {  };
	PROCESS_INFORMATION pi;
	std::wstring env;
	si.dwFlags = STARTF_USESTDHANDLES;
	si.hStdError = hStdOutPipeWrite;
	si.hStdOutput = hStdOutPipeWrite;
	si.hStdInput = hStdInPipeRead;
	si.cb = sizeof(si);

	ok = CreateProcess(NULL,		// No module name (use command line)
		(TCHAR*)command.c_str(),	//command
		NULL,						// Process handle not inheritable
		NULL,						// Thread handle not inheritable
		true,						// Set handle inheritance to true
		dwCreationFlags,			// Create no additional window
		NULL,						// Use parent's environment block
		(TCHAR*)wdir_w.c_str(),		// Current directory
		&si,						// Pointer to STARTUPINFO structure
		&pi);						// Pointer to PROCESS_INFORMATION structure
	if (!ok)
	{
		LOG(ERROR) << "Could not initialize simulation process." << GetLastError();
		return messagedata(messagedata::error, messagedata::response, "Could not initialize simulation process." + GetLastError());
	}


	//we have to assign process to Job Object hJob
	ok = AssignProcessToJobObject(hJob, pi.hProcess);
	if (ok == FALSE)
	{
		LOG(ERROR) << "Could not assign Process to Job : error " << GetLastError();
		return messagedata(messagedata::error, messagedata::response, "Could not assign Process to Job :(");
	}

	// Close pipes we do not need.
	CloseHandle(hStdOutPipeWrite);
	CloseHandle(hStdInPipeRead);

	if (stopRequired)
	{
		LOG(INFO) << "TERMINATING";
		TerminateProcess(pi.hProcess, NULL);
		LOG(ERROR) << "Simulation stopped without results.";
		return messagedata(messagedata::error, messagedata::response, "Simulation stopped without results.");
	}

	//std::cout << GetLastError();

	// read from batch buffer asynchronously
	auto reader_future = std::async(std::launch::async, &batchRunner::readBuffer, this, hStdOutPipeRead, analyzer);

	// if stop is required, terminate process
	do
	{
		Sleep(100);
		if (stopRequired)
		{
			LOG(INFO) << "TERMINATING";
			TerminateProcess(pi.hProcess, NULL);
			reader_future.wait();
		}

	} while (reader_future.wait_for(std::chrono::milliseconds(1)) != std::future_status::ready);


	// Wait until child process exits.
	WaitForSingleObject(pi.hProcess, INFINITE);

	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	// Close process and thread handles. 
	CloseHandle(hStdOutPipeRead);
	CloseHandle(hStdInPipeWrite);

	//we should close Job object handle
	CloseHandle(hJob);


	// if reader has found errors in getdp output, return them
	messagedata reader_return = reader_future.get();
	if (reader_return.msgStatus == messagedata::error)
		return reader_return;

	return messagedata(messagedata::success, messagedata::response, "{}");
}

void batchRunner::stop()
{
	stopRequired = true;
}


messagedata batchRunner::readBuffer(HANDLE hStdOutPipeRead, std::shared_ptr<OutputAnalyzer> analyzer)
{
	char buf[512 + 1] = { };
	DWORD dwRead = 0;
	DWORD dwAvail = 0;
	bool ok = ReadFile(hStdOutPipeRead, buf, 512, &dwRead, NULL);
	while (ok == TRUE and !stopRequired)
	{
		buf[dwRead] = '\0';
		messagedata logAnalysis = analyzer->AddToBuffer(buf);
		// return logAnalysis if error received
		if (logAnalysis.isDefined)
		{
			if (logAnalysis.msgStatus == messagedata::error)
			{
				return logAnalysis;
			}
		}
		
		ok = ReadFile(hStdOutPipeRead, buf, 512, &dwRead, NULL);
	}

	return messagedata(messagedata::success, messagedata::response, "");
}