#include "localsettings.h"
#include "platform.h"
#include "sys_log.h"
#include <string.h>
#include <stdlib.h>

#define max_recent (8)
#define magic (0x44417279)
#define version (1)
static char* settingsfile = NULL;

static int read_version(yrFile* f);
static int write_version(yrFile* f);

static int read_window_settings(yrFile* f);
static int write_window_settings(yrFile* f);
static void default_window_settings(void);
static void cleanup_window_settings(void);

static int read_locale_settings(yrFile* f);
static int write_locale_settings(yrFile* f);
static void default_locale_settings(void);
static void cleanup_locale_settings(void);

static int read_recent_settings(yrFile* f);
static int write_recent_settings(yrFile* f);
static void default_recent_settings(void);
static void cleanup_recent_settings(void);

static int read_export_settings(yrFile* f);
static int write_export_settings(yrFile* f);
static void default_export_settings(void);
static void cleanup_export_settings(void);

int yrLocalSettings_init(void)
{
	int err = 0;
	//create filename
	const char filename[] = "settings.ycf";
	char* localpath = yrFile_path_localdata();
	if(!localpath) err = -1;
	else {
		settingsfile = realloc(localpath, strlen(localpath) + sizeof(filename));
		if(settingsfile) {
			strcat(settingsfile, filename);
		} else {
			yrLog(0, "Out of memory");
			free(localpath);
			err = -1;
		}
	}

	//open file to read settings if possible
	int exists = !err && yr_path_exists(settingsfile);
	if(exists) {
		yrFile f = yrFile_open(settingsfile, yrF_read);
		if(!f) {
			yrLog(0, "Settings file could not be opened for reading.");
			free(settingsfile);
			settingsfile = NULL;
			return -1;
		}
		if(!err) err = read_version(f);
		if(!err) err = read_window_settings(f);
		if(!err) err = read_locale_settings(f);
		if(!err) err = read_recent_settings(f);
		if(!err) err = read_export_settings(f);
		yrFile_close(f);
	}
	//load defaults if necesarry
	if(err || !exists) {
		default_window_settings();
		default_locale_settings();
		default_recent_settings();
		default_export_settings();
	}

	return err;
}
void yrLocalSettings_cleanup(void)
{
	//open file to write settings
	if(settingsfile) {
		yr_create_directory(settingsfile);
		yrFile f = yrFile_open(settingsfile, yrF_write | yrF_create);
		if(!f) {
			yrLog(0, "Settings file could not be opened for writing.");
		} else {
			int err = 0;
			if(!err) err = write_version(f);
			if(!err) err = write_window_settings(f);
			if(!err) err = write_locale_settings(f);
			if(!err) err = write_recent_settings(f);
			if(!err) err = write_export_settings(f);
			yrFile_close(f);
			if(err) {
				yrLog(0, "Error while writing settings file.");
				yr_delete_file(settingsfile);
			}
		}
	}

	//cleanup
	cleanup_export_settings();
	cleanup_recent_settings();
	cleanup_locale_settings();
	cleanup_window_settings();
	free(settingsfile);
	settingsfile = NULL;
}

/******************
* Magic and version
*******************/
static int read_version(yrFile* f)
{
	uint32_t rmagic;
	uint32_t rversion;
	size_t rw;
	rw = yrFile_read(f, 4, &rmagic);	if(rw != 4) return -1;
	rw = yrFile_read(f, 4, &rversion);	if(rw != 4) return -1;
	if(rmagic != magic) {yrLog(0, "Invalid local settings file, loading defaults."); return -1;}
	if(rversion != version) {yrLog(0, "Outdated local settings file, loading defaults."); return -1;} //TODO: attempt upgrade
	return 0;
}

static int write_version(yrFile* f)
{
	uint32_t wmagic = magic;
	uint32_t wversion =  version;
	size_t rw;
	rw = yrFile_write(f, 4, &wmagic);	if(rw != 4) return -1;
	rw = yrFile_write(f, 4, &wversion);	if(rw != 4) return -1;
	return 0;
}

/****************
* Window settings
*****************/
static struct {
	size_t len;
	void* data;
	int enable_mirror;
}
window = {0};

static int read_window_settings(yrFile* f)
{
	size_t rw;
	rw = yrFile_read(f, 8, &window.len);					if(rw != 8) return -1;
	if(window.len) {
		window.data = malloc(window.len);					if(!window.data) {yrLog(0, "Out of memory"); return -1;}
		rw = yrFile_read(f, window.len, window.data);		if(rw != window.len) return -1;
	}
	rw = yrFile_read(f, 1, &window.enable_mirror);			if(rw != 1) return -1;
	return 0;
}

static int write_window_settings(yrFile* f)
{
	size_t rw;
	rw = yrFile_write(f, 8, &window.len);					if(rw != 8) return -1;
	if(window.len) {
		rw = yrFile_write(f, window.len, window.data);		if(rw != window.len) return -1;
	}
	rw = yrFile_write(f, 1, &window.enable_mirror);			if(rw != 1) return -1;
	return 0;
}

static void default_window_settings(void)
{
	cleanup_window_settings();
}

static void cleanup_window_settings(void)
{
	window.len = 0;
	free(window.data);
	window.data = NULL;
	window.enable_mirror = 1;
}

void yrLocalSettings_window_set(size_t len, void* data)
{
	if(!len) {
		free(window.data);
		window.data = NULL;
	} else {
		if(window.len != len) {
			free(window.data);
			window.data = malloc(len);
			window.len = window.data ? len : 0;
			if(!window.len) {yrLog(0, "Out of memory"); return;}
		}
		memcpy(window.data, data, len);
	}
}
void yrLocalSettings_window_get(size_t* len, void** data)
{
	*len = window.len;
	*data = window.data;
}

void yrLocalSettings_mirror_set(int m)
{
	window.enable_mirror = m;
}
int yrLocalSettings_mirror_get(void)
{
	return window.enable_mirror;
}

/****************
* Locale settings
*****************/
static struct {
	char* loc;
	char* sub;
}
locale = {NULL, NULL};

static int read_locale_settings(yrFile* f)
{
	size_t rw;
	unsigned char loclen = 0;
	unsigned char sublen = 0;
	rw = yrFile_read(f, 1, &loclen);						if(rw != 1) return -1;
	rw = yrFile_read(f, 1, &sublen);						if(rw != 1) return -1;
	if(loclen) { locale.loc = malloc(loclen);				if(!locale.loc) {yrLog(0, "Out of memory"); goto onerror;}}
	if(sublen) { locale.sub = malloc(sublen);				if(!locale.sub) {yrLog(0, "Out of memory"); goto onerror;}}
	if(loclen) { rw = yrFile_read(f, loclen, locale.loc);	if(rw != loclen) goto onerror;}
	if(sublen) { rw = yrFile_read(f, sublen, locale.sub);	if(rw != sublen) goto onerror;}
	return 0;
onerror:
	free(locale.loc);
	free(locale.sub);
	locale.loc = NULL;
	locale.sub = NULL;
	return -1;
}

static int write_locale_settings(yrFile* f)
{
	size_t rw;
	unsigned char loclen = locale.loc ? (unsigned char)(strlen(locale.loc) + 1) : 0;
	unsigned char sublen = locale.loc ? (unsigned char)(strlen(locale.sub) + 1) : 0;
	rw = yrFile_write(f, 1, &loclen);						if(rw != 1) return -1;
	rw = yrFile_write(f, 1, &sublen);						if(rw != 1) return -1;
	if(loclen) { rw = yrFile_write(f, loclen, locale.loc);	if(rw != loclen) return -1;}
	if(sublen) { rw = yrFile_write(f, sublen, locale.sub);	if(rw != sublen) return -1;}
	return 0;
}

static void default_locale_settings(void)
{
	cleanup_locale_settings();
}

static void cleanup_locale_settings(void)
{
	free(locale.loc);
	free(locale.sub);
	locale.loc = NULL;
	locale.sub = NULL;
}

void yrLocalSettings_locale_set(const char* loc, const char* sub)
{
	//set locale
	free(locale.loc);
	locale.loc = NULL;
	if(loc) {
		size_t len = strlen(loc) + 1;
		if(len > 255) len = 255;
		locale.loc = malloc(len);
		if(!locale.loc) yrLog(0, "Out of memory");
		else {
			memcpy(locale.loc, loc, len);
			locale.loc[len-1] = 0;
		}
	}
	//set sub
	free(locale.sub);
	locale.sub = NULL;
	if(sub) {
		size_t len = strlen(sub) + 1;
		if(len > 255) len = 255;
		locale.sub = malloc(len);
		if(!locale.sub) yrLog(0, "Out of memory");
		else {
			memcpy(locale.sub, sub, len);
			locale.sub[len-1] = 0;
		}
	}
}
void yrLocalSettings_locale_get(const char** loc, const char** sub)
{
	*loc = locale.loc;
	*sub = locale.sub;
}

/*********************
* Last used export dir
**********************/
static struct {
	char* dir;
}
quadexport = {NULL};

static int read_export_settings(yrFile* f)
{
	size_t rw;
	unsigned dirlen = 0;
	rw = yrFile_read(f, 4, &dirlen);					if(rw != 4) return -1;
	if(dirlen) {
		quadexport.dir = malloc(dirlen);				if(!quadexport.dir) {yrLog(0, "Out of memory"); goto onerror;}
		rw = yrFile_read(f, dirlen, quadexport.dir);	if(rw != dirlen) goto onerror;
	}
	return 0;
onerror:
	free(quadexport.dir);
	quadexport.dir = NULL;
	return -1;
}

static int write_export_settings(yrFile* f)
{
	size_t rw;
	unsigned dirlen = quadexport.dir ? (unsigned char)(strlen(quadexport.dir) + 1) : 0;
	rw = yrFile_write(f, 4, &dirlen);							if(rw != 4) return -1;
	if(dirlen) { rw = yrFile_write(f, dirlen, quadexport.dir);	if(rw != dirlen) return -1;}
	return 0;
}

static void default_export_settings(void)
{
	cleanup_export_settings();
}

static void cleanup_export_settings(void)
{
	free(quadexport.dir);
	quadexport.dir = NULL;
}

void yrLocalSettings_export_setdir(const char* path)
{
	free(quadexport.dir);
	quadexport.dir = NULL;
	if(path) {
		quadexport.dir = _strdup(path);
		if(!quadexport.dir) yrLog(0, "Out of memory");
	}
}

const char* yrLocalSettings_export_getdir(void)
{
	return quadexport.dir;
}

/******************
* Recent files list
*******************/
static struct {
	struct {
		char* scenec;
		char* path;
		int64_t timestamp;
	}
	entry[max_recent];
}
recent = {NULL};

static int read_recent_settings(yrFile* f)
{
	size_t rw;
	unsigned ri;
	unsigned wi;
	for(ri = 0, wi = 0; ri < max_recent; ++ri)
	{
		unsigned pathlen;
		unsigned nameoff;
		int64_t timestamp;
		char* path;
		rw = yrFile_read(f, 4, &pathlen);		if(rw != 4) return -1;
		rw = yrFile_read(f, 4, &nameoff);		if(rw != 4) return -1;
		rw = yrFile_read(f, 8, &timestamp);		if(rw != 8) return -1;
												if(nameoff > pathlen) continue;
		if(pathlen) {
			path = malloc(pathlen);					if(!path) continue;
			rw = yrFile_read(f, pathlen, path);		if(rw != pathlen) return -1;
			if(yr_path_exists(path));				else continue;
		} else {
			path = NULL;
		}
		recent.entry[wi].path = path;
		recent.entry[wi].scenec = path + nameoff;
		recent.entry[wi].timestamp = timestamp;
		wi += 1;
	}
	return 0;
}

static int write_recent_settings(yrFile* f)
{
	size_t rw;
	unsigned i;
	for(i = 0; i < max_recent; ++i)
	{
		if(!recent.entry[i].path) break;
		char* path = recent.entry[i].path;
		unsigned pathlen = (unsigned) strlen(path) + 1;
		unsigned nameoff = (unsigned) (recent.entry[i].scenec - path);
		int64_t timestamp = recent.entry[i].timestamp;
		rw = yrFile_write(f, 4, &pathlen);		if(rw != 4) return -1;
		rw = yrFile_write(f, 4, &nameoff);		if(rw != 4) return -1;
		rw = yrFile_write(f, 8, &timestamp);	if(rw != 8) return -1;
		rw = yrFile_write(f, pathlen, path);	if(rw != pathlen) return -1;
	}
	for(;i < max_recent; ++i)
	{
		unsigned pathlen = 0;
		unsigned nameoff = 0;
		int64_t timestamp = 0;
		rw = yrFile_write(f, 4, &pathlen);		if(rw != 4) return -1;
		rw = yrFile_write(f, 4, &nameoff);		if(rw != 4) return -1;
		rw = yrFile_write(f, 8, &timestamp);	if(rw != 8) return -1;
	}
	return 0;
}

static void default_recent_settings(void)
{
	cleanup_recent_settings();
}

static void cleanup_recent_settings(void)
{
	for(unsigned i = 0; i < max_recent; ++i)
	{
		free(recent.entry[i].path);
		recent.entry[i].scenec = NULL;
		recent.entry[i].path = NULL;
		recent.entry[i].timestamp = 0;
	}
}

void yrLocalSettings_recent_add(const char* path, int64_t timestamp)
{
	//do we already have this entry?
	unsigned i;
	for(i = 0; i < max_recent; ++i)
	{
		if(!recent.entry[i].path) { i = max_recent; break; }
		if(strcmp(path, recent.entry[i].path) == 0) break;
	}
	if(i == 0) {recent.entry[0].timestamp = timestamp; return;} //yes, and it is on top
	if(i != max_recent) //yes, move it to the top
	{
		char* sc = recent.entry[i].scenec;
		char* pa = recent.entry[i].path;
		memmove(recent.entry + 1, recent.entry + 0, i * sizeof(recent.entry[0]));
		recent.entry[0].scenec = sc;
		recent.entry[0].path = pa;
		recent.entry[0].timestamp = timestamp;
		return;
	}
	//no, shift the other entries down and add it on top
	char* pa = _strdup(path); if(!pa) {yrLog(0, "Out of memory"); return;}
	char* fws_pos = strrchr(pa, '/');
	char* bws_pos = strrchr(pa, '\\');
	char* sc = pa;
	if(fws_pos) sc = fws_pos + 1;
	if(bws_pos && bws_pos + 1 > sc) sc = bws_pos + 1;
	
	free(recent.entry[max_recent - 1].path);
	memmove(recent.entry + 1, recent.entry + 0, (max_recent - 1) * sizeof(recent.entry[0]));
	recent.entry[0].scenec = sc;
	recent.entry[0].path = pa;
	recent.entry[0].timestamp = timestamp;
}
void yrLocalSettings_recent_remove(unsigned idx)
{
	if(idx >= max_recent) return;
	if(recent.entry[idx].scenec == NULL) return;
	free(recent.entry[idx].path);
	memmove(recent.entry + idx, recent.entry + idx + 1, (max_recent - idx - 1) * sizeof(recent.entry[0]));
	recent.entry[max_recent - 1].scenec = NULL;
	recent.entry[max_recent - 1].path = NULL;
	recent.entry[max_recent - 1].timestamp = 0;
}

void yrLocalSettings_recent_get(unsigned idx, const char** scenec,  const char** path, int64_t* timestamp)
{
	if(idx < max_recent) {
		*scenec = recent.entry[idx].scenec;
		*path = recent.entry[idx].path;
		*timestamp = recent.entry[idx].timestamp;
	} else {
		*scenec = NULL;
		*path = NULL;
		*timestamp = 0;
	}
}
