/*
*Copyright(c)2021, Jeffrey Lee
*Allrightsreserved.
*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"
#include "toml.h"

static server_config_t *configs;
static int num_configs;

global_config_t global_config;

void configs_shutdown()
{
  if (configs) free(configs);
  configs = NULL;
  num_configs = 0;
}

typedef struct {
  toml_table_t *defaults;
  toml_table_t *overrides;
} config_file;

#define config_get_func(TYPE) \
toml_datum_t config_get_##TYPE(config_file *config, toml_table_t *tab, const char *key) \
{ \
  toml_datum_t val; \
  val.ok = 0; \
  /* Check the override */ \
  if (config->overrides) \
  { \
    val = toml_##TYPE##_in(config->overrides, key); \
    if (val.ok) return val; \
  } \
  /* Check the current node */ \
  if (tab) \
  { \
    val = toml_##TYPE##_in(tab, key); \
    if (val.ok) return val; \
  } \
  /* Check the default */ \
  if (config->defaults) \
  { \
    val = toml_##TYPE##_in(config->defaults, key); \
  } \
  return val; \
}

config_get_func(string)
config_get_func(bool)
config_get_func(int)

static _kernel_oserror *read_server_config(server_config_t *out, config_file *config, toml_table_t *tab)
{
  memset(out, 0, sizeof(server_config_t));

  /* Builtin defaults */
  out->port = 5900;
  out->zlib_level = -1;
  out->allow_exclusive = true;
  strcpy(out->name, "RISC OS desktop");

  /* Configs */
  toml_datum_t dat = config_get_int(config, tab, "port");
  if (dat.ok) out->port = dat.u.i;
  dat = config_get_string(config, tab, "password");
  if (dat.ok)
  {
    strncpy(out->password, dat.u.s, PASSWORD_LEN);
    free(dat.u.s);
  }
  else
  {
    return (_kernel_oserror *) "\0\0\0\0Invalid configuration: No password setting";
  }

  dat = config_get_string(config, tab, "name");
  if (dat.ok)
  {
    strncpy(out->name, dat.u.s, sizeof(out->name)-1);
    free(dat.u.s);
  }
  
  dat = config_get_bool(config, tab, "enable_mouse");
  if (dat.ok) out->enable_mouse = dat.u.b;
  dat = config_get_bool(config, tab, "enable_keyboard");
  if (dat.ok) out->enable_kbd = dat.u.b;
  dat = config_get_bool(config, tab, "swap_adjust_menu");
  if (dat.ok) out->swap_adjust_menu = dat.u.b;
  dat = config_get_int(config, tab, "prefer_xcursor");
  if (dat.ok) out->prefer_xcursor = dat.u.i;
  dat = config_get_bool(config, tab, "enable_clipboard");
  if (dat.ok) out->enable_clipboard = dat.u.b;
  dat = config_get_int(config, tab, "zlib_level");
  if (dat.ok)
  {
    if (dat.u.i < -1) dat.u.i = -1;
    if (dat.u.i > 9) dat.u.i = 9;
    out->zlib_level = dat.u.i;
  }
  dat = config_get_bool(config, tab, "allow_exclusive");
  if (dat.ok) out->allow_exclusive = dat.u.b;

  return NULL;
}

_kernel_oserror *config_load(const char *filename)
{
  FILE *f = fopen(filename,"r");
  if (!f)
  {
    return (_kernel_oserror *) "\0\0\0\0Failed to open config file";
  }
  static _kernel_oserror err;
  toml_table_t *t = toml_parse_file(f, err.errmess, sizeof(err.errmess));
  fclose(f);
  if (!t)
  {
    err.errnum = 0;
    return &err;
  }

  config_file config;
  config.defaults = toml_table_in(t, "default");
  config.overrides = toml_table_in(t, "override");

  /* Global config */
  global_config_t newglobal;
  memset(&newglobal,0,sizeof(newglobal));
  newglobal.stretch_keys = true;
  newglobal.multiple_connections = true;
  newglobal.broadcast_clipboard = true;
  toml_datum_t dat = config_get_bool(&config, NULL, "dont_nudge_mouse");
  if (dat.ok) newglobal.dont_nudge_mouse = dat.u.b;
  dat = config_get_bool(&config, NULL, "enable_filter");
  if (dat.ok) newglobal.enable_filter = dat.u.b;
  dat = config_get_bool(&config, NULL, "stretch_keys");
  if (dat.ok) newglobal.stretch_keys = dat.u.b;
  dat = config_get_bool(&config, NULL, "multiple_connections");
  if (dat.ok) newglobal.multiple_connections = dat.u.b;
  dat = config_get_bool(&config, NULL, "broadcast_clipboard");
  if (dat.ok) newglobal.broadcast_clipboard = dat.u.b;

  /* Server configs */
  toml_array_t *servers = toml_array_in(t, "servers");
  int nelem = (servers ? toml_array_nelem(servers) : 0);
  server_config_t *newconfigs = malloc(sizeof(server_config_t)*nelem);
  for(int i=0;i<nelem;i++)
  {
    _kernel_oserror *e = read_server_config(&newconfigs[i], &config, toml_table_at(servers, i));
    if (!e)
    {
      for(int j=0;j<i;j++)
      {
        if (newconfigs[i].port == newconfigs[j].port)
        {
          e = (_kernel_oserror *) "\0\0\0\0Invalid configuration: Multiple servers listening on same port";
          break;
        }
      }
    }
    if (e)
    {
      free(newconfigs);
      toml_free(t);
      return e;
    }
    newglobal.enable_clipboard |= newconfigs[i].enable_clipboard;
    newglobal.enable_input |= newconfigs[i].enable_mouse | newconfigs[i].enable_kbd;
  }
  toml_free(t);
  if (configs)
  {
    free(configs);
  }
  configs = newconfigs;
  num_configs = nelem;
  global_config = newglobal;
  return NULL;
}

const server_config_t *configs_get(int *count)
{
  *count = num_configs;
  return configs;
}
