#define EFL_BETA_API_SUPPORT
#define EFL_EO_API_SUPPORT

#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <Eina.h>
#include <Eo.h>
#include <Efl_Core.h>

// use buffered stream io
//#define BUFF

static int count = 0;
static int pipes[1024];
static int pnum = 0;

static double
randtimeout(void)
{
   return 4.0;
   return (double)((rand() % 5) + 1) / 3.0;
}

static void
exe_exit(void *data, const Efl_Event *event)
{
   printf("--- EXE %p [%s] exited with %i\n",
          event->object,
          efl_task_command_get(event->object),
          efl_task_exit_code_get(event->object));
   efl_del(event->object);
   if (data) efl_del(data);
}

static void
exe_read_change(void *data, const Efl_Event *event)
{
   char buf[4096];
   Eina_Rw_Slice rw_slice = EINA_SLICE_ARRAY(buf);

   while (efl_io_reader_can_read_get(event->object))
     {
        Eina_Error err = efl_io_reader_read(event->object, &rw_slice);
        if (!err)
          {
             buf[rw_slice.len] = 0;
             printf("--- READ [%p] [%s] ok %i bytes '%s'\n",
                    event->object,
                    efl_task_command_get(data),
                    (int)rw_slice.len,
                    buf);
          }
     }
}

static void
_write_cmd(Eo *obj, Eo *exe)
{
   char *buf = "hello-out-there\n";
   Eina_Slice slice = { 17, buf };

   printf("--- DO WRITE\n");
   Eina_Error err = efl_io_writer_write(obj, &slice, NULL);
   if (!err)
     {
        printf("--- WRITE [%p] [%s] ok %i bytes\n",
               obj, efl_task_command_get(exe), (int)slice.len);
     }
}

static void
exe_write_change(void *data, const Efl_Event *event)
{
   if (efl_io_writer_can_write_get(event->object))
     {
        printf("--- CAN WRITE...\n");
     }
}

static Eina_Value
timeout(void *data, const Eina_Value t, const Eina_Future *dead EINA_UNUSED)
{
   // handle cancellation
   if (t.type == EINA_VALUE_TYPE_ERROR) return t;

   Eo *loop = data;
   Eina_Future *f = efl_loop_timeout(loop, randtimeout());
   eina_future_then(f, timeout, loop);
   if (loop == efl_main_loop_get())
     {
        int i;

        printf("# timeout loop main\n");
        count++;
        if (count == 500)
          {
             Eina_Value exitstatus;

             eina_value_setup(&exitstatus, EINA_VALUE_TYPE_UCHAR);
             eina_value_set(&exitstatus, EINA_TRUE);
             efl_loop_quit(loop, exitstatus);
          }
        for (i = 0; i < pnum; i++)
          {
             char buf[2] = { 1, 2 };

             write(pipes[i], buf, 2);
          }
     }
   else
     printf("# timeout loop %p\n", loop);

   Eo *exe = efl_add(EFL_EXE_CLASS, loop,
#ifndef BUFF
                     efl_event_callback_add
                       (efl_added, EFL_IO_READER_EVENT_CAN_READ_CHANGED,
                        exe_read_change, efl_added),
                     efl_event_callback_add
                       (efl_added, EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED,
                        exe_write_change, efl_added),
#endif
                     efl_exe_flags_set
                       (efl_added,
                        efl_exe_flags_get(efl_added) |
                        EFL_EXE_FLAGS_USE_STDOUT | EFL_EXE_FLAGS_USE_STDIN),
                     efl_task_command_set(efl_added, "./loop.sh"),
                     efl_task_env_set(efl_added, "TZ", "GMT"),
                     efl_task_run(efl_added)
                    );
#ifndef BUFF
   efl_event_callback_add(exe, EFL_TASK_EVENT_EXIT, exe_exit, NULL),
   _write_cmd(exe, exe);
#endif
#ifdef BUFF
   size_t buffer_limit = 4;
   Eo *stream = efl_add(EFL_IO_BUFFERED_STREAM_CLASS, exe,
                        efl_io_buffered_stream_inner_io_set(efl_added, exe),
                        efl_io_buffered_stream_max_queue_size_input_set(efl_added, buffer_limit),
                        efl_io_buffered_stream_max_queue_size_output_set(efl_added, buffer_limit),
                        efl_event_callback_add
                          (efl_added, EFL_IO_READER_EVENT_CAN_READ_CHANGED,
                           exe_read_change, exe));
   efl_event_callback_add(exe, EFL_TASK_EVENT_EXIT, exe_exit, stream),
   _write_cmd(stream, exe);
#endif
   return t;
}

static void
idle_enter(void *data, const Efl_Event *event)
{
   Eo *loop = event->object;

   if (loop == efl_main_loop_get())
     printf("# idle,enter main\n");
   else
     printf("# idle,enter %p\n", loop);
}

static void
idle_exit(void *data, const Efl_Event *event)
{
   Eo *loop = event->object;

   if (loop == efl_main_loop_get())
     printf("# idle,exit main\n");
   else
     printf("# idle,exit %p\n", loop);
}

static void
pipe_read(void *data, const Efl_Event *event)
{
   int fd = efl_loop_handler_fd_get(event->object);
   char buf[1];
   int ret;

   ret = read(fd, buf, 1);
   printf("# read %p ... %i\n", data, ret);
}

static void *
thread_main(void *data, Eina_Thread th)
{
   Eo *loop;
   Eina_Value *exitval;
   Eina_Future *f;
   int fd = (int)(uintptr_t)data;
   Eo *handler;

   loop = efl_add(EFL_LOOP_CLASS, NULL);
   if (!loop)
     {
        fprintf(stderr, "# Cannot create loop in thread %p\n", data);
     }
   handler = efl_add
     (EFL_LOOP_HANDLER_CLASS, loop,
      efl_loop_handler_active_set(efl_added, EFL_LOOP_HANDLER_FLAGS_READ),
      efl_loop_handler_fd_set(efl_added, fd),
      efl_event_callback_add(efl_added, EFL_LOOP_HANDLER_EVENT_READ,
                             pipe_read, loop));
   efl_event_callback_add(loop, EFL_LOOP_EVENT_IDLE_ENTER,
                          idle_enter, NULL);
   efl_event_callback_add(loop, EFL_LOOP_EVENT_IDLE_EXIT,
                          idle_exit, NULL);
   f = efl_loop_timeout(loop, randtimeout());
   eina_future_then(f, timeout, loop);
   exitval = efl_loop_begin(loop);
   eina_value_free(exitval);
   return NULL;
}

static void
thread_add(Eo *loop, int num)
{
   Eina_Bool ok;
   Eina_Thread th;
   int fds[2];

   pipe(fds);
   pipes[pnum] = fds[1];
   pnum++;
   ok = eina_thread_create(&th, EINA_THREAD_NORMAL, -1, thread_main,
                          (void *)(uintptr_t)fds[0]);
}

EAPI_MAIN void
efl_main(void *data EINA_UNUSED, const Efl_Event *ev)
{
   Eo *loop = ev->object;
   Efl_Loop_Arguments *args = ev->info;
   Eina_Future *f;

   efl_event_callback_add(loop, EFL_LOOP_EVENT_IDLE_ENTER,
                          idle_enter, NULL);
   efl_event_callback_add(loop, EFL_LOOP_EVENT_IDLE_EXIT,
                          idle_exit, NULL);
   if (eina_array_count(args->argv) == 1)
     {
        const char *s = eina_array_data_get(args->argv, 0);
        int i, threads = atoi(s);

        for (i = 0; i < threads; i++)
          {
             thread_add(ev->object, i);
          }
     }
   f = efl_loop_timeout(loop, randtimeout());
   eina_future_then(f, timeout, loop);
}

EFL_MAIN()
