/*
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  * Additional permission under GNU GPL version 3 section 7
  *
  * If you modify this Program, or any covered work, by linking or combining
  * it with OpenSSL (or a modified version of that library), containing parts
  * covered by the terms of OpenSSL License and SSLeay License, the licensors
  * of this Program grant you additional permission to convey the resulting work.
  *
  */

#include <thread>
#include <string>
#include <cmath>
#include <algorithm>
#include <assert.h>
#include <time.h>
#include "executor.h"
#include "jpsock.h"
#include "minethd.h"
#include "jconf.h"
#include "console.h"
#include "donate-level.h"
#include "webdesign.h"

#ifdef __GNUC__
#include <mm_malloc.h>
#else
#include <malloc.h>
#endif // __GNUC__

#ifdef _WIN32
#define strncasecmp _strnicmp
#endif // _WIN32

executor* executor::oInst = NULL;

executor::executor()
{
	cpu_ctx = (cryptonight_ctx*)_mm_malloc(sizeof(cryptonight_ctx), 4096);
}

void executor::push_timed_event(ex_event&& ev, size_t sec)
{
	std::unique_lock<std::mutex> lck(timed_event_mutex);
	lTimedEvents.emplace_back(std::move(ev), sec_to_ticks(sec));
}

void executor::ex_clock_thd()
{
	size_t iSwitchPeriod = sec_to_ticks(iDevDonatePeriod);
	size_t iDevPortion = (size_t)floor(((double)iSwitchPeriod) * fDevDonationLevel);

	//No point in bothering with less than 10 sec
	if(iDevPortion < sec_to_ticks(10))
		iDevPortion = 0;

	//Add 2 seconds to compensate for connect
	if(iDevPortion != 0)
		iDevPortion += sec_to_ticks(2);

	while (true)
	{
		std::this_thread::sleep_for(std::chrono::milliseconds(size_t(iTickTime)));

		push_event(ex_event(EV_PERF_TICK));

		// Service timed events
		std::unique_lock<std::mutex> lck(timed_event_mutex);
		std::list<timed_event>::iterator ev = lTimedEvents.begin();
		while (ev != lTimedEvents.end())
		{
			ev->ticks_left--;
			if(ev->ticks_left == 0)
			{
				push_event(std::move(ev->event));
				ev = lTimedEvents.erase(ev);
			}
			else
				ev++;
		}
		lck.unlock();

		if(iDevPortion == 0)
			continue;

		iSwitchPeriod--;
		if(iSwitchPeriod == 0)
		{
			push_event(ex_event(EV_SWITCH_POOL, usr_pool_id));
			iSwitchPeriod = sec_to_ticks(iDevDonatePeriod);
		}
		else if(iSwitchPeriod == iDevPortion)
		{
			push_event(ex_event(EV_SWITCH_POOL, dev_pool_id));
		}
	}
}

void executor::sched_reconnect()
{
	iReconnectAttempts++;
	size_t iLimit = jconf::inst()->GetGiveUpLimit();
	if(iLimit != 0 && iReconnectAttempts > iLimit)
	{
		printer::inst()->print_msg(L0, "Give up limit reached. Exitting.");
		exit(0);
	}

	long long unsigned int rt = jconf::inst()->GetNetRetry();
	printer::inst()->print_msg(L1, "Pool connection lost. Waiting %lld s before retry (attempt %llu).",
		rt, int_port(iReconnectAttempts));

	auto work = minethd::miner_work();
	minethd::switch_work(work);

	push_timed_event(ex_event(EV_RECONNECT, usr_pool_id), rt);
}

void executor::log_socket_error(std::string&& sError)
{
	vSocketLog.emplace_back(std::move(sError));
	printer::inst()->print_msg(L1, "SOCKET ERROR - %s", vSocketLog.back().msg.c_str());
}

void executor::log_result_error(std::string&& sError)
{
	size_t i = 1, ln = vMineResults.size();
	for(; i < ln; i++)
	{
		if(vMineResults[i].compare(sError))
		{
			vMineResults[i].increment();
			break;
		}
	}

	if(i == ln) //Not found
		vMineResults.emplace_back(std::move(sError));
	else
		sError.clear();
}

void executor::log_result_ok(uint64_t iActualDiff)
{
	iPoolHashes += iPoolDiff;

	size_t ln = iTopDiff.size() - 1;
	if(iActualDiff > iTopDiff[ln])
	{
		iTopDiff[ln] = iActualDiff;
		std::sort(iTopDiff.rbegin(), iTopDiff.rend());
	}

	vMineResults[0].increment();
}

jpsock* executor::pick_pool_by_id(size_t pool_id)
{
	assert(pool_id != invalid_pool_id);

	if(pool_id == dev_pool_id)
		return dev_pool;
	else
		return usr_pool;
}

void executor::on_sock_ready(size_t pool_id)
{
	jpsock* pool = pick_pool_by_id(pool_id);

	if(pool_id == dev_pool_id)
	{
		if(!pool->cmd_login("", ""))
			pool->disconnect();

		current_pool_id = dev_pool_id;
		printer::inst()->print_msg(L1, "Dev pool logged in. Switching work.");
		return;
	}

	printer::inst()->print_msg(L1, "Connected. Logging in...");

	if (!pool->cmd_login(jconf::inst()->GetWalletAddress(), jconf::inst()->GetPoolPwd()))
	{
		if(!pool->have_sock_error())
		{
			log_socket_error(pool->get_call_error());
			pool->disconnect();
		}
	}
	else
	{
		iReconnectAttempts = 0;
		reset_stats();
	}
}

void executor::on_sock_error(size_t pool_id, std::string&& sError)
{
	jpsock* pool = pick_pool_by_id(pool_id);

	if(pool_id == dev_pool_id)
	{
		pool->disconnect();

		if(current_pool_id != dev_pool_id)
			return;

		printer::inst()->print_msg(L1, "Dev pool connection error. Switching work.");
		on_switch_pool(usr_pool_id);
		return;
	}

	log_socket_error(std::move(sError));
	pool->disconnect();
	sched_reconnect();
}

void executor::on_pool_have_job(size_t pool_id, pool_job& oPoolJob)
{
	if(pool_id != current_pool_id)
		return;

	jpsock* pool = pick_pool_by_id(pool_id);

	minethd::miner_work oWork(oPoolJob.sJobID, oPoolJob.bWorkBlob,
		oPoolJob.iWorkLen, oPoolJob.iResumeCnt, oPoolJob.iTarget, pool_id);

	minethd::switch_work(oWork);

	if(pool_id == dev_pool_id)
		return;

	if(iPoolDiff != pool->get_current_diff())
	{
		iPoolDiff = pool->get_current_diff();
		printer::inst()->print_msg(L2, "Difficulty changed. Now: %llu.", int_port(iPoolDiff));
	}

	printer::inst()->print_msg(L3, "New block detected.");
}

void executor::on_miner_result(size_t pool_id, job_result& oResult)
{
	jpsock* pool = pick_pool_by_id(pool_id);

	*(uint32_t*)(oResult.bWorkBlob + 39) = oResult.iNonce;

	if(jconf::inst()->HaveHardwareAes())
		cryptonight_hash_ctx(oResult.bWorkBlob, oResult.iWorkLen, oResult.bResult, cpu_ctx);
	else
		cryptonight_hash_ctx_soft(oResult.bWorkBlob, oResult.iWorkLen, oResult.bResult, cpu_ctx);

	bool bVerified = ((uint32_t*)oResult.bResult)[7] < oResult.iTarget;

	if(pool_id == dev_pool_id)
	{
		//Ignore errors silently
		if(pool->is_running() && pool->is_logged_in() && bVerified)
			pool->cmd_submit(oResult.sJobID, oResult.iNonce, oResult.bResult);

		return;
	}

	if (!bVerified)
	{
		log_result_error("[GPU COMPUTE ERROR]");
		return;
	}

	if (!pool->is_running() || !pool->is_logged_in())
	{
		log_result_error("[NETWORK ERROR]");
		return;
	}

	using namespace std::chrono;
	size_t t_start = time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count();
	bool bResult = pool->cmd_submit(oResult.sJobID, oResult.iNonce, oResult.bResult);
	size_t t_len = time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count() - t_start;

	if(t_len > 0xFFFF)
		t_len = 0xFFFF;
	iPoolCallTimes.push_back((uint16_t)t_len);

	if(bResult)
	{
		uint64_t* targets = (uint64_t*)oResult.bResult;
		log_result_ok(jpsock::t64_to_diff(targets[3]));
		printer::inst()->print_msg(L3, "Result accepted by the pool.");
	}
	else
	{
		if(!pool->have_sock_error())
		{
			printer::inst()->print_msg(L3, "Result rejected by the pool.");

			std::string error = pool->get_call_error();

			if(strncasecmp(error.c_str(), "Unauthenticated", 15) == 0)
			{
				printer::inst()->print_msg(L2, "Your miner was unable to find a share in time. Either the pool difficulty is too high, or the pool timeout is too low.");
				pool->disconnect();
			}

			log_result_error(std::move(error));
		}
		else
			log_result_error("[NETWORK ERROR]");
	}
}

void executor::on_reconnect(size_t pool_id)
{
	jpsock* pool = pick_pool_by_id(pool_id);

	std::string error;
	if(pool_id == dev_pool_id)
		return;

	printer::inst()->print_msg(L1, "Connecting to pool %s ...", jconf::inst()->GetPoolAddress());

	if(!pool->connect(jconf::inst()->GetPoolAddress(), error))
	{
		log_socket_error(std::move(error));
		sched_reconnect();
	}
}

void executor::on_switch_pool(size_t pool_id)
{
	if(pool_id == current_pool_id)
		return;

	jpsock* pool = pick_pool_by_id(pool_id);
	if(pool_id == dev_pool_id)
	{
		std::string error;

		// If it fails, it fails, we carry on on the usr pool
		// as we never receive further events
		printer::inst()->print_msg(L1, "Connecting to dev pool...");
		const char* dev_pool_addr = jconf::inst()->GetTlsSetting() ? "donate.xmr-stak.net:6666" : "donate.xmr-stak.net:3333";
		if(!pool->connect(dev_pool_addr, error))
			printer::inst()->print_msg(L1, "Error connecting to dev pool. Staying with user pool.");
	}
	else
	{
		printer::inst()->print_msg(L1, "Switching back to user pool.");

		current_pool_id = pool_id;
		pool_job oPoolJob;

		if(!pool->get_current_job(oPoolJob))
		{
			pool->disconnect();
			return;
		}

		minethd::miner_work oWork(oPoolJob.sJobID, oPoolJob.bWorkBlob,
			oPoolJob.iWorkLen, oPoolJob.iResumeCnt, oPoolJob.iTarget, pool_id);

		minethd::switch_work(oWork);

		if(dev_pool->is_running())
			push_timed_event(ex_event(EV_DEV_POOL_EXIT), 5);
	}
}

void executor::ex_main()
{
	assert(1000 % iTickTime == 0);

	minethd::miner_work oWork = minethd::miner_work();
	pvThreads = minethd::thread_starter(oWork);
	telem = new telemetry(pvThreads->size());

	current_pool_id = usr_pool_id;
	usr_pool = new jpsock(usr_pool_id, jconf::inst()->GetTlsSetting());
	dev_pool = new jpsock(dev_pool_id, jconf::inst()->GetTlsSetting());

	ex_event ev;
	std::thread clock_thd(&executor::ex_clock_thd, this);

	//This will connect us to the pool for the first time
	push_event(ex_event(EV_RECONNECT, usr_pool_id));

	// Place the default success result at postion 0, it needs to
	// be here even if our first result is a failure
	vMineResults.emplace_back();

	// If the user requested it, start the autohash printer
	if(jconf::inst()->GetVerboseLevel() >= 4)
		push_timed_event(ex_event(EV_HASHRATE_LOOP), jconf::inst()->GetAutohashTime());

	size_t cnt = 0, i;
	while (true)
	{
		ev = oEventQ.pop();
		switch (ev.iName)
		{
		case EV_SOCK_READY:
			on_sock_ready(ev.iPoolId);
			break;

		case EV_SOCK_ERROR:
			on_sock_error(ev.iPoolId, std::move(ev.sSocketError));
			break;

		case EV_POOL_HAVE_JOB:
			on_pool_have_job(ev.iPoolId, ev.oPoolJob);
			break;

		case EV_MINER_HAVE_RESULT:
			on_miner_result(ev.iPoolId, ev.oJobResult);
			break;

		case EV_RECONNECT:
			on_reconnect(ev.iPoolId);
			break;

		case EV_SWITCH_POOL:
			on_switch_pool(ev.iPoolId);
			break;

		case EV_DEV_POOL_EXIT:
			dev_pool->disconnect();
			break;

		case EV_PERF_TICK:
			for (i = 0; i < pvThreads->size(); i++)
				telem->push_perf_value(i, pvThreads->at(i)->iHashCount.load(std::memory_order_relaxed),
				pvThreads->at(i)->iTimestamp.load(std::memory_order_relaxed));

			if((cnt++ & 0xF) == 0) //Every 16 ticks
			{
				double fHps = 0.0;
				for (i = 0; i < pvThreads->size(); i++)
					fHps += telem->calc_telemetry_data(10000, i);

				if(fHighestHps < fHps)
					fHighestHps = fHps;
			}
		break;

		case EV_USR_HASHRATE:
		case EV_USR_RESULTS:
		case EV_USR_CONNSTAT:
			print_report(ev.iName);
			break;

		case EV_HTML_HASHRATE:
		case EV_HTML_RESULTS:
		case EV_HTML_CONNSTAT:
			http_report(ev.iName);
			break;

		case EV_HASHRATE_LOOP:
			print_report(EV_USR_HASHRATE);
			push_timed_event(ex_event(EV_HASHRATE_LOOP), jconf::inst()->GetAutohashTime());
			break;

		case EV_INVALID_VAL:
		default:
			assert(false);
			break;
		}
	}
}

inline const char* hps_format(double h, char* buf, size_t l)
{
	if(std::isnormal(h) || h == 0.0)
	{
		snprintf(buf, l, " %03.1f", h);
		return buf;
	}
	else
		return "  (na)";
}

void executor::hashrate_report(std::string& out)
{
	char num[32];
	size_t nthd = pvThreads->size();

	out.reserve(256 + nthd * 64);

	double fTotal[3] = { 0.0, 0.0, 0.0};
	size_t i;

	out.append("HASHRATE REPORT\n");
	out.append("| ID |   10s |   60s |   15m |");
	if(nthd != 1)
		out.append(" ID |   10s |   60s |   15m |\n");
	else
		out.append(1, '\n');

	for (i = 0; i < nthd; i++)
	{
		double fHps[3];

		fHps[0] = telem->calc_telemetry_data(10000, i);
		fHps[1] = telem->calc_telemetry_data(60000, i);
		fHps[2] = telem->calc_telemetry_data(900000, i);

		snprintf(num, sizeof(num), "| %2u |", (unsigned int)i);
		out.append(num);
		out.append(hps_format(fHps[0], num, sizeof(num))).append(" |");
		out.append(hps_format(fHps[1], num, sizeof(num))).append(" |");
		out.append(hps_format(fHps[2], num, sizeof(num))).append(1, ' ');

		fTotal[0] += fHps[0];
		fTotal[1] += fHps[1];
		fTotal[2] += fHps[2];

		if((i & 0x1) == 1) //Odd i's
			out.append("|\n");
	}

	if((i & 0x1) == 1) //We had odd number of threads
		out.append("|\n");

	if(nthd != 1)
		out.append("-----------------------------------------------------\n");
	else
		out.append("---------------------------\n");

	out.append("Totals:  ");
	out.append(hps_format(fTotal[0], num, sizeof(num)));
	out.append(hps_format(fTotal[1], num, sizeof(num)));
	out.append(hps_format(fTotal[2], num, sizeof(num)));
	out.append(" H/s\nHighest: ");
	out.append(hps_format(fHighestHps, num, sizeof(num)));
	out.append(" H/s\n");
}

char* time_format(char* buf, size_t len, std::chrono::system_clock::time_point time)
{
	time_t ctime = std::chrono::system_clock::to_time_t(time);
	tm stime;

	/*
	 * Oh for god's sake... this feels like we are back to the 90's...
	 * and don't get me started on lack strcpy_s because NIH - use non-standard strlcpy...
	 * And of course C++ implements unsafe version because... reasons
	 */

#ifdef _WIN32
	localtime_s(&stime, &ctime);
#else
	localtime_r(&ctime, &stime);
#endif // __WIN32
	strftime(buf, len, "%F %T", &stime);

	return buf;
}

void executor::result_report(std::string& out)
{
	char num[128];
	char date[32];

	out.reserve(1024);

	size_t iGoodRes = vMineResults[0].count, iTotalRes = iGoodRes;
	size_t ln = vMineResults.size();

	for(size_t i=1; i < ln; i++)
		iTotalRes += vMineResults[i].count;

	out.append("RESULT REPORT\n");
	if(iTotalRes == 0)
	{
		out.append("You haven't found any results yet.\n");
		return;
	}

	double dConnSec;
	{
		using namespace std::chrono;
		dConnSec = (double)duration_cast<seconds>(system_clock::now() - tPoolConnTime).count();
	}

	snprintf(num, sizeof(num), " (%.1f %%)\n", 100.0 * iGoodRes / iTotalRes);

	out.append("Difficulty       : ").append(std::to_string(iPoolDiff)).append(1, '\n');
	out.append("Good results     : ").append(std::to_string(iGoodRes)).append(" / ").
		append(std::to_string(iTotalRes)).append(num);

	if(iPoolCallTimes.size() != 0)
	{
		// Here we use iPoolCallTimes since it also gets reset when we disconnect
		snprintf(num, sizeof(num), "%.1f sec\n", dConnSec / iPoolCallTimes.size());
		out.append("Avg result time  : ").append(num);
	}
	out.append("Pool-side hashes : ").append(std::to_string(iPoolHashes)).append(2, '\n');
	out.append("Top 10 best results found:\n");

	for(size_t i=0; i < 10; i += 2)
	{
		snprintf(num, sizeof(num), "| %2llu | %16llu | %2llu | %16llu |\n",
			int_port(i), int_port(iTopDiff[i]), int_port(i+1), int_port(iTopDiff[i+1]));
		out.append(num);
	}

	out.append("\nError details:\n");
	if(ln > 1)
	{
		out.append("| Count | Error text                       | Last seen           |\n");
		for(size_t i=1; i < ln; i++)
		{
			snprintf(num, sizeof(num), "| %5llu | %-32.32s | %s |\n", int_port(vMineResults[i].count),
				vMineResults[i].msg.c_str(), time_format(date, sizeof(date), vMineResults[i].time));
			out.append(num);
		}
	}
	else
		out.append("Yay! No errors.\n");
}

void executor::connection_report(std::string& out)
{
	char num[128];
	char date[32];

	out.reserve(512);

	jpsock* pool = pick_pool_by_id(dev_pool_id + 1);

	out.append("CONNECTION REPORT\n");
	if (pool->is_running() && pool->is_logged_in())
		out.append("Connected since : ").append(time_format(date, sizeof(date), tPoolConnTime)).append(1, '\n');
	else
		out.append("Connected since : <not connected>\n");

	size_t n_calls = iPoolCallTimes.size();
	if (n_calls > 1)
	{
		//Not-really-but-good-enough median
		std::nth_element(iPoolCallTimes.begin(), iPoolCallTimes.begin() + n_calls/2, iPoolCallTimes.end());
		out.append("Pool ping time  : ").append(std::to_string(iPoolCallTimes[n_calls/2])).append(" ms\n");
	}
	else
		out.append("Pool ping time  : (n/a)\n");

	out.append("\nNetwork error log:\n");
	size_t ln = vSocketLog.size();
	if(ln > 0)
	{
		out.append("| Date                | Error text                                             |\n");
		for(size_t i=0; i < ln; i++)
		{
			snprintf(num, sizeof(num), "| %s | %-54.54s |\n",
				time_format(date, sizeof(date), vSocketLog[i].time), vSocketLog[i].msg.c_str());
			out.append(num);
		}
	}
	else
		out.append("Yay! No errors.\n");
}

void executor::print_report(ex_event_name ev)
{
	std::string out;
	switch(ev)
	{
	case EV_USR_HASHRATE:
		hashrate_report(out);
		break;

	case EV_USR_RESULTS:
		result_report(out);
		break;

	case EV_USR_CONNSTAT:
		connection_report(out);
		break;
	default:
		assert(false);
		break;
	}

	printer::inst()->print_str(out.c_str());
}

void executor::http_hashrate_report(std::string& out)
{
	char num_a[32], num_b[32], num_c[32], num_d[32];
	char buffer[4096];
	size_t nthd = pvThreads->size();

	out.reserve(4096);

	snprintf(buffer, sizeof(buffer), sHtmlCommonHeader, "Hashrate Report", "Hashrate Report");
	out.append(buffer);

	snprintf(buffer, sizeof(buffer), sHtmlHashrateBodyHigh, (unsigned int)nthd + 3);
	out.append(buffer);

	double fTotal[3] = { 0.0, 0.0, 0.0};
	for(size_t i=0; i < nthd; i++)
	{
		double fHps[3];

		fHps[0] = telem->calc_telemetry_data(10000, i);
		fHps[1] = telem->calc_telemetry_data(60000, i);
		fHps[2] = telem->calc_telemetry_data(900000, i);

		num_a[0] = num_b[0] = num_c[0] ='\0';
		hps_format(fHps[0], num_a, sizeof(num_a));
		hps_format(fHps[1], num_b, sizeof(num_b));
		hps_format(fHps[2], num_c, sizeof(num_c));

		fTotal[0] += fHps[0];
		fTotal[1] += fHps[1];
		fTotal[2] += fHps[2];

		snprintf(buffer, sizeof(buffer), sHtmlHashrateTableRow, (unsigned int)i, num_a, num_b, num_c);
		out.append(buffer);
	}

	num_a[0] = num_b[0] = num_c[0] = num_d[0] ='\0';
	hps_format(fTotal[0], num_a, sizeof(num_a));
	hps_format(fTotal[1], num_b, sizeof(num_b));
	hps_format(fTotal[2], num_c, sizeof(num_c));
	hps_format(fHighestHps, num_d, sizeof(num_d));

	snprintf(buffer, sizeof(buffer), sHtmlHashrateBodyLow, num_a, num_b, num_c, num_d);
	out.append(buffer);
}

void executor::http_result_report(std::string& out)
{
	char date[128];
	char buffer[4096];

	out.reserve(4096);

	snprintf(buffer, sizeof(buffer), sHtmlCommonHeader, "Result Report", "Result Report");
	out.append(buffer);

	size_t iGoodRes = vMineResults[0].count, iTotalRes = iGoodRes;
	size_t ln = vMineResults.size();

	for(size_t i=1; i < ln; i++)
		iTotalRes += vMineResults[i].count;

	double fGoodResPrc = 0.0;
	if(iTotalRes > 0)
		fGoodResPrc = 100.0 * iGoodRes / iTotalRes;

	double fAvgResTime = 0.0;
	if(iPoolCallTimes.size() > 0)
	{
		using namespace std::chrono;
		fAvgResTime = ((double)duration_cast<seconds>(system_clock::now() - tPoolConnTime).count())
			/ iPoolCallTimes.size();
	}

	snprintf(buffer, sizeof(buffer), sHtmlResultBodyHigh,
		iPoolDiff, iGoodRes, iTotalRes, fGoodResPrc, fAvgResTime, iPoolHashes,
		int_port(iTopDiff[0]), int_port(iTopDiff[1]), int_port(iTopDiff[2]), int_port(iTopDiff[3]),
		int_port(iTopDiff[4]), int_port(iTopDiff[5]), int_port(iTopDiff[6]), int_port(iTopDiff[7]),
		int_port(iTopDiff[8]), int_port(iTopDiff[9]));

	out.append(buffer);

	for(size_t i=1; i < vMineResults.size(); i++)
	{
		snprintf(buffer, sizeof(buffer), sHtmlResultTableRow, vMineResults[i].msg.c_str(),
			int_port(vMineResults[i].count), time_format(date, sizeof(date), vMineResults[i].time));
		out.append(buffer);
	}

	out.append(sHtmlResultBodyLow);
}

void executor::http_connection_report(std::string& out)
{
	char date[128];
	char buffer[4096];

	out.reserve(4096);

	snprintf(buffer, sizeof(buffer), sHtmlCommonHeader, "Connection Report", "Connection Report");
	out.append(buffer);

	jpsock* pool = pick_pool_by_id(dev_pool_id + 1);
	const char* cdate = "not connected";
	if (pool->is_running() && pool->is_logged_in())
		cdate = time_format(date, sizeof(date), tPoolConnTime);

	size_t n_calls = iPoolCallTimes.size();
	unsigned int ping_time = 0;
	if (n_calls > 1)
	{
		//Not-really-but-good-enough median
		std::nth_element(iPoolCallTimes.begin(), iPoolCallTimes.begin() + n_calls/2, iPoolCallTimes.end());
		ping_time = iPoolCallTimes[n_calls/2];
	}

	snprintf(buffer, sizeof(buffer), sHtmlConnectionBodyHigh,
		jconf::inst()->GetPoolAddress(),
		cdate, ping_time);
	out.append(buffer);


	for(size_t i=0; i < vSocketLog.size(); i++)
	{
		snprintf(buffer, sizeof(buffer), sHtmlConnectionTableRow,
			time_format(date, sizeof(date), vSocketLog[i].time), vSocketLog[i].msg.c_str());
		out.append(buffer);
	}

	out.append(sHtmlConnectionBodyLow);
}

void executor::http_report(ex_event_name ev)
{
	assert(pHttpString != nullptr);

	switch(ev)
	{
	case EV_HTML_HASHRATE:
		http_hashrate_report(*pHttpString);
		break;

	case EV_HTML_RESULTS:
		http_result_report(*pHttpString);
		break;

	case EV_HTML_CONNSTAT:
		http_connection_report(*pHttpString);
		break;
	default:
		assert(false);
		break;
	}

	httpReady.set_value();
}

void executor::get_http_report(ex_event_name ev_id, std::string& data)
{
	std::lock_guard<std::mutex> lck(httpMutex);

	assert(pHttpString == nullptr);
	assert(ev_id == EV_HTML_HASHRATE || ev_id == EV_HTML_RESULTS || ev_id == EV_HTML_CONNSTAT);

	pHttpString = &data;
	httpReady = std::promise<void>();
	std::future<void> ready = httpReady.get_future();

	push_event(ex_event(ev_id));

	ready.wait();
	pHttpString = nullptr;
}
