#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>

#include <Eina.h>
#include <Eet.h>
#include <Ecore.h>
#include <Ecore_Getopt.h>
#include <Elementary.h>

#include "common.h"

#define DBG(...) EINA_LOG_DOM_DBG(_log_domain, __VA_ARGS__)
#define INF(...) EINA_LOG_DOM_INFO(_log_domain, __VA_ARGS__)

static int _log_domain = -1;

static Eina_Stringshare *_src_filename = NULL;
static Exactness_Unit *_src_unit = NULL;
static int _verbose = 0;

static Eina_Debug_Session *_session = NULL;
static int _cid = -1, _pid = -1;
static Eina_List *_cur_event_list = NULL;

static int _all_apps_get_op = EINA_DEBUG_OPCODE_INVALID;
static int _mouse_in_op = EINA_DEBUG_OPCODE_INVALID;
static int _mouse_out_op = EINA_DEBUG_OPCODE_INVALID;
static int _mouse_wheel_op = EINA_DEBUG_OPCODE_INVALID;
static int _multi_down_op = EINA_DEBUG_OPCODE_INVALID;
static int _multi_up_op = EINA_DEBUG_OPCODE_INVALID;
static int _multi_move_op = EINA_DEBUG_OPCODE_INVALID;
static int _key_down_op = EINA_DEBUG_OPCODE_INVALID;
static int _key_up_op = EINA_DEBUG_OPCODE_INVALID;
static int _take_shot_op = EINA_DEBUG_OPCODE_INVALID;
static int _efl_event_op = EINA_DEBUG_OPCODE_INVALID;
static int _click_on_op = EINA_DEBUG_OPCODE_INVALID;
static int _stabilize_op = EINA_DEBUG_OPCODE_INVALID;
static int _finish_op = EINA_DEBUG_OPCODE_INVALID;

static Eina_Bool _all_apps_get_cb(Eina_Debug_Session *, int , void *, int);

EINA_DEBUG_OPCODES_ARRAY_DEFINE(_debug_ops,
      {"Daemon/Client/register_observer", &_all_apps_get_op, NULL},
      {"Daemon/Client/added", NULL, &_all_apps_get_cb},
      {"Exactness/Actions/Mouse In", &_mouse_in_op, NULL},
      {"Exactness/Actions/Mouse Out", &_mouse_out_op, NULL},
      {"Exactness/Actions/Mouse Wheel", &_mouse_wheel_op, NULL},
      {"Exactness/Actions/Multi Down", &_multi_down_op, NULL},
      {"Exactness/Actions/Multi Up", &_multi_up_op, NULL},
      {"Exactness/Actions/Multi Move", &_multi_move_op, NULL},
      {"Exactness/Actions/Key Down", &_key_down_op, NULL},
      {"Exactness/Actions/Key Up", &_key_up_op, NULL},
      {"Exactness/Actions/Take Shot", &_take_shot_op, NULL},
      {"Exactness/Actions/EFL Event", &_efl_event_op, NULL},
      {"Exactness/Actions/Click On", &_click_on_op, NULL},
      {"Exactness/Actions/Stabilize", &_stabilize_op, NULL},
      {"Exactness/Actions/Finish", &_finish_op, NULL},
      {NULL, NULL, NULL}
      );

static void
_feed_event(Exactness_Action_Type type, unsigned int n_evas, void *data)
{
   switch (type)
     {
      case EXACTNESS_ACTION_MOUSE_IN:
           {
              INF("Mouse in\n");
              DBG("%s evas_event_feed_mouse_in n_evas=<%d>\n", __func__, n_evas);
              eina_debug_session_send(_session, _cid, _mouse_in_op, &n_evas, sizeof(int));
              break;
           }
      case EXACTNESS_ACTION_MOUSE_OUT:
           {
              INF("Mouse out\n");
              DBG("%s evas_event_feed_mouse_out n_evas=<%d>\n", __func__, n_evas);
              eina_debug_session_send(_session, _cid, _mouse_out_op, &n_evas, sizeof(int));
              break;
           }
      case EXACTNESS_ACTION_MOUSE_WHEEL:
           {
              Exactness_Action_Mouse_Wheel *t = data;
              int len = 3*sizeof(int);
              char *buf = malloc(len), *tmp = buf;
              INF("Mouse wheel\n");
              DBG("%s evas_event_feed_mouse_wheel n_evas=<%d>\n", __func__, n_evas);
              STORE_INT(tmp, n_evas);
              STORE_INT(tmp, t->direction);
              STORE_INT(tmp, t->z);
              eina_debug_session_send(_session, _cid, _mouse_wheel_op, buf, len);
              free(buf);
              break;
           }
      case EXACTNESS_ACTION_MULTI_DOWN:
      case EXACTNESS_ACTION_MULTI_UP:
           {
              Exactness_Action_Multi_Event *t = data;
              int len = 5*sizeof(int)+7*sizeof(double)+sizeof(int);
              char *buf = malloc(len), *tmp = buf;
              DBG("%s %s n_evas=<%d>\n", __func__,
                    type == EXACTNESS_ACTION_MULTI_DOWN ? "evas_event_feed_multi_down" :
                    "evas_event_feed_multi_up", n_evas);
              STORE_INT(tmp, n_evas);
              STORE_INT(tmp, t->d);
              STORE_INT(tmp, t->b);
              STORE_INT(tmp, t->x);
              STORE_INT(tmp, t->y);
              STORE_DOUBLE(tmp, t->rad);
              STORE_DOUBLE(tmp, t->radx);
              STORE_DOUBLE(tmp, t->rady);
              STORE_DOUBLE(tmp, t->pres);
              STORE_DOUBLE(tmp, t->ang);
              STORE_DOUBLE(tmp, t->fx);
              STORE_DOUBLE(tmp, t->fy);
              STORE_INT(tmp, t->flags);
              eina_debug_session_send(_session, _cid,
                    type == EXACTNESS_ACTION_MULTI_DOWN ? _multi_down_op : _multi_up_op,
                    buf, len);
              free(buf);
              break;
           }
      case EXACTNESS_ACTION_MULTI_MOVE:
           {
              Exactness_Action_Multi_Move *t = data;
              int len = 4*sizeof(int)+7*sizeof(double);
              char *buf = malloc(len), *tmp = buf;
              DBG("%s evas_event_feed_multi_move n_evas=<%d>\n", __func__, n_evas);
              STORE_INT(tmp, n_evas);
              STORE_INT(tmp, t->d);
              STORE_INT(tmp, t->x);
              STORE_INT(tmp, t->y);
              STORE_DOUBLE(tmp, t->rad);
              STORE_DOUBLE(tmp, t->radx);
              STORE_DOUBLE(tmp, t->rady);
              STORE_DOUBLE(tmp, t->pres);
              STORE_DOUBLE(tmp, t->ang);
              STORE_DOUBLE(tmp, t->fx);
              STORE_DOUBLE(tmp, t->fy);
              eina_debug_session_send(_session, _cid, _multi_move_op, buf, len);
              free(buf);
              break;
           }
      case EXACTNESS_ACTION_KEY_DOWN:
      case EXACTNESS_ACTION_KEY_UP:
           {
              Exactness_Action_Key_Down_Up *t = data;
              int len = 2*sizeof(int) + 4 + 1;
              len += t->keyname ? strlen(t->keyname) : 0;
              len += t->key ? strlen(t->key) : 0;
              len += t->string ? strlen(t->string) : 0;
              len += t->compose ? strlen(t->compose) : 0;
              char *buf = malloc(len), *tmp = buf;
              DBG("%s %s n_evas=<%d>\n", __func__,
                    type == EXACTNESS_ACTION_KEY_DOWN ? "evas_event_feed_key_down " :
                    "evas_event_feed_key_up", n_evas);
              STORE_INT(tmp, n_evas);
              STORE_STRING(tmp, t->keyname);
              STORE_STRING(tmp, t->key);
              STORE_STRING(tmp, t->string);
              STORE_STRING(tmp, t->compose);
              STORE_INT(tmp, t->keycode);
              eina_debug_session_send(_session, _cid,
                    type == EXACTNESS_ACTION_KEY_DOWN ? _key_down_op : _key_up_op,
                    buf, len);
              free(buf);
              break;
           }
      case EXACTNESS_ACTION_TAKE_SHOT:
           {
              DBG("%s take shot n_evas=<%d>\n", __func__, n_evas);
              eina_debug_session_send(_session, _cid, _take_shot_op, &n_evas, sizeof(int));
              break;
           }
      case EXACTNESS_ACTION_EFL_EVENT:
           {
              Exactness_Action_Efl_Event *t = data;
              int len = 2;
              len += t->wdg_name ? strlen(t->wdg_name) : 0;
              len += t->event_name ? strlen(t->event_name) : 0;
              char *buf = malloc(len), *tmp = buf;
              DBG("%s %s\n", __func__, "EFL event");
              STORE_STRING(tmp, t->wdg_name);
              STORE_STRING(tmp, t->event_name);
              eina_debug_session_send(_session, _cid, _efl_event_op, buf, len);
              free(buf);
              break;
           }
      case EXACTNESS_ACTION_CLICK_ON:
           {
              Exactness_Action_Click_On *t = data;
              int len = 1;
              len += t->wdg_name ? strlen(t->wdg_name) : 0;
              char *buf = malloc(len), *tmp = buf;
              DBG("%s %s\n", __func__, "Click On");
              STORE_STRING(tmp, t->wdg_name);
              eina_debug_session_send(_session, _cid, _click_on_op, buf, len);
              free(buf);
              break;
           }
      case EXACTNESS_ACTION_STABILIZE:
           {
              DBG("%s stabilize\n", __func__);
              eina_debug_session_send(_session, _cid, _stabilize_op, NULL, 0);
              break;
           }
      default: /* All non-input events are not handeled */
         break;
     }
}

static Eina_Bool
_feed_event_timer_cb(void *data EINA_UNUSED)
{
   Exactness_Action *act = eina_list_data_get(_cur_event_list);
   _feed_event(act->type, act->n_evas, act->data);

   _cur_event_list = eina_list_next(_cur_event_list);

   if (!_cur_event_list)
     {  /* Finished reading all events */
        eina_debug_session_send(_session, _cid, _finish_op, NULL, 0);
        ecore_main_loop_quit();
     }
   else
     {
        Exactness_Action *cur_act = eina_list_data_get(_cur_event_list);
        ecore_timer_add(cur_act->delay_ms / 1000.0, _feed_event_timer_cb, NULL);
     }
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_src_open()
{
   double diff_time = 0; /* Time to wait before feeding the first event */

   DBG("<%s> Source file is <%s>\n", __func__, _src_filename);
   if (!strcmp(_src_filename + strlen(_src_filename) - 4,".exu"))
     {
        _src_unit = exactness_unit_file_read(_src_filename);
     }
   if (!_src_unit) return EINA_FALSE;
   _cur_event_list = _src_unit->actions;
   Exactness_Action *act = eina_list_data_get(_cur_event_list);

   if (act->delay_ms)
     {
        DBG("  Waiting <%f>\n", diff_time);
        ecore_timer_add(act->delay_ms / 1000.0, _feed_event_timer_cb, NULL);
     }
   else
     {
        _feed_event_timer_cb(NULL);
     }
   return EINA_TRUE;
}

static void
_main_loop_all_apps_get_cb(Eina_Debug_Session *session EINA_UNUSED, int srcid EINA_UNUSED, void *buffer, int size EINA_UNUSED)
{
   char *buf = buffer;
   int chosen_cid = -1;
   if (_cid != -1) return;
   while (size > 0)
     {
        int cid, pid, len;
        cid = EXTRACT_INT(buf);
        pid = EXTRACT_INT(buf);
        if (_pid != -1)
          {
             if (_pid == pid)
               {
                  _cid = cid;
                  _src_open();
                  return;
               }
          }
        else
          {
             if (!strcmp(buf, "exactness_play"))
               {
                  if (chosen_cid != -1)
                    {
                       fprintf(stderr, "Need to specify a PID - too much choice\n");
                       return;
                    }
                  chosen_cid = cid;
               }
          }
        len = strlen(buf) + 1;
        buf += len;
        size -= (2 * sizeof(int) + len);
     }
   if (chosen_cid != -1)
     {
        _cid = chosen_cid;
        _src_open();
     }
}

WRAPPER_TO_XFER_MAIN_LOOP(_all_apps_get_cb)

static void
_ops_ready_cb(void *data EINA_UNUSED, Eina_Bool status)
{
   if (status)
      eina_debug_session_send(_session, 0, _all_apps_get_op, NULL, 0);
}

static const Ecore_Getopt optdesc = {
  "exactness_inject",
  "%prog [options] <-v|-p|-t|-h> command",
  PACKAGE_VERSION,
  "(C) 2018 Enlightenment",
  "BSD",
  "A scenario events injector for EFL based applications.",
  1,
  {
    ECORE_GETOPT_STORE_STR('t', "test", "Test to run on the given application"),
    ECORE_GETOPT_STORE_INT('p', "pid", "PID of the application to connect to"),
    ECORE_GETOPT_STORE_INT('r', "remote-port", "Port to connect remotely to the daemon. Local connection if not specified"),
    ECORE_GETOPT_COUNT('v', "verbose", "Turn verbose messages on."),

    ECORE_GETOPT_LICENSE('L', "license"),
    ECORE_GETOPT_COPYRIGHT('C', "copyright"),
    ECORE_GETOPT_VERSION('V', "version"),
    ECORE_GETOPT_HELP('h', "help"),
    ECORE_GETOPT_SENTINEL
  }
};

int main(int argc, char **argv)
{
   int opt_args = 0, real__ = 1, port = -1;
   char *src = NULL;
   Eina_Value *ret__;
   Eina_Bool want_quit = EINA_FALSE;

   Ecore_Getopt_Value values[] = {
     ECORE_GETOPT_VALUE_STR(src),
     ECORE_GETOPT_VALUE_INT(_pid),
     ECORE_GETOPT_VALUE_INT(port),
     ECORE_GETOPT_VALUE_INT(_verbose),

     ECORE_GETOPT_VALUE_BOOL(want_quit),
     ECORE_GETOPT_VALUE_BOOL(want_quit),
     ECORE_GETOPT_VALUE_BOOL(want_quit),
     ECORE_GETOPT_VALUE_BOOL(want_quit),
     ECORE_GETOPT_VALUE_NONE
   };

   _log_domain = eina_log_domain_register("exactness_injector", NULL);

   if (!ecore_evas_init())
      return EXIT_FAILURE;

   opt_args = ecore_getopt_parse(&optdesc, values, argc, argv);
   if (opt_args < 0)
     {
        fprintf(stderr, "Failed parsing arguments.\n");
        goto end;
     }
   if (want_quit) goto end;

   if (!src)
     {
        fprintf(stderr, "no test file specified\n");
        goto end;
     }
   _src_filename = eina_stringshare_add(src);

   if (port == -1)
      _session = eina_debug_local_connect(EINA_TRUE);
   else
      _session = eina_debug_remote_connect(port);
   eina_debug_opcodes_register(_session, _debug_ops(), _ops_ready_cb, NULL);

   elm_init(argc, argv);
   ret__ = efl_loop_begin(efl_main_loop_get());
   real__ = efl_loop_exit_code_process(ret__);
   elm_shutdown();
end:
   ecore_evas_shutdown();
   eina_log_domain_unregister(_log_domain);
   _log_domain = -1;
   return real__;
}
