#include "ephoto.h"
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif

#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

#ifdef _WIN32
# include <winsock2.h>
#endif

#define SHSH(n, v) ((((v) << (n)) & 0xffffffff) | ((v) >> (32 - (n))))

typedef struct _E_Thumb E_Thumb;

struct _E_Thumb
{
   int   objid;
   int   w, h;
   char *file;
   char *key;
};

/* local subsystem functions */
static int       _e_ipc_init(void);
static Eina_Bool _e_ipc_cb_server_add(void *data,
                                      int type,
                                      void *event);
static Eina_Bool _e_ipc_cb_server_del(void *data,
                                      int type,
                                      void *event);
static Eina_Bool _e_ipc_cb_server_data(void *data,
                                       int type,
                                       void *event);
static Eina_Bool _e_cb_timer(void *data);
static void      _e_thumb_generate(E_Thumb *eth);
static char     *_e_thumb_file_id(char *file,
                                  char *key);

/* local subsystem globals */
static Ecore_Ipc_Server *_e_ipc_server = NULL;
static Eina_List *_thumblist = NULL;
static Ecore_Timer *_timer = NULL;
static char _thumbdir[4096] = "";

/* externally accessible functions */
int
main(int argc,
     char **argv)
{
   int i;

   for (i = 1; i < argc; i++)
     {
        if ((!strcmp(argv[i], "-h")) ||
            (!strcmp(argv[i], "-help")) ||
            (!strcmp(argv[i], "--help")))
          {
             printf(
               "This is an internal tool for Ephoto.\n"
               "do not use it.\n"
               );
             exit(0);
          }
        else if (!strncmp(argv[i], "--nice=", 7))
          {
             const char *val;

             val = argv[i] + 7;
             if (*val)
               {
                  if (nice(atoi(val)) < 0) perror("nice");
               }
          }
     }

   ecore_app_no_system_modules();
   ecore_init();
   ecore_app_args_set(argc, (const char **)argv);
   eet_init();
   evas_init();
   ecore_evas_init();
   edje_init();
   ecore_file_init();
   ecore_ipc_init();

   snprintf(_thumbdir, PATH_MAX, "%s/ephoto/thumbnails", efreet_cache_home_get());
   ecore_file_mkpath(_thumbdir);

   if (_e_ipc_init()) ecore_main_loop_begin();

   if (_e_ipc_server)
     {
        ecore_ipc_server_del(_e_ipc_server);
        _e_ipc_server = NULL;
     }

   ecore_ipc_shutdown();
   ecore_file_shutdown();
   ecore_evas_shutdown();
   edje_shutdown();
   evas_shutdown();
   eet_shutdown();
   ecore_shutdown();

   return 0;
}

/* local subsystem functions */
static int
_e_ipc_init(void)
{
   const char *port_str = getenv("EPHOTO_IPC_PORT");
   int port;
   if ((!port_str) || ((port = atoi(port_str)) == 0))
     {
        printf("Error: could not query Ephoto IPC port=%s\n", port_str);
        return 0;
     }
   _e_ipc_server = ecore_ipc_server_connect
       (ECORE_IPC_LOCAL_SYSTEM, "ephoto", port, NULL);
   if (!_e_ipc_server)
     {
        printf("Error: could not connect to Ephoto IPC port=%d\n", port);
        return 0;
     }

   ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_ADD, _e_ipc_cb_server_add, NULL);
   ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_DEL, _e_ipc_cb_server_del, NULL);
   ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_DATA, _e_ipc_cb_server_data, NULL);

   return 1;
}

static Eina_Bool
_e_ipc_cb_server_add(void *data EINA_UNUSED,
                     int type EINA_UNUSED,
                     void *event)
{
   Ecore_Ipc_Event_Server_Add *e;

   e = event;
   ecore_ipc_server_send(e->server,
                         EPHOTO_IPC_DOMAIN_THUMB,
                         1 /*hello*/,
                         0, 0, 0, NULL, 0); /* send hello */
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_ipc_cb_server_del(void *data EINA_UNUSED,
                     int type EINA_UNUSED,
                     void *event EINA_UNUSED)
{
   /* quit now */
   ecore_main_loop_quit();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_ipc_cb_server_data(void *data EINA_UNUSED,
                      int type EINA_UNUSED,
                      void *event)
{
   Ecore_Ipc_Event_Server_Data *e;
   E_Thumb *eth;
   Eina_List *l;
   char *file = NULL;
   char *key = NULL;

   e = event;
   if (e->major != EPHOTO_IPC_DOMAIN_THUMB) return ECORE_CALLBACK_PASS_ON;
   switch (e->minor)
     {
      case 1:
        if (e->data)
          {
             /* begin thumb */
             /* don't check stuff. since this connects TO E it is connecting */
             /* TO a trusted process that WILL send this message properly */
             /* formatted. if the thumbnailer dies anyway - it's not a big loss */
             /* but it is a sign of a bug in e formatting messages maybe */
             file = e->data;
             key = file + strlen(file) + 1;
             if (!key[0]) key = NULL;
             eth = calloc(1, sizeof(E_Thumb));
             if (eth)
               {
                  eth->objid = e->ref;
                  eth->w = e->ref_to;
                  eth->h = e->response;
                  eth->file = strdup(file);
                  if (key) eth->key = strdup(key);
                  _thumblist = eina_list_append(_thumblist, eth);
                  if (!_timer) _timer = ecore_timer_loop_add(0.001, _e_cb_timer, NULL);
               }
          }
        break;

      case 2:
        /* end thumb */
        EINA_LIST_FOREACH(_thumblist, l, eth)
          {
             if (eth->objid == e->ref)
               {
                  _thumblist = eina_list_remove_list(_thumblist, l);
                  free(eth->file);
                  free(eth->key);
                  free(eth);
                  break;
               }
          }
        break;

      case 3:
        /* quit now */
        ecore_main_loop_quit();
        break;

      default:
        break;
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_cb_timer(void *data EINA_UNUSED)
{
   E_Thumb *eth;
   /*
      Eina_List *del_list = NULL, *l;
    */

   /* take thumb at head of list */
   if (_thumblist)
     {
        eth = eina_list_data_get(_thumblist);
        _thumblist = eina_list_remove_list(_thumblist, _thumblist);
        _e_thumb_generate(eth);
        free(eth->file);
        free(eth->key);
        free(eth);

        if (_thumblist) _timer = ecore_timer_loop_add(0.01, _e_cb_timer, NULL);
        else _timer = NULL;
     }
   else
     _timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

typedef struct _Color Color;

struct _Color
{
   Color        *closest;
   int           closest_dist;
   int           use;
   unsigned char r, g, b;
};

static void
_e_thumb_generate(E_Thumb *eth)
{
   char buf[4096], dbuf[4096], *id, *td, *ext = NULL;
   Evas *evas = NULL, *evas_im = NULL;
   Ecore_Evas *ee = NULL, *ee_im = NULL;
   Evas_Object *im = NULL, *edje = NULL;
   Eet_File *ef = NULL;
   int iw, ih, alpha, ww, hh;
   const unsigned int *data = NULL;
   time_t mtime_orig, mtime_thumb;

   id = _e_thumb_file_id(eth->file, eth->key);
   if (!id) return;

   td = strdup(id);
   if (!td)
     {
        free(id);
        return;
     }
   td[2] = 0;

   snprintf(dbuf, sizeof(dbuf), "%s/%s", _thumbdir, td);
   snprintf(buf, sizeof(buf), "%s/%s/%s-%ix%i.thm",
            _thumbdir, td, id + 2, eth->w, eth->h);
   free(id);
   free(td);

   mtime_orig = ecore_file_mod_time(eth->file);
   mtime_thumb = ecore_file_mod_time(buf);
   while (mtime_thumb <= mtime_orig)
     {
        unsigned int *data1;
        Eina_Bool sortkey;
        Evas_Object *im2, *bg;

        im = NULL;
        im2 = NULL;
        bg = NULL;

        ecore_file_mkdir(dbuf);

        edje_file_cache_set(0);
        edje_collection_cache_set(0);
        ee = ecore_evas_buffer_new(1, 1);
        evas = ecore_evas_get(ee);
        evas_image_cache_set(evas, 0);
        evas_font_cache_set(evas, 0);
        ww = 0;
        hh = 0;
        alpha = 1;
        ext = strrchr(eth->file, '.');

        sortkey = EINA_FALSE;

        if ((ext) && (eth->key) &&
            ((!strcasecmp(ext, ".edj")) ||
             (!strcasecmp(ext, ".eap"))))
          {
             ww = eth->w;
             hh = eth->h;
             im = ecore_evas_object_image_new(ee);
             ee_im = evas_object_data_get(im, "Ecore_Evas");
             evas_im = ecore_evas_get(ee_im);
             evas_image_cache_set(evas_im, 0);
             evas_font_cache_set(evas_im, 0);
             evas_object_image_size_set(im, ww * 4, hh * 4);
             evas_object_image_fill_set(im, 0, 0, ww, hh);
             edje = edje_object_add(evas_im);
             if ((eth->key) &&
                 ((!strcmp(eth->key, "e/desktop/background")) ||
                  (!strcmp(eth->key, "e/init/splash"))))
               alpha = 0;
             if (edje_object_file_set(edje, eth->file, eth->key))
               {
                  evas_object_move(edje, 0, 0);
                  evas_object_resize(edje, ww * 4, hh * 4);
                  evas_object_show(edje);
               }
             evas_object_move(im, 0, 0);
             evas_object_resize(im, ww, hh);
             sortkey = EINA_TRUE;
          }
        else if ((ext) &&
                 ((!strcasecmp(ext, ".ttf")) ||
                  (!strcasecmp(ext, ".pcf")) ||
                  (!strcasecmp(ext, ".bdf")) ||
                  (!strcasecmp(ext, ".ttx")) ||
                  (!strcasecmp(ext, ".pfa")) ||
                  (!strcasecmp(ext, ".pfb")) ||
                  (!strcasecmp(ext, ".afm")) ||
                  (!strcasecmp(ext, ".sfd")) ||
                  (!strcasecmp(ext, ".snf")) ||
                  (!strcasecmp(ext, ".otf")) ||
                  (!strcasecmp(ext, ".psf")) ||
                  (!strcasecmp(ext, ".ttc")) ||
                  (!strcasecmp(ext, ".ttx")) ||
                  (!strcasecmp(ext, ".gsf")) ||
                  (!strcasecmp(ext, ".spd"))
                 ))
          {
             Evas_Coord tx = 0, ty = 0, tw = 0, th = 0;
             ww = eth->w;
             hh = eth->h;
             alpha = 0;

             bg = evas_object_rectangle_add(evas);
             evas_object_color_set(bg, 96, 96, 96, 255);
             evas_object_move(bg, 0, 0);
             evas_object_resize(bg, ww, hh);
             evas_object_show(bg);

             im = evas_object_text_add(evas);
             evas_object_text_font_set(im, eth->file, hh / 4);
             evas_object_color_set(im, 192, 192, 192, 255);
             evas_object_text_ellipsis_set(im, 0.0);
             evas_object_text_text_set(im, "ABCabc");
             evas_object_geometry_get(im, NULL, NULL, &tw, &th);
             if (tw > ww) tw = ww;
             tx = 0 + ((ww - tw) / 2);
             ty = 0 + (((hh / 2) - th) / 2);
             evas_object_move(im, tx, ty);
             evas_object_resize(im, tw, th);
             evas_object_show(im);

             im2 = evas_object_text_add(evas);
             evas_object_text_font_set(im2, eth->file, hh / 4);
             evas_object_color_set(im2, 255, 255, 255, 255);
             evas_object_text_ellipsis_set(im2, 0.0);
             evas_object_text_text_set(im2, "123!@?");
             evas_object_geometry_get(im2, NULL, NULL, &tw, &th);
             if (tw > ww) tw = ww;
             tx = 0 + ((ww - tw) / 2);
             ty = (hh / 2) + (((hh / 2) - th) / 2);
             evas_object_move(im2, tx, ty);
             evas_object_resize(im2, tw, th);
             evas_object_show(im2);
          }
        else if (evas_object_image_extension_can_load_get(ext))
          {
             im = evas_object_image_add(evas);
             evas_object_image_load_orientation_set(im, EINA_TRUE);
             evas_object_image_load_size_set(im, eth->w, eth->h);
             evas_object_image_file_set(im, eth->file, NULL);
             iw = 0; ih = 0;
             evas_object_image_size_get(im, &iw, &ih);
             alpha = evas_object_image_alpha_get(im);
             if ((iw > 0) && (ih > 0))
               {
                  ww = eth->w;
                  hh = (eth->w * ih) / iw;
                  if (hh > eth->h)
                    {
                       hh = eth->h;
                       ww = (eth->h * iw) / ih;
                    }
                  evas_object_image_fill_set(im, 0, 0, ww, hh);
               }
             evas_object_move(im, 0, 0);
             evas_object_resize(im, ww, hh);
             sortkey = EINA_TRUE;
          }
        else
          goto end;

        ecore_evas_alpha_set(ee, alpha);
        ecore_evas_resize(ee, ww, hh);
        evas_object_show(im);
        if (ww <= 0) goto end;
        data = ecore_evas_buffer_pixels_get(ee);
        if (!data) goto end;
        ef = eet_open(buf, EET_FILE_MODE_WRITE);
        if (!ef) goto end;
        eet_write(ef, "/thumbnail/orig_file",
                  eth->file, strlen(eth->file), 1);
        if (eth->key)
          eet_write(ef, "/thumbnail/orig_key",
                    eth->key, strlen(eth->key), 1);
        eet_data_image_write(ef, "/thumbnail/data",
                             (void *)data, ww, hh, alpha,
                             0, 91, 1);
        if (sortkey)
          {
             ww = 4; hh = 4;
             evas_object_image_fill_set(im, 0, 0, ww, hh);
             evas_object_resize(im, ww, hh);
             ecore_evas_resize(ee, ww, hh);
             data = ecore_evas_buffer_pixels_get(ee);
             if (!data) goto end;

             data1 = malloc(ww * hh * sizeof(unsigned int));
             memcpy(data1, data, ww * hh * sizeof(unsigned int));
             ww = 2; hh = 2;
             evas_object_image_fill_set(im, 0, 0, ww, hh);
             evas_object_resize(im, ww, hh);
             ecore_evas_resize(ee, ww, hh);
             data = ecore_evas_buffer_pixels_get(ee);
             if (data)
               {
                  unsigned int *data2;

                  data2 = malloc(ww * hh * sizeof(unsigned int));
                  memcpy(data2, data, ww * hh * sizeof(unsigned int));
                  ww = 1; hh = 1;
                  evas_object_image_fill_set(im, 0, 0, ww, hh);
                  evas_object_resize(im, ww, hh);
                  ecore_evas_resize(ee, ww, hh);
                  data = ecore_evas_buffer_pixels_get(ee);
                  if (data)
                    {
                       unsigned int *data3;
                       unsigned char id2[(21 * 4) + 1];
                       int n, i;
                       int hi, si, vi;
                       float h, s, v;
                       const int pat2[4] =
                       {
                          0, 3, 1, 2
                       };
                       const int pat1[16] =
                       {
                          5, 10, 6, 9,
                          0, 15, 3, 12,
                          1, 14, 7, 8,
                          4, 11, 2, 13
                       };

                       /* ww = hh = 1 here */
                       data3 = malloc(sizeof(unsigned int));
                       memcpy(data3, data, sizeof(unsigned int));
                       // sort_id
                       n = 0;
#define A(v) (((v) >> 24) & 0xff)
#define R(v) (((v) >> 16) & 0xff)
#define G(v) (((v) >> 8) & 0xff)
#define B(v) (((v)) & 0xff)
#define HSV(p)                                         \
  evas_color_rgb_to_hsv(R(p), G(p), B(p), &h, &s, &v); \
  hi = 20 * (h / 360.0);                               \
  si = 20 * s;                                         \
  vi = 20 * v;                                         \
  if (si < 2) hi = 25;
#define SAVEHSV(h, s, v) \
  id2[n++] = 'a' + h;    \
  id2[n++] = 'a' + v;    \
  id2[n++] = 'a' + s;
#define SAVEX(x) \
  id2[n++] = 'a' + x;
#if 0
                       HSV(data3[0]);
                       SAVEHSV(hi, si, vi);
                       for (i = 0; i < 4; i++)
                         {
                            HSV(data2[pat2[i]]);
                            SAVEHSV(hi, si, vi);
                         }
                       for (i = 0; i < 16; i++)
                         {
                            HSV(data1[pat1[i]]);
                            SAVEHSV(hi, si, vi);
                         }
#else
                       HSV(data3[0]);
                       SAVEX(hi);
                       for (i = 0; i < 4; i++)
                         {
                            HSV(data2[pat2[i]]);
                            SAVEX(hi);
                         }
                       for (i = 0; i < 16; i++)
                         {
                            HSV(data1[pat1[i]]);
                            SAVEX(hi);
                         }
                       HSV(data3[0]);
                       SAVEX(vi);
                       for (i = 0; i < 4; i++)
                         {
                            HSV(data2[pat2[i]]);
                            SAVEX(vi);
                         }
                       for (i = 0; i < 16; i++)
                         {
                            HSV(data1[pat1[i]]);
                            SAVEX(vi);
                         }
                       HSV(data3[0]);
                       SAVEX(si);
                       for (i = 0; i < 4; i++)
                         {
                            HSV(data2[pat2[i]]);
                            SAVEX(si);
                         }
                       for (i = 0; i < 16; i++)
                         {
                            HSV(data1[pat1[i]]);
                            SAVEX(si);
                         }
#endif
                       id2[n++] = 0;
                       eet_write(ef, "/thumbnail/sort_id", id2, n, 1);
                       free(data3);
                    }
                  free(data2);
               }
             free(data1);
          }
end:
        if (ef) eet_close(ef);

        /* will free all */
        if (edje) evas_object_del(edje);
        if (ee_im) ecore_evas_free(ee_im);
        else if (im)
          evas_object_del(im);
        if (im2) evas_object_del(im2);
        if (bg) evas_object_del(bg);
        ecore_evas_free(ee);
        eet_clearcache();
        break;
     }
   /* send back path to thumb */
   ecore_ipc_server_send(_e_ipc_server, EPHOTO_IPC_DOMAIN_THUMB, 2, eth->objid,
       0, 0, buf, strlen(buf) + 1);
}

static int
e_sha1_sum(unsigned char *data, int size, unsigned char *dst)
{
   unsigned int digest[5], word[80], wa, wb, wc, wd, we, t;
   unsigned char buf[64], *d;
   int idx, left, i;
   const unsigned int magic[4] =
   {
      0x5a827999,
      0x6ed9eba1,
      0x8f1bbcdc,
      0xca62c1d6
   };

   idx = 0;
   digest[0] = 0x67452301;
   digest[1] = 0xefcdab89;
   digest[2] = 0x98badcfe;
   digest[3] = 0x10325476;
   digest[4] = 0xc3d2e1f0;

   memset(buf, 0, sizeof(buf));
   for (left = size, d = data; left > 0; left--, d++)
     {
        if ((idx == 0) && (left < 64))
          {
             memset(buf, 0, 60);
             buf[60] = (size >> 24) & 0xff;
             buf[61] = (size >> 16) & 0xff;
             buf[62] = (size >> 8) & 0xff;
             buf[63] = (size) & 0xff;
          }
        buf[idx] = *d;
        idx++;
        if ((idx == 64) || (left == 1))
          {
             if ((left == 1) && (idx < 64)) buf[idx] = 0x80;
             for (i = 0; i < 16; i++)
               {
                  word[i] = (unsigned int)buf[(i * 4)    ] << 24;
                  word[i] |= (unsigned int)buf[(i * 4) + 1] << 16;
                  word[i] |= (unsigned int)buf[(i * 4) + 2] << 8;
                  word[i] |= (unsigned int)buf[(i * 4) + 3];
               }
             for (i = 16; i < 80; i++)
               word[i] = SHSH(1,
                              word[i - 3 ] ^ word[i - 8 ] ^
                              word[i - 14] ^ word[i - 16]);
             wa = digest[0];
             wb = digest[1];
             wc = digest[2];
             wd = digest[3];
             we = digest[4];
             for (i = 0; i < 80; i++)
               {
                  if (i < 20)
                    t = SHSH(5, wa) + ((wb & wc) | ((~wb) & wd)) +
                      we + word[i] + magic[0];
                  else if (i < 40)
                    t = SHSH(5, wa) + (wb ^ wc ^ wd) +
                      we + word[i] + magic[1];
                  else if (i < 60)
                    t = SHSH(5, wa) + ((wb & wc) | (wb & wd) | (wc & wd)) +
                      we + word[i] + magic[2];
                  else if (i < 80)
                    t = SHSH(5, wa) + (wb ^ wc ^ wd) +
                      we + word[i] + magic[3];
                  we = wd;
                  wd = wc;
                  wc = SHSH(30, wb);
                  wb = wa;
                  wa = t;
               }
             digest[0] += wa;
             digest[1] += wb;
             digest[2] += wc;
             digest[3] += wd;
             digest[4] += we;
             idx = 0;
          }
     }

   t = eina_htonl(digest[0]); digest[0] = t;
   t = eina_htonl(digest[1]); digest[1] = t;
   t = eina_htonl(digest[2]); digest[2] = t;
   t = eina_htonl(digest[3]); digest[3] = t;
   t = eina_htonl(digest[4]); digest[4] = t;

   memcpy(dst, digest, 5 * 4);
   return 1;
}

static char *
_e_thumb_file_id(char *file,
                 char *key)
{
   char s[64];
   const char *chmap = "0123456789abcdef";
   unsigned char *buf, id[20];
   int i, len, lenf;

   len = 0;
   lenf = strlen(file);
   len += lenf;
   len++;
   if (key)
     {
        key += strlen(key);
        len++;
     }
   buf = alloca(len);

   strcpy((char *)buf, file);
   if (key) strcpy((char *)(buf + lenf + 1), key);

   e_sha1_sum(buf, len, id);

   for (i = 0; i < 20; i++)
     {
        s[(i * 2) + 0] = chmap[(id[i] >> 4) & 0xf];
        s[(i * 2) + 1] = chmap[(id[i]) & 0xf];
     }
   s[(i * 2)] = 0;
   return strdup(s);
}

