/* -*- Mode: C; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * Copyright (C) 2009,2010 Canonical Ltd.
 * Authors:
 *  Gustavo Sverzut Barbieri <gustavo.barbieri@canonical.com>
 *  Michael Terry <michael.terry@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/>.
 */

/**
 * This file takes care of gluing two main loops: Ecore and GLib.
 *
 * It does so by using one thread per loop and exchanging functions to
 * be called on the other thread. This is done asynchronously, so
 * either use locks around your data or copy it.
 *
 * Do not call GLib functions from the Ecore thread and vice-versa!
 *
 * The communication is LOCKLESS except by queues, that use locks
 * internally. Do not add useless locks around parts of the system,
 * try to use given primitives to avoid UI blocking on backend actions
 * and vice versa.
 *
 * Pipes are used to wakeup the other main loop, functions
 * gstuff_glib_run() and gstuff_ecore_run() will take care of internals to
 * add function context to queue and then wakeup the thread.
 */

#include "netbook-launcher.h"
#include <Ecore.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <pthread.h>

void
gstuff_shutdown(void)
{
}

Eina_Bool
gstuff_init(void)
{
  g_thread_init(NULL);
  gtk_init(NULL, NULL);
  g_set_application_name(_("Netbook Launcher"));

  ecore_main_loop_glib_integrate();
 return 1;
}

static int
_dispatch(void *data)
{
  struct func_ctxt *ctxt = data;
  ctxt->func(ctxt->data);
  ctxt->free(ctxt);
  return 0;
}

static void
_add_dispatch(struct func_ctxt *ctxt)
{
   ecore_idler_add(_dispatch, ctxt);
}

/**
 * Have the given function context to run on GLib thread/main loop.
 *
 * @warning: just call this function from Ecore thread!
 *
 * @param ctxt: context to call function on GLib thread.
 * @return: 1 on success, 0 on error (ctxt->free(ctxt) is called on errors).
 */
Eina_Bool
gstuff_glib_run(struct func_ctxt *ctxt)
{
  if (!ctxt)
  {
    ERR("gstuff_glib_run(NULL)\n");
    return 0;
  }

  _add_dispatch(ctxt);
  return 1;
}

/**
 * Have the given function context to run on Ecore thread/main loop.
 *
 * @warning: just call this function from GLib thread!
 *
 * @param ctxt: context to call function on Ecore thread.
 * @return: 1 on success, 0 on error (ctxt->free(ctxt) is called on errors).
 */
Eina_Bool
gstuff_ecore_run(struct func_ctxt *ctxt)
{
  if (!ctxt)
  {
    g_warning("gstuff_ecore_run(NULL)");
    return 0;
  }

  _add_dispatch(ctxt);
  return 1;
}

static void
_gstuff_func_ctxt_dumb_free(void *data __UNUSED__)
{
}

static void
_gstuff_glib_exit(void *data __UNUSED__)
{
  g_debug("quit inexistent gtk main loop. ;)");
}

/**
 * Request glib main loop to exit.
 */
Eina_Bool
gstuff_glib_exit(void)
{
  return 1;
}

static void
_gstuff_ecore_exit(void *data __UNUSED__)
{
  ecore_main_loop_quit();
}

/**
 * Request ecore main loop to exit.
 */
Eina_Bool
gstuff_ecore_exit(void)
{
  static struct func_ctxt ctxt = {
    _gstuff_ecore_exit,
    _gstuff_func_ctxt_dumb_free,
    NULL
  };
  return gstuff_ecore_run(&ctxt);
}

struct func_ctxt_noargs
{
  struct func_ctxt base;
  void (*real_func)(void);
};

static void
_gstuff_noargs(void *data)
{
  struct func_ctxt_noargs *ctxt = data;
  ctxt->real_func();
}

static struct func_ctxt *
_gstuff_noargs_ctxt_new(void (*func)(void))
{
  struct func_ctxt_noargs *ctxt;

  if (!func)
    return NULL;

  ctxt = malloc(sizeof(*ctxt));
  if (!ctxt)
    return NULL;
  ctxt->base.func = _gstuff_noargs;
  ctxt->base.free = free;
  ctxt->base.data = ctxt;
  ctxt->real_func = func;
  return &ctxt->base;
}

/**
 * Request function to be called at GLib thread.
 */
Eina_Bool
gstuff_glib_run_noargs(void (*func)(void))
{
  return gstuff_glib_run(_gstuff_noargs_ctxt_new(func));
}

/**
 * Request function to be called at Ecore thread.
 */
Eina_Bool
gstuff_ecore_run_noargs(void (*func)(void))
{
  return gstuff_ecore_run(_gstuff_noargs_ctxt_new(func));
}

static struct func_ctxt *
_gstuff_simple_ctxt_new(void (*func)(void *data), const void *data)
{
  struct func_ctxt *ctxt;

  if (!func)
    return NULL;

  ctxt = malloc(sizeof(*ctxt));
  if (!ctxt)
    return NULL;
  ctxt->func = func;
  ctxt->free = free;
  ctxt->data = (void *)data;
  return ctxt;
}

/**
 * Request function to be called at GLib thread.
 *
 * @param data: any pointer to give to @a func, will not be copied.
 */
Eina_Bool
gstuff_glib_run_simple(void (*func)(void *data), const void *data)
{
  return gstuff_glib_run(_gstuff_simple_ctxt_new(func, data));
}

/**
 * Request function to be called at Ecore thread.
 *
 * @param data: any pointer to give to @a func, will not be copied.
 */
Eina_Bool
gstuff_ecore_run_simple(void (*func)(void *data), const void *data)
{
  return gstuff_ecore_run(_gstuff_simple_ctxt_new(func, data));
}

struct func_ctxt_string
{
  struct func_ctxt base;
  void (*real_func)(const char *);
  char str[];
};

static void
_gstuff_string(void *data)
{
  struct func_ctxt_string *ctxt = data;
  ctxt->real_func(ctxt->str);
}

static struct func_ctxt *
_gstuff_string_ctxt_new(void (*func)(const char *str), const char *str)
{
  struct func_ctxt_string *ctxt;
  size_t len;

  if (!func)
    return NULL;

  if (!str)
    return NULL;
  len = strlen(str);
  ctxt = malloc(sizeof(*ctxt) + len + 1);
  if (!ctxt)
    return NULL;

  ctxt->base.func = _gstuff_string;
  ctxt->base.free = free;
  ctxt->base.data = ctxt;
  ctxt->real_func = func;
  memcpy(ctxt->str, str, len + 1);
  return &ctxt->base;
}

/**
 * Request function to be called with a copy of str at GLib thread.
 *
 * @param str: string to be COPIED, no references will be kept after return.
 */
Eina_Bool
gstuff_glib_run_string(void (*func)(const char *str), const char *str)
{
  return gstuff_glib_run(_gstuff_string_ctxt_new(func, str));
}

/**
 * Request function to be called with a copy of str at Ecore thread.
 *
 * @param str: string to be COPIED, no references will be kept after return.
 */
Eina_Bool
gstuff_ecore_run_string(void (*func)(const char *str), const char *str)
{
  return gstuff_ecore_run(_gstuff_string_ctxt_new(func, str));
}

struct func_ctxt_ptr_string
{
  struct func_ctxt base;
  void (*real_func)(void *, const char *);
  const void *real_data;
  char str[];
};

static void
_gstuff_ptr_string(void *data)
{
  struct func_ctxt_ptr_string *ctxt = data;
  ctxt->real_func((void *)ctxt->real_data, ctxt->str);
}

static struct func_ctxt *
_gstuff_ptr_string_ctxt_new(void (*func)(void *data, const char *str), const void *data, const char *str)
{
  struct func_ctxt_ptr_string *ctxt;
  size_t len;

  if (!func)
    return NULL;

  if (!str)
    return NULL;
  len = strlen(str);
  ctxt = malloc(sizeof(*ctxt) + len + 1);
  if (!ctxt)
    return NULL;

  ctxt->base.func = _gstuff_ptr_string;
  ctxt->base.free = free;
  ctxt->base.data = ctxt;
  ctxt->real_func = func;
  ctxt->real_data = data;
  memcpy(ctxt->str, str, len + 1);
  return &ctxt->base;
}

/**
 * Request function to be called with a copy of str at GLib thread.
 *
 * @param data: extra pointer to give to function, not copied or touched.
 * @param str: string to be COPIED, no references will be kept after return.
 */
Eina_Bool
gstuff_glib_run_ptr_string(void (*func)(void *data, const char *str), const void *data, const char *str)
{
  return gstuff_glib_run(_gstuff_ptr_string_ctxt_new(func, data, str));
}

/**
 * Request function to be called with a copy of str at Ecore thread.
 *
 * @param data: extra pointer to give to function, not copied or touched.
 * @param str: string to be COPIED, no references will be kept after return.
 */
Eina_Bool
gstuff_ecore_run_ptr_string(void (*func)(void *data, const char *str), const void *data, const char *str)
{
  return gstuff_ecore_run(_gstuff_ptr_string_ctxt_new(func, data, str));
}

struct func_ctxt_string_array
{
  struct func_ctxt base;
  void (*real_func)(unsigned int, const char **);
  unsigned int count;
  char **array;
};

static void
_gstuff_string_array(void *data)
{
  struct func_ctxt_string_array *ctxt = data;
  ctxt->real_func(ctxt->count, (const char **)ctxt->array);
}

static struct func_ctxt *
_gstuff_string_array_ctxt_new(void (*func)(unsigned int count, const char **array), unsigned int count, const char **array)
{
  struct func_ctxt_string_array *ctxt;
  unsigned int i;
  size_t len;
  char *s;

  if (!func)
    return NULL;

  if (!array)
    return NULL;

  if (count == 0)
    while (array[count] != NULL)
      count++;

  len = 0;
  for (i = 0; i < count; i++)
    len += strlen(array[i]) + 1;

  ctxt = malloc(sizeof(*ctxt) + ((count + 1) * sizeof(char *)) + len);
  if (!ctxt)
    return NULL;

  ctxt->base.func = _gstuff_string_array;
  ctxt->base.free = free;
  ctxt->base.data = ctxt;
  ctxt->real_func = func;
  ctxt->count = count;

  ctxt->array = (char **)((char *)ctxt + sizeof(*ctxt));
  s = (char *)(ctxt->array + count + 1);
  for (i = 0; i < count;  i++)
  {
    len = strlen(array[i]) + 1;
    ctxt->array[i] = s;
    memcpy(s, array[i], len);
    s += len;
  }
  ctxt->array[i] = NULL;

  return &ctxt->base;
}

/**
 * Request function to run on GLib thread with an array of strings.
 *
 * @note: function will always receive the number of elements and the
 *        copied array will always be NULL terminated. Functions can
 *        always rely on such values even if they were not given as
 *        original parameter.
 *
 * @param func: function to call, first argument is the number of
 *        elements and second is the array, with a NULL terminator.
 * @param count: elements in array or 0 to count @a array until a NULL
 *        terminator is found.
 * @param array: elements to COPY. If @a count is 0, then it must be NULL
 *        terminated. Strings and array will be copied, no reference will
 *        be kept after function returns.
 */
Eina_Bool
gstuff_glib_run_string_array(void (*func)(unsigned int count, const char **array), unsigned int count, const char **array)
{
  return gstuff_glib_run(_gstuff_string_array_ctxt_new(func, count, array));
}

/**
 * Request function to run on Ecore thread with an array of strings.
 *
 * @note: function will always receive the number of elements and the
 *        copied array will always be NULL terminated. Functions can
 *        always rely on such values even if they were not given as
 *        original parameter.
 *
 * @param func: function to call, first argument is the number of
 *        elements and second is the array, with a NULL terminator.
 * @param count: elements in array or 0 to count @a array until a NULL
 *        terminator is found.
 * @param array: elements to COPY. If @a count is 0, then it must be NULL
 *        terminated. Strings and array will be copied, no reference will
 *        be kept after function returns.
 */
Eina_Bool
gstuff_ecore_run_string_array(void (*func)(unsigned int count, const char **array), unsigned int count, const char **array)
{
  return gstuff_ecore_run(_gstuff_string_array_ctxt_new(func, count, array));
}

struct func_ctxt_ptr_string_array
{
  struct func_ctxt base;
  void (*real_func)(void *, unsigned int, const char **);
  void *real_data;
  unsigned int count;
  char **array;
};

static void
_gstuff_ptr_string_array(void *data)
{
  struct func_ctxt_ptr_string_array *ctxt = data;
  ctxt->real_func(ctxt->real_data, ctxt->count, (const char **)ctxt->array);
}

static struct func_ctxt *
_gstuff_ptr_string_array_ctxt_new(void (*func)(void *data, unsigned int count, const char **array), const void *data, unsigned int count, const char **array)
{
  struct func_ctxt_ptr_string_array *ctxt;
  unsigned int i;
  size_t len;
  char *s;

  if (!func)
    return NULL;

  if (!array)
    return NULL;

  if (count == 0)
    while (array[count] != NULL)
      count++;

  len = 0;
  for (i = 0; i < count; i++)
    len += strlen(array[i]) + 1;

  ctxt = malloc(sizeof(*ctxt) + ((count + 1) * sizeof(char *)) + len);
  if (!ctxt)
    return NULL;

  ctxt->base.func = _gstuff_ptr_string_array;
  ctxt->base.free = free;
  ctxt->base.data = ctxt;
  ctxt->real_func = func;
  ctxt->real_data = (void *)data;
  ctxt->count = count;

  ctxt->array = (char **)((char *)ctxt + sizeof(*ctxt));
  s = (char *)(ctxt->array + count + 1);
  for (i = 0; i < count;  i++)
  {
    len = strlen(array[i]) + 1;
    ctxt->array[i] = s;
    memcpy(s, array[i], len);
    s += len;
  }
  ctxt->array[i] = NULL;

  return &ctxt->base;
}

/**
 * Request function to run on GLib thread with data and array of strings.
 *
 * @note: function will always receive the number of elements and the
 *        copied array will always be NULL terminated. Functions can
 *        always rely on such values even if they were not given as
 *        original parameter.
 *
 * @param func: function to call, first argument is the number of
 *        elements and second is the array, with a NULL terminator.
 * @param data: extra data to give to function.
 * @param count: elements in array or 0 to count @a array until a NULL
 *        terminator is found.
 * @param array: elements to COPY. If @a count is 0, then it must be NULL
 *        terminated. Strings and array will be copied, no reference will
 *        be kept after function returns.
 */
Eina_Bool
gstuff_glib_run_ptr_string_array(void (*func)(void *data, unsigned int count, const char **array), const void *data, unsigned int count, const char **array)
{
  return gstuff_glib_run(
    _gstuff_ptr_string_array_ctxt_new(func, data, count, array));
}

/**
 * Request function to run on Ecore thread with data and array of strings.
 *
 * @note: function will always receive the number of elements and the
 *        copied array will always be NULL terminated. Functions can
 *        always rely on such values even if they were not given as
 *        original parameter.
 *
 * @param func: function to call, first argument is the number of
 *        elements and second is the array, with a NULL terminator.
 * @param data: extra data to give to function.
 * @param count: elements in array or 0 to count @a array until a NULL
 *        terminator is found.
 * @param array: elements to COPY. If @a count is 0, then it must be NULL
 *        terminated. Strings and array will be copied, no reference will
 *        be kept after function returns.
 */
Eina_Bool
gstuff_ecore_run_ptr_string_array(void (*func)(void *data, unsigned int count, const char **array), const void *data, unsigned int count, const char **array)
{
  return gstuff_ecore_run
    (_gstuff_ptr_string_array_ctxt_new(func, data, count, array));
}

char *
gstuff_pixbuf_to_file(GdkPixbuf *pixbuf, const char *name)
{
  static char *dir = NULL;
  char *filename;

  if (!pixbuf)
    return NULL;

  if (!dir) {
    dir = g_build_filename(g_get_user_cache_dir(), PACKAGE, "icons", NULL);
    g_mkdir_with_parents(dir, 0644);
  }

  filename = g_build_filename(dir, name, NULL);
  if (gdk_pixbuf_save(pixbuf, filename, "png", NULL, NULL))
    return filename;
  else {
    ERR("cannot not save pixbuf %p to file %s.\n", pixbuf, filename);
    g_free(filename);
    return NULL;
  }
}

struct _gdk_evas_key
{
   guint       gdkkey;
   const char* keyname;
};

static const struct _gdk_evas_key keys[] =
{
  { GDK_BackSpace,    "BackSpace" },
  { GDK_Tab,          "Tab" },
  { GDK_Clear,        "Clear" },
  { GDK_Return,       "Return" },
  { GDK_Pause,        "Pause" },
  { GDK_Sys_Req,      "SysReq" },
  { GDK_Escape,       "Escape" },
  { GDK_Delete,       "Delete" },
  { GDK_Left,         "Left" },
  { GDK_Up,           "Up" },
  { GDK_Right,        "Right" },
  { GDK_Down,         "Down" },
  { GDK_Insert,       "Insert" },
  { GDK_Home,         "Home" },
  { GDK_End,          "End" },
  { GDK_Page_Up,      "Page_Up" },
  { GDK_Page_Down,    "Page_Down" },
  { GDK_KP_0,         "KP0" },
  { GDK_KP_1,         "KP1" },
  { GDK_KP_2,         "KP2" },
  { GDK_KP_3,         "KP3" },
  { GDK_KP_4,         "KP4" },
  { GDK_KP_5,         "KP5" },
  { GDK_KP_6,         "KP6" },
  { GDK_KP_7,         "KP7" },
  { GDK_KP_8,         "KP8" },
  { GDK_KP_9,         "KP9" },
  { GDK_KP_Divide,    "KP_Divide" },
  { GDK_KP_Multiply,  "KP_Multiply" },
  { GDK_KP_Subtract,  "KP_Minus" },
  { GDK_KP_Add,       "KP_Plus" },
  { GDK_KP_Enter,     "KP_Enter" },
  { GDK_KP_Equal,     "KP_Equals" },
  { GDK_space,        "space" },
  { GDK_exclam,       "exclam" },
  { GDK_quotedbl,     "quotedbl" },
  { GDK_numbersign,   "numbersign" },
  { GDK_dollar,       "dollar" },
  { GDK_ampersand,    "ampersand" },
  { GDK_apostrophe,   "apostrophe" },
  { GDK_parenleft,    "parenleft" },
  { GDK_parenright,   "parenright" },
  { GDK_asterisk,     "asterik" },
  { GDK_plus,         "plus" },
  { GDK_comma,        "comma" },
  { GDK_minus,        "minus" },
  { GDK_period,       "period" },
  { GDK_slash,        "slash" },
  { GDK_0,            "0" },
  { GDK_1,            "1" },
  { GDK_2,            "2" },
  { GDK_3,            "3" },
  { GDK_4,            "4" },
  { GDK_5,            "5" },
  { GDK_6,            "6" },
  { GDK_7,            "7" },
  { GDK_8,            "8" },
  { GDK_9,            "9" },
  { GDK_colon,        "colon" },
  { GDK_semicolon,    "semicolon" },
  { GDK_less,         "less" },
  { GDK_equal,        "equal" },
  { GDK_greater,      "greater" },
  { GDK_question,     "question" },
  { GDK_at,           "at" },
  { GDK_A,            "A" },
  { GDK_B,            "B" },
  { GDK_C,            "C" },
  { GDK_D,            "D" },
  { GDK_E,            "E" },
  { GDK_F,            "F" },
  { GDK_G,            "G" },
  { GDK_H,            "H" },
  { GDK_I,            "I" },
  { GDK_J,            "J" },
  { GDK_K,            "K" },
  { GDK_L,            "L" },
  { GDK_M,            "M" },
  { GDK_N,            "N" },
  { GDK_O,            "O" },
  { GDK_P,            "P" },
  { GDK_Q,            "Q" },
  { GDK_R,            "R" },
  { GDK_S,            "S" },
  { GDK_T,            "T" },
  { GDK_U,            "U" },
  { GDK_V,            "V" },
  { GDK_W,            "W" },
  { GDK_X,            "X" },
  { GDK_Y,            "Y" },
  { GDK_Z,            "Z" },
  { GDK_bracketleft,  "bracketleft" },
  { GDK_backslash,    "backslash" },
  { GDK_bracketright, "bracketright" },
  { GDK_asciicircum,  "asciicircumm" },
  { GDK_underscore,   "underscore" },
  { GDK_a,            "a" },
  { GDK_b,            "b" },
  { GDK_c,            "c" },
  { GDK_d,            "d" },
  { GDK_e,            "e" },
  { GDK_f,            "f" },
  { GDK_g,            "g" },
  { GDK_h,            "h" },
  { GDK_i,            "i" },
  { GDK_j,            "j" },
  { GDK_k,            "k" },
  { GDK_l,            "l" },
  { GDK_m,            "m" },
  { GDK_n,            "n" },
  { GDK_o,            "o" },
  { GDK_p,            "p" },
  { GDK_q,            "q" },
  { GDK_r,            "r" },
  { GDK_s,            "s" },
  { GDK_t,            "t" },
  { GDK_u,            "u" },
  { GDK_v,            "v" },
  { GDK_w,            "w" },
  { GDK_x,            "x" },
  { GDK_y,            "y" },
  { GDK_z,            "z" },
  { GDK_asciitilde,   "asciitilde" },
  { GDK_F1,           "F1" },
  { GDK_F2,           "F2" },
  { GDK_F3,           "F3" },
  { GDK_F4,           "F4" },
  { GDK_F5,           "F5" },
  { GDK_F6,           "F6" },
  { GDK_F7,           "F7" },
  { GDK_F8,           "F8" },
  { GDK_F9,           "F9" },
  { GDK_F10,          "F10" },
  { GDK_F11,          "F11" },
  { GDK_F12,          "F12" },
  { GDK_F13,          "F13" },
  { GDK_F14,          "F14" },
  { GDK_F15,          "F15" },
  { GDK_Num_Lock,     "Num_Lock" },
  { GDK_Caps_Lock,    "Caps_Lock" },
  { GDK_Scroll_Lock,  "Scroll_Lock" },
  { GDK_Shift_R,      "Shift_R" },
  { GDK_Shift_L,      "Shift_L" },
  { GDK_Control_R,    "Control_R" },
  { GDK_Control_L,    "Control_L" },
  { GDK_Alt_R,        "Alt_R" },
  { GDK_Alt_L,        "Alt_L" },
  { GDK_Meta_R,       "Meta_R" },
  { GDK_Meta_L,       "Meta_L" },
  { GDK_Super_L,      "Super_L" },
  { GDK_Super_R,      "Super_R" },
  { GDK_Help,         "Help" },
  { GDK_Print,        "Print" },
  { GDK_Break,        "Break" },
  { GDK_Menu,         "Menu" },
  { GDK_EuroSign,     "Euro" },
  { GDK_Undo,         "Undo" },
  { 0,                NULL }
};

guint
gstuff_keyname_to_key(const char *keyname)
{
  int i;
  if (!keyname || strcmp(keyname, "Shift_R") == 0)
    return 0;
  for (i = 0; keys[i].keyname; ++i)
  {
    if (strcmp(keys[i].keyname, keyname) == 0)
      return keys[i].gdkkey;
  }
  ERR("Could not find GDK version of keyname %s\n", keyname);
  return 0;
}
