
#include "stratum.h"

//client->difficulty_remote = 0;
//debuglog(" returning %x, %s, %s\n", job->id, client->sock->ip, #condition); \

#define RETURN_ON_CONDITION(condition, ret) \
	if(condition) \
	{ \
		return ret; \
	}

static bool job_assign_client(VEXON_JOB *job, VEXON_CLIENT *client, double maxhash)
{
	RETURN_ON_CONDITION(client->deleted, true);
	RETURN_ON_CONDITION(client->jobid_next, true);
	RETURN_ON_CONDITION(client->jobid_locked && client->jobid_locked != job->id, true);
	RETURN_ON_CONDITION(client_find_job_history(client, job->id), true);
	RETURN_ON_CONDITION(maxhash > 0 && job->speed + client->speed > maxhash, true);

	if(!g_autoexchange && maxhash >= 0. && client->coinid != job->coind->id) {
		//debuglog("prevent client %c on %s, not the right coin\n",
		//	client->username[0], job->coind->symbol);
		return true;
	}

	if(job->remote)
	{
		VEXON_REMOTE *remote = job->remote;

		if(g_stratum_reconnect)
			{RETURN_ON_CONDITION(!client->extranonce_subscribe && !client->reconnectable, true);}
		else
			{RETURN_ON_CONDITION(!client->extranonce_subscribe, true);}

		RETURN_ON_CONDITION(client->reconnecting, true);
		RETURN_ON_CONDITION(job->count >= VEXON_JOB_MAXSUBIDS, false);
//		RETURN_ON_CONDITION(client->difficulty_actual > remote->difficulty_actual, false);

		double difficulty_remote = client->difficulty_remote;
		if(remote->difficulty_actual < client->difficulty_actual)
		{
			RETURN_ON_CONDITION(client->difficulty_fixed, true);
			RETURN_ON_CONDITION(remote->difficulty_actual*4 < client->difficulty_actual, true);

			difficulty_remote = remote->difficulty_actual;
		}

		else if(remote->difficulty_actual > client->difficulty_actual)
			difficulty_remote = 0;

		if(remote->nonce2size == 2)
		{
			RETURN_ON_CONDITION(job->count > 0, false);

			strcpy(client->extranonce1, remote->nonce1);
			client->extranonce2size = 2;
		}

		else if(job->id != client->jobid_sent)
		{
			if(!job->remote_subids[client->extranonce1_id])
				job->remote_subids[client->extranonce1_id] = true;

			else
			{
				int i=0;
				for(; i<VEXON_JOB_MAXSUBIDS; i++) if(!job->remote_subids[i])
				{
					job->remote_subids[i] = true;
					client->extranonce1_id = i;

					break;
				}

				RETURN_ON_CONDITION(i == VEXON_JOB_MAXSUBIDS, false);
			}

			sprintf(client->extranonce1, "%s%02x", remote->nonce1, client->extranonce1_id);
			client->extranonce2size = remote->nonce2size-1;
			client->difficulty_remote = difficulty_remote;
		}

		client->jobid_locked = job->id;
	}

	else
	{
		strcpy(client->extranonce1, client->extranonce1_default);
		client->extranonce2size = client->extranonce2size_default;

		// decred uses an extradata field in block header, 2 first uint32 are set by the miner
		if (g_current_algo->name && !strcmp(g_current_algo->name,"NOEXTRADATA")) {
			memset(client->extranonce1, '0', sizeof(client->extranonce1));
			memcpy(&client->extranonce1[16], client->extranonce1_default, 8);
			client->extranonce1[24] = '\0';
		}

		client->difficulty_remote = 0;
		client->jobid_locked = 0;
	}

	client->jobid_next = job->id;

	job->speed += client->speed;
	job->count++;

//	debuglog(" assign %x, %f, %d, %s\n", job->id, client->speed, client->reconnecting, client->sock->ip);
	if(strcmp(client->extranonce1, client->extranonce1_last) || client->extranonce2size != client->extranonce2size_last)
	{
//		debuglog("new nonce %x %s %s\n", job->id, client->extranonce1_last, client->extranonce1);
		bool relaxed_decred = !job->remote && g_decred_relaxed_extranonce &&
			g_current_algo->name && !strcmp(g_current_algo->name, "decred");

		if(!client->extranonce_subscribe && !relaxed_decred)
		{
			strcpy(client->extranonce1_reconnect, client->extranonce1);
			client->extranonce2size_reconnect = client->extranonce2size;

			strcpy(client->extranonce1, client->extranonce1_default);
			client->extranonce2size = client->extranonce2size_default;

			client->reconnecting = true;
			client->lock_count++;
			client->unlock = true;
			client->jobid_sent = client->jobid_next;

			socket_send(client->sock, "{\"id\":null,\"method\":\"client.reconnect\",\"params\":[\"%s\",%d,0]}\n", g_tcp_server, g_tcp_port);
		}

		else
		{
			strcpy(client->extranonce1_last, client->extranonce1);
			client->extranonce2size_last = client->extranonce2size;

			if(client->extranonce_subscribe)
				socket_send(client->sock, "{\"id\":null,\"method\":\"mining.set_extranonce\",\"params\":[\"%s\",%d]}\n",
					client->extranonce1, client->extranonce2size);
		}
	}

	return true;
}

void job_assign_clients(VEXON_JOB *job, double maxhash)
{
	if (!job) return;

	job->speed = 0;
	job->count = 0;

	g_list_client.Enter();

	// pass0 locked
	for(CLI li = g_list_client.first; li; li = li->next)
	{
		VEXON_CLIENT *client = (VEXON_CLIENT *)li->data;
		if(client->jobid_locked && client->jobid_locked != job->id) continue;

		bool b = job_assign_client(job, client, maxhash);
		if(!b) break;
	}

	// pass1 sent
	for(CLI li = g_list_client.first; li; li = li->next)
	{
		VEXON_CLIENT *client = (VEXON_CLIENT *)li->data;
		if(client->jobid_sent != job->id) continue;

		bool b = job_assign_client(job, client, maxhash);
		if(!b) break;
	}

	// pass2 extranonce_subscribe
	if(job->remote)	for(CLI li = g_list_client.first; li; li = li->next)
	{
		VEXON_CLIENT *client = (VEXON_CLIENT *)li->data;
		if(!client->extranonce_subscribe) continue;

		bool b = job_assign_client(job, client, maxhash);
		if(!b) break;
	}

	// pass3 the rest
	for(CLI li = g_list_client.first; li; li = li->next)
	{
		VEXON_CLIENT *client = (VEXON_CLIENT *)li->data;

		bool b = job_assign_client(job, client, maxhash);
		if(!b) break;
	}

	g_list_client.Leave();
}

void job_assign_clients_left(double factor)
{
	bool b;
	for(CLI li = g_list_coind.first; li; li = li->next)
	{
		if(!job_has_free_client()) return;

		VEXON_COIND *coind = (VEXON_COIND *)li->data;
		if(!coind_can_mine(coind)) continue;
		if(!coind->job) continue;

		double nethash = coind_nethash(coind);
		g_list_client.Enter();

		for(CLI li = g_list_client.first; li; li = li->next)
		{
			VEXON_CLIENT *client = (VEXON_CLIENT *)li->data;
			if (!g_autoexchange) {
				if (client->coinid == coind->id)
					factor = 100.;
				else
					factor = 0.;
			}

			//debuglog("%s %s factor %f nethash %.3f\n", coind->symbol, client->username, factor, nethash);

			if (factor > 0.) {
				b = job_assign_client(coind->job, client, nethash*factor);
				if(!b) break;
			}
		}

		g_list_client.Leave();
	}
}

////////////////////////////////////////////////////////////////////////

pthread_mutex_t g_job_mutex;
pthread_cond_t g_job_cond;

void *job_thread(void *p)
{
	CommonLock(&g_job_mutex);
	while(!g_exiting)
	{
		job_update();
		pthread_cond_wait(&g_job_cond, &g_job_mutex);
	}
}

void job_init()
{
	pthread_mutex_init(&g_job_mutex, 0);
	pthread_cond_init(&g_job_cond, 0);

	pthread_t thread3;
	pthread_create(&thread3, NULL, job_thread, NULL);
}

void job_signal()
{
	CommonLock(&g_job_mutex);
	pthread_cond_signal(&g_job_cond);
	CommonUnlock(&g_job_mutex);
}

void job_update()
{
//	debuglog("job_update()\n");
	job_reset_clients();

	//////////////////////////////////////////////////////////////////////////////////////////////////////

	g_list_job.Enter();
	job_sort();

	for(CLI li = g_list_job.first; li; li = li->next)
	{
		VEXON_JOB *job = (VEXON_JOB *)li->data;
		if(!job_can_mine(job)) continue;

		job_assign_clients(job, job->maxspeed);
		job_unlock_clients(job);

		if(!job_has_free_client()) break;
	}

	job_unlock_clients();
	g_list_job.Leave();

	////////////////////////////////////////////////////////////////////////////////////////////////

	g_list_coind.Enter();
	coind_sort();

	job_assign_clients_left(1);
	job_assign_clients_left(1);
	job_assign_clients_left(-1);

	g_list_coind.Leave();

	////////////////////////////////////////////////////////////////////////////////////////////////

	int no_job_coinid = 0;
	char no_job_username[256] = { 0 };
	VEXON_CLIENT *no_job_client = NULL;

	g_list_client.Enter();
	for(CLI li = g_list_client.first; li; li = li->next)
	{
		VEXON_CLIENT *client = (VEXON_CLIENT *)li->data;
		if(client->deleted) continue;
		if(client->jobid_next) continue;
		if(!client->coinid) continue;

		debuglog("clients with no job\n");
		g_current_algo->overflow = true;
		no_job_coinid = client->coinid;
		strncpy(no_job_username, client->username, sizeof(no_job_username)-1);
		no_job_client = client;
		object_lock(no_job_client);
		break;
	}
	g_list_client.Leave();

	if(no_job_coinid)
	{
		VEXON_COIND *coind = (VEXON_COIND *)object_find(&g_list_coind, no_job_coinid, true);
		if(coind)
		{
			debuglog("client %s has no job, creating job for coin %s\n", no_job_username, coind->symbol);
			job_reset_clients(coind->job);
			bool created = coind_create_job(coind, true);
			if(created && coind->job)
			{
				job_assign_client(coind->job, no_job_client, -1);
				debuglog("%s: direct job assignment for %s job=%x count=%d next=%x sent=%x\n",
					coind->symbol, no_job_username, coind->job->id, coind->job->count,
					no_job_client->jobid_next, no_job_client->jobid_sent);
				job_broadcast(coind->job);
			}
			else
				debuglog("%s: forced job creation failed for client %s\n", coind->symbol, no_job_username);
			object_unlock(coind);
		}
	}

	if(no_job_client)
		object_unlock(no_job_client);

	////////////////////////////////////////////////////////////////////////////////////////////////

//	usleep(100*VEXON_MS);

//	int ready = 0;
//	debuglog("job_update\n");

	g_list_job.Enter();
	for(CLI li = g_list_job.first; li; li = li->next)
	{
		VEXON_JOB *job = (VEXON_JOB *)li->data;
		if(!job_can_mine(job)) continue;

		job_broadcast(job);
//		ready++;
	}

//	debuglog("job_update %d / %d jobs\n", ready, g_list_job.count);
	g_list_job.Leave();

}




