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

#include <Ector.h>
#include <software/Ector_Software.h>

#include "ector_private.h"
#include "ector_software_private.h"

#define MY_CLASS ECTOR_SOFTWARE_SURFACE_CLASS

typedef struct _Ector_Software_Task Ector_Software_Task;

struct _Ector_Software_Task
{
   Eina_Thread_Queue_Msg member;

   Ector_Thread_Worker_Cb cb;
   Eina_Free_Cb done;
   void *data;
};

static int _count_init = 0;
static unsigned int current = 0;
static unsigned int cpu_core = 0;
static Ector_Software_Thread *ths = NULL;
static Eina_Thread_Queue *render_queue = NULL;
static Ector_Software_Thread render_thread;

static void *
_prepare_process(void *data, Eina_Thread t)
{
   Ector_Software_Thread *th = data;

   eina_thread_name_set(t, "Ector Preparing Thread");
   do
     {
        Ector_Software_Task *task, todo;
        void *ref;

        task = eina_thread_queue_wait(th->queue, &ref);

        if (!task) break ;
        todo.cb = task->cb;
        todo.data = task->data;
        todo.done = task->done;

        eina_thread_queue_wait_done(th->queue, ref);

        if (!todo.cb) break ;

        todo.cb(todo.data, th);

        task = eina_thread_queue_send(render_queue, sizeof (Ector_Software_Task), &ref);
        task->cb = todo.cb;
        task->data = todo.data;
        task->done = todo.done;
        eina_thread_queue_send_done(render_queue, ref);
     }
   while (1);

   return th;
}


static void
_ector_software_init(void)
{
   int cpu, i;

   ++_count_init;
   if (_count_init != 1) return;

   cpu = eina_cpu_count() - 1;
   if (cpu < 1)
     {
        render_thread.queue = NULL;
        ector_software_thread_init(&render_thread);
        return ;
     }
   cpu = cpu > 8 ? 8 : cpu;
   cpu_core = cpu;

   render_queue = eina_thread_queue_new();

   ths = malloc(sizeof(Ector_Software_Thread) * cpu);
   for (i = 0; i < cpu; i++)
     {
        Ector_Software_Thread *t;

        t = &ths[i];
        t->queue = eina_thread_queue_new();
        ector_software_thread_init(t);
        if (!eina_thread_create(&t->thread, EINA_THREAD_NORMAL, -1,
                                _prepare_process, t))
          {
             eina_thread_queue_free(t->queue);
             t->queue = NULL;
          }
     }
}

static void
_ector_software_shutdown(void)
{
   Ector_Software_Thread *t;
   unsigned int i;

   --_count_init;
   if (_count_init != 0) return;

   if (!ths)
     {
        ector_software_thread_shutdown(&render_thread);
        return ;
     }

   for (i = 0; i < cpu_core; i++)
     {
        Ector_Software_Task *task;
        void *ref;

        t = &ths[i];

        task = eina_thread_queue_send(t->queue, sizeof (Ector_Software_Task), &ref);
        task->cb = NULL;
        task->data = NULL;
        eina_thread_queue_send_done(t->queue, ref);

        eina_thread_join(t->thread);
        eina_thread_queue_free(t->queue);
        ector_software_thread_shutdown(t);
     }

   eina_thread_queue_free(render_queue);
   render_queue = NULL;

   free(ths);
   ths = NULL;
}

void
ector_software_schedule(Ector_Thread_Worker_Cb cb, Eina_Free_Cb done, void *data)
{
   Ector_Software_Thread *t;
   Ector_Software_Task *task;
   void *ref;

   // Not enough CPU, doing it inline in the rendering thread
   if (!ths) return ;

   t = &ths[current];
   current = (current + 1) % cpu_core;

   task = eina_thread_queue_send(t->queue, sizeof (Ector_Software_Task), &ref);
   task->cb = cb;
   task->done = done;
   task->data = data;
   eina_thread_queue_send_done(t->queue, ref);
}

// Do not call this function if the done function has already called
void
ector_software_wait(Ector_Thread_Worker_Cb cb, Eina_Free_Cb done, void *data)
{
   Ector_Software_Task *task, covering;

   // First handle case with just inlined prepare code call inside the rendering thread
   if (!ths)
     {
        render_thread.thread = eina_thread_self();
        cb(data, &render_thread);
        done(data);

        return ;
     }

   // We don't know which task is going to be done first, so
   // we iterate until we find ourself back and trigger all
   // the done call along the way.
   do
     {
        void *ref;

        task = eina_thread_queue_wait(render_queue, &ref);
        if (!task) break;
        covering.cb = task->cb;
        covering.done = task->done;
        covering.data = task->data;
        eina_thread_queue_wait_done(render_queue, ref);

        covering.done(covering.data);
     }
   while (covering.cb != cb ||
          covering.done != done ||
          covering.data != data);
}

static Ector_Renderer *
_ector_software_surface_ector_surface_renderer_factory_new(Eo *obj,
                                                           Ector_Software_Surface_Data *pd EINA_UNUSED,
                                                           const Efl_Class *type)
{
   if (type == ECTOR_RENDERER_SHAPE_MIXIN)
     return efl_add_ref(ECTOR_RENDERER_SOFTWARE_SHAPE_CLASS, NULL, ector_renderer_surface_set(efl_added, obj));
   else if (type == ECTOR_RENDERER_IMAGE_MIXIN)
     return efl_add_ref(ECTOR_RENDERER_SOFTWARE_IMAGE_CLASS, NULL, ector_renderer_surface_set(efl_added, obj));
   else if (type == ECTOR_RENDERER_GRADIENT_LINEAR_MIXIN)
     return efl_add_ref(ECTOR_RENDERER_SOFTWARE_GRADIENT_LINEAR_CLASS, NULL, ector_renderer_surface_set(efl_added, obj));
   else if (type == ECTOR_RENDERER_GRADIENT_RADIAL_MIXIN)
     return efl_add_ref(ECTOR_RENDERER_SOFTWARE_GRADIENT_RADIAL_CLASS, NULL, ector_renderer_surface_set(efl_added, obj));

   ERR("Couldn't find class for type: %s", efl_class_name_get(type));
   return NULL;
}

static Eo *
_ector_software_surface_efl_object_constructor(Eo *obj, Ector_Software_Surface_Data *pd)
{
   _ector_software_init();

   obj = efl_constructor(efl_super(obj, MY_CLASS));
   pd->rasterizer = (Software_Rasterizer *) calloc(1, sizeof(Software_Rasterizer));
   ector_software_rasterizer_init(pd->rasterizer);
   pd->rasterizer->fill_data.raster_buffer = efl_data_ref(obj, ECTOR_SOFTWARE_BUFFER_BASE_MIXIN);
   return obj;
}

static void
_ector_software_surface_efl_object_destructor(Eo *obj, Ector_Software_Surface_Data *pd)
{
   efl_data_unref(obj, pd->rasterizer->fill_data.raster_buffer);
   free(pd->rasterizer);
   pd->rasterizer = NULL;
   efl_destructor(efl_super(obj, ECTOR_SOFTWARE_SURFACE_CLASS));

   _ector_software_shutdown();
}

static void
_ector_software_surface_ector_surface_reference_point_get(const Eo *obj EINA_UNUSED,
                                                          Ector_Software_Surface_Data *pd,
                                                          int* x, int* y)
{
   if (x) *x = pd->x;
   if (y) *y = pd->y;
}

static void
_ector_software_surface_ector_surface_reference_point_set(Eo *obj EINA_UNUSED,
                                                          Ector_Software_Surface_Data *pd,
                                                          int x, int y)
{
   pd->x = x;
   pd->y = y;
}

static Eina_Bool
_ector_software_surface_ector_surface_draw_image(Eo *obj EINA_UNUSED,
                                                 Ector_Software_Surface_Data *pd,
                                                 Ector_Buffer *buffer, int x, int y, int alpha)
{
   if (!buffer || !pd->rasterizer || !pd->rasterizer->fill_data.raster_buffer->pixels.u32)
     return EINA_FALSE;

   Ector_Software_Buffer_Base_Data *bd = efl_data_scope_get(buffer, ECTOR_SOFTWARE_BUFFER_BASE_MIXIN);
   const int pix_stride = pd->rasterizer->fill_data.raster_buffer->stride / 4;

   uint32_t *src = bd->pixels.u32;
   if (!src) return EINA_FALSE;

   for (unsigned int local_y = 0; local_y <  bd->generic->h; local_y++)
     {
        uint32_t *dst = pd->rasterizer->fill_data.raster_buffer->pixels.u32 + (x + ((local_y + y) * pix_stride));
        for (unsigned int local_x = 0; local_x <  bd->generic->w; local_x++)
          {
             *src = draw_mul_256(alpha, *src);
             int inv_alpha = 255 - ((*src) >> 24);
             *dst = *src + draw_mul_256(inv_alpha, *dst);
             dst++;
             src++;
          }
     }
   return EINA_TRUE;
}
#include "ector_software_surface.eo.c"
#include "ector_renderer_software.eo.c"
