/* -*- 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/>.
 */

#include "netbook-launcher.h"
#include <Elementary.h>
#ifndef ELM_LIB_QUICKLAUNCH
#include <glib/gi18n.h>
#include <unique/unique.h>
#include <Ecore_Getopt.h>
#include <Ecore.h>
#include <Ecore_X.h>
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>

struct prefs _nl_prefs = {NULL};
int EVENT_ICON_INFO_ENTER = 0;
int EVENT_ICON_INFO_LEAVE = 0;

static Eina_Bool _nl_restarted = 0;

static Eina_Bool
_auto_geometry(int *x, int *y, int *w, int *h)
{
  ecore_x_window_geometry_get(0, x, y, w, h);
  return 1;
}

static void
_sidebar_menu_selected(void *data, Evas_Object *sidebar __UNUSED__, void *event_info)
{
  Evas_Object *win = data;
  const char *category = event_info;
  win_apps_show_category(win, category);
}

static void
_sidebar_gads_selected(void *data, Evas_Object *sidebar __UNUSED__, void *event_info __UNUSED__)
{
  Evas_Object *win = data;
  win_apps_show_category(win, "Gadgets");
}

static void
_sidebar_favs_selected(void *data, Evas_Object *sidebar __UNUSED__, void *event_info __UNUSED__)
{
  Evas_Object *win = data;
  win_apps_show_favorites(win);
}

static void
_sidebar_prefs_selected(void *data, Evas_Object *sidebar __UNUSED__, void *event_info __UNUSED__)
{
  Evas_Object *win = data;
  win_preferences_show(win);
}

static void
_sidebar_places_selected(void *data, Evas_Object *sidebar __UNUSED__, void *event_info __UNUSED__)
{
  Evas_Object *win = data;
  win_places_show(win);
}

static void
_sidebar_quit_selected(void *data, Evas_Object *sidebar __UNUSED__, void *event_info __UNUSED__)
{
  Evas_Object *win = data;
  win_quit_show(win);
}

static void
_apps_quit_selected(void *data, Evas_Object *apps __UNUSED__, void *event_info __UNUSED__)
{
  Evas_Object *win = data;
  win_quit_show(win);
}

static void
_apps_add_item_selected(void *data, Evas_Object *apps __UNUSED__, void *event_info __UNUSED__)
{
  Evas_Object *win = data;
  win_add_item_show(win);
}


static void
_launchfeedback_show(void *data, Evas_Object *apps __UNUSED__, void *event_info __UNUSED__)
{
  Evas_Object *win = data;
  win_hide_sidebar_and_current_section(win);
}

static void
_launchfeedback_hide(void *data, Evas_Object *apps __UNUSED__, void *event_info __UNUSED__)
{
  Evas_Object *win = data;
  win_show_sidebar_and_current_section(win);
}


static void
_showing_desktop_changed(WnckScreen *screen, Evas_Object *win)
{
  Evas_Object *sidebar;

  // Move to Favorites screen if user just went to desktop
  if (wnck_screen_get_showing_desktop(screen))
  {
    sidebar = win_sidebar_get(win);
    sidebar_favorites_activate(sidebar);
  }
}

Evas_Object *
_nl_icon_load_sized(Evas_Object *parent, const char *path, unsigned int size)
{
   Evas_Object *icon;

   if ((!path) || (path[0] != '/'))
   {
     ERR("invalid path '%s'\n", path);
     return NULL;
   }

   icon = icon_async_add(parent);
   if (!icon)
   {
     ERR("could not create icon async\n");
     return NULL;
   }

   icon_async_size_set(icon, size, size);
   if (!icon_async_file_set(icon, path))
   {
     ERR("could not set icon file '%s'\n", path);
     evas_object_del(icon);
     return NULL;
   }

   return icon;
}

void
_nl_edje_part_size_get(Evas_Object *parent, const char *group, const char *part, Evas_Coord *w, Evas_Coord *h)
{
  Evas_Object *layout, *edje;

  if (w) *w = 0;
  if (h) *h = 0;

  layout = elm_layout_add(parent);
  if (!layout)
  {
    ERR("could not add layout.\n");
    return;
  }

  edje = elm_layout_edje_get(layout);
  if (!elm_layout_file_set(layout, _nl_prefs.theme, group))
  {
    int err = edje_object_load_error_get(edje);
    const char *errmsg = edje_load_error_str(err);
    ERR("cannot load theme '%s', group '%s': %s\n",
        _nl_prefs.theme, group, errmsg);
    evas_object_del(layout);
    return;
  }

  edje_object_part_geometry_get(edje, part, NULL, NULL, w, h);
  evas_object_del(layout);
}

Eina_Bool
_nl_edje_item_size_calc(struct item_size *is, Evas_Object *parent, const char *group)
{
  Evas_Object *ed, *tmp;
  const char *str;
  double scale_factor;

  ed = elm_layout_edje_get(parent);
  if (!ed)
  {
    ERR("could not get layout edje object\n");
    return 0;
  }

  tmp = edje_object_add(evas_object_evas_get(parent));
  if (!tmp)
  {
    ERR("could not create Edje object.\n");
    return 0;
  }

  if (!edje_object_file_set(tmp, _nl_prefs.theme, group))
  {
    int err = edje_object_load_error_get(tmp);
    const char *errmsg = edje_load_error_str(err);
    ERR("cannot load theme '%s', group '%s': %s\n",
        _nl_prefs.theme, group, errmsg);
    evas_object_del(tmp);
    return 0;
  }

  scale_factor = elm_object_scale_get(parent);

  edje_object_size_min_get(tmp, &is->w, &is->h);
  if ((is->w == 0) && (is->h == 0))
    edje_object_size_min_calc(tmp, &is->w, &is->h);

  str = edje_object_data_get(tmp, "scale_min");
  if (str && (strcmp(str, "on") == 0))
  {
    is->w *= scale_factor;
    is->h *= scale_factor;
  }

  if ((is->w < is->h) && (is->w > 0))
    is->icon_size = is->w;
  else if ((is->h <= is->w) && (is->h > 0))
    is->icon_size = is->h;

  str = edje_object_data_get(tmp, "icon_size");
  if (str)
    is->icon_size = atoi(str);

  str = edje_object_data_get(tmp, "scale_icon");
  if (str && (strcmp(str, "on") == 0))
    is->icon_size *= scale_factor;

  evas_object_del(tmp);
  return 1;
}

void
_nl_restart(void)
{
  DBG("requested restart.\n");
  _nl_restarted = 1;
  ecore_main_loop_quit();
}

static void
_nl_icon_info_event_enter_free(void *data __UNUSED__, void *event)
{
  struct icon_info_event_enter *ev = event;
  eina_stringshare_del(ev->name);
  eina_stringshare_del(ev->icon);
  eina_stringshare_del(ev->comment);
  free(ev);
}

Eina_Bool
_nl_icon_info_event_enter(const char *name, const char *icon, const char *comment)
{
  struct icon_info_event_enter *ev = malloc(sizeof(*ev));
  if (!ev)
    return 0;
  ev->name = eina_stringshare_add(name);
  ev->icon = eina_stringshare_add(icon);
  ev->comment = eina_stringshare_add(comment);
  return !!ecore_event_add
    (EVENT_ICON_INFO_ENTER, ev, _nl_icon_info_event_enter_free, NULL);
}

Eina_Bool
_nl_icon_info_event_leave(void)
{
  return !!ecore_event_add(EVENT_ICON_INFO_LEAVE, NULL, NULL, NULL);
}

static const char *
_mypath_get(const char *argv0)
{
  static char mypath[PATH_MAX];
  char procpath[PATH_MAX];
  const char *env;
  ssize_t len;
  long pid;

  if (argv0[0] == '/')
    return argv0;

  if ((argv0[0] == '.') && realpath(argv0, mypath))
    return mypath;

  pid = getpid();
  len = snprintf(procpath, sizeof(procpath), "/proc/%ld/exe", pid);
  if (len < (ssize_t)sizeof(procpath))
  {
    len = readlink(procpath, mypath, sizeof(mypath) - 1);
    if (len < 0)
      fprintf(stderr, "ERROR: readlink '%s' failed: %s\n",
              procpath, strerror(errno));
    else if (len < (ssize_t)sizeof(mypath) - 1)
    {
      mypath[len] = '\0';
      return mypath;
    }
  }

  env = getenv("PATH");
  if (!env)
    fputs("ERROR: no $PATH?\n", stderr);
  else
  {
    size_t argv0len = strlen(argv0);
    while (env)
    {
      const char *p = strchr(env, ':');
      ssize_t len;
      if (p)
        len = p - env;
      else
        len = strlen(env);

      if (len + argv0len + 2 >= (ssize_t)sizeof(mypath))
        continue;

      memcpy(mypath, env, len);
      mypath[len++] = '/';
      memcpy(mypath + len, argv0, argv0len + 1);

      if (access(mypath, R_OK | X_OK) == 0)
        return mypath;

      if (p)
        env = p + 1;
      else break;
    }
  }

  fprintf(stderr, "ERROR: could not find '%s' binary in $PATH.\n", argv0);
  return argv0;
}

Eina_Bool
_nl_theme_resolve(char *buf, const char *value)
{
  char tmp[PATH_MAX];

  if (!value)
    return 0;

  if (!strchr(value, '/'))
    {
      const char *ext = strchr(value, '.') ? "" : ".edj";
      size_t len;

      len = snprintf
        (tmp, sizeof(tmp), PACKAGE_DATA_DIR"/data/themes/%s%s", value, ext);
      if (len >= (size_t)sizeof(tmp))
      {
        ERR("path '"PACKAGE_DATA_DIR"/data/themes/%s%s' is too long.\n",
            value, ext);
        return 0;
      }

      value = tmp;
    }

  if (!realpath(value, buf))
  {
    ERR("could not get real path for theme '%s': %s\n", value, strerror(errno));
    return 0;
  }

  return 1;
}

const Ecore_Getopt optdesc = {
  PACKAGE,
  NULL,
  PACKAGE_VERSION,
  "(C) 2009 - Canonical Ltd. All rights reserved.",
  "License: GNU GPLv3, see COPYING.",
  "Application launcher for netbooks.\n"
  "\n"
  "Fancy application that stays below all other applications and over desktop "
  "and presents applications by categories and also quick file access."
  "\n"
  "Inspired by Ubuntu Netbook Remix (UNR) netbook-launcher.",
  0,
  {
    ECORE_GETOPT_CALLBACK_ARGS
    ('g', "geometry", "window geometry to use.",
     "X:Y:W:H", ecore_getopt_callback_geometry_parse, NULL),
    ECORE_GETOPT_STORE_INT('d', "debug", "debug level (see -q and -v)"),
    ECORE_GETOPT_COUNT('q', "quiet", "print out less information"),
    ECORE_GETOPT_COUNT('v', "verbose", "print out more information"),
    ECORE_GETOPT_LICENSE('L', "license"),
    ECORE_GETOPT_COPYRIGHT('C', "copyright"),
    ECORE_GETOPT_VERSION('V', "version"),
    ECORE_GETOPT_HELP('h', "help"),
    ECORE_GETOPT_SENTINEL
  }
};

EAPI int
elm_main(int argc, char **argv)
{
  UniqueApp *unique;
  Evas_Object *win, *error, *launchfeedback, *sidebar, *apps, *places,
    *prefs, *bg, *add_item;
  Eina_Bool quit_option = 0;
  int debug_level = EINA_LOG_LEVEL_WARN;
  Eina_Rectangle geometry = {-1, -1, -1, -1};
  char *theme = NULL, realtheme[PATH_MAX];
  Ecore_Getopt_Value values[] = {
    ECORE_GETOPT_VALUE_PTR_CAST(geometry),
    ECORE_GETOPT_VALUE_INT(debug_level),
    ECORE_GETOPT_VALUE_INT(debug_level),
    ECORE_GETOPT_VALUE_INT(debug_level),
    ECORE_GETOPT_VALUE_BOOL(quit_option),
    ECORE_GETOPT_VALUE_BOOL(quit_option),
    ECORE_GETOPT_VALUE_BOOL(quit_option),
    ECORE_GETOPT_VALUE_BOOL(quit_option),
    ECORE_GETOPT_VALUE_NONE
  };
  int retval = 0;

  if (ecore_getopt_parse(&optdesc, values, argc, argv) < 0)
  {
    ERR("could not parse arguments.\n");
    retval = -1;
    goto shutdown_elm;
  }

  if (quit_option)
    goto shutdown_elm;

  EVENT_ICON_INFO_ENTER = ecore_event_type_new();
  EVENT_ICON_INFO_LEAVE = ecore_event_type_new();

  eina_log_level_set(debug_level);

  setlocale(LC_ALL, "");
  bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
  bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
  textdomain(GETTEXT_PACKAGE);

  if (!ecore_file_init())
  {
    ERR("could not init ecore_file subsystem.\n");
    retval = -3;
    goto shutdown_elm;
  }

  theme = getenv("NETBOOK_LAUNCHER_EFL_THEME");

  if (!conf_init())
  {
    ERR("could not init conf subsystem.\n");
    retval = -4;
    goto shutdown_ecore_file;
  }

  if (!theme)
    conf_theme_get((const char **)&theme);

  if (!theme)
    theme = PACKAGE_DATA_DIR"/data/themes/hedley.edj";

  if (!_nl_theme_resolve(realtheme, theme))
    goto shutdown_conf;

  _nl_prefs.theme = realtheme;
  INF("theme: %s\n", _nl_prefs.theme);
  if (_nl_prefs.theme)
    elm_theme_overlay_add(_nl_prefs.theme);

  if (!sound_init())
  {
    ERR("could not init sound.\n");
    retval = -6;
    goto shutdown_conf;
  }

  if (!gstuff_init())
  {
    ERR("could not init gstuff.\n");
    retval = -7;
    goto shutdown_sound;
  }

  /* Make sure we're the only NLE */
  unique = unique_app_new_with_commands ("net.launchpad.launch-lite-proj", NULL, NULL, NULL);
  if (unique_app_is_running (unique))
  {
    ERR("already running.\n");
    /* Show other instance before we go */
    wnck_screen_toggle_showing_desktop (wnck_screen_get_default(), TRUE);
    goto shutdown_gstuff;
  }

  win = win_add();
  if (!win)
  {
    ERR("could not create window.\n");
    retval = -8;
    goto shutdown_gstuff;
  }

  error = error_add(win);
  if (!error)
  {
    ERR("could not create error reporting system.\n");
    retval = -9;
    goto shutdown_win;
  }

  launchfeedback = launchfeedback_add(win);
  if (!launchfeedback)
  {
    ERR("could not create launch feedback system.\n");
    retval = -10;
    goto shutdown_error;
  }

  evas_object_smart_callback_add
    (launchfeedback, CALLBACK_LAUNCHFEEDBACK_SHOW, _launchfeedback_show, win);
  evas_object_smart_callback_add
    (launchfeedback, CALLBACK_LAUNCHFEEDBACK_HIDE, _launchfeedback_hide, win);

  sidebar = sidebar_add(win);
  if (!sidebar)
  {
    ERR("could not create sidebar list.\n");
    retval = -11;
    goto shutdown_launchfeedback;
  }
  win_sidebar_set(win, sidebar);

  apps = apps_add(win);
  if (!apps)
  {
    ERR("could not create apps list.\n");
    retval = -12;
    goto shutdown_sidebar;
  }
  win_apps_set(win, apps);

  evas_object_smart_callback_add
    (apps, CALLBACK_APPS_QUIT_SELECTED, _apps_quit_selected, win);

  evas_object_smart_callback_add
    (apps, CALLBACK_APPS_ADD_ITEM_SELECTED, _apps_add_item_selected, win);

  places = places_add(win);
  if (!places)
  {
    ERR("could not create places list.\n");
    retval = -13;
    goto shutdown_apps;
  }
  win_places_set(win, places);

  add_item = add_item_add(win);
  if (!add_item)
  {
    ERR("could not create add_item screen.\n");
    retval = -14;
    goto shutdown_places;
  }
  win_add_item_set(win, add_item);

  prefs = preferences_add(win);
  if (!prefs)
  {
    ERR("could not create preferences list.\n");
    retval = -14;
    goto shutdown_add_item;
  }
  win_preferences_set(win, prefs);

  const struct {
    const char *sig;
    void (*func)(void *, Evas_Object *, void *);
  } *itr, maps[] = {
    {CALLBACK_SIDEBAR_MENU_SELECTED, _sidebar_menu_selected},
    {CALLBACK_SIDEBAR_FAVORITES_SELECTED, _sidebar_favs_selected},
    {CALLBACK_SIDEBAR_GADGETS_SELECTED, _sidebar_gads_selected},
    {CALLBACK_SIDEBAR_PLACES_SELECTED, _sidebar_places_selected},
    {CALLBACK_SIDEBAR_PREFERENCES_SELECTED, _sidebar_prefs_selected},
    {CALLBACK_SIDEBAR_QUIT_SELECTED, _sidebar_quit_selected},
    {NULL, NULL}
  };
  for (itr = maps; itr->sig != NULL; itr++)
    evas_object_smart_callback_add(sidebar, itr->sig, itr->func, win);

  if ((geometry.x < 0) && (geometry.y < 0) &&
      (geometry.w < 0) && (geometry.y < 0))
  {
    if (!_auto_geometry(&geometry.x, &geometry.y,
                        &geometry.w, &geometry.h))
    {
      ERR("could not discover window geometry!\n");
      retval = -15;
      goto shutdown_prefs;
    }
  }

  g_signal_connect(wnck_screen_get_default(), "showing-desktop-changed",
                   G_CALLBACK(_showing_desktop_changed), win);

  bg = bg_add(win);
  if (!bg)
  {
    ERR("could not create background.\n");
    retval = -16;
    goto shutdown_prefs;
  }
  win_bg_set(win, bg);

#ifdef HAVE_GOOGLE_GADGETS
  efl_gadget_init(argc, argv);
#endif

  evas_object_move(win, geometry.x, geometry.y);
  evas_object_resize(win, geometry.w, geometry.h);
  evas_object_show(win);
  evas_object_focus_set(win, 1);

  elm_run();

  evas_object_del(bg);
shutdown_prefs:
  evas_object_del(prefs);
shutdown_add_item:
  evas_object_del(add_item);
shutdown_places:
  evas_object_del(places);
shutdown_apps:
  evas_object_del(apps);
shutdown_sidebar:
  evas_object_del(sidebar);
shutdown_launchfeedback:
  evas_object_del(launchfeedback);
shutdown_error:
  evas_object_del(error);
shutdown_win:
  evas_object_del(win);
shutdown_gstuff:
  g_object_unref (unique);
  gstuff_shutdown();
shutdown_sound:
  sound_shutdown();
shutdown_conf:
  conf_shutdown();
shutdown_ecore_file:
  ecore_file_shutdown();
shutdown_elm:
  elm_shutdown();

#ifdef HAVE_GOOGLE_GADGETS
  efl_gadget_shutdown();
#endif

  if (_nl_restarted)
  {
    const char *mypath = _mypath_get(argv[0]);

    retval = execve(mypath, argv, environ);
    fprintf(stderr, "failed to execute '%s': %s\n", mypath, strerror(errno));
    return retval;
  }

  return retval;
}

int
main(int argc, char **argv)
{
  /* TODO: check if this is really required.
   *
   * From XInitThreads() man page it is required to lock displays to
   * have them to be used by different threads. This is NOT the case:
   * here we have different displays per thread, so this application
   * will connect to X Server twice, one from EFL and another from
   * GTK. That's why none of the toolkits need locking.
   *
   * If this is true, then this call should be required by use of
   * other Xlib globals, like atoms or other display independent
   * data. Check if this is the case, if not remove this call.
   */
  if (!XInitThreads())
  {
    fputs("ERROR: XInitThreads() failed.\n", stderr);
    return -1;
  }
  elm_init(argc, argv);
  return elm_main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
  return elm_quicklaunch_fallback(argc, argv);
}
#endif
