/**
 * @file
 *
 * Copyright (C) 2009 by ProFUSION embedded systems
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library;
 * if not, see <http://www.gnu.org/licenses/>.
 *
 * @author Rafael Antognolli <antognolli@profusion.mobi>
 */

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

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <errno.h>

#include <Eina.h>
#include <Ecore.h>
#include <Ethumb.h>

#include "ethumbd_private.h"

#define DBG(...) EINA_LOG_DOM_DBG(_log_domain, __VA_ARGS__)
#define INF(...) EINA_LOG_DOM_INFO(_log_domain, __VA_ARGS__)
#define WRN(...) EINA_LOG_DOM_WARN(_log_domain, __VA_ARGS__)
#define ERR(...) EINA_LOG_DOM_ERR(_log_domain, __VA_ARGS__)

#define NETHUMBS 100

static int _log_domain = -1;

struct _Ethumbd_Child
{
#ifndef _WIN32
   Ecore_Fd_Handler *fd_handler;
#else
   Ecore_Win32_Handler *fd_handler;
#endif

   Ethumb *ethumbt[NETHUMBS];
};


static int
_ec_read_safe(FILE* stream, void *buf, ssize_t size)
{
   ssize_t todo;
   unsigned char *p;
   int c;

   todo = size;
   p = buf;

   while (todo > 0)
     {
        c = getc(stream);
        if (c == EOF)
          {
             ERR("could not read from stream %p", stream);
             return 0;
          }
        *p = c;
        ++p;
        --todo;
     }
   return 1;
}

static int
_ec_write_safe(FILE *stream, const void *buf, ssize_t size)
{
   ssize_t todo;
   const unsigned char *p;

   todo = size;
   p = buf;

   while (todo > 0)
     {
        if (putc(*p, stream) == EOF)
          {
             ERR("could not write to stream %p", stream);
             return 0;
          }
        ++p;
        --todo;
     }

   return 1;
}

static int
_ec_pipe_str_read(struct _Ethumbd_Child *ec EINA_UNUSED, char **str)
{
   int size;
   int r;
   char buf[PATH_MAX] = { '\0' };

   r = _ec_read_safe(stdin, &size, sizeof(size));
   if (!r)
     {
	*str = NULL;
	return 0;
     }
   if ((size < 0) || (size >= PATH_MAX))
     {
	*str = NULL;
	return 0;
     }

   if (!size)
     {
	*str = NULL;
	return 1;
     }

   r = _ec_read_safe(stdin, buf, size);
   if (!r)
     {
	*str = NULL;
	return 0;
     }
   buf[size] = 0;

   *str = strdup(buf);
   return 1;
}

static struct _Ethumbd_Child *
_ec_new(void)
{
   struct _Ethumbd_Child *ec = calloc(1, sizeof(*ec));

   return ec;
}

static void
_ec_free(struct _Ethumbd_Child *ec)
{
   int i;

   if (ec->fd_handler)
     {
#ifndef _WIN32
     ecore_main_fd_handler_del(ec->fd_handler);
#else
     ecore_main_win32_handler_del(ec->fd_handler);
#endif
     }

   for (i = 0; i < NETHUMBS; i++)
     {
	if (ec->ethumbt[i])
	  ethumb_free(ec->ethumbt[i]);
     }

   free(ec);
}

static int
_ec_op_new(struct _Ethumbd_Child *ec)
{
   int r;
   int idx;

   r = _ec_read_safe(stdin, &idx, sizeof(idx));
   if (!r)
     return 0;
   if ((idx < 0) || (idx >= NETHUMBS))
     return 0;

   DBG("ethumbd new(). idx = %d", idx);

   ec->ethumbt[idx] = ethumb_new();
   return 1;
}

static int
_ec_op_del(struct _Ethumbd_Child *ec)
{
   int r;
   int idx;

   r = _ec_read_safe(stdin, &idx, sizeof(idx));
   if (!r)
     return 0;
   if ((idx < 0) || (idx >= NETHUMBS))
     return 0;

   DBG("ethumbd del(). idx = %d", idx);

   ethumb_free(ec->ethumbt[idx]);
   ec->ethumbt[idx] = NULL;
   return 1;
}

static void
_ec_op_generated_cb(void *data EINA_UNUSED, Ethumb *e, Eina_Bool success)
{
   const char *thumb_path, *thumb_key;
   int size_path, size_key, size_cmd;

   DBG("thumb generated (%i)!", success);
   ethumb_thumb_path_get(e, &thumb_path, &thumb_key);

   if (!thumb_path)
     size_path = 0;
   else
     size_path = strlen(thumb_path) + 1;

   if (!thumb_key)
     size_key = 0;
   else
     size_key = strlen(thumb_key) + 1;

   size_cmd = sizeof(success) + sizeof(size_path) + size_path +
      sizeof(size_key) + size_key;

   _ec_write_safe(stdout, &size_cmd, sizeof(size_cmd));
   _ec_write_safe(stdout, &success, sizeof(success));

   _ec_write_safe(stdout, &size_path, sizeof(size_path));
   _ec_write_safe(stdout, thumb_path, size_path);

   _ec_write_safe(stdout, &size_key, sizeof(size_key));
   _ec_write_safe(stdout, thumb_key, size_key);
   fflush(stdout);
}

static int
_ec_op_generate(struct _Ethumbd_Child *ec)
{
   int idx;
   char *path, *key, *thumb_path, *thumb_key;
   int r;

   r = _ec_read_safe(stdin, &idx, sizeof(idx));
   if (!r)
     return 0;
   if ((idx < 0) || (idx >= NETHUMBS))
     return 0;

   r = _ec_pipe_str_read(ec, &path);
   if (!r)
     return 0;
   r = _ec_pipe_str_read(ec, &key);
   if (!r)
     {
        free(path);
        return 0;
     }
   r = _ec_pipe_str_read(ec, &thumb_path);
   if (!r)
     {
        free(path);
        free(key);
        return 0;
     }
   r = _ec_pipe_str_read(ec, &thumb_key);
   if (!r)
     {
        free(path);
        free(key);
        free(thumb_path);
        return 0;
     }

   ethumb_file_set(ec->ethumbt[idx], path, key);
   ethumb_thumb_path_set(ec->ethumbt[idx], thumb_path, thumb_key);

   if (ethumb_exists(ec->ethumbt[idx]))
     {
        _ec_op_generated_cb(ec, ec->ethumbt[idx], EINA_TRUE);
     }
   else
     {
        ethumb_generate(ec->ethumbt[idx], _ec_op_generated_cb, ec, NULL);
     }

   free(path);
   free(key);
   free(thumb_path);
   free(thumb_key);

   return 1;
}

static int
_ec_fdo_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_thumb_fdo_set(e, value);
   DBG("fdo = %d", value);

   return 1;
}

static int
_ec_size_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int w, h;
   int type;

   r = _ec_read_safe(stdin, &w, sizeof(w));
   if (!r)
     return 0;
   r = _ec_read_safe(stdin, &type, sizeof(type));
   if (!r)
     return 0;
   r = _ec_read_safe(stdin, &h, sizeof(h));
   if (!r)
     return 0;
   ethumb_thumb_size_set(e, w, h);
   DBG("size = %dx%d", w, h);

   return 1;
}

static int
_ec_format_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_thumb_format_set(e, value);
   DBG("format = %d", value);

   return 1;
}

static int
_ec_aspect_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_thumb_aspect_set(e, value);
   DBG("aspect = %d", value);

   return 1;
}

static int
_ec_orientation_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_thumb_orientation_set(e, value);
   DBG("orientation = %d", value);

   return 1;
}

static int
_ec_crop_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   float x, y;
   int type;

   r = _ec_read_safe(stdin, &x, sizeof(x));
   if (!r)
     return 0;
   r = _ec_read_safe(stdin, &type, sizeof(type));
   if (!r)
     return 0;
   r = _ec_read_safe(stdin, &y, sizeof(y));
   if (!r)
     return 0;
   ethumb_thumb_crop_align_set(e, x, y);
   DBG("crop = %fx%f", x, y);

   return 1;
}

static int
_ec_quality_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_thumb_quality_set(e, value);
   DBG("quality = %d", value);

   return 1;
}

static int
_ec_compress_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_thumb_compress_set(e, value);
   DBG("compress = %d", value);

   return 1;
}

static int
_ec_frame_set(struct _Ethumbd_Child *ec, Ethumb *e)
{
   int r;
   int type;
   char *theme_file, *group, *swallow;

   r = _ec_pipe_str_read(ec, &theme_file);
   if (!r)
     return 0;
   r = _ec_read_safe(stdin, &type, sizeof(type));
   if (!r)
     {
        free(theme_file);
        return 0;
     }
   r = _ec_pipe_str_read(ec, &group);
   if (!r)
     {
        free(theme_file);
        return 0;
     }
   r = _ec_read_safe(stdin, &type, sizeof(type));
   if (!r)
     {
        free(theme_file);
        free(group);
        return 0;
     }
   r = _ec_pipe_str_read(ec, &swallow);
   if (!r)
     {
        free(theme_file);
        free(group);
        return 0;
     }
   DBG("frame = %s:%s:%s", theme_file, group, swallow);
   ethumb_frame_set(e, theme_file, group, swallow);
   free(theme_file);
   free(group);
   free(swallow);

   return 1;
}

static int
_ec_directory_set(struct _Ethumbd_Child *ec, Ethumb *e)
{
   int r;
   char *directory;

   r = _ec_pipe_str_read(ec, &directory);
   if (!r)
     return 0;
   ethumb_thumb_dir_path_set(e, directory);
   DBG("directory = %s", directory);
   free(directory);

   return 1;
}

static int
_ec_category_set(struct _Ethumbd_Child *ec, Ethumb *e)
{
   int r;
   char *category;

   r = _ec_pipe_str_read(ec, &category);
   if (!r)
     return 0;
   ethumb_thumb_category_set(e, category);
   DBG("category = %s", category);
   free(category);

   return 1;
}

static int
_ec_video_time_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   float value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_video_time_set(e, value);
   DBG("video_time = %f", value);

   return 1;
}

static int
_ec_video_start_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   float value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_video_start_set(e, value);
   DBG("video_start = %f", value);

   return 1;
}

static int
_ec_video_interval_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   float value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_video_interval_set(e, value);
   DBG("video_interval = %f", value);

   return 1;
}

static int
_ec_video_ntimes_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_video_ntimes_set(e, value);
   DBG("video_ntimes = %d", value);

   return 1;
}

static int
_ec_video_fps_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_video_fps_set(e, value);
   DBG("video_fps = %d", value);

   return 1;
}

static int
_ec_document_page_set(struct _Ethumbd_Child *ec EINA_UNUSED, Ethumb *e)
{
   int r;
   int value;

   r = _ec_read_safe(stdin, &value, sizeof(value));
   if (!r)
     return 0;
   ethumb_document_page_set(e, value);
   DBG("document_page = %d", value);

   return 1;
}

static void
_ec_setup_process(struct _Ethumbd_Child *ec, int idx, int type)
{
   Ethumb *e;

   e = ec->ethumbt[idx];

   switch (type)
     {
      case ETHUMBD_FDO:
	 _ec_fdo_set(ec, e);
	 break;
      case ETHUMBD_SIZE_W:
	 _ec_size_set(ec, e);
	 break;
      case ETHUMBD_FORMAT:
	 _ec_format_set(ec, e);
	 break;
      case ETHUMBD_ASPECT:
	 _ec_aspect_set(ec, e);
	 break;
      case ETHUMBD_ORIENTATION:
	 _ec_orientation_set(ec, e);
	 break;
      case ETHUMBD_CROP_X:
	 _ec_crop_set(ec, e);
	 break;
      case ETHUMBD_QUALITY:
	 _ec_quality_set(ec, e);
	 break;
      case ETHUMBD_COMPRESS:
	 _ec_compress_set(ec, e);
	 break;
      case ETHUMBD_FRAME_FILE:
	 _ec_frame_set(ec, e);
	 break;
      case ETHUMBD_DIRECTORY:
	 _ec_directory_set(ec, e);
	 break;
      case ETHUMBD_CATEGORY:
	 _ec_category_set(ec, e);
	 break;
      case ETHUMBD_VIDEO_TIME:
	 _ec_video_time_set(ec, e);
	 break;
      case ETHUMBD_VIDEO_START:
	 _ec_video_start_set(ec, e);
	 break;
      case ETHUMBD_VIDEO_INTERVAL:
	 _ec_video_interval_set(ec, e);
	 break;
      case ETHUMBD_VIDEO_NTIMES:
	 _ec_video_ntimes_set(ec, e);
	 break;
      case ETHUMBD_VIDEO_FPS:
	 _ec_video_fps_set(ec, e);
	 break;
      case ETHUMBD_DOCUMENT_PAGE:
	 _ec_document_page_set(ec, e);
	 break;
      default:
	 ERR("wrong type!");
     }
}

static int
_ec_op_setup(struct _Ethumbd_Child *ec)
{
   int r;
   int idx;
   int type;

   r = _ec_read_safe(stdin, &idx, sizeof(idx));
   if (!r)
     return 0;
   if ((idx < 0) || (idx >= NETHUMBS))
     return 0;

   r = _ec_read_safe(stdin, &type, sizeof(type));
   if (!r)
     return 0;
   while (type != ETHUMBD_SETUP_FINISHED)
     {
	_ec_setup_process(ec, idx, type);
	r = _ec_read_safe(stdin, &type, sizeof(type));
	if (!r)
	  return 0;
     }

   return 1;
}

#ifndef _WIN32
static Eina_Bool
_ec_fd_handler(void *data, Ecore_Fd_Handler *fd_handler)
#else
static Eina_Bool
_ec_fd_handler(void *data, Ecore_Win32_Handler *fd_handler EINA_UNUSED)
#endif
{
   struct _Ethumbd_Child *ec = data;
   int op_id;
   int r;

#ifndef _WIN32
   if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_ERROR))
     {
	ERR("error on pipein! child exiting...");
	ec->fd_handler = NULL;
	ecore_main_loop_quit();
	return 0;
     }
#endif

   r = _ec_read_safe(stdin, &op_id, sizeof(op_id));
   if (!r)
     {
	DBG("ethumbd exited! child exiting...");
	ec->fd_handler = NULL;
	ecore_main_loop_quit();
	return 0;
     }

   DBG("received op: %d", op_id);

   switch (op_id)
     {
      case ETHUMBD_OP_NEW:
	 r = _ec_op_new(ec);
	 break;
      case ETHUMBD_OP_GENERATE:
	 r = _ec_op_generate(ec);
	 break;
      case ETHUMBD_OP_SETUP:
	 r = _ec_op_setup(ec);
	 break;
      case ETHUMBD_OP_DEL:
	 r = _ec_op_del(ec);
	 break;
      default:
	 ERR("invalid operation: %d", op_id);
	 r = 0;
     }

   if (!r)
     {
	ERR("ethumbd exited! child exiting...");
	ec->fd_handler = NULL;
	ecore_main_loop_quit();
     }

   return r;
}

static void
_ec_setup(struct _Ethumbd_Child *ec)
{
#ifndef _WIN32
   ec->fd_handler = ecore_main_fd_handler_add(
      STDIN_FILENO, ECORE_FD_READ | ECORE_FD_ERROR,
      _ec_fd_handler, ec, NULL, NULL);
#else
   ec->fd_handler = ecore_main_win32_handler_add(
      GetStdHandle(STD_INPUT_HANDLE),
      _ec_fd_handler, ec);
#endif
}

int
main(int argc EINA_UNUSED, const char *argv[] EINA_UNUSED)
{
   struct _Ethumbd_Child *ec;

   ethumb_init();

   if (_log_domain < 0)
     {
	_log_domain = eina_log_domain_register("ethumbd_child", NULL);

	if (_log_domain < 0)
	  {
	     EINA_LOG_CRIT("could not register log domain 'ethumbd_child'");
	     ethumb_shutdown();
	     return 1;
	  }
     }

   ec = _ec_new();

   _ec_setup(ec);

   DBG("child started!");
   ecore_main_loop_begin();
   DBG("child finishing.");

   _ec_free(ec);

   if (_log_domain >= 0)
     {
	eina_log_domain_unregister(_log_domain);
	_log_domain = -1;
     }
   ethumb_shutdown();

   return 0;
}
