#define MODULE_LOG_PREFIX "anticasc"

//FIXME Not checked on threadsafety yet; after checking please remove this line
#include "globals.h"

#ifdef CS_ANTICASC

#include "module-anticasc.h"
#include "oscam-conf.h"
#include "oscam-client.h"
#include "oscam-garbage.h"
#include "oscam-string.h"
#include "oscam-time.h"

#define cs_ac "oscam.ac"

static FILE *ac_log;
static uint8_t ac_ecmd5[CS_ECMSTORESIZE];

bool anticasc_logging(char *txt)
{
	if (!ac_log)
		return false;
	if (strstr(txt, "acasc: "))
	{
		fprintf(ac_log, "%s\n", txt);
		fflush(ac_log);
		return true;
	}
	return false;
}

static int32_t ac_init_log(void)
{
	if(ac_log)
		{ return 1; }
	if(!cfg.ac_logfile)
	{
		cs_log("ERROR: anti cascading is enabled but ac_logfile is not set.");
		return 0;
	}
	ac_log = fopen(cfg.ac_logfile, "a+");
	if(!ac_log)
	{
		cs_log("ERROR: Can't open anti cascading logfile: %s (errno=%d %s)",
			   cfg.ac_logfile, errno, strerror(errno));
		return 0;
	}
	cs_log("anti cascading log initialized");
	return 1;
}


void ac_clear(void)
{
	struct s_client *client;
	struct s_auth *account;

	for(client = first_client; client; client = client->next)
	{
		if(client->typ != 'c') { continue; }
		memset(&client->acasc, 0, sizeof(client->acasc));
	}

	for(account = cfg.account; account; account = account->next)
		{ memset(&account->ac_stat, 0, sizeof(account->ac_stat)); }
}

void ac_init_stat(void)
{
	if(!cfg.ac_enabled)
		{ return; }
	ac_clear();
	ac_init_log();
}

void ac_do_stat(void)
{
	int32_t j, idx, exceeds, maxval, prev_deny = 0;

	struct s_client *client;
	for(client = first_client; client; client = client->next)
	{
		if(client->typ != 'c') { continue; }

		struct s_acasc *ac_stat = &client->account->ac_stat;
		struct s_acasc_shm *acasc = &client->acasc;

		idx = ac_stat->idx;
		ac_stat->stat[idx] = acasc->ac_count;
		acasc->ac_count = 0;

		if(ac_stat->stat[idx])
		{
			if(client->ac_penalty == 2)    // banned
			{
				cs_log_dbg(D_CLIENT, "acasc: user '%s' banned", client->account->usr);
				acasc->ac_deny = 1;
			}
			else
			{
				for(j = exceeds = maxval = 0; j < cfg.ac_samples; j++)
				{
					if(ac_stat->stat[j] > maxval)
						{ maxval = ac_stat->stat[j]; }
					exceeds += (ac_stat->stat[j] > client->ac_limit);
				}
				prev_deny = acasc->ac_deny;
				acasc->ac_deny = (exceeds >= cfg.ac_denysamples);

				cs_log_dbg(D_CLIENT, "acasc: %s limit=%d, max=%d, samples=%d, dsamples=%d, [idx=%d]:",
							  client->account->usr, client->ac_limit, maxval,
							  cfg.ac_samples, cfg.ac_denysamples, idx);
				cs_log_dbg(D_CLIENT, "acasc: %d %d %d %d %d %d %d %d %d %d ", ac_stat->stat[0],
							  ac_stat->stat[1], ac_stat->stat[2], ac_stat->stat[3],
							  ac_stat->stat[4], ac_stat->stat[5], ac_stat->stat[6],
							  ac_stat->stat[7], ac_stat->stat[8], ac_stat->stat[9]);
				if(acasc->ac_deny)
				{
					cs_log("acasc: user '%s' exceeds limit", client->account->usr);
					ac_stat->stat[idx] = 0;
				}
				else if(prev_deny)
					{ cs_log("acasc: user '%s' restored access", client->account->usr); }
			}
		}
		else if(acasc->ac_deny)
		{
			prev_deny = 1;
			acasc->ac_deny = 0;
			cs_log("acasc: restored access for inactive user '%s'", client->account->usr);
		}

		if(!acasc->ac_deny && !prev_deny)
			{ ac_stat->idx = (ac_stat->idx + 1) % cfg.ac_samples; }
	}
}

void ac_init_client(struct s_client *client, struct s_auth *account)
{
	client->ac_limit = 0;
	client->ac_penalty = account->ac_penalty == -1 ? cfg.ac_penalty : account->ac_penalty;
	client->ac_fakedelay = account->ac_fakedelay == -1 ? cfg.ac_fakedelay : account->ac_fakedelay;
	if(cfg.ac_enabled)
	{
		int32_t numusers = account->ac_users;
		if(numusers == -1)
			{ numusers = cfg.ac_users; }

		if(numusers)
		{
			client->ac_limit = (numusers * 100 + 80) * cfg.ac_stime;
			cs_log_dbg(D_CLIENT, "acasc: user '%s', users=%d, stime=%d min, dwlimit=%d per min, penalty=%d",
						  account->usr, numusers, cfg.ac_stime,
						  numusers * 100 + 80, client->ac_penalty);
		}
		else
		{
			cs_log_dbg(D_CLIENT, "acasc: anti-cascading not used for user '%s'", account->usr);
		}
	}
}

static int32_t ac_dw_weight(ECM_REQUEST *er)
{
	struct s_cpmap *cpmap;

	for(cpmap = cfg.cpmap; (cpmap) ; cpmap = cpmap->next)
		if((cpmap->caid  == 0 || cpmap->caid  == er->caid)  &&
				(cpmap->provid == 0 || cpmap->provid == er->prid)  &&
				(cpmap->sid   == 0 || cpmap->sid   == er->srvid) &&
				(cpmap->chid  == 0 || cpmap->chid  == er->chid))
			{ return (cpmap->dwtime * 100 / 60); }

	cs_log_dbg(D_CLIENT, "acasc: WARNING: CAID %04X, PROVID %06X, SID %04X, CHID %04X not found in oscam.ac",
				  er->caid, er->prid, er->srvid, er->chid);
	cs_log_dbg(D_CLIENT, "acasc: set DW lifetime 10 sec");
	return 16; // 10*100/60
}

void ac_chk(struct s_client *cl, ECM_REQUEST *er, int32_t level)
{
	if(!cl->ac_limit || !cfg.ac_enabled) { return; }

	struct s_acasc_shm *acasc = &cl->acasc;

	if(level == 1)
	{
		if(er->rc == E_FAKE)
			{ acasc->ac_count++; }

		if(er->rc >= E_NOTFOUND)
			{ return; } // not found

		if(memcmp(ac_ecmd5, er->ecmd5, CS_ECMSTORESIZE) != 0)
		{
			acasc->ac_count += ac_dw_weight(er);
			memcpy(ac_ecmd5, er->ecmd5, CS_ECMSTORESIZE);
		}
		return;
	}

	if(acasc->ac_deny)
	{
		if(cl->ac_penalty)
		{
			if(cl->ac_penalty == 3)
			{
				if(cl->ac_fakedelay > 0)
					{ cs_log_dbg(D_CLIENT, "acasc: fake delay %d ms", cl->ac_fakedelay); }
			}
			else
			{
				cs_log_dbg(D_CLIENT, "acasc: send fake dw");
				er->rc = E_FAKE; // fake
				er->rcEx = 0;
			}
			if(cl->ac_fakedelay > 0)
				{ cs_sleepms(cl->ac_fakedelay); }
		}
	}
}

static void ac_load_config(void)
{
	FILE *fp = open_config_file(cs_ac);
	if(!fp)
		{ return; }

	int32_t nr;
	char *saveptr1 = NULL, *token;
	if(!cs_malloc(&token, MAXLINESIZE))
		{ return; }
	struct s_cpmap *cur_cpmap, *first_cpmap = NULL, *last_cpmap = NULL;

	for(nr = 0; fgets(token, MAXLINESIZE, fp);)
	{
		int32_t i, skip;
		uint16_t caid, sid, chid, dwtime;
		uint32_t  provid;
		char *ptr, *ptr1;

		if(cs_strlen(token) < 4) { continue; }

		caid = sid = chid = dwtime = 0;
		provid = 0;
		skip = 0;
		ptr1 = 0;
		for(i = 0, ptr = strtok_r(token, "=", &saveptr1); (i < 2) && (ptr); ptr = strtok_r(NULL, "=", &saveptr1), i++)
		{
			trim(ptr);
			if(*ptr == ';' || *ptr == '#' || *ptr == '-')
			{
				skip = 1;
				break;
			}
			switch(i)
			{
			case 0:
				ptr1 = ptr;
				break;
			case 1:
				dwtime = atoi(ptr);
				break;
			}
		}

		if(!skip)
		{
			for(i = 0, ptr = strtok_r(ptr1, ":", &saveptr1); (i < 4) && (ptr); ptr = strtok_r(NULL, ":", &saveptr1), i++)
			{
				trim(ptr);
				switch(i)
				{
				case 0:
					if(*ptr == '*') { caid = 0; }
					else { caid = a2i(ptr, 4); }
					break;
				case 1:
					if(*ptr == '*') { provid = 0; }
					else { provid = a2i(ptr, 6); }
					break;
				case 2:
					if(*ptr == '*') { sid = 0; }
					else { sid = a2i(ptr, 4); }
					break;
				case 3:
					if(*ptr == '*') { chid = 0; }
					else { chid = a2i(ptr, 4); }
					break;
				}
			}
			if(!cs_malloc(&cur_cpmap, sizeof(struct s_cpmap)))
			{
				for(cur_cpmap = first_cpmap; cur_cpmap;)
				{
					last_cpmap = cur_cpmap;
					cur_cpmap = cur_cpmap->next;
					NULLFREE(last_cpmap);
				}
				NULLFREE(token);
				return;
			}
			if(last_cpmap)
				{ last_cpmap->next = cur_cpmap; }
			else
				{ first_cpmap = cur_cpmap; }
			last_cpmap = cur_cpmap;

			cur_cpmap->caid   = caid;
			cur_cpmap->provid = provid;
			cur_cpmap->sid    = sid;
			cur_cpmap->chid   = chid;
			cur_cpmap->dwtime = dwtime;
			cur_cpmap->next   = 0;

			cs_log_dbg(D_CLIENT, "nr=%d, caid=%04X, provid=%06X, sid=%04X, chid=%04X, dwtime=%d",
						  nr, caid, provid, sid, chid, dwtime);
			nr++;
		}
	}
	NULLFREE(token);
	fclose(fp);

	last_cpmap = cfg.cpmap;
	cfg.cpmap = first_cpmap;
	for(cur_cpmap = last_cpmap; cur_cpmap; cur_cpmap = cur_cpmap->next)
		{ add_garbage(cur_cpmap); }
	//cs_log("%d lengths for caid guessing loaded", nr);
	return;
}

void ac_copy_vars(struct s_auth *src, struct s_auth *dst)
{
	dst->ac_stat    = src->ac_stat;
}

void ac_init(void)
{
	if(!cfg.ac_enabled)
	{
		cs_log("anti cascading disabled");
		return;
	}

	ac_load_config();
	ac_init_stat();
}

int8_t get_caid_weight(ECM_REQUEST *er)
{
	switch(er->caid)
	{
		case 0x0100:
			switch (er->prid)
			{
				case 0x00003D:
					return 20;
				case 0x000065:
					return 7;
				default:
					return 10;
			}
		case 0x0500:
			switch(er->prid)
			{
				case 0x020910:
					return 30;
				case 0x024400:
				case 0x032830:
					return 10;
				case 0x043800:
					return 25;
				default:
					return 15;
			}
		case 0x0604:
			return 11;
		case 0x1702:
		case 0x1722:
		case 0x1833:
		case 0x09C4:
			switch(er->srvid)
			{
				case 0x0022: // Disney Channel
				case 0x0016: // Heimatkanal
				case 0x0203: // MGM
				case 0x0008: // Sky Comedy
				case 0x002B: // Sky Cinema +24
				case 0x000B: // Sky Cinema +1
				case 0x0009: // Sky Action
				case 0x000A: // Sky Cinema
				case 0x0014: // Sky Emotion
				case 0x0029: // Sky Hits
				case 0x0204: // Sky Nostalgie
				case 0x0011: // Sky Sport News
				case 0x0024: // Syfy
					return 10;
				default:
					return 7;
			}
		case 0x1830:
			return 15;
		case 0x1843:
		case 0x1834:
		case 0x09C7:
			return 7;
		case 0x4A70:
			return 14;
		case 0x183D:
			return 13;
		case 0x1810:
		case 0x0D05:
		case 0x0D95:
		case 0x093B:
		case 0x098C:
		case 0x098D:
		case 0x0B00:
		default:
			return 10;

	}
}

void insert_zaplist(ECM_REQUEST *er, struct s_client *client)
{
	bool new_zaplist_entry = false;
	int8_t zap_caid_weight;
	zap_caid_weight = get_caid_weight(er);
	int8_t k = 0;
	bool found = false;
	time_t zaptime = time(NULL);

	for(k=0; k<15 ; k++)
	{
		if(er->caid == client->client_zap_list[k].caid && er->prid == client->client_zap_list[k].provid && er->chid == client->client_zap_list[k].chid && er->srvid == client->client_zap_list[k].sid) //found
		{
			if(zaptime-zap_caid_weight*2 < client->client_zap_list[k].lasttime)
			{
				cs_log_dbg(D_TRACE, "[zaplist] update Entry [%i] for Client: %s  %04X@%06X/%04X/%04X TIME: %ld Diff: %ld zcw: %i(%i)", k, username(client), er->caid, er->prid, er->chid, er->srvid, zaptime, zaptime-client->client_zap_list[k].lasttime, zap_caid_weight, zap_caid_weight*2);
				client->client_zap_list[k].lasttime = zaptime;
				if(client->client_zap_list[k].request_stage < 10)
				{
					client->client_zap_list[k].request_stage ++;
				}
				found = true;
				break;
			}
		}
	}

	if(!found)
	{
		for(k=0; k<15 ; k++)
		{
			if(zaptime-30 > client->client_zap_list[k].lasttime) //make a new Entry and use a memoryplace of a old entry
			{
				client->client_zap_list[k].caid = er->caid;
				client->client_zap_list[k].provid = er->prid;
				client->client_zap_list[k].chid = er->chid;
				client->client_zap_list[k].sid = er->srvid;
				client->client_zap_list[k].request_stage = 1; //need for ACoSC
				client->client_zap_list[k].lasttime = zaptime;
				cs_log_dbg(D_TRACE, "[zaplist] new Entry [%i] for Client: %s  %04X@%06X/%04X/%04X TIME: %ld", k, username(client), er->caid, er->prid, er->chid, er->srvid, zaptime);
				new_zaplist_entry = true;
				break;
			}
		}
		if(!new_zaplist_entry)
			{ cs_log_dbg(D_TRACE, "[zaplist] no free slot for client: %s", username(client)); }

		if(client->account->acosc_user_zap_count_start_time+60 > zaptime)
			{ client->account->acosc_user_zap_count ++; }
		else
		{
			client->account->acosc_user_zap_count_start_time = zaptime;
			client->account->acosc_user_zap_count = 0;
			cs_log_dbg(D_TRACE, "[zaplist] Client: %s reset acosc_user_zap_count_start_time", username(client));
			for(k=0; k<15 ; k++)
			{
				if(client->client_zap_list[k].lasttime > zaptime-60)
				{
					client->account->acosc_user_zap_count ++;
				}
			}
			cs_log_dbg(D_TRACE, "[zaplist] Client: %s zap_count: %i", username(client), client->account->acosc_user_zap_count);
		}
	}
}

#endif
