static void
_size_message(Evas_Object *o, double v)
{
   Edje_Message_Float msg;

   // display sqrt of 0.0-?1.0 so we don't have single huge files push every
   // other bar out
   if (v > 0.0) v = sqrt(v);
   msg.val = v;
   edje_object_message_send(o, EDJE_MESSAGE_FLOAT, 1, &msg);
   edje_object_message_signal_process(o);
}

static void
_size_bars_update(Smart_Data *sd)
{
   Eina_List *bl, *il;
   Icon *icon;
   Block *block;
   const char *s;

   if (sd->config.view_mode != EFM_VIEW_MODE_LIST_DETAILED) return;
   EINA_LIST_FOREACH(sd->blocks, bl, block)
     {
        if (block->realized_num <= 0) continue;
        EINA_LIST_FOREACH(block->icons, il, icon)
          {
             if (!icon->realized) continue;
             s = cmd_key_find(icon->cmd, "size");
             if (s)
               {
                  unsigned long long size = atoll(s);

                  if (sd->file_max > 0)
                    _size_message(icon->o_list_detail_swallow2[0],
                                  (double)size / (double)sd->file_max);
                  else
                    _size_message(icon->o_list_detail_swallow2[0], 0.0);
               }
          }
     }
}

static void
_cb_size_bars_update_job(void *data)
{
   Smart_Data *sd = data;

   sd->size_bars_update_job = NULL;
   _size_bars_update(sd);
}

static void
_size_bars_update_queue(Smart_Data *sd)
{
   if (sd->size_bars_update_job) ecore_job_del(sd->size_bars_update_job);
   sd->size_bars_update_job = ecore_job_add(_cb_size_bars_update_job, sd);
}

static void
_cb_size_max_update_job(void *data)
{
   Smart_Data *sd = data;
   Eina_List *il;
   Icon *icon;
   const char *s;
   unsigned long long new_max = 0;

   sd->size_max_update_job = NULL;
   EINA_LIST_FOREACH(sd->icons, il, icon)
     {
        s = cmd_key_find(icon->cmd, "size");
        if (s)
          {
             unsigned long long size = atoll(s);

             if (size > new_max) new_max = size;
          }
     }
   if (sd->file_max != new_max)
     {
        sd->file_max = new_max;
        _size_bars_update_queue(sd);
     }
}

static void
_size_bars_max_update_queue(Smart_Data *sd)
{
   if (sd->size_max_update_job) ecore_job_del(sd->size_max_update_job);
   sd->size_max_update_job = ecore_job_add(_cb_size_max_update_job, sd);
}

static void
_command(Smart_Data *sd, const char *cmd)
{
   Cmd *c = cmd_parse(cmd);
   Msg *msg;
   void *ref;

   if (!c) return;
   // send the cmd to our thread to deal with
   msg = eina_thread_queue_send(sd->thread_data->thq, sizeof(Msg), &ref);
   msg->c = c;
   eina_thread_queue_send_done(sd->thread_data->thq, ref);
}

static void
_process(Smart_Data_Thread *std, Ecore_Thread *th, Eina_List *batch)
{ // process a batch of commands that come from the back-end open process
   Cmd *c, *c_tmp = NULL;
   Eina_List *batch_new = NULL;
   Eina_List *batch_tmp = NULL;

   if (!batch) return;

   // sort batch into batch_new where each set of things like add, mod, del
   // if they are the same cmd then the files are sorted by path. we do this
   // to speed up inserts in the main loop so we can assume a sorted list
   // per batch. this speeds up file adds a lot as we can just do a walk of
   // the batch (as long as commands stay the same like file-add) and then also
   // walk the current file/icon list at the same time and skip to the insert
   // spot which makes inserts very fast as both lists are known to be
   // pre-sorted so at worst we walk N file icons in the current icon list
   // per batch (and loading a dir is probably a series of batches of N
   // file-add's where N is white reasonable
   EINA_LIST_FREE(batch, c)
     {
        c->sort_mode = std->sd->config.sort_mode;
        if (!batch_tmp)
          {
             batch_tmp = eina_list_append(batch_tmp, c);
             c_tmp = c;
          }
        else if (!strcmp(c->command, c_tmp->command))
          {
             // works for file-add, file-del, file-mod
             batch_tmp = eina_list_sorted_insert(batch_tmp, sort_cmd, c);
          }
        else
          {
             batch_new = eina_list_merge(batch_new, batch_tmp);
             batch_tmp = NULL;
          }
     }
   if (batch_tmp)
     {
        batch_new = eina_list_merge(batch_new, batch_tmp);
        batch_tmp = NULL;
     }
   if (batch_new) ecore_thread_feedback(th, batch_new);
}

static Eina_Bool
_cb_exe_del(void *data, int ev_type EINA_UNUSED, void *event)
{
   Smart_Data *sd = data;
   Ecore_Exe_Event_Del *ev = event;
   Eina_List *l;
   Pending_Exe_Del *pend;

   // remove this exited slave process from our pending exe deletions
   // this list should be pretty small of pending deletions so we don't
   // need to optimize this with a hash or whatever
   EINA_LIST_FOREACH(_pending_exe_dels, l, pend)
     {
        if (pend->exe == ev->exe)
          {
             pend->exe = NULL;
             if (pend->timer)
               {
                  ecore_timer_del(pend->timer);
                  pend->timer = NULL;
               }
             free(pend);
             _pending_exe_dels = eina_list_remove_list(_pending_exe_dels, l);
             break;
          }
     }
   if (ev->exe == sd->exe_open)
     { // this process exiting is the back-end open process for active view
        printf("ERROR: back-end open process died unexpectedly\n");
        return ECORE_CALLBACK_DONE;
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_cb_exe_data(void *data, int ev_type EINA_UNUSED, void *event)
{
   Smart_Data *sd = data;
   Ecore_Exe_Event_Data *ev = event;
   int i;

   // if this exe doesn't match the view it is for - pass it on and skip
   if (ev->exe != sd->exe_open) return ECORE_CALLBACK_PASS_ON;
   // this exe data is for thijs view
   for (i = 0; ev->lines[i].line; i++) _command(sd, ev->lines[i].line);
   return ECORE_CALLBACK_DONE;
}

static Eina_Bool
_cb_exe_pending_timer(void *data)
{ // timeout trying to kill off back-end open process
   Pending_Exe_Del *pend = data;

   pend->timer = NULL;
   _pending_exe_dels = eina_list_remove(_pending_exe_dels, pend);
   if (pend->exe)
     { // forcibly kill the back-end process as it did not exit on its own
        ecore_exe_kill(pend->exe);
        pend->exe = NULL;
     }
   free(pend);
   return EINA_FALSE;
}

static void
_cb_thread_main(void *data, Ecore_Thread *th)
{ // thread sits processing commands read from stdout from the back-end open
   Smart_Data_Thread *std = data;
   Msg *msg;
   void *ref;
   Cmd *c;
   Eina_Bool block = EINA_FALSE;
   const char *prev_cmd = NULL;
   Eina_List *batch = NULL;

   for (;;)
     { // sit in a loop soaking up commands on the input queue
        if (ecore_thread_check(th)) break;
        if (!block) msg = eina_thread_queue_poll(std->thq, &ref);
        else
          {
             usleep(4000); // wait 4ms to collect more msg's
             msg = eina_thread_queue_wait(std->thq, &ref);
             block = EINA_FALSE;
          }
        if (msg)
          {
             if (!batch)
               {
                  batch = eina_list_append(batch, msg->c);
                  eina_stringshare_replace(&(prev_cmd), msg->c->command);
               }
             else if ((prev_cmd) && (!strcmp(msg->c->command, prev_cmd)))
               {
                  batch = eina_list_append(batch, msg->c);
               }
             else
               {
                  _process(std, th, batch);
                  batch = NULL;
                  eina_stringshare_replace(&(prev_cmd), NULL);
                  batch = eina_list_append(batch, msg->c);
               }
             eina_thread_queue_wait_done(std->thq, ref);
          }
        else
          {
             block = EINA_TRUE;
             _process(std, th, batch);
             batch = NULL;
             eina_stringshare_replace(&(prev_cmd), NULL);
          }
     }
   EINA_LIST_FREE(batch, c)
     {
        cmd_free(c);
     }
   eina_stringshare_replace(&(prev_cmd), NULL);
}

static void
_cb_thread_notify(void *data, Ecore_Thread *th EINA_UNUSED, void *msg)
{ // handle data from the view thread to the UI - this will be a batch of cmds
   Smart_Data_Thread *std = data;
   Smart_Data *sd = std->sd;
   Eina_List *batch = msg;
   Cmd *c, *cprev = NULL;
   Eina_List *l, *il2, *il = NULL;
   Icon *icon, *icon2;
   Block *block;
   const char *file, *label, *s;
   int file_adds = 0, file_dels = 0;

   if (!sd)
     { // on cusp point - view gone but buffered thread feedback exists
        EINA_LIST_FREE(batch, c) cmd_free(c);
        return;
     }
   printf("XXXXX BATCH %i\n", eina_list_count(batch));
   EINA_LIST_FOREACH(batch, l, c)
     {
        c->sort_mode = sd->config.sort_mode;
#define CMD_DONE cmd_free(c); continue
        if (!strcmp(c->command, "list-begin"))
          {
             printf("XXXXX LIST BEGIN\n");
             edje_object_part_text_set(sd->o_overlay_info,
                                       "e.text.busy_label",
                                       "Loading");
             edje_object_signal_emit(sd->o_overlay_info,
                                     "e,state,busy,start", "e");
             CMD_DONE;
          }
        else if (!strcmp(c->command, "list-end"))
          {
             printf("XXXXX LIST END\n");
             edje_object_signal_emit(sd->o_overlay_info,
                                     "e,state,busy,stop", "e");
             CMD_DONE;
          }
        file = cmd_key_find(c, "path");
        printf("XXXXX [%s] [%s]\n", c->command, file);
        if (file)
          {
             s = strrchr(file, '/');
             if (s) file = s + 1;
             if (file[0] == '.') // XXX filter dor files or not
               {
                  CMD_DONE;
               }
          }
        if ((!file) || (!file[0]))
          { // somehow we didn't get a sane filename from the back-end
             CMD_DONE;
          }
        label = cmd_key_find(c, "link-label");
        if (!label) label = cmd_key_find(c, "label");

        if ((!cprev) || (!!strcmp(cprev->command, c->command)))
          { // we start a new batch of commands - these are sorted
             il = sd->icons;
          }

        if (!strcmp(c->command, "file-add"))
          {
             icon = calloc(1, sizeof(Icon));
             if (!icon) abort();

             file_adds++;
             icon->sd = sd;
             icon->cmd = c;
             icon->changed = EINA_TRUE;
             icon->info.file = eina_stringshare_add(file);
             eina_stringshare_replace(&(icon->info.label), label);
             s = cmd_key_find(c, "label-clicked");
             if (!s) s = cmd_key_find(c, "link-label-clicked");
             eina_stringshare_replace(&(icon->info.label_clicked), s);
             s = cmd_key_find(c, "label-selected");
             if (!s) s = cmd_key_find(c, "link-label-selected");
             eina_stringshare_replace(&(icon->info.label_selected), s);
             s = cmd_key_find(c, "mime");
             if (s) eina_stringshare_replace(&(icon->info.mime), s);
             if (s) printf("XXXXX   mime=%s\n", icon->info.mime);
             s = cmd_key_find(c, "desktop-icon");
             if (!s) s = cmd_key_find(c, "link-desktop-icon");
             if (s) eina_stringshare_replace(&(icon->info.pre_lookup_icon), s);
             s = cmd_key_find(c, "desktop-icon.lookup");
             if (!s) s = cmd_key_find(c, "link-desktop-icon.lookup");
             if (!s) s = cmd_key_find(c, "icon");
             if ((s) && (s[0] == '/')) eina_stringshare_replace(&(icon->info.icon), s);
             s = cmd_key_find(c, "link-desktop-icon-clicked");
             if (!s) s = cmd_key_find(c, "desktop-icon-clicked");
             if (s) eina_stringshare_replace(&(icon->info.icon_clicked), s);
             s = cmd_key_find(c, "link-desktop-icon-selected");
             if (!s) s = cmd_key_find(c, "desktop-icon-selected");
             if (s) eina_stringshare_replace(&(icon->info.icon_selected), s);
             s = cmd_key_find(c, "mime-icon");
             if (s) eina_stringshare_replace(&(icon->info.mime_icon), s);
             s = cmd_key_find(c, "thumb");
             if (s) eina_stringshare_replace(&(icon->info.thumb), s);
             s = cmd_key_find(c, "type");
             if (s)
               {
                  if (!strcmp(s, "link"))
                    {
                       icon->info.link = EINA_TRUE;
                       s = cmd_key_find(c, "broken-link");
                       if ((s) && (!strcmp(s, "true")))
                         icon->info.broken = EINA_TRUE;
                       s = cmd_key_find(c, "link-type");
                    }
                  if (s)
                    {
                       if (!strcmp(s, "dir"))
                         {
                            icon->info.dir = EINA_TRUE;
                            eina_stringshare_replace((&icon->info.mime),
                                                     "inode/directory");
                         }
                       else if (!strcmp(s, "block"))
                         {
                            icon->info.special = EINA_TRUE;
                            eina_stringshare_replace((&icon->info.mime),
                                                     "inode/blockdevice");
                         }
                       else if (!strcmp(s, "char"))
                         {
                            icon->info.special = EINA_TRUE;
                            eina_stringshare_replace((&icon->info.mime),
                                                     "inode/chardevice");
                         }
                       else if (!strcmp(s, "fifo"))
                         {
                            icon->info.special = EINA_TRUE;
                            eina_stringshare_replace((&icon->info.mime),
                                                     "inode/fifo");
                         }
                       else if (!strcmp(s, "socket"))
                         {
                            icon->info.special = EINA_TRUE;
                            eina_stringshare_replace((&icon->info.mime),
                                                     "inode/socket");
                         }
                    }
               }
             s = cmd_key_find(c, "size");
             if (s)
               {
                  unsigned long long size = atoll(s);

                  if (size > sd->file_max)
                    {
                       sd->file_max = size;
                       _size_bars_update_queue(sd);
                    }
               }
             if (!icon->info.mime)
               eina_stringshare_replace((&icon->info.mime),
                                        "inode/file");

             for ( ; il; il = il->next)
               {
                  icon2 = il->data;
                  if (!strcmp(file, icon2->info.file))
                    { // handle the case we get an add for an existing file
                       file_dels++;
                       il2 = il->next;
                       sd->icons = eina_list_remove_list(sd->icons, il);
                       il = il2;
                       if (sd->last_focused)
                         {
                            // XXX: select prev or next icon
                         }
                       s = cmd_key_find(icon->cmd, "size");
                       if (s)
                         {
                            unsigned long long size = atoll(s);

                            if (size == sd->file_max)
                              {
                                 _size_bars_max_update_queue(sd);
                              }
                         }
                       _icon_free(icon2);
                       if (il) icon2 = il->data;
                       else break;
                    }
                  if (sort_cmd(icon2->cmd, icon->cmd) > 0)
                    {
                       sd->icons = eina_list_prepend_relative_list
                         (sd->icons, icon, il);
                       break;
                    }
               }
             if (!il)
               {
                  sd->icons = eina_list_append(sd->icons, icon);
               }
          }
        else if (!strcmp(c->command, "file-del"))
          {
             for ( ; il; il = il->next)
               {
                  icon = il->data;
                  if (!strcmp(file, icon->info.file))
                    {
                       file_dels++;
                       il2 = il->next;
                       sd->icons = eina_list_remove_list(sd->icons, il);
                       il = il2;
                       if (sd->last_focused)
                         {
                            // XXX: select prev or next icon
                         }
                       s = cmd_key_find(icon->cmd, "size");
                       if (s)
                         {
                            unsigned long long size = atoll(s);

                            if (size == sd->file_max)
                              {
                                 _size_bars_max_update_queue(sd);
                              }
                         }
                       _icon_free(icon);
                       break;
                    }
               }
             cmd_free(c);
             c = NULL;
          }
        else if (!strcmp(c->command, "file-mod"))
          {
             for ( ; il; il = il->next)
               {
                  icon = il->data;
                  if (!strcmp(file, icon->info.file))
                    {
                       icon->changed = EINA_TRUE;
                       eina_stringshare_replace(&(icon->info.label), label);
                       s = cmd_key_find(c, "label-clicked");
                       if (!s) s = cmd_key_find(c, "link-label-clicked");
                       eina_stringshare_replace(&(icon->info.label_clicked), s);
                       s = cmd_key_find(c, "label-selected");
                       if (!s) s = cmd_key_find(c, "link-label-selected");
                       eina_stringshare_replace(&(icon->info.label_selected), s);
                       s = cmd_key_find(c, "mime");
                       if (s) eina_stringshare_replace(&(icon->info.mime), s);
                       if (!icon->info.mime)
                         eina_stringshare_replace((&icon->info.mime),
                                                  "inode/file");
                       s = cmd_key_find(c, "desktop-icon");
                       if (!s) s = cmd_key_find(c, "link-desktop-icon");
                       if (s) eina_stringshare_replace(&(icon->info.pre_lookup_icon), s);
                       s = cmd_key_find(c, "desktop-icon.lookup");
                       if (!s) s = cmd_key_find(c, "link-desktop-icon.lookup");
                       if (!s) s = cmd_key_find(c, "icon");
                       if ((s) && (s[0] == '/')) eina_stringshare_replace(&(icon->info.icon), s);
                       s = cmd_key_find(c, "link-desktop-icon-clicked");
                       if (!s) s = cmd_key_find(c, "desktop-icon-clicked");
                       if (s) eina_stringshare_replace(&(icon->info.icon_clicked), s);
                       s = cmd_key_find(c, "link-desktop-icon-selected");
                       if (!s) s = cmd_key_find(c, "desktop-icon-selected");
                       if (s) eina_stringshare_replace(&(icon->info.icon_selected), s);
                       s = cmd_key_find(c, "mime-icon");
                       if (s) eina_stringshare_replace(&(icon->info.mime_icon), s);
                       s = cmd_key_find(c, "thumb");
                       if (s) eina_stringshare_replace(&(icon->info.thumb), s);
                       s = cmd_key_find(c, "broken-link");
                       if ((s) && (!strcmp(s, "true")))
                         icon->info.broken = EINA_TRUE;
                       else
                         icon->info.broken = EINA_FALSE;
                       s = cmd_key_find(icon->cmd, "size");
                       if (s)
                         {
                            unsigned long long size = atoll(s);

                            if (size == sd->file_max)
                              {
                                 _size_bars_max_update_queue(sd);
                              }
                         }
                       s = cmd_key_find(c, "size");
                       if (s)
                         {
                            unsigned long long size = atoll(s);

                            if (size > sd->file_max)
                              {
                                 sd->file_max = size;
                                 _size_bars_update_queue(sd);
                              }
                         }
                       cmd_free(icon->cmd);
                       icon->cmd = c;
                       break;
                    }
               }
          }
        cprev = c;
     }
   eina_list_free(batch);
   if ((sd->config.view_mode >= EFM_VIEW_MODE_LIST) &&
       ((file_adds > 0) || (file_dels > 0)))
     { // if it's one of the list modes, unrealize realized icons
        EINA_LIST_FOREACH(sd->blocks, l, block)
          {
             if (block->realized_num <= 0) continue;
             EINA_LIST_FOREACH(block->icons, il, icon)
               { // unrealize the icon - we odd/event forces this
                  if (!icon->realized) continue;
                  icon->realized = EINA_FALSE;
                  icon->block->realized_num--;
                  _icon_object_clear(icon);
               }
          }
     }
   if (sd->reblock_job) ecore_job_del(sd->reblock_job);
   sd->reblock_job = ecore_job_add(_cb_reblock, sd);
}

static void
_cb_thread_done(void *data, Ecore_Thread *th EINA_UNUSED)
{
   Smart_Data_Thread *std = data;

   if (std->sd) std->sd->thread_data = NULL;
   if (std->thq)
     {
        eina_thread_queue_free(std->thq);
        std->thq = NULL;
     }
   free(std);
}
