# ifdef HAVE_CONFIG_H
#  include "config.h"
# endif

#include <Ecore_Getopt.h>
#include <Efreet.h>
#include <Efreet_Mime.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/wait.h>

# ifdef E_API
#  undef E_API
# endif
# ifdef WIN32
#  ifdef BUILDING_DLL
#   define E_API __declspec(dllexport)
#  else
#   define E_API __declspec(dllimport)
#  endif
# else
#  ifdef __GNUC__
#   if __GNUC__ >= 4
/* BROKEN in gcc 4 on amd64 */
#    if 0
#     pragma GCC visibility push(hidden)
#    endif
#    define E_API __attribute__ ((visibility("default")))
#   else
#    define E_API
#   endif
#  else
#   define E_API
#  endif
# endif

static const char *
xdg_defaults_get(const char *path, const char *mime)
{
   Efreet_Ini *ini;
   const char *str;

   if (access(path, R_OK) != 0)
     return NULL;

   ini = efreet_ini_new(path);
   if (!ini)
     return NULL;

   efreet_ini_section_set(ini, "Default Applications");
   str = eina_stringshare_add(efreet_ini_string_get(ini, mime));
   efreet_ini_free(ini);

   return str;
}

static Efreet_Desktop *
xdg_desktop_from_string_list(const char *strlist)
{
   Efreet_Desktop *desktop = NULL;
   char **array = eina_str_split(strlist, ";", 0);
   unsigned int i;

   if (!array)
     return NULL;

   for (i = 0; array[i] != NULL; i++)
     {
        const char *name = array[i];
        if (name[0] == '/')
          desktop = efreet_desktop_new(name);
        else
          desktop = efreet_util_desktop_file_id_find(name);

        if (desktop)
          {
             if (desktop->exec) break;
             else
               {
                  efreet_desktop_free(desktop);
                  desktop = NULL;
               }
          }
     }

   free(array[0]);
   free(array);

   return desktop;
}

static Efreet_Desktop *
desktop_first_free_others(Eina_List *lst)
{
   Efreet_Desktop *desktop, *d;
   if (!lst)
     return NULL;

   desktop = lst->data;
   efreet_desktop_ref(desktop);

   EINA_LIST_FREE(lst, d)
     efreet_desktop_free(d);

   return desktop;
}

static Efreet_Desktop *
handler_find(const char *mime)
{
   Efreet_Desktop *desktop = NULL;
   char path[PATH_MAX];
   const char *name;

   snprintf(path, sizeof(path), "%s/mimeapps.list",
            efreet_config_home_get());
   name = xdg_defaults_get(path, mime);
   if (!name)
     {
        const Eina_List *n, *dirs = efreet_data_dirs_get();
        const char *d;
        EINA_LIST_FOREACH(dirs, n, d)
          {
             snprintf(path, sizeof(path), "%s/applications/defaults.list", d);
             name = xdg_defaults_get(path, mime);
             if (name)
               break;
          }
     }

   if (name)
     {
        desktop = xdg_desktop_from_string_list(name);
        eina_stringshare_del(name);
     }

   if (!desktop)
     desktop = desktop_first_free_others(efreet_util_desktop_mime_list(mime));

   return desktop;
}

static void *
get_command(void *data, Efreet_Desktop *desktop EINA_UNUSED, char *command, int remaining EINA_UNUSED)
{
   Eina_List **p_cmd = data;
   *p_cmd = eina_list_append(*p_cmd, command);
   return NULL;
}

static char **
mime_open(const char *mime, const char *const *argv, int argc)
{
   Efreet_Desktop *desktop = handler_find(mime);
   Eina_List *files = NULL;
   Eina_List *cmds = NULL;
   char **ret;

   if (!desktop)
     return NULL;

   for (; argc > 0; argc--, argv++)
     files = eina_list_append(files, *argv);

   efreet_desktop_command_get(desktop, files, get_command, &cmds);

   if (!cmds) ret = NULL;
   else
     {
        char *c;

        ret = calloc(eina_list_count(cmds) + 1, sizeof(char *));
        if (ret)
          {
             unsigned int i = 0;
             EINA_LIST_FREE(cmds, c)
               {
                  ret[i] = c; /* was strdup by efreet_desktop_command_get() */
                  i++;
               }
             ret[i] = NULL;
          }
        else
          {
             EINA_LIST_FREE(cmds, c)
               free(c);
          }
     }

   eina_list_free(files);

   return ret;
}

static void
append_single_quote_escaped(Eina_Strbuf *b, const char *str)
{
   const char *itr = str;
   for (; *itr != '\0'; itr++)
     {
        if (*itr != '\'')
          eina_strbuf_append_char(b, *itr);
        else
          eina_strbuf_append(b, "\\'");
     }
}

static char **
single_command_open(const char *command, const char *const *argv, int argc)
{
   char **ret = calloc(2, sizeof(char *));
   Eina_Strbuf *b;
   int i;

   if (!ret)
     return NULL;

   b = eina_strbuf_new();
   if (!b)
     {
        free(ret);
        return NULL;
     }
   eina_strbuf_append(b, command);

   for (i = 0; i < argc; i++)
     {
        Eina_Bool has_space = EINA_FALSE;
        int s_idx_sq = -1, s_idx_dq = -1;
        int l_idx_sq = -1, l_idx_dq = -1;
        int idx;
        const char *itr;

        for (idx = 0, itr = argv[i]; *itr != '\0'; itr++, idx++)
          {
             if ((!has_space) && (isspace(*itr)))
               has_space = EINA_TRUE;

             if (*itr == '\'')
               {
                  l_idx_sq = idx;
                  if (s_idx_sq < 0)
                    s_idx_sq = idx;
               }

             if (*itr == '\'')
               {
                  l_idx_dq = idx;
                  if (s_idx_dq < 0)
                    s_idx_dq = idx;
               }
          }
        idx--;

        eina_strbuf_append_char(b, ' ');
        if ((!has_space) ||
            ((s_idx_sq == 0) && (l_idx_sq == idx)) ||
            ((s_idx_dq == 0) && (l_idx_dq == idx)))
          eina_strbuf_append(b, argv[i]);
        else
          {
             char c;
             if ((s_idx_sq >= 0) && (s_idx_dq < 0))
               c = '"';
             else if ((s_idx_sq < 0) && (s_idx_dq >= 0))
               c = '\'';
             else
               c = 0;

             if (c)
               {
                  eina_strbuf_append_char(b, c);
                  eina_strbuf_append(b, argv[i]);
                  eina_strbuf_append_char(b, c);
               }
             else
               {
                  eina_strbuf_append_char(b, '\'');
                  append_single_quote_escaped(b, argv[i]);
                  eina_strbuf_append_char(b, '\'');
               }
          }
     }

   ret[0] = eina_strbuf_string_steal(b);
   eina_strbuf_free(b);

   return ret;
}

static Efreet_Desktop *
_terminal_get(const char *defaults_list)
{
   Efreet_Desktop *tdesktop = NULL;
   Efreet_Ini *ini;
   const char *s;

   ini = efreet_ini_new(defaults_list);
   if ((ini) && (ini->data) &&
       (efreet_ini_section_set(ini, "Default Applications")) &&
       (ini->section))
     {
        s = efreet_ini_string_get(ini, "x-scheme-handler/terminal");
        if (s) tdesktop = efreet_util_desktop_file_id_find(s);
     }
   if (ini) efreet_ini_free(ini);
   return tdesktop;
}

static char **
terminal_open(void)
{
   const char *terms[] =
   {
      "terminology.desktop",
      "rxvt.desktop",
      "gnome-terimnal.desktop",
      "konsole.desktop",
      NULL
   };
   const char *s;
   char buf[PATH_MAX], **ret;
   Efreet_Desktop *tdesktop = NULL, *td;
   Eina_List *l;
   int i;

   s = efreet_data_home_get();
   if (s)
     {
        snprintf(buf, sizeof(buf), "%s/mimeapps.list",
                 efreet_config_home_get());
        tdesktop = _terminal_get(buf);
     }
   if (tdesktop) goto have_desktop;
   EINA_LIST_FOREACH(efreet_data_dirs_get(), l, s)
     {
        snprintf(buf, sizeof(buf), "%s/applications/defaults.list", s);
        tdesktop = _terminal_get(buf);
        if (tdesktop) goto have_desktop;
     }

   for (i = 0; terms[i]; i++)
     {
        tdesktop = efreet_util_desktop_file_id_find(terms[i]);
        if (tdesktop) goto have_desktop;
     }
   if (!tdesktop)
     {
        l = efreet_util_desktop_category_list("TerminalEmulator");
        if (l)
          {
             // just take first one since above list doesn't work.
             tdesktop = l->data;
             EINA_LIST_FREE(l, td)
               {
                  // free/unref the desktosp we are not going to use
                  if (td != tdesktop) efreet_desktop_free(td);
               }
          }
     }
   if (!tdesktop) return NULL;
have_desktop:
   if (!tdesktop->exec)
     {
        efreet_desktop_free(tdesktop);
        return NULL;
     }
   ret = malloc(sizeof(char *) * 2);
   if (!ret) return NULL;
   ret[0] = strdup(tdesktop->exec);
   ret[1] = NULL;
   if (!ret[0])
     {
        free(ret);
        efreet_desktop_free(tdesktop);
        return NULL;
     }
   efreet_desktop_free(tdesktop);
   return ret;
}

static char **
browser_open(const char *const *argv, int argc)
{
   const char *env = getenv("BROWSER");
   if (env) return single_command_open(env, argv, argc);
   return mime_open("x-scheme-handler/http", argv, argc);
}

static char **
local_open(const char *path)
{
   const char *mime = efreet_mime_type_get(path);
   if (mime)
     {
        char **ret = mime_open(mime, &path, 1);
        if (ret)
          return ret;
        return single_command_open("enlightenment_filemanager", &path, 1);
     }

   fprintf(stderr, "ERROR: Could not get mime type for: %s\n", path);
   return NULL;
}

static char **
protocol_open(const char *str)
{
   Efreet_Uri *uri = efreet_uri_decode(str);
   char **ret = NULL;

   if (!uri)
     {
        fprintf(stderr, "ERROR: Could not decode uri: %s\n", str);
        return NULL;
     }

   if (!uri->protocol)
     fprintf(stderr, "ERROR: Could not get protocol from uri: %s\n", str);
   else if (strcmp(uri->protocol, "file") == 0)
     ret = local_open(uri->path);
   else
     {
        char mime[256];
        snprintf(mime, sizeof(mime), "x-scheme-handler/%s", uri->protocol);
        ret = mime_open(mime, &str, 1);
     }
   efreet_uri_free(uri);
   return ret;
}

static const struct type_mime
{
   const char *type;
   const char *mime;
} type_mimes[] = {
   /* {"browser", "x-scheme-handler/http"}, */
   {"mail", "x-scheme-handler/mailto"},
   /*  {"terminal", NULL}, */
   {"filemanager", "x-scheme-handler/file"},
   {"image", "image/jpeg"},
   {"video", "video/x-mpeg"},
   {"music", "audio/mp3"},
   {NULL, NULL}
};

static const char *type_choices[] = {
   "browser",
   "mail",
   "terminal",
   "filemanager",
   "image",
   "video",
   "music",
   NULL
};

static const Ecore_Getopt options = {
   "enlightenment_open",
   "%prog [options] <file-or-folder-or-url>",
   PACKAGE_VERSION,
   "(C) 2012 Gustavo Sverzut Barbieri and others",
   "BSD 2-Clause",
   "Opens the file using Enlightenment standards.",
   EINA_FALSE,
   {
      ECORE_GETOPT_CHOICE('t', "type", "Choose program type to launch.",
                          type_choices),
      ECORE_GETOPT_VERSION('V', "version"),
      ECORE_GETOPT_COPYRIGHT('C', "copyright"),
      ECORE_GETOPT_LICENSE('L', "license"),
      ECORE_GETOPT_HELP('h', "help"),
      ECORE_GETOPT_SENTINEL
   }
};

E_API int
main(int argc, char *argv[])
{
   Eina_Bool quit_option = EINA_FALSE;
   char *type = NULL;
   Ecore_Getopt_Value values[] = {
      ECORE_GETOPT_VALUE_STR(type),
      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 args;
   char **cmds;

   args = ecore_getopt_parse(&options, values, argc, argv);
   if (args < 0)
     {
        fputs("ERROR: Could not parse command line options.\n", stderr);
        return EXIT_FAILURE;
     }
   if (quit_option) return EXIT_SUCCESS;

   if ((type == NULL) && (args == argc))
     {
        fputs("ERROR: Missing file, directory or URL or --type.\n", stderr);
        return EXIT_FAILURE;
     }

   efreet_init();
   efreet_mime_init();

   if (type)
     {
        if (strcmp(type, "terminal") == 0)
          cmds = terminal_open();
        else if (strcmp(type, "browser") == 0)
          cmds = browser_open((const char *const *)argv + args, argc - args);
        else
          {
             const struct type_mime *itr;

             for (itr = type_mimes; itr->type != NULL; itr++)
               {
                  if (strcmp(type, itr->type) == 0)
                    {
                       cmds = mime_open(itr->mime,
                                        (const char *const *)argv + args,
                                        argc - args);
                       break;
                    }
               }
             if (!itr->type)
               {
                  fprintf(stderr, "ERROR: type not supported %s\n", type);
                  cmds = NULL;
               }
          }
     }
   else if (strstr(argv[args], ":"))
     cmds = protocol_open(argv[args]);
   else
     cmds = local_open(argv[args]);

   efreet_mime_shutdown();
   efreet_shutdown();

   /* No EFL, plain boring sequential system() calls */
   if (!cmds)
     return EXIT_FAILURE;
   else
     {
        char **itr;
        int ret = EXIT_SUCCESS;

        for (itr = cmds; *itr != NULL; itr++)
          {
             /* Question: should we execute them in parallel? */
             int r = system(*itr);
             if (r < 0)
               fprintf(stderr, "ERROR: %s executing %s\n", strerror(errno),
                       *itr);
             free(*itr);
             if (r > 0) /* Question: should we stop the loop on first faiure? */
               ret = r;
          }
        free(cmds);

        return WEXITSTATUS(ret);
     }
}

