#include <Eina.h>
#include <Ecore.h>
#include <Evas.h>
#include <Ecore_Evas.h>
#include <Ecore_File.h>
#include <Edje.h>
#include <Emotion.h>
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "private.h"
#include "tycommon.h"

enum {
  CENTER,
  FILL,
  STRETCH,
  NOIMG
};

#define VIDEO_DECODE_TIMEOUT 1.0

static Evas *evas = NULL;
static struct termios told, tnew;
static int tw = 0, th = 0, cw = 0, ch = 0, maxw = 0, maxh = 0, _mode = CENTER;
static Ecore_Timer *timeout_t = NULL;

#include "extns.h"

static int
echo_off(void)
{
   if (tcgetattr(0, &told) != 0) return -1;
   tnew = told;
   tnew.c_lflag &= ~ECHO;
   if (tcsetattr(0, TCSAFLUSH, &tnew) != 0) return -1;
   return 0;
}

static int
echo_on(void)
{
   return tcsetattr(0, TCSAFLUSH, &told);
}

static void
scaleterm(int w, int h, int *iw, int *ih)
{
   int width = maxw ? maxw : tw;
   if (w > (width * cw))
     {
        *iw = width;
        *ih = ((h * (width * cw) / w) + (ch - 1)) / ch;
     }
   else
     {
        *iw = (w + (cw - 1)) / cw;
        *ih = (h + (ch - 1)) / ch;
     }
   if (maxh && *ih > maxh)
     {
        *ih = maxh;
        *iw = ((w * (maxh * ch) / h) + (cw - 1)) / cw;
     }
}

static const char *
is_fmt(const char *f, const char **extn)
{
   int i, len;

   len = strlen(f);
   for (i = 0; extn[i]; i++)
     {
        int l = strlen(extn[i]);
        if (len < l) continue;
        if (!strcasecmp(extn[i], f + len - l)) return extn[i];
     }
   return NULL;
}

static void
prnt(const char *path, int w, int h, int mode)
{
   int x, y, i;
   char *line, buf[4096];

   if ((w >= 512) || (h >= 512)) return;
   line = malloc(w + 100);
   if (!line) return;
   if (mode == CENTER)
     snprintf(buf, sizeof(buf), "%c}ic#%i;%i;%s", 0x1b, w, h, path);
   else if (mode == FILL)
     snprintf(buf, sizeof(buf), "%c}if#%i;%i;%s", 0x1b, w, h, path);
   else
     snprintf(buf, sizeof(buf), "%c}is#%i;%i;%s", 0x1b, w, h, path);
   if (ty_write(1, buf, strlen(buf) + 1) < 0)
     perror("write");
   i = 0;
   line[i++] = 0x1b;
   line[i++] = '}';
   line[i++] = 'i';
   line[i++] = 'b';
   line[i++] = 0;
   for (x = 0; x < w; x++) line[i++] = '#';
   line[i++] = 0x1b;
   line[i++] = '}';
   line[i++] = 'i';
   line[i++] = 'e';
   line[i++] = 0;
   line[i++] = '\n';
   for (y = 0; y < h; y++)
     {
        if (ty_write(1, line, i) < 0)
          perror("write");
     }
   free(line);
}

static void
print_usage(const char *argv0)
{
   printf("Usage: %s "HELP_ARGUMENT_SHORT" [-s|-f|-c] [-g <width>x<height>] FILE1 [FILE2 ...]\n"
          "\n"
          "  -s  Stretch file to fill nearest character cell size\n"
          "  -f  Fill file to totally cover character cells with no gaps\n"
          "  -c  Center file in nearest character cells but only scale down (default)\n"
          "  -g <width>x<height>  Set maximum geometry for the image (cell count)\n"
          HELP_ARGUMENT_DOC"\n",
         argv0);
}

static Eina_Bool
timeout_cb(void *data)
{
   evas_object_del(data);
   timeout_t = NULL;

   return ECORE_CALLBACK_CANCEL;
}

static int
handle_image(char *rp)
{
   Evas_Object *o;
   int w = 0, h = 0;
   int iw = 0, ih = 0;
   int r = -1;

   if (!is_fmt(rp, extn_img) &&
       !is_fmt(rp, extn_scale) &&
       !is_fmt(rp, extn_mov))
     return -1;

   o = evas_object_image_add(evas);
   evas_object_image_file_set(o, rp, NULL);
   evas_object_image_size_get(o, &w, &h);
   if ((w >= 0) && (h > 0))
     {
        scaleterm(w, h, &iw, &ih);
        prnt(rp, iw, ih, _mode);
        r = 0;
     }

   evas_object_del(o);

   return r;
}

static int
handle_edje(char *rp)
{
   Evas_Object *o;
   int iw = 0, ih = 0;
   int r = -1;

   if (!is_fmt(rp, extn_edj)) return -1;

   o = edje_object_add(evas);
   if (edje_object_file_set
       (o, rp, "terminology/background") == EINA_TRUE ||
       !edje_object_file_set
       (o, rp, "e/desktop/background"))
     {
        Evas_Coord mw = 0, mh = 0;
        edje_object_size_min_get(o, &mw, &mh);
        if ((mw <= 0) || (mh <= 0))
          edje_object_size_min_calc(o, &mw, &mh);
        if ((mw <= 0) || (mh <= 0))
          {
             mw = (tw) * cw;
             mh = (th - 1) * ch;
          }
        scaleterm(mw, mh, &iw, &ih);
        prnt(rp, iw, ih, _mode);
        r = 0;
     }

   evas_object_del(o);

   return r;
}

static void
video_decoded(void *data, Evas_Object *o, void *ei EINA_UNUSED)
{
   int w = 0, h = 0;
   int iw = 0, ih = 0;

   if (emotion_object_video_handled_get(o))
     {
        emotion_object_size_get(o, &w, &h);
        if ((w >= 0) && (h > 0))
          {
             scaleterm(w, h, &iw, &ih);
             prnt(data, iw, ih, _mode);
             goto done;
          }
        else
          {
             double ar = emotion_object_ratio_get(o);
             if (ar > 0.0)
               {
                  scaleterm(tw * cw, (int) ((tw * cw) / ar), &iw, &ih);
                  prnt(data, iw, ih, _mode);
                  goto done;
               }
          }
     }

   prnt(data, tw, 3, NOIMG);

done:
   ecore_timer_del(timeout_t);
   timeout_t = NULL;
   evas_object_del(o);
   free(data);
}

static int
handle_video(char *rp)
{
   Evas_Object *o;

   if (!is_fmt(rp, extn_aud) &&
       !is_fmt(rp, extn_mov))
     return -1;

   o = emotion_object_add(evas);
   if (emotion_object_init(o, NULL) == EINA_TRUE)
     {
        if (emotion_object_file_set(o, rp))
          {
             emotion_object_audio_mute_set(o, EINA_TRUE);
             emotion_object_play_set(o, EINA_TRUE);
             timeout_t = ecore_timer_add(VIDEO_DECODE_TIMEOUT,
                                         timeout_cb, o);
             evas_object_smart_callback_add(o, "frame_decode",
                                            video_decoded,
                                            strdup(rp));
             return 0;
          }

     }

   evas_object_del(o);

   return -1;
}

static Eina_Bool
handle_file(void *data)
{
   Eina_List **file_q = data;

   int (*handlers[])(char *rp) = {
     handle_image,
     handle_edje,
     handle_video,
     NULL
   };
   char *rp;
   int i;

   if (timeout_t) return ECORE_CALLBACK_RENEW;
   if (!(*file_q))
     {
        ecore_main_loop_quit();
        return ECORE_CALLBACK_CANCEL;
     }

   rp = eina_list_data_get(*file_q);
   *file_q = eina_list_remove_list(*file_q, *file_q);
   if (!rp) return ECORE_CALLBACK_RENEW;

   for (i = 0; handlers[i]; i++)
     {
        if (handlers[i](rp) == 0) break;
     }
   free(rp);

   return ECORE_CALLBACK_RENEW;
}

int
main(int argc, char **argv)
{
   Ecore_Evas *ee;
   char buf[64];
   int i;
   char *rp;
   Eina_List *file_q = NULL;

   ON_NOT_RUNNING_IN_TERMINOLOGY_EXIT_1();
   ARGUMENT_ENTRY_CHECK(argc, argv, print_usage);
   if (argc <= 1)
     {
        print_usage(argv[0]);
        return 0;
     }

   eina_init();
   ecore_app_no_system_modules();
   ecore_init();
   ecore_file_init();
   evas_init();
   ecore_evas_init();
   edje_init();
   emotion_init();

   ee = ecore_evas_buffer_new(1, 1);
   if (!ee)
     goto shutdown;
   evas = ecore_evas_get(ee);
   echo_off();
   snprintf(buf, sizeof(buf), "%c}qs", 0x1b);
   if (ty_write(1, buf, strlen(buf) + 1) < 0)
     {
        perror("write");
        echo_on();
        goto shutdown;
     }
   if (scanf("%i;%i;%i;%i", &tw, &th, &cw, &ch) != 4 ||
       ((tw <= 0) || (th <= 0) || (cw <= 1) || (ch <= 1)))
     {
        echo_on();
        goto shutdown;
     }
   echo_on();

   for (i = 1; i < argc; i++)
     {
        char *path;

        if (!strcmp(argv[i], "-c"))
          {
             _mode = CENTER;
             i++;
             if (i >= argc) goto done;
          }
        else if (!strcmp(argv[i], "-s"))
          {
             _mode = STRETCH;
             i++;
             if (i >= argc) goto done;
          }
        else if (!strcmp(argv[i], "-f"))
          {
             _mode = FILL;
             i++;
             if (i >= argc) goto done;
          }

        if (!strcmp(argv[i], "-g"))
          {
             unsigned int width = 0, height = 0;
             int cnt;

             if (i + 2 >= argc)
               {
                  print_usage(argv[0]);
                  goto shutdown;
               }
             i++;
             cnt = sscanf(argv[i], "%ux%u", &width, &height);
             if (cnt != 2)
               {
                  print_usage(argv[0]);
                  goto done;
               }
             if (!width || tw > (int)width) maxw = width;
             maxh = height;
          }
        path = argv[i];
        rp = ecore_file_realpath(path);
        if (rp)
          file_q = eina_list_append(file_q, rp);
     }
   if (!file_q) goto done;

   ecore_idler_add(handle_file, &file_q);
   ecore_main_loop_begin();

done:
   EINA_LIST_FREE(file_q, rp)
     free(rp);

   ecore_evas_free(ee);

shutdown:
   emotion_shutdown();
   edje_shutdown();
   ecore_evas_shutdown();
   evas_shutdown();
   ecore_file_shutdown();
   ecore_shutdown();
   eina_shutdown();

   return 0;
}
