/* -*- Mode: C; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * Copyright (C) 2009 Canonical Ltd.
 * Authors:
 *  Gustavo Sverzut Barbieri <gustavo.barbieri@canonical.com>
 *
 * This file is part of Netbook Launcher EFL.
 *
 * Netbook Launcher EFL is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * Netbook Launcher EFL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Netbook Launcher EFL.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "netbook-launcher.h"
#include <Eet.h>
#include <Ecore.h>
#include <Ecore_File.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

struct conf_bg
{
  const char *filename;
  unsigned int option;
  unsigned int color;
  Eina_Bool draw;
};

struct conf_font
{
  const char *font;
  unsigned int size;
};

struct pending_font
{
  struct conf_font font;
  char name[];
};

static unsigned int _init_count = 0;
static Eet_File *ef = NULL;
static Eet_Data_Descriptor *bg_edd = NULL, *font_edd = NULL;
static Ecore_Timer *save_timer = NULL;
static struct conf_bg *pending_bg = NULL;
static Eina_List *pending_fonts = NULL;
static float pending_dpi = 0.0;
static const char *pending_theme = NULL;

static Eina_Hash *
eet_eina_hash_add(Eina_Hash *hash, const char *key, const void *data)
{
  if (!hash) hash = eina_hash_string_superfast_new(NULL);
  if (!hash) return NULL;
  eina_hash_add(hash, key, data);
  return hash;
}

static char *
eet_read_string(Eet_File *ef, const char *key, int *size)
{
  int s;
  char *value;

  value = eet_read(ef, key, &s);
  if (!value)
    goto empty;
  if (s < 1)
  {
    free(value);
    goto empty;
  }

  if (value[s - 1] != '\0')
  {
    char *tmp;

    s++;
    tmp = realloc(value, s);
    if (!tmp)
    {
      ERR("could not allocate memory.\n");
      free(value);
      goto empty;
    }
    value = tmp;
    value[s - 1] = '\0';
  }

  *size = s;
  return value;

empty:
  *size = 0;
  return NULL;
}

static Eet_Data_Descriptor_Class bg_edd_class = {
  EET_DATA_DESCRIPTOR_CLASS_VERSION,
  "bg_edd_class",
  sizeof(struct conf_bg),
  {
    NULL, /* mem_alloc */
    NULL, /* mem_free */
    (char *(*)(const char *)) eina_stringshare_add,
    (void (*)(const char *)) eina_stringshare_del,
    (void *(*)(void *)) eina_list_next,
    (void *(*)(void *l, void *d)) eina_list_append,
    (void *(*)(void *)) eina_list_data_get,
    (void *(*)(void *)) eina_list_free,
    (void  (*) (void *, int (*) (void *, const char *, void *, void *), void *)) eina_hash_foreach,
    (void * (*) (void *, const char *, void *)) eet_eina_hash_add,
    (void  (*) (void *)) eina_hash_free,
    NULL, /* str_direct_alloc */
    NULL /* str_direct_free */
  }
};

static Eet_Data_Descriptor_Class font_edd_class = {
  EET_DATA_DESCRIPTOR_CLASS_VERSION,
  "font_edd_class",
  sizeof(struct conf_font),
  {
    NULL, /* mem_alloc */
    NULL, /* mem_free */
    (char *(*)(const char *)) eina_stringshare_add,
    (void (*)(const char *)) eina_stringshare_del,
    (void *(*)(void *)) eina_list_next,
    (void *(*)(void *l, void *d)) eina_list_append,
    (void *(*)(void *)) eina_list_data_get,
    (void *(*)(void *)) eina_list_free,
    (void  (*) (void *, int (*) (void *, const char *, void *, void *), void *)) eina_hash_foreach,
    (void * (*) (void *, const char *, void *)) eet_eina_hash_add,
    (void  (*) (void *)) eina_hash_free,
    NULL, /* str_direct_alloc */
    NULL /* str_direct_free */
  }
};

static char conf_path[PATH_MAX] = "";
static char conf_path_tmp[PATH_MAX] = "";

static struct conf_bg *
_conf_bg_new(const char *filename, unsigned int option, unsigned int color, Eina_Bool draw)
{
  struct conf_bg *bg = malloc(sizeof(*bg));
  if (!bg) return NULL;
  bg->filename = eina_stringshare_add(filename);
  bg->option = option;
  bg->color = color;
  bg->draw = draw;
  return bg;
}

static void
_conf_bg_free(struct conf_bg *bg)
{
  eina_stringshare_del(bg->filename);
  free(bg);
}

static void
_conf_font_free(struct conf_font *font)
{
  eina_stringshare_del(font->font);
  free(font);
}

static struct pending_font *
_conf_pending_font_new(const char *name, const char *font, unsigned int size)
{
  struct pending_font *pf;
  size_t len = strlen(name);

  pf = malloc(len + sizeof("font/") + sizeof(struct pending_font));
  if (!pf) return NULL;

  pf->font.font = eina_stringshare_add(font);
  pf->font.size = size;
  memcpy(pf->name, "font/", sizeof("font/") - 1);
  memcpy(pf->name + sizeof("font/") - 1, name, len + 1);
  return pf;
}

static void
_conf_pending_font_free(struct pending_font *pf)
{
  eina_stringshare_del(pf->font.font);
  free(pf);
}

static int
_conf_save(void *data __UNUSED__)
{
  Eet_File *new_ef;
  Eina_List *l;
  struct pending_font *pf;
  Eina_Bool exists;
  Eet_Error error;

  if ((!pending_bg) && (!pending_fonts) && (pending_dpi <= 0.0) &&
      (!pending_theme))
    goto end;

  unlink(conf_path_tmp);
  exists = ecore_file_exists(conf_path);
  if (exists && !ecore_file_cp(conf_path, conf_path_tmp))
  {
    ERR("could not copy file.\n");
    return 1;
  }

  if (exists)
    new_ef = eet_open(conf_path_tmp, EET_FILE_MODE_READ_WRITE);
  else
    new_ef = eet_open(conf_path_tmp, EET_FILE_MODE_WRITE);
  if (!new_ef)
  {
    ERR("could not open file for write '%s'\n", conf_path_tmp);
    goto error;
  }

  if (pending_bg)
  {
    if (!eet_data_write(new_ef, bg_edd, "bg", pending_bg, 0))
    {
      ERR("could not write 'bg' to configuration file.\n");
      goto error;
    }
  }

  EINA_LIST_FOREACH(pending_fonts, l, pf)
  {
    if (!eet_data_write(new_ef, font_edd, pf->name, &pf->font, 0))
    {
      ERR("could not write '%s' to configuration file.\n", pf->name);
      goto error;
    }
  }

  if (pending_dpi > 0.0)
  {
    char buf[64];
    int len;

    len = snprintf(buf, sizeof(buf), "%0.2lf", pending_dpi);
    if (len >= (int)sizeof(buf))
    {
      ERR("could not convert '%0.2f' to string.\n", pending_dpi);
      goto error;
    }

    if (!eet_write(new_ef, "dpi", buf, len, 0))
    {
      ERR("could not write 'dpi' to configuration file.\n");
      goto error;
    }
  }

  if (pending_theme)
  {
    int len = eina_stringshare_strlen(pending_theme);
    if (!eet_write(new_ef, "theme", pending_theme, len, 0))
    {
      ERR("could not write 'theme' to configuration file.\n");
      goto error;
    }
  }

  error = eet_close(new_ef);
  new_ef = NULL;
  if (error != EET_ERROR_NONE)
  {
    ERR("could not close configuration file. Error #%d\n", error);
    goto error;
  }

  eet_close(ef);
  ef = NULL;

  unlink(conf_path);
  if (rename(conf_path_tmp, conf_path) == -1)
  {
    ERR("could not rename '%s' to '%s': %s\n",
        conf_path_tmp, conf_path, strerror(errno));
    goto error;
  }

  if (pending_bg)
  {
    _conf_bg_free(pending_bg);
    pending_bg = NULL;
  }

  EINA_LIST_FREE(pending_fonts, pf)
    _conf_pending_font_free(pf);

  pending_dpi = 0.0;
  if (pending_theme)
  {
    eina_stringshare_del(pending_theme);
    pending_theme = NULL;
  }

  DBG("saved configuration file '%s'\n", conf_path);

end:
  save_timer = NULL;
  return 0;

error:
  if (new_ef)
  {
    eet_close(new_ef);
    unlink(conf_path_tmp);
  }
  return 1;
}

static void
_conf_save_now(void)
{
  if (!save_timer)
    return;

  ecore_timer_del(save_timer);
  save_timer = NULL;
  _conf_save(NULL);
}

static void
_conf_save_request(void)
{
  if (save_timer)
    ecore_timer_del(save_timer);
  save_timer = ecore_timer_add(1.0, _conf_save, NULL);
}

static Eet_File *
_conf_ef_get(void)
{
  _conf_save_now();

  if (ef)
    return ef;

  ef = eet_open(conf_path, EET_FILE_MODE_READ);
  if (!ef)
  {
    DBG("no configuration file '%s'\n", conf_path);
    return NULL;
  }

  return ef;
}

unsigned int
conf_init(void)
{
  const char *home;
  int len;

  if (_init_count > 0)
    return ++_init_count;

  if (!eet_init())
  {
    ERR("could not init eet.\n");
    return 0;
  }

  if (!ecore_file_init())
  {
    ERR("coult not init ecore_file.\n");
    eet_shutdown();
    return 0;
  }

  home = getenv("HOME");
  if (!home)
  {
    home = getenv("TMPDIR");
    if (!home)
    {
      ERR("could not get $HOME or $TMPDIR!\n");
      goto error;
    }
  }

  len = snprintf(conf_path, sizeof(conf_path), "%s/." PACKAGE, home);
  if ((unsigned int)len >= sizeof(conf_path) - sizeof("/conf.tmp"))
  {
    ERR("conf_path is too long.\n");
    goto error;
  }

  if (!ecore_file_mkpath(conf_path))
  {
    ERR("could not create base configuration directory: %s", conf_path);
    goto error;
  }

  memcpy(conf_path + len, "/conf", sizeof("/conf"));
  memcpy(conf_path_tmp, conf_path, len);
  memcpy(conf_path_tmp + len, "/conf.tmp", sizeof("/conf.tmp"));

  bg_edd = eet_data_descriptor2_new(&bg_edd_class);
  if (!bg_edd)
  {
    ERR("could not create bg_edd\n");
    goto error;
  }

  font_edd = eet_data_descriptor2_new(&font_edd_class);
  if (!font_edd)
  {
    ERR("could not create font_edd\n");
    eet_data_descriptor_free(bg_edd);
    bg_edd = NULL;
    goto error;
  }

#define ADD(member, type)                                               \
  EET_DATA_DESCRIPTOR_ADD_BASIC(bg_edd, struct conf_bg, #member, member, type)
  ADD(filename, EET_T_STRING);
  ADD(option, EET_T_UINT);
  ADD(color, EET_T_UINT);
  ADD(draw, EET_T_UCHAR);

#undef ADD
#define ADD(member, type)                                               \
  EET_DATA_DESCRIPTOR_ADD_BASIC(font_edd, struct conf_font, #member, member, type)
  ADD(font, EET_T_STRING);
  ADD(size, EET_T_UINT);
#undef ADD

  _init_count = 1;
  return 1;

error:
  ecore_file_shutdown();
  eet_shutdown();
  ERR("could not init conf.\n");
  return 0;
}

unsigned int
conf_shutdown(void)
{
  struct pending_font *pf;

  /* do it often so we don't miss a save when do not shutdown properly  */
  if (save_timer)
    _conf_save_now();

  if (_init_count > 1) return --_init_count;
  _init_count = 0;

  if (ef)
  {
    eet_close(ef);
    ef = NULL;
  }

  if (pending_bg)
    _conf_bg_free(pending_bg);
  EINA_LIST_FREE(pending_fonts, pf)
    _conf_pending_font_free(pf);

  ecore_file_shutdown();
  eet_shutdown();

  return 0;
}

Eina_Bool
conf_bg_set(const char *filename, unsigned int option, unsigned int color, Eina_Bool draw)
{
  struct conf_bg *bg = _conf_bg_new(filename, option, color, draw);
  if (!bg)
  {
    ERR("could not create conf_bg structure.\n");
    return 0;
  }

  if (pending_bg)
    _conf_bg_free(pending_bg);
  pending_bg = bg;

  _conf_save_request();
  return 1;
}

/**
 * Get current settings.
 *
 * @param filename: stringshared reference.
 */
Eina_Bool
conf_bg_get(const char **filename, unsigned int *option, unsigned int *color, Eina_Bool *draw)
{
  Eet_File *ef = _conf_ef_get();
  struct conf_bg *bg;

  if (!ef)
  {
    DBG("no configuration file.\n");
    goto error;
  }
  if (!bg_edd)
  {
    ERR("invalid setup: no bg_edd.\n");
    goto error;
  }

  bg = eet_data_read(ef, bg_edd, "bg");
  if (!bg)
  {
    WRN("no 'bg' section in configuration file.\n");
    goto error;
  }

  if (filename) *filename = eina_stringshare_ref(bg->filename);
  if (option) *option = bg->option;
  if (color) *color = bg->color;
  if (draw) *draw = bg->draw;

  _conf_bg_free(bg);

  return 1;

error:
  if (filename) *filename = NULL;
  if (option) *option = 0;
  if (color) *color = 0;
  if (draw) *draw = 0;
  return 0;
}

Eina_Bool
conf_font_set(const char *section, const char *font, unsigned int size)
{
  struct pending_font *pf = _conf_pending_font_new(section, font, size);
  if (!pf)
  {
    ERR("could not create pending_font structure.\n");
    return 0;
  }

  pending_fonts = eina_list_append(pending_fonts, pf);
  _conf_save_request();
  return 1;
}

/**
 * Get current settings.
 *
 * @param font: stringshared reference.
 */
Eina_Bool
conf_font_get(const char *section, const char **font, unsigned int *size)
{
  Eet_File *ef = _conf_ef_get();
  struct conf_font *f;
  char name[1024];

  if (!ef)
  {
    DBG("no configuration file.\n");
    goto error;
  }
  if (!font_edd)
  {
    ERR("invalid setup: no font_edd.\n");
    goto error;
  }

  if (snprintf(name, sizeof(name), "font/%s", section) >= (int)sizeof(name))
  {
    ERR("section name is too long.\n");
    goto error;
  }

  f = eet_data_read(ef, font_edd, name);
  if (!f)
  {
    WRN("no '%s' section in configuration file.\n", name);
    goto error;
  }

  if (font) *font = eina_stringshare_ref(f->font);
  if (size) *size = f->size;

  _conf_font_free(f);

  return 1;

error:
  if (font) *font = NULL;
  if (size) *size = 0;
  return 0;
}

Eina_Bool
conf_dpi_set(double dpi)
{
  pending_dpi = dpi;
  _conf_save_request();
  return 1;
}

Eina_Bool
conf_dpi_get(double *dpi)
{
  Eet_File *ef = _conf_ef_get();
  char *buf;
  int size;

  if (!ef)
  {
    DBG("no configuration file.\n");
    goto error;
  }

  buf = eet_read_string(ef, "dpi", &size);
  if (!buf)
  {
    ERR("no DPI entry.\n");
    goto error;
  }

  if (sscanf(buf, "%lf", dpi) != 1)
  {
    ERR("could not parse float '%s'\n", buf);
    free(buf);
    goto error;
  }
  free(buf);
  return 1;

error:
  if (dpi) *dpi = 0.0;
  return 0;
}

Eina_Bool
conf_theme_set(const char *theme_path)
{
  eina_stringshare_replace(&pending_theme, theme_path);
  _conf_save_request();
  return 1;
}

/**
 * Get current theme.
 *
 * @param theme_path: stringshared reference.
 */
Eina_Bool
conf_theme_get(const char **theme_path)
{
  Eet_File *ef = _conf_ef_get();
  char *buf;
  int size;

  if (!theme_path)
  {
    ERR("theme_path == NULL\n");
    return 0;
  }

  if (!ef)
  {
    DBG("no configuration file.\n");
    goto error;
  }

  buf = eet_read_string(ef, "theme", &size);
  if (!buf)
  {
    ERR("no theme entry.\n");
    goto error;
  }

  for (size--; size >= 0; size--)
    if (!isspace(buf[size]))
      break;

  if (size <= 0)
  {
    ERR("empty theme.\n");
    free(buf);
    goto error;
  }

  *theme_path = eina_stringshare_add_length(buf, size);
  free(buf);
  return 1;

error:
  *theme_path = NULL;
  return 0;
}
