#include "common.h"
#include "config.h"

#include <esp_spiffs.h>
#include <ctype.h>

#define CONFIG_FILE	"/spiffs/config.txt"

static int save_config(void);


static bool is_end_of_string(int c)
{
    return c <= 0 || c == '\n' || c == '\r';
}

/*
 * Note: the string must be mutable; use strdup() if necessary.
 * The "separator" allows the string to be separated into multiple
 * arguments; no other decoding is done.
 */
static int set_config_string(char *str, unsigned int separator)
{
    char *p, *q;
    unsigned char c;

    p = str;
    do {
	const char *var = p;

	do {
	    c = *p++;
	} while (isalnum(c) || c == '.' || c == '-');
	if (c != '=')
	    return -EINVAL; /* Invalid config line (blank, comment...) */

	p[-1] = '\0';

	q = p;

	do {
	    c = *q++;
	} while (!is_end_of_string(c) && c != separator);

	/* Overlong line */
	if (q >= str + MAX_CONFIG_LINE)
	    return -EOVERFLOW;

	q[-1] = '\0';
	size_t nvar = sysvar_find(sysvar_ns_config, var);
	if (nvar) {
	    setvar_fromstr(nvar, p);
	}

	p = q;
    } while (c == separator);

    return 0;
}

static void finish_config_update(bool save)
{
    if (sysvar_changed < sysvar_ns[sysvar_ns_status].first) {
	if (save)
	    save_config();

	/* Can do other things here... */
    }
    sysvar_changed = sysvar_count;
}

/*
 * At the moment "url" just allows values to be separated by semicolons;
 * no other decoding is done, and '&' is not supported.
 */
int set_config_url_string(const char *str)
{
    char *wstr = strdup(str);

    if (!wstr)
	return -ENOMEM;

    int err = set_config_string(wstr, ';');
    free(wstr);

    finish_config_update(true);
    return err;
}

static void skip_rest_of_line(FILE *f)
{
    int c;
    do {
	c = getc(f);
    } while (!is_end_of_string(c));
}

/* Calling with with f == NULL just finalizes the update */
int read_config(FILE *f, bool save)
{
    char *linebuf = NULL;
    int err = -1;

    if (f) {
	linebuf = malloc(MAX_CONFIG_LINE);
	if (!linebuf) {
	    err = -ENOMEM;
	    goto exit;
	}

	while (fgets(linebuf, MAX_CONFIG_LINE, f)) {
	    if (set_config_string(linebuf, -1) == -EOVERFLOW)
		skip_rest_of_line(f);
	}
    }

    err = 0;

    if (linebuf)
	free(linebuf);

exit:
    finish_config_update(save);
    return err;
};

int write_sysvars(FILE *f, bool status)
{
    size_t ns = status ? sysvar_ns_status : sysvar_ns_config;
    const sysvar_ns_t * const nsi = &sysvar_ns[ns];

    for (enum sysvar_enum var = nsi->first; var < nsi[1].first; var++) {
	fputs(sysvar_name[var], f);
	putc('=', f);
	fputs(notempty(getvar_tostr(var)), f);
	putc('\n', f);
    }

    return ferror(f) ? -1 : 0;
}

static int save_config(void)
{
    int err = -ENOENT;

    FILE *f = fopen(CONFIG_FILE, "w");
    if (f) {
	err = write_sysvars(f, false);
	fclose(f);
    }

    if (err)
	printf("[CONF] Failed to save configuration (error %d)\n", err);

    return err;
}

static const esp_vfs_spiffs_conf_t spiffs_conf = {
    .base_path              = "/spiffs",
    .partition_label        = NULL,
    .max_files              = 4,
    .format_if_mount_failed = true
};

void init_config(void)
{
    if (!esp_spiffs_mounted(spiffs_conf.partition_label)) {
	esp_err_t err;
	err = esp_vfs_spiffs_register(&spiffs_conf);
	if (err)
	    printf("[CONF] Failed to mount %s (error 0x%x)\n",
		   spiffs_conf.base_path, err);
    }

    sysvar_reset(sysvar_ns_config);

    FILE *f = fopen(CONFIG_FILE, "r");
    if (!f)
	printf("[CONF] No configuration file found, using defaults\n");

    read_config(f, false);
    if (f)
	fclose(f);
}

void log_config_status(void)
{
    const sysvar_ns_t *nsi = &sysvar_ns[0];

    for (enum sysvar_enum var = 1; var < sysvar_count; var++) {
	if (var >= nsi[1].first)
	    nsi++;

	logmsg(nsi->name, "%s = %s\n", sysvar_name[var],
	       notempty(getvar_tostr(var)));
    }
}
