#include "cmd.h"
#include "sort.h"
#include "efm.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>

// sorting helpers
enum
{
   SORT_LESS = -1,
   SORT_MORE = 1,
   SORT_INVALID = -100
};

static char
_str_mode_file_type(int mode)
{
   char c = '?';

   if      (S_ISREG(mode))  c = '-';
   else if (S_ISDIR(mode))  c = 'd';
   else if (S_ISBLK(mode))  c = 'b';
   else if (S_ISCHR(mode))  c = 'c';
#ifdef S_ISFIFO
   else if (S_ISFIFO(mode)) c = 'p';
#endif
#ifdef S_ISLNK
   else if (S_ISLNK(mode))  c = 'l';
#endif
#ifdef S_ISSOCK
   else if (S_ISSOCK(mode)) c = 's';
#endif
#ifdef S_ISDOOR
   else if (S_ISDOOR(mode)) c = 'D';
#endif
   return c;
}

static void
_str_mode(int mode, char str[11])
{ // generate string mode i9nto dest str buffer
   static const char *rwx[] = {
      "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"
   };

   str[0] = _str_mode_file_type(mode);
   strcpy(&str[1], rwx[(mode >> 6)& 7]);
   strcpy(&str[4], rwx[(mode >> 3)& 7]);
   strcpy(&str[7], rwx[(mode & 7)]);
   if (mode & S_ISUID) str[3] = (mode & S_IXUSR) ? 's' : 'S';
   if (mode & S_ISGID) str[6] = (mode & S_IXGRP) ? 's' : 'l';
   if (mode & S_ISVTX) str[9] = (mode & S_IXOTH) ? 't' : 'T';
   str[10] = 0;
}

static int
_sort_str_field(const Cmd *c1, const Cmd *c2, int (*cmpfn) (const char *s1, const char *s2), const char *field)
{ // a string fields - get it and sort using compare func
   const char *field1, *field2, *path1, *path2;
   int res;

   field1 = cmd_key_find(c1, field);
   if (!field1) return 0;

   field2 = cmd_key_find(c2, field);
   if (!field2) return 0;

   res = cmpfn(field1, field2);
   if (!res)
     { // they are the same string! use pure filename path as it's unique
        path1 = cmd_key_find(c1, "path");
        path2 = cmd_key_find(c2, "path");
        res = cmpfn(path1, path2);
        if (!res) return strcmp(path1, path2);
        return res;
     }
   return cmpfn(field1, field2);
}

static int
_sort_num_field(const Cmd *c1, const Cmd *c2, const char *field)
{ // user a named field that is a number like mtime, ctime, size etc ...
   const char *f, *path1, *path2;
   unsigned long long field1 = 0, field2 = 0;

   // get field and convert to ulonglong
   f = cmd_key_find(c1, field);
   if (f) field1 = strtoull(f, NULL, 10);

   // get field and convert to ulonglong
   f = cmd_key_find(c2, field);
   if (f) field2 = strtoull(f, NULL, 10);

   if (field1 == field2)
     { // they are the same string! use pure filename path as it's unique
        path1 = cmd_key_find(c1, "path");
        path2 = cmd_key_find(c2, "path");
        return strcmp(path1, path2);
     }
   // sort result
   if (field1 > field2) return SORT_MORE;
   return SORT_LESS;
}

static int
_sort_path(const Cmd *c1, const Cmd *c2, int (*cmpfn) (const char *s1, const char *s2))
{ // use pure filename path not label
   return _sort_str_field(c1, c2, cmpfn, "path");
}

static int
_sort_label(const Cmd *c1, const Cmd *c2, int (*cmpfn) (const char *s1, const char *s2))
{ // sort by label or path - some files like .desktop files have labels
   const char *path1, *path2;
   int res;

   // get a label instead of filename path element if available
   path1 = cmd_key_find(c1, "label");
   if (!path1) path1 = cmd_key_find(c1, "path");

   // get a label instead of filename path element if available
   path2 = cmd_key_find(c2, "label");
   if (!path2) path2 = cmd_key_find(c2, "path");

   res = cmpfn(path1, path2);
   if (!res)
     { // they are the same string! use pure filename path as it's unique
        path1 = cmd_key_find(c1, "path");
        path2 = cmd_key_find(c2, "path");
        res = cmpfn(path1, path2);
        if (!res) return strcmp(path1, path2);
        return res;
     }
   return res;
}

static int
_sort_mode(const Cmd *c1, const Cmd *c2)
{ // sort by mode string like "drwxr-xr-x" or "-rw-r--r--"
   const char *f;
   int mode1 = 0, mode2 = 0, ret = 0;
   char m1[11], m2[11];

   // get mdoe and generate mode string
   f = cmd_key_find(c1, "mode");
   if (f) mode1 = strtoull(f, NULL, 16);
   _str_mode(mode1, m1);

   // get mdoe and generate mode string
   f = cmd_key_find(c2, "mode");
   if (f) mode2 = strtoull(f, NULL, 16);
   _str_mode(mode2, m2);

   // sort by the mode string
   ret = strcmp(m1, m2);
   return ret;
}

static int
_sort_dir_not_dir(const Cmd *c1, const Cmd *c2)
{
   const char *type1, *type2;

   // get file types for both - and if link resolve the link dest type
   type1 = cmd_key_find(c1, "type");
   if ((type1) && (!strcmp(type1, "link")))
     type1 = cmd_key_find(c1, "link-type");
   if (!type1) type1 = "file";

   type2 = cmd_key_find(c2, "type");
   if ((type2) && (!strcmp(type2, "link")))
     type2 = cmd_key_find(c2, "link-type");
   if (!type2) type2 = "file";

   // 1st is dir, 2nd is not dir
   if (( !strcmp(type1, "dir")) && (!!strcmp(type2, "dir"))) return SORT_LESS;
   // 1st is not dir, 2nd is dir
   if ((!!strcmp(type1, "dir")) && ( !strcmp(type2, "dir"))) return SORT_MORE;
   // both are dir or both are not dir
   return SORT_INVALID;
}

// sorter logic
static int
_sort_cmd_do(const Cmd *c1, const Cmd *c2)
{
   int dir_not_dir = SORT_INVALID;
   Efm_Sort_Mode sort_mode;
   int (*cmpfn) (const char *s1, const char *s2) = strcmp;
   Eina_Bool label_sort = EINA_FALSE;

   sort_mode = c1->sort_mode;
   // handle flags
   if (sort_mode & EFM_SORT_MODE_DIRS_FIRST)
     dir_not_dir = _sort_dir_not_dir(c1, c2);
   if (sort_mode & EFM_SORT_MODE_NOCASE)
     cmpfn = strcasecmp;
   if (sort_mode & EFM_SORT_MODE_LABEL_NOT_PATH)
     label_sort = EINA_TRUE;

   // handle each sort mode

   // if one is a dir and one is not - other sorting compares are not useful
   // so just return this status
   if (dir_not_dir != SORT_INVALID) return dir_not_dir;

   // handle the actual specific sort field to use
   switch (sort_mode & EFM_SORT_MODE_MASK)
     {
      case EFM_SORT_MODE_NAME:
        if (label_sort) return _sort_label(c1, c2, cmpfn);
        return                 _sort_path(c1, c2, cmpfn);
      case EFM_SORT_MODE_SIZE:
        return _sort_num_field(c1, c2, "size");
      case EFM_SORT_MODE_DATE:
        return _sort_num_field(c1, c2, "mtime");
      case EFM_SORT_MODE_MIME:
        return _sort_str_field(c1, c2, strcmp, "mime");
      case EFM_SORT_MODE_USER:
        return _sort_str_field(c1, c2, strcmp, "user");
      case EFM_SORT_MODE_GROUP:
        return _sort_str_field(c1, c2, strcmp, "group");
      case EFM_SORT_MODE_PERMISSIONS:
        return _sort_mode(c1, c2);
      default: // unknown - so just plain path
        goto def;
        break;
     }
def:
   return _sort_path(c1, c2, cmpfn);
}

int
sort_cmd(const void *c1, const void *c2)
{
   return _sort_cmd_do(c1, c2);
}
