
#include "stratum.h"

//void check_job(VEXON_JOB *job)
//{
//	if(job->coind && job->remote)
//	{
//		debuglog("error memory\n");
//	}
//}

static VEXON_WORKER *share_find_worker(VEXON_CLIENT *client, VEXON_JOB *job, bool valid)
{
	for(CLI li = g_list_worker.first; li; li = li->next)
	{
		VEXON_WORKER *worker = (VEXON_WORKER *)li->data;
		if(worker->deleted) continue;

		if(	worker->userid == client->userid &&
			worker->workerid == client->workerid &&
			worker->valid == valid)
		{
			if(!job && !worker->coinid && !worker->remoteid)
				return worker;

			else if(!job)
				continue;

			else if((job->coind && worker->coinid == job->coind->id) ||
				(job->remote && worker->remoteid == job->remote->id))
				return worker;
		}
	}

	return NULL;
}

static void share_add_worker(VEXON_CLIENT *client, VEXON_JOB *job, bool valid, char *ntime, double share_diff, int error_number)
{
//	check_job(job);
	g_list_worker.Enter();

	VEXON_WORKER *worker = share_find_worker(client, job, valid);
	if(!worker)
	{
		worker = new VEXON_WORKER;
		memset(worker, 0, sizeof(VEXON_WORKER));

		worker->userid = client->userid;
		worker->workerid = client->workerid;
		worker->coinid = job? (job->coind? job->coind->id: 0): 0;
		worker->remoteid = job? (job->remote? job->remote->id: 0): 0;
		worker->valid = valid;
		worker->error_number = error_number;
		sscanf(ntime, "%x", &worker->ntime);
		worker->share_diff = share_diff;

		if(g_stratum_reconnect)
			worker->extranonce1 = !client->reconnecting && (client->reconnectable || client->extranonce_subscribe);
		else
			worker->extranonce1 = client->extranonce_subscribe;

		g_list_worker.AddTail(worker);
	}

	if(valid)
	{
		worker->difficulty += client->difficulty_actual / g_current_algo->diff_multiplier;
		client->speed += client->difficulty_actual / g_current_algo->diff_multiplier * 42;
	//	client->source->speed += client->difficulty_actual / g_current_algo->diff_multiplier * 42;
	}

	g_list_worker.Leave();
}

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

void share_add(VEXON_CLIENT *client, VEXON_JOB *job, bool valid, char *extranonce2, char *ntime, char *nonce, double share_diff, int error_number)
{
//	check_job(job);
	g_shares_counter++;
	share_add_worker(client, job, valid, ntime, share_diff, error_number);

	VEXON_SHARE *share = new VEXON_SHARE;
	memset(share, 0, sizeof(VEXON_SHARE));

	share->jobid = job? job->id: 0;
	strcpy(share->extranonce2, extranonce2);
	strcpy(share->ntime, ntime);
	strcpy(share->nonce, nonce);
	strcpy(share->nonce1, client->extranonce1);

	g_list_share.AddTail(share);
}

VEXON_SHARE *share_find(int jobid, char *extranonce2, char *ntime, char *nonce, char *nonce1)
{
	g_list_share.Enter();
	for(CLI li = g_list_share.first; li; li = li->next)
	{
		VEXON_SHARE *share = (VEXON_SHARE *)li->data;
		if(share->deleted) continue;

		if(	share->jobid == jobid &&
			!strcmp(share->extranonce2, extranonce2) && !strcmp(share->ntime, ntime) &&
			!strcmp(share->nonce, nonce) && !strcmp(share->nonce1, nonce1))
		{
			g_list_share.Leave();
			return share;
		}
	}

	g_list_share.Leave();
	return NULL;
}

void share_write(VEXON_DB *db)
{
	int pid = getpid();
	int count = 0;
	int now = time(NULL);

	char buffer[1024*1024] = "insert into shares (userid, workerid, coinid, jobid, pid, valid, extranonce1, difficulty, share_diff, time, algo, error) values ";
	g_list_worker.Enter();

	for(CLI li = g_list_worker.first; li; li = li->next)
	{
		VEXON_WORKER *worker = (VEXON_WORKER *)li->data;
		if(worker->deleted) continue;

		if(!worker->workerid) {
			object_delete(worker);
			continue;
		}

		if(count) strcat(buffer, ",");
		sprintf(buffer+strlen(buffer), "(%d, %d, %d, %d, %d, %d, %d, %f, %f, %d, '%s', %d)",
			worker->userid, worker->workerid, worker->coinid, worker->remoteid, pid,
			worker->valid, worker->extranonce1, worker->difficulty, worker->share_diff, now, g_stratum_algo, worker->error_number);

		// todo: link max_ttf ?
		if((now - worker->ntime) > 15*60 || worker->ntime > now) {
			debuglog("ntime warning: value %d (%08x) offset %d secs from uid %d\n", worker->ntime, worker->ntime, (now - worker->ntime), worker->userid);
		}

		if(++count >= 1000)
		{
			db_query(db, buffer);

			strcpy(buffer, "insert into shares (userid, workerid, coinid, jobid, pid, valid, extranonce1, difficulty, share_diff, time, algo, error) values ");
			count = 0;
		}

		object_delete(worker);
	}

	g_list_worker.Leave();
	if(count) db_query(db, buffer);
}

void share_prune(VEXON_DB *db)
{
	g_list_share.Enter();
	for(CLI li = g_list_share.first; li; li = li->next)
	{
		VEXON_SHARE *share = (VEXON_SHARE *)li->data;
		if(share->deleted) continue;

		VEXON_JOB *job = (VEXON_JOB *)object_find(&g_list_job, share->jobid);
		if(job) continue;

		object_delete(share);
	}

	g_list_share.Leave();
}

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

void block_prune(VEXON_DB *db)
{
	int count = 0;
	char buffer[128*1024] = "insert into blocks (height, blockhash, coin_id, userid, workerid, category, difficulty, difficulty_user, time, algo, segwit) values ";

	g_list_block.Enter();
	for(CLI li = g_list_block.first; li; li = li->next)
	{
		VEXON_BLOCK *block = (VEXON_BLOCK *)li->data;
		if(!block->confirmed)
		{
			int elapsed = 30;
			// slow block time...
			if(g_stratum_algo && !strcmp(g_stratum_algo, "decred")) elapsed = 60 * 15; // 15mn

			if((block->created + elapsed) < time(NULL))
				object_delete(block);

			continue;
		}

		if(count) strcat(buffer, ",");
		sprintf(buffer+strlen(buffer), "(%d, '%s', %d, %d, %d, 'new', %f, %f, %d, '%s', %d)",
			block->height, block->hash, block->coinid, block->userid, block->workerid,
			block->difficulty, block->difficulty_user, (int)block->created, g_stratum_algo, block->segwit?1:0);

		object_delete(block);
		count++;
	}

	g_list_block.Leave();
	if(count) db_query(db, buffer);
}

void block_add(int userid, int workerid, int coinid, int height, double diff, double diff_user, const char *h1, const char *h2, int segwit)
{
	VEXON_BLOCK *block = new VEXON_BLOCK;
	memset(block, 0, sizeof(VEXON_BLOCK));

	block->created = time(NULL);
	block->userid = userid;
	block->workerid = workerid;
	block->coinid = coinid;
	block->height = height;
	block->difficulty = diff;
	block->difficulty_user = diff_user;
	block->segwit = segwit;

	strcpy(block->hash1, h1);
	strcpy(block->hash2, h2);

	g_list_block.AddTail(block);
}

// called from blocknotify tool
bool block_confirm(int coinid, const char *blockhash)
{
	char hash[192];
	if(strlen(blockhash) < 64) return false;

	snprintf(hash, 161, "%s", blockhash);

	// required for multi algos wallets where pow hash is not the blockhash
	g_list_coind.Enter();
	for(CLI li = g_list_coind.first; li ; li = li->next)
	{
		VEXON_COIND *coind = (VEXON_COIND *)li->data;
		if(coind->id != coinid || coind->deleted) continue;

		if(coind->multialgos) {
			char params[192];
			sprintf(params, "[\"%s\"]", blockhash);
			json_value *json = rpc_call(&coind->rpc, "getblock", params);
			if(!json) {
				debuglog("%s: error getblock, no answer\n", __func__);
				break;
			}
			json_value *json_res = json_get_object(json, "result");
			if(!json_res) {
				debuglog("%s: error getblock, no result\n", __func__);
				break;
			}
			const char *h1 = json_get_string(json_res, "pow_hash"); // DGB, MYR, J
			const char *h2 = json_get_string(json_res, "mined_hash"); // XVG
			const char *h3 = json_get_string(json_res, "phash"); // XSH
			if (h1) snprintf(hash, 161, "%s", h1);
			else if (h2) snprintf(hash, 161, "%s", h2);
			else if (h3) snprintf(hash, 161, "%s", h3);
			//debuglog("%s: getblock %s -> pow %s\n", __func__, blockhash, hash);
			json_value_free(json);
			break;
		} else if (strcmp(coind->symbol,"ORB") == 0) {
			char params[192];
			sprintf(params, "[\"%s\"]", blockhash);
			json_value *json = rpc_call(&coind->rpc, "getblock", params);
			if(!json) {
				debuglog("%s: error getblock, no answer\n", __func__);
				break;
			}
			json_value *json_res = json_get_object(json, "result");
			if(!json_res) {
				debuglog("%s: error getblock, no result\n", __func__);
				break;
			}
			const char *h = json_get_string(json_res, "proofhash");
			if (h) snprintf(hash, 161, "%s", h);
			json_value_free(json);
			break;
		}
	}
	g_list_coind.Leave();

	for(CLI li = g_list_block.first; li; li = li->next)
	{
		VEXON_BLOCK *block = (VEXON_BLOCK *)li->data;
		if(block->coinid == coinid && !block->deleted)
		{
			if(strcmp(block->hash1, hash) && strcmp(block->hash2, hash)) continue;
			if (!block->confirmed) {
				debuglog("*** CONFIRMED %d : %s\n", block->height, block->hash2);
				strncpy(block->hash, blockhash, 65);
				block->confirmed = true;
			}
			return true;
		}
	}
	return false;
}

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

VEXON_SUBMIT *submit_add(int remoteid, double difficulty)
{
	VEXON_SUBMIT *submit = new VEXON_SUBMIT;
	memset(submit, 0, sizeof(VEXON_SUBMIT));

	submit->created = time(NULL);
	submit->valid = true;
	submit->remoteid = remoteid;
	submit->difficulty = difficulty / g_current_algo->diff_multiplier;

	g_list_submit.AddTail(submit);
	return submit;
}

void submit_prune(VEXON_DB *db)
{
	int count = 0;
	char buffer[128*1024] = "insert into jobsubmits (jobid, valid, difficulty, time, algo, status) values ";

	g_list_submit.Enter();
	for(CLI li = g_list_submit.first; li; li = li->next)
	{
		VEXON_SUBMIT *submit = (VEXON_SUBMIT *)li->data;

		if(count) strcat(buffer, ",");
		sprintf(buffer+strlen(buffer), "(%d, %d, %f, %d, '%s', 0)", submit->remoteid, submit->valid,
			submit->difficulty, (int)submit->created, g_stratum_algo);

		if(++count >= 1000)
		{
			db_query(db, buffer);

			strcpy(buffer, "insert into jobsubmits (jobid, valid, difficulty, time, algo, status) values ");
			count = 0;
		}

		object_delete(submit);
	}

	g_list_submit.Leave();
	if(count) db_query(db, buffer);
}


