#define MODULE_LOG_PREFIX "emmcache"

#include "globals.h"
#include "oscam-config.h"
#include "oscam-string.h"
#include "oscam-emm-cache.h"
#include "oscam-files.h"
#include "oscam-time.h"
#include "oscam-lock.h"
#include "cscrypt/md5.h"
#define LINESIZE 1024
#define DEFAULT_LOCK_TIMEOUT 1000000

static LLIST *emm_cache;

bool emm_cache_configured(void)
{
	struct s_reader *rdr;
	bool enable = false;
	LL_ITER itr = ll_iter_create(configured_readers);
	while((rdr = ll_iter_next(&itr)))
	{
		if(rdr->cachemm == 1)
		{
			enable = true;
		}
	}
	return enable;
}

static char *get_emmcache_filename(char *dest, size_t destlen, const char *filename)
{
	const char *slash = "";
	if(cfg.emmlogdir[strlen(cfg.emmlogdir) - 1] != '/')
	{
	slash = "/";
	}
	snprintf(dest, destlen, "%s%s%s", cfg.emmlogdir, slash, filename);
	return dest;
}

void emm_save_cache(void)
{
	if(boxtype_is("dbox2")) return; // don't save emmcache on these boxes, they lack resources and will crash!

	if(!emm_cache_configured()){
		cs_log("saving emmcache disabled since no reader is using it!");
		return;
	}

	char fname[256];
	struct timeb ts, te;

	if(cfg.emmlogdir)
	{
		get_emmcache_filename(fname, sizeof(fname), "oscam.emmcache");
	}
	else
	{
		get_config_filename(fname, sizeof(fname), "oscam.emmcache");
	}
	FILE *file = fopen(fname, "w");

	if(!file)
	{
		cs_log("can't write emmcache to file %s", fname);
		return;
	}

	cs_ftime(&ts);
	int32_t count = 0, result = 0;
	LL_ITER it = ll_iter_create(emm_cache);
	struct s_emmcache *c;

	while((c = ll_iter_next(&it)))
	{
		uint8_t tmp_emmd5[MD5_DIGEST_LENGTH * 2 + 1];
		char_to_hex(c->emmd5, MD5_DIGEST_LENGTH, tmp_emmd5);
		uint8_t tmp_emm[c->len * 2 + 1];
		char_to_hex(c->emm, c->len, tmp_emm);

		result = fprintf(file, "%s,%" PRId64 ",%" PRId64 ",%02X,%04X,%s\n", tmp_emmd5, (int64_t)c->firstseen.time, (int64_t)c->lastseen.time, c->type, c->len, tmp_emm);
		if(result < 0)
		{
			fclose(file);
			result = remove(fname);
			if(!result)
			{
				cs_log("error writing cache -> cache file removed!");
			}
			else
			{
				cs_log("error writing cache -> cache file could not be removed either!");
			}
			return;
		}
		count++;
	}

	fclose(file);
	cs_ftime(&te);
	int64_t load_time = comp_timeb(&te, &ts);
	cs_log("saved %d emmcache records to %s in %"PRId64" ms", count, fname, load_time);
}

void load_emmstat_from_file(void)
{
	if(boxtype_is("dbox2")) return; // dont load emmstat on these boxes, they lack resources and will crash!

	if(!emm_cache_configured()){
		cs_log("loading emmstats disabled since no reader is using it!");
		return;
	}

	char buf[256];
	char fname[256];
	char *line;
	FILE *file;

	if(cfg.emmlogdir)
	{
		get_emmcache_filename(fname, sizeof(fname), "oscam.emmstat");
	}
	else
	{
		get_config_filename(fname, sizeof(fname), "oscam.emmstat");
	}

	file = fopen(fname, "r");
	if(!file)
	{
		cs_log_dbg(D_TRACE, "can't read emmstats from file %s", fname);
		return;
	}

	if(!cs_malloc(&line, LINESIZE))
	{
		fclose(file);
		return;
	}

	struct timeb ts, te;
	cs_ftime(&ts);

	struct s_reader *rdr = NULL;
	struct s_emmstat *s;

	int32_t i = 1;
	int32_t valid = 0;
	int32_t count = 0;
	char *ptr, *saveptr1 = NULL;
	char *split[7];

	while(fgets(line, LINESIZE, file))
	{
		if(!line[0] || line[0] == '#' || line[0] == ';')
			{ continue; }

		if(!cs_malloc(&s, sizeof(struct s_emmstat)))
			{ continue; }

		for(i = 0, ptr = strtok_r(line, ",", &saveptr1); ptr && i < 7 ; ptr = strtok_r(NULL, ",", &saveptr1), i++)
		{ split[i] = ptr; }

		valid = (i == 6);
		if(valid)
		{
			cs_strncpy(buf, split[0], sizeof(buf));
			key_atob_l(split[1], s->emmd5, MD5_DIGEST_LENGTH * 2);
			s->firstwritten.time = atol(split[2]);
			s->lastwritten.time = atol(split[3]);
			s->type = a2i(split[4], 2);
			s->count = a2i(split[5], 4);

			LL_ITER itr = ll_iter_create(configured_readers);

			while((rdr = ll_iter_next(&itr)))
			{
				if(rdr->cachemm !=1) // skip: emmcache save is disabled
				{
					continue;
				}

				if(strcmp(rdr->label, buf) == 0)
				{
					break;
				}
			}

			if(rdr != NULL)
			{
				if(!rdr->emmstat)
				{
					rdr->emmstat = ll_create("emmstat");
					cs_lock_create(__func__, &rdr->emmstat_lock, rdr->label, DEFAULT_LOCK_TIMEOUT);
				}

				ll_append(rdr->emmstat, s);
				count++;
			}
			else
			{
				cs_log("emmstats could not be loaded for %s", buf);
				NULLFREE(s);
			}
		}
		else
		{
			cs_log_dbg(D_EMM, "emmstat ERROR: %s count=%d type=%d", buf, s->count, s->type);
			NULLFREE(s);
		}
	}

	fclose(file);
	NULLFREE(line);

	cs_ftime(&te);
	int64_t load_time = comp_timeb(&te, &ts);
	cs_log("loaded %d emmstat records from %s in %"PRId64" ms", count, fname, load_time);
}

void save_emmstat_to_file(void)
{
	if(boxtype_is("dbox2")) return; // don't save emmstat on these boxes, they lack resources and will crash!

	if(!emm_cache_configured())
	{
		cs_log("saving emmstats disabled since no reader is using it!");
		return;
	}

	char fname[256];

	if(cfg.emmlogdir)
	{
		get_emmcache_filename(fname, sizeof(fname), "oscam.emmstat");
	}
	else
	{
		get_config_filename(fname, sizeof(fname), "oscam.emmstat");
	}
	FILE *file = fopen(fname, "w");

	if(!file)
	{
		cs_log("can't write to file %s", fname);
		return;
	}

	struct timeb ts, te;
	cs_ftime(&ts);

	int32_t count = 0, result = 0;
	struct s_reader *rdr;
	LL_ITER itr = ll_iter_create(configured_readers);
	while((rdr = ll_iter_next(&itr)))
	{
		if(!rdr->cachemm || rdr->cachemm == 2)
		{
			cs_log("reader %s skipped since emmcache save is disabled", rdr->label);
			continue;
		}

		if(rdr->emmstat)
		{
			cs_writelock(__func__, &rdr->emmstat_lock);
			LL_ITER it = ll_iter_create(rdr->emmstat);
			struct s_emmstat *s;
			while((s = ll_iter_next(&it)))
			{
				uint8_t tmp_emmd5[MD5_DIGEST_LENGTH * 2 + 1];
				char_to_hex(s->emmd5, MD5_DIGEST_LENGTH, tmp_emmd5);
				result = fprintf(file, "%s,%s,%" PRId64 ",%" PRId64 ",%02X,%04X\n", rdr->label, tmp_emmd5, (int64_t)s->firstwritten.time, (int64_t)s->lastwritten.time, s->type, s->count);
				if(result < 0)
				{
					cs_writeunlock(__func__, &rdr->emmstat_lock);
					fclose(file);
					result = remove(fname);
					if(!result)
					{
						cs_log("error writing stats -> stat file removed!");
					}
					else
					{
						cs_log("error writing stats -> stat file could not be removed either!");
					}
					return;
				}
				count++;
			}
			cs_writeunlock(__func__, &rdr->emmstat_lock);
		}
	}

	fclose(file);

	cs_ftime(&te);
	int64_t load_time = comp_timeb(&te, &ts);

	cs_log("saved %d emmstat records to %s in %"PRId64" ms", count, fname, load_time);
}

void emm_load_cache(void)
{
	if(boxtype_is("dbox2")) return; // don't load emmcache on these boxes, they lack resources and will crash!

	if(!emm_cache_configured()){
		cs_log("loading emmcache disabled since no reader is using it!");
		return;
	}

	char fname[256];
	char line[1024];
	FILE *file;
	struct s_emmcache *c;

	if(cfg.emmlogdir)
	{
		get_emmcache_filename(fname, sizeof(fname), "oscam.emmcache");
	}
	else
	{
		get_config_filename(fname, sizeof(fname), "oscam.emmcache");
	}

	file = fopen(fname, "r");
	if(!file)
	{
		cs_log_dbg(D_TRACE, "can't read emmcache from file %s", fname);
		return;
	}

	struct timeb ts, te;
	cs_ftime(&ts);

	int32_t count = 0;
	int32_t i = 1;
	int32_t valid = 0;
	char *ptr, *saveptr1 = NULL;
	char *split[7];

	memset(line, 0, sizeof(line));
	while(fgets(line, sizeof(line), file))
	{
		if(!line[0] || line[0] == '#' || line[0] == ';')
			{ continue; }

		for(i = 0, ptr = strtok_r(line, ",", &saveptr1); ptr && i < 7 ; ptr = strtok_r(NULL, ",", &saveptr1), i++)
		{
			split[i] = ptr;
		}

		valid = (i == 6);
		if(valid)
		{
			if(!cs_malloc(&c, sizeof(struct s_emmcache)))
			{ continue; }
			key_atob_l(split[0], c->emmd5, MD5_DIGEST_LENGTH*2);
			c->firstseen.time = atol(split[1]);
			c->lastseen.time = atol(split[2]);
			c->type = a2i(split[3], 2);
			c->len = a2i(split[4], 4);
			key_atob_l(split[5], c->emm, c->len*2);

			if(valid && c->len != 0)
			{
				if(!emm_cache)
				{
					emm_cache = ll_create("emm cache");
				}

				ll_append(emm_cache, c);
				count++;
			}
			else
			{
				NULLFREE(c);
			}
		}
	}
	fclose(file);
	cs_ftime(&te);
	int64_t load_time = comp_timeb(&te, &ts);
	cs_log("loaded %d emmcache records from %s in %"PRId64" ms", count, fname, load_time);
}

struct s_emmcache *find_emm_cache(uint8_t *emmd5)
{
	struct s_emmcache *c;
	LL_ITER it;

	if(!emm_cache)
		{ emm_cache = ll_create("emm cache"); }

	it = ll_iter_create(emm_cache);
	while((c = ll_iter_next(&it)))
	{
		if(!memcmp(emmd5, c->emmd5, MD5_DIGEST_LENGTH))
		{
			cs_log_dump_dbg(D_EMM, c->emmd5, MD5_DIGEST_LENGTH, "found emmcache match");
			return c;
		}
	}
	return NULL;
}

int32_t clean_stale_emm_cache_and_stat(uint8_t *emmd5, int64_t gone)
{
	struct timeb now;
	cs_ftime(&now);
	int32_t count = 0;

	struct s_emmcache *c;
	LL_ITER it;

	if(!emm_cache)
		{ emm_cache = ll_create("emm cache"); }

	it = ll_iter_create(emm_cache);
	while((c = ll_iter_next(&it)))
	{
		if(comp_timeb(&now, &c->lastseen) > gone && memcmp(c->emmd5, emmd5, MD5_DIGEST_LENGTH)) // clean older than gone ms and dont clean if its the current emm!
		{
			struct s_reader *rdr;
			LL_ITER rdr_itr = ll_iter_create(configured_readers);
			while((rdr = ll_iter_next(&rdr_itr)))
			{
				if(rdr->emmstat && !(caid_is_irdeto(rdr->caid) || caid_is_videoguard(rdr->caid)))
				{
					remove_emm_stat(rdr, c->emmd5); // clean stale entry from stats
					count++;
				}
			}
			ll_iter_remove_data(&it); // clean stale entry from emmcache
		}
	}
	return count;
}

int32_t emm_edit_cache(uint8_t *emmd5, EMM_PACKET *ep, bool add)
{
	struct s_emmcache *c;
	LL_ITER it;
	int32_t count = 0;

	if(!emm_cache)
		{ emm_cache = ll_create("emm cache"); }

	it = ll_iter_create(emm_cache);
	while((c = ll_iter_next(&it)))
	{
		if(!memcmp(emmd5, c->emmd5, MD5_DIGEST_LENGTH))
		{
			if(add)
			{
				return 0; // already added
			}
			ll_iter_remove_data(&it);
			count++;
		}
	}

	if(add)
	{
		if(!cs_malloc(&c, sizeof(struct s_emmcache)))
			{ return count; }
		memcpy(c->emmd5, emmd5, MD5_DIGEST_LENGTH);
		c->type = ep->type;
		c->len = SCT_LEN(ep->emm);
		cs_ftime(&c->firstseen);
		c->lastseen = c->firstseen;
		memcpy(c->emm, ep->emm, c->len);
		ll_append(emm_cache, c);
#ifdef WITH_DEBUG
		cs_log_dump_dbg(D_EMM, c->emmd5, MD5_DIGEST_LENGTH, "added emm to cache:");
#endif
		count++;
	}

	return count;
}

int32_t remove_emm_stat(struct s_reader *rdr, uint8_t *emmd5)
{
	int32_t count = 0;
	if(rdr && rdr->emmstat)
	{
		cs_writelock(__func__, &rdr->emmstat_lock);
		struct s_emmstat *c;
		LL_ITER itr = ll_iter_create(rdr->emmstat);
		while((c = ll_iter_next(&itr)))
		{
			if(!memcmp(emmd5, c->emmd5, MD5_DIGEST_LENGTH))
			{
				ll_iter_remove_data(&itr);
				count++;
				break;
			}
		}

		cs_writeunlock(__func__, &rdr->emmstat_lock);
	}
	return count;
}

struct s_emmstat *get_emm_stat(struct s_reader *rdr, uint8_t *emmd5, uint8_t emmtype)
{
	if(!rdr->cachemm) return NULL;

	struct s_emmstat *c;
	LL_ITER it;

	if(!rdr->emmstat)
		{ rdr->emmstat = ll_create("emm stat"); }

	it = ll_iter_create(rdr->emmstat);
	while((c = ll_iter_next(&it)))
	{
		if(!memcmp(emmd5, c->emmd5, MD5_DIGEST_LENGTH))
		{
			cs_log_dump_dbg(D_EMM, c->emmd5, MD5_DIGEST_LENGTH, "found emmstat match (reader:%s, count:%d)", rdr->label, c->count);
			return c;
		}
	}

	if(cs_malloc(&c, sizeof(struct s_emmstat)))
	{
		memcpy(c->emmd5, emmd5, MD5_DIGEST_LENGTH);
		c->type = emmtype;
		ll_append(rdr->emmstat, c);
		cs_log_dump_dbg(D_EMM, c->emmd5, MD5_DIGEST_LENGTH, "added emmstat (reader:%s, count:%d)", rdr->label, c->count);
	}
	return c;
}
