#include "evas_map.h"

// FIXME: cur vs. prev is not handled (may be an issue?)
// FIXME: some render artifacts appear when this API is used (green pixels)

#define EINA_INLIST_REMOVE(l,i) do { l = (__typeof__(l)) eina_inlist_remove(EINA_INLIST_GET(l), EINA_INLIST_GET(i)); } while (0)
#define EINA_INLIST_APPEND(l,i) do { l = (__typeof__(l)) eina_inlist_append(EINA_INLIST_GET(l), EINA_INLIST_GET(i)); } while (0)
#define EINA_INLIST_PREPEND(l,i) do { l = (__typeof__(l)) eina_inlist_prepend(EINA_INLIST_GET(l), EINA_INLIST_GET(i)); } while (0)
#define EINA_INLIST_NEXT(l) (typeof(l)) EINA_INLIST_CONTAINER_GET(EINA_INLIST_GET(l)->next, typeof(*l))

#define MY_CLASS EFL_GFX_MAPPING_MIXIN

typedef struct _Gfx_Map               Gfx_Map;
typedef struct _Gfx_Map_Op            Gfx_Map_Op;
typedef struct _Gfx_Map_Pivot         Gfx_Map_Pivot;
typedef struct _Efl_Gfx_Mapping_Data  Efl_Gfx_Mapping_Data;
typedef struct _Gfx_Map_Point         Gfx_Map_Point;
typedef enum _Gfx_Map_Op_Type         Gfx_Map_Op_Type;

enum _Gfx_Map_Op_Type {
   GFX_MAPPING_RAW_COORD,
   GFX_MAPPING_COLOR,
   GFX_MAPPING_ROTATE_2D,
   GFX_MAPPING_ROTATE_3D,
   GFX_MAPPING_ROTATE_QUAT,
   GFX_MAPPING_ZOOM,
   GFX_MAPPING_TRANSLATE,
   GFX_MAPPING_LIGHTING_3D,
   GFX_MAPPING_PERSPECTIVE_3D,
};

struct _Gfx_Map_Op {
   EINA_INLIST;

   Gfx_Map_Op_Type op;
   union {
      struct {
         int idx;
         double x, y, z;
      } raw_coord;
      struct {
         int idx;
         uint8_t r, g, b, a;
      } color;
      struct {
         double degrees;
      } rotate_2d;
      struct {
         double dx, dy, dz;
      } rotate_3d;
      struct {
         double qx, qy, qz, qw;
      } rotate_quat;
      struct {
         double zx, zy;
      } zoom;
      struct {
         double dx, dy, dz;
      } translate;
      struct {
         uint8_t lr, lg, lb, ar, ag, ab;
      } lighting_3d;
      struct {
         double z0, foc;
      } perspective_3d;
   };
   struct {
      Gfx_Map_Pivot  *pivot;
      double          cx, cy, cz;
      Eina_Bool       is_absolute;
      Eina_Bool       is_self;
   } pivot;
};

struct _Gfx_Map_Pivot
{
   EINA_INLIST;

   Evas_Object_Protected_Data *map_obj;
   Eo             *eo_obj; // strong or weak ref?
   Eina_Rect       geometry;
   Eina_Bool       event_cbs;
   Eina_Bool       is_evas;
   Eina_Bool       is_canvas;
   Eina_Bool       changed;
};

struct _Gfx_Map_Point {
     double u, v;
};

struct _Gfx_Map {
   Gfx_Map_Op *ops;
   Gfx_Map_Point *points;

   Gfx_Map_Pivot *pivots;
   Evas_Map      *map;
   Gfx_Map_Op    *last_calc_op;
   int            imw, imh;
   int            count;

   // FIXME: Those need a quality vs. performance setting instead
   Eina_Bool alpha;
   Eina_Bool smooth;
   Eina_Bool event_cbs;
};

struct _Efl_Gfx_Mapping_Data {
   const Gfx_Map *cow;
};

// ----------------------------------------------------------------------------

static Eo *gfx_mapping_absolute = NULL;
static Eina_Cow *gfx_mapping_cow = NULL;
static const Gfx_Map gfx_mapping_cow_default = {
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   0, 0,
   4,
   EINA_TRUE,
   EINA_TRUE,
   EINA_FALSE
};

#define MAPCOW_BEGIN(_pd) eina_cow_write(gfx_mapping_cow, (const Eina_Cow_Data**)&(_pd->cow))
#define MAPCOW_END(_mapcow, _pd) eina_cow_done(gfx_mapping_cow, (const Eina_Cow_Data**)&(_pd->cow), _mapcow, EINA_FALSE)
#define MAPCOW_WRITE(pd, name, value) do { \
   if (pd->cow->name != (value)) { \
     Gfx_Map *_cow = MAPCOW_BEGIN(pd); \
     _cow->name = (value); \
     MAPCOW_END(_cow, pd); \
   }} while (0)

#define PIVOT_REF(_pivot) (_pivot ? efl_xref((Eo *) _pivot, eo_obj) : NULL)
#define PIVOT_UNREF(_pivot) (_pivot ? efl_xunref(_pivot, eo_obj) : NULL)

static inline void _map_clean(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd);

// ----------------------------------------------------------------------------

void
_efl_gfx_mapping_init(void)
{
   gfx_mapping_cow = eina_cow_add("Efl.Gfx.Mapping", sizeof(Gfx_Map), 8,
                              &gfx_mapping_cow_default, EINA_FALSE);
}

void
_efl_gfx_mapping_shutdown(void)
{
   eina_cow_del(gfx_mapping_cow);
   gfx_mapping_cow = NULL;

   efl_unref(gfx_mapping_absolute);
   gfx_mapping_absolute = NULL;
}

// ----------------------------------------------------------------------------

EOLIAN static Efl_Object *
_efl_gfx_mapping_efl_object_constructor(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd)
{
   eo_obj = efl_constructor(efl_super(eo_obj, MY_CLASS));
   pd->cow = eina_cow_alloc(gfx_mapping_cow);
   return eo_obj;
}

EOLIAN static void
_efl_gfx_mapping_efl_object_destructor(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd)
{
   if (pd->cow)
     {
        _map_clean(eo_obj, pd);
        eina_cow_free(gfx_mapping_cow, (const Eina_Cow_Data **) &pd->cow);
     }
   efl_destructor(efl_super(eo_obj, MY_CLASS));
}

// ----------------------------------------------------------------------------

static void
_geometry_changed_cb(void *data, const Efl_Event *ev EINA_UNUSED)
{
   Evas_Object_Protected_Data *obj = data;
   Efl_Gfx_Mapping_Data *pd = efl_data_scope_get(obj->object, MY_CLASS);

   MAPCOW_WRITE(pd, last_calc_op, NULL);
   obj->gfx_mapping_update = EINA_TRUE;
}

EFL_CALLBACKS_ARRAY_DEFINE(_geometry_changes,
                           { EFL_GFX_ENTITY_EVENT_POSITION_CHANGED, _geometry_changed_cb },
                           { EFL_GFX_ENTITY_EVENT_SIZE_CHANGED, _geometry_changed_cb });

static void
_pivot_changed_cb(void *data, const Efl_Event *ev EINA_UNUSED)
{
   Gfx_Map_Pivot *pivot = data;
   Evas_Object_Protected_Data *obj = pivot->map_obj;

   obj->gfx_mapping_update = EINA_TRUE;
   pivot->changed = EINA_TRUE;
}

EFL_CALLBACKS_ARRAY_DEFINE(_pivot_changes,
                           { EFL_GFX_ENTITY_EVENT_POSITION_CHANGED, _pivot_changed_cb },
                           { EFL_GFX_ENTITY_EVENT_SIZE_CHANGED, _pivot_changed_cb });

static inline void
_map_dirty(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd, Eina_Bool reset)
{
   Evas_Object_Protected_Data *obj = EVAS_OBJ_GET_OR_RETURN(eo_obj);
   Gfx_Map_Pivot *pivot;

   obj->gfx_mapping_has = EINA_TRUE;
   obj->gfx_mapping_update |= !reset;
   obj->changed_map = EINA_TRUE;
   evas_object_change(eo_obj, obj);

   if (reset)
     {
        _evas_map_reset(pd->cow->map);
        return;
     }

   // FIXME: Only add if there is a self-pivot (relative coordinates)
   if (!pd->cow->event_cbs)
     {
        MAPCOW_WRITE(pd, event_cbs, EINA_TRUE);
        efl_event_callback_array_add(eo_obj, _geometry_changes(), obj);
     }

   EINA_INLIST_FOREACH(pd->cow->pivots, pivot)
     {
        if (pivot->event_cbs) continue;
        pivot->event_cbs = EINA_TRUE;
        efl_event_callback_array_add(pivot->eo_obj, _pivot_changes(), pivot);
     }
}

static Evas_Map *
_map_calc(const Eo *eo_obj, Evas_Object_Protected_Data *obj, Efl_Gfx_Mapping_Data *pd)
{
   Gfx_Map_Op *op, *first_op = pd->cow->ops, *last_op;
   Gfx_Map_Pivot *pivot;
   Gfx_Map *mcow;
   Evas_Map *m;
   int imw, imh;
   int count;
   Eina_Bool map_alloc = EINA_FALSE;

   if (pd->cow == &gfx_mapping_cow_default)
     return NULL;

   m = pd->cow->map;
   if (!obj->gfx_mapping_update) return m;

   last_op = pd->cow->last_calc_op;
   count = pd->cow->count < 4 ? 4 : pd->cow->count;

   EINA_INLIST_FOREACH(pd->cow->pivots, pivot)
     {
        if (!pivot->changed) continue;
        last_op = NULL;
        pivot->changed = EINA_FALSE;
        if (!pivot->is_canvas)
          {
             pivot->geometry = efl_gfx_entity_geometry_get(pivot->eo_obj);
          }
        else
          {
             // Note: pivot can not be an Evas when using pure EO API
             if (pivot->is_evas)
               evas_output_size_get(pivot->eo_obj, &pivot->geometry.w, &pivot->geometry.h);
             else
               pivot->geometry.size = efl_gfx_entity_size_get(pivot->eo_obj);
             pivot->geometry.x = 0;
             pivot->geometry.y = 0;
          }
     }

   if (!pd->cow->points)
     {
        Gfx_Map_Point *ps = calloc(1, count * sizeof(Gfx_Map_Point));
        if (!ps) return m;
        ps[0].u = 0.0; ps[0].v = 0.0;
        ps[1].u = 1.0; ps[1].v = 0.0;
        ps[2].u = 1.0; ps[2].v = 1.0;
        ps[3].u = 0.0; ps[3].v = 1.0;
        MAPCOW_WRITE(pd, points, ps);
     }

   if (m && last_op)
     {
        first_op = EINA_INLIST_NEXT(last_op);
        imw = pd->cow->imw;
        imh = pd->cow->imh;
     }
   else
     {
        if (!m)
          {
             m = evas_map_new(count);
             if (!m) return NULL;
             map_alloc = EINA_TRUE;
          }
        else _evas_map_reset(m);
        m->alpha = pd->cow->alpha;
        m->smooth = pd->cow->smooth;
        m->move_sync.enabled = EINA_FALSE;

        _evas_map_util_points_populate(m,
                                       obj->cur->geometry.x, obj->cur->geometry.y,
                                       obj->cur->geometry.w, obj->cur->geometry.h,
                                       0);

        if (obj->is_image_object)
          {
             // Image is a special case in terms of geometry
             Eina_Size2D sz;
             sz = efl_gfx_view_size_get(eo_obj);
             imw = sz.w;
             imh = sz.h;
          }
        else
          {
             imw = obj->cur->geometry.w;
             imh = obj->cur->geometry.h;
          }

        last_op = NULL;
        first_op = pd->cow->ops;
     }

   for (int k = 0; k < count; k++)
     {
        Evas_Map_Point *p = &(m->points[k]);
        p->u = pd->cow->points[k].u * imw;
        p->v = pd->cow->points[k].v * imh;
     }

   EINA_INLIST_FOREACH(first_op, op)
     {
        int k, kmin = 0, kmax = count - 1;
        double cx, cy, cz;
        Evas_Map_Point *p;

        if (!op->pivot.is_absolute)
          {
             int px = 0, py = 0, pw = 1, ph = 1;

             if (op->pivot.is_self)
               {
                  px = obj->cur->geometry.x;
                  py = obj->cur->geometry.y;
                  pw = obj->cur->geometry.w;
                  ph = obj->cur->geometry.h;
               }
             else
               {
                  if (!op->pivot.pivot)
                    {
                       EINA_SAFETY_ERROR("safety check failed: op->pivot.pivot == NULL");
                       if (map_alloc) evas_map_free(m);
                       return NULL;
                    }

                  pivot = op->pivot.pivot;
                  px = pivot->geometry.x;
                  py = pivot->geometry.y;
                  pw = pivot->geometry.w;
                  ph = pivot->geometry.h;
               }

             cx = (double) px + (double) pw * op->pivot.cx;
             cy = (double) py + (double) ph * op->pivot.cy;
             cz = op->pivot.cz;
          }
        else
          {
             cx = op->pivot.cx;
             cy = op->pivot.cy;
             cz = op->pivot.cz;
          }

        switch (op->op)
          {
           case GFX_MAPPING_RAW_COORD:
             if (op->raw_coord.idx != -1)
               kmin = kmax = op->raw_coord.idx;
             for (k = kmin; k <= kmax; k++)
               {
                  p = &(m->points[k]);
                  p->px = p->x = op->raw_coord.x;
                  p->py = p->y = op->raw_coord.y;
                  p->z = op->raw_coord.z;
               }
             break;
           case GFX_MAPPING_COLOR:
             if (op->raw_coord.idx != -1)
               kmin = kmax = op->raw_coord.idx;
             for (k = kmin; k <= kmax; k++)
               {
                  p = &(m->points[k]);
                  p->r = op->color.r;
                  p->g = op->color.g;
                  p->b = op->color.b;
                  p->a = op->color.a;
               }
             break;
           case GFX_MAPPING_ROTATE_2D:
             _map_util_rotate(m, op->rotate_2d.degrees, cx, cy);
             break;
           case GFX_MAPPING_ROTATE_3D:
             _map_util_3d_rotate(m, op->rotate_3d.dx, op->rotate_3d.dy,
                                 op->rotate_3d.dz, cx, cy, cz);
             break;
           case GFX_MAPPING_ROTATE_QUAT:
             _map_util_quat_rotate(m, op->rotate_quat.qx, op->rotate_quat.qy,
                                   op->rotate_quat.qz, op->rotate_quat.qw,
                                   cx, cy, cz);
             break;
           case GFX_MAPPING_ZOOM:
             _map_util_zoom(m, op->zoom.zx, op->zoom.zy, cx, cy);
             break;
           case GFX_MAPPING_TRANSLATE:
             _map_util_translate(m, op->translate.dx, op->translate.dy,
                                 op->translate.dz);
             break;
           case GFX_MAPPING_LIGHTING_3D:
             _map_util_3d_lighting(m, cx, cy, cz, op->lighting_3d.lr,
                                   op->lighting_3d.lg, op->lighting_3d.lb,
                                   op->lighting_3d.ar, op->lighting_3d.ag,
                                   op->lighting_3d.ab);
             break;
           case GFX_MAPPING_PERSPECTIVE_3D:
             _map_util_3d_perspective(m, cx, cy, op->perspective_3d.z0,
                                      op->perspective_3d.foc);
             break;
          }

        last_op = op;
     }

   mcow = MAPCOW_BEGIN(pd);
   mcow->map = m;
   mcow->last_calc_op = last_op;
   mcow->imw = imw;
   mcow->imh = imh;
   MAPCOW_END(mcow, pd);
   obj->gfx_mapping_update = EINA_FALSE;

   return m;
}

void
_efl_gfx_mapping_update(Eo *eo_obj)
{
   Evas_Object_Protected_Data *obj = EVAS_OBJ_GET_OR_RETURN(eo_obj);
   Efl_Gfx_Mapping_Data *pd = efl_data_scope_get(eo_obj, MY_CLASS);
   Evas_Map *m;

   m = _map_calc(eo_obj, obj, pd);
   evas_object_map_set(eo_obj, m);
   _evas_object_map_enable_set(eo_obj, obj, m != NULL);
   obj->gfx_mapping_has = (m != NULL);
}

static inline void
_map_clean(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd)
{
   free(pd->cow->points);
   if (pd->cow->map) evas_map_free(pd->cow->map);
   if (pd->cow->ops)
     {
        Gfx_Map_Pivot *pivot;
        Gfx_Map_Op *op;
        Gfx_Map *mcow;

        mcow = MAPCOW_BEGIN(pd);
        EINA_INLIST_FREE(mcow->ops, op)
          {
             EINA_INLIST_REMOVE(mcow->ops, op);
             free(op);
          }
        EINA_INLIST_FREE(mcow->pivots, pivot)
          {
             EINA_INLIST_REMOVE(mcow->pivots, pivot);
             if (pivot->event_cbs)
               efl_event_callback_array_del(pivot->eo_obj, _pivot_changes(), pivot);
             PIVOT_UNREF(pivot->eo_obj);
             free(pivot);
          }
        MAPCOW_END(mcow, pd);
     }
}

EOLIAN Eina_Bool
_efl_gfx_mapping_mapping_has(Eo *eo_obj EINA_UNUSED, Efl_Gfx_Mapping_Data *pd EINA_UNUSED)
{
   if (pd->cow == &gfx_mapping_cow_default) return EINA_FALSE;
   if (pd->cow->ops) return EINA_TRUE;
   if (pd->cow->map) return EINA_TRUE;
   return EINA_FALSE;
}

EOLIAN static void
_efl_gfx_mapping_mapping_reset(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd)
{
   Evas_Object_Protected_Data *obj = EVAS_OBJ_GET_OR_RETURN(eo_obj);
   Eina_Bool alpha, smooth;

   alpha = pd->cow->alpha;
   smooth = pd->cow->smooth;
   _map_clean(eo_obj, pd);
   if (pd->cow->event_cbs)
     efl_event_callback_array_del(eo_obj, _geometry_changes(), obj);

   eina_cow_memcpy(gfx_mapping_cow, (const Eina_Cow_Data * const *) &pd->cow,
                   (const Eina_Cow_Data *) &gfx_mapping_cow_default);
   _map_dirty(eo_obj, pd, EINA_TRUE);
   MAPCOW_WRITE(pd, alpha, alpha);
   MAPCOW_WRITE(pd, smooth, smooth);
}

EOLIAN static int
_efl_gfx_mapping_mapping_point_count_get(const Eo *eo_obj EINA_UNUSED, Efl_Gfx_Mapping_Data *pd)
{
   return pd->cow->count;
}

EOLIAN static void
_efl_gfx_mapping_mapping_point_count_set(Eo *eo_obj EINA_UNUSED, Efl_Gfx_Mapping_Data *pd, int count)
{
   Gfx_Map *mcow;

   if ((count <= 0) || (count % 4 != 0))
     {
        ERR("Map point count (%d) should be multiples of 4", count);
        return;
     }
   if (pd->cow->count == count) return;

   mcow = MAPCOW_BEGIN(pd);
   if (mcow->points == NULL)
     {
        mcow->points = calloc(1, count * sizeof(Gfx_Map_Point));
        if (mcow->points)
          mcow->count = count;
        else
          ERR("Failed to allocate memory with calloc");
     }
   else
     {
        Gfx_Map_Point *ps = realloc(mcow->points, count * sizeof(Gfx_Map_Point));
        if (ps)
          {
             mcow->points = ps;
             mcow->count = count;
             memset(mcow->points, 0, count * sizeof(Gfx_Map_Point));
          }
        else
          ERR("Failed to allocate memory with realloc");
     }
   MAPCOW_END(mcow, pd);
}

EOLIAN static Eina_Bool
_efl_gfx_mapping_mapping_clockwise_get(const Eo *eo_obj, Efl_Gfx_Mapping_Data *pd)
{
   Evas_Object_Protected_Data *obj = EVAS_OBJ_GET_OR_RETURN(eo_obj, EINA_TRUE);
   Evas_Map *m;

   m = _map_calc(eo_obj, obj, pd);
   if (!m) return EINA_TRUE;
   return evas_map_util_clockwise_get(m);
}

EOLIAN static void
_efl_gfx_mapping_mapping_smooth_set(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd, Eina_Bool smooth)
{
   if (pd->cow->smooth == smooth) return;

   MAPCOW_WRITE(pd, smooth, smooth);

   _map_dirty(eo_obj, pd, EINA_FALSE);
}

EOLIAN static Eina_Bool
_efl_gfx_mapping_mapping_smooth_get(const Eo *eo_obj EINA_UNUSED, Efl_Gfx_Mapping_Data *pd)
{
   return pd->cow->smooth;
}

EOLIAN static void
_efl_gfx_mapping_mapping_alpha_set(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd, Eina_Bool alpha)
{
   if (pd->cow->alpha == alpha) return;

   MAPCOW_WRITE(pd, alpha, alpha);

   _map_dirty(eo_obj, pd, EINA_FALSE);
}

EOLIAN static Eina_Bool
_efl_gfx_mapping_mapping_alpha_get(const Eo *eo_obj EINA_UNUSED, Efl_Gfx_Mapping_Data *pd)
{
   return pd->cow->alpha;
}

EOLIAN static void
_efl_gfx_mapping_mapping_coord_absolute_get(const Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                                    int idx, double *x, double *y, double *z)
{
   Evas_Object_Protected_Data *obj = EVAS_OBJ_GET_OR_RETURN(eo_obj);
   Evas_Map *m;

   EINA_SAFETY_ON_FALSE_RETURN((idx >= 0) && (idx < pd->cow->count));

   m = _map_calc(eo_obj, obj, pd);
   if (!m)
     {
        int X, Y, W, H;

        X = obj->cur->geometry.x;
        Y = obj->cur->geometry.y;
        W = obj->cur->geometry.w;
        H = obj->cur->geometry.h;

        if (x)
          {
             if ((idx == 0) || (idx == 3)) *x = X;
             else *x = X + W;
          }
        if (y)
          {
             if ((idx == 0) || (idx == 1)) *y = Y;
             else *y = Y + H;
          }
        if (z) *z = 0;
        return;
     }

   _map_point_coord_get(m, idx, x, y, z);
}

EOLIAN static void
_efl_gfx_mapping_mapping_uv_set(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                        int idx, double u, double v)
{
   Gfx_Map *mcow;

   EINA_SAFETY_ON_FALSE_RETURN((idx >= 0) && (idx < pd->cow->count));

   if (!pd->cow->points)
     {
        Gfx_Map_Point *ps = calloc(1, pd->cow->count * sizeof(Gfx_Map_Point));
        if (!ps) return;
        MAPCOW_WRITE(pd, points, ps);
     }
   if (EINA_DBL_EQ(pd->cow->points[idx].u, u) &&
       EINA_DBL_EQ(pd->cow->points[idx].v, v))
     return;

   mcow = MAPCOW_BEGIN(pd);
   mcow->points[idx].u = CLAMP(0.0, u, 1.0);
   mcow->points[idx].v = CLAMP(0.0, v, 1.0);
   MAPCOW_END(mcow, pd);

   _map_dirty(eo_obj, pd, EINA_FALSE);
}

EOLIAN static void
_efl_gfx_mapping_mapping_uv_get(const Eo *eo_obj EINA_UNUSED, Efl_Gfx_Mapping_Data *pd,
                        int idx, double *u, double *v)
{
   EINA_SAFETY_ON_FALSE_RETURN((idx >= 0) && (idx < pd->cow->count)
                               && (pd->cow->points));

   if (u) *u = pd->cow->points[idx].u;
   if (v) *v = pd->cow->points[idx].v;
}

EOLIAN static void
_efl_gfx_mapping_mapping_color_get(const Eo *eo_obj EINA_UNUSED, Efl_Gfx_Mapping_Data *pd,
                           int idx, int *r, int *g, int *b, int *a)
{
   Evas_Object_Protected_Data *obj = EVAS_OBJ_GET_OR_RETURN(eo_obj);
   Evas_Map_Point *p;
   Evas_Map *m;

   EINA_SAFETY_ON_FALSE_RETURN((idx >= 0) && (idx < pd->cow->count));

   if (!r && !g && !b && !a) return;

   m = _map_calc(eo_obj, obj, pd);
   if (!m)
     {
        if (r) *r = 255;
        if (g) *g = 255;
        if (b) *b = 255;
        if (a) *a = 255;
        return;
     }

   p = &(m->points[idx]);
   if (r) *r = p->r;
   if (g) *g = p->g;
   if (b) *b = p->b;
   if (a) *a = p->a;
}

static Gfx_Map_Op *
_gfx_mapping_op_add(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd, Gfx_Map_Op_Type type,
                const Efl_Gfx_Entity *eo_pivot, double cx, double cy, double cz,
                Eina_Bool is_absolute)
{
   Eina_Bool is_self = EINA_FALSE;
   Gfx_Map_Pivot *pivot = NULL;
   Gfx_Map_Op *op;
   Gfx_Map *mcow;

   op = calloc(1, sizeof(*op));
   if (!op) return NULL;

   mcow = MAPCOW_BEGIN(pd);

   if (!is_absolute)
     {
        if ((eo_pivot == eo_obj) || !eo_pivot)
          {
             eo_pivot = NULL;
             is_self = EINA_TRUE;
          }
        else
          {
             Evas_Object_Protected_Data *obj = efl_data_scope_get(eo_obj, MY_CLASS);

             EINA_INLIST_FOREACH(mcow->pivots, pivot)
               if (pivot->eo_obj == eo_pivot) break;
             if (!pivot)
               {
                  pivot = calloc(1, sizeof(*pivot));
                  pivot->eo_obj = PIVOT_REF(eo_pivot);
                  pivot->changed = EINA_TRUE;
                  if (efl_isa(eo_pivot, EVAS_CANVAS_CLASS))
                    {
                       pivot->is_evas = EINA_TRUE;
                       pivot->is_canvas = EINA_TRUE;
                    }
                  else if (efl_isa(eo_pivot, EFL_CANVAS_SCENE_INTERFACE))
                    pivot->is_canvas = EINA_TRUE;
                  pivot->map_obj = obj;
                  EINA_INLIST_APPEND(mcow->pivots, pivot);
               }
          }
     }

   op->op = type;
   op->pivot.is_absolute = is_absolute;
   op->pivot.is_self = is_self;
   op->pivot.pivot = pivot;
   op->pivot.cx = cx;
   op->pivot.cy = cy;
   op->pivot.cz = cz;

   EINA_INLIST_APPEND(mcow->ops, op);
   MAPCOW_END(mcow, pd);

   _map_dirty(eo_obj, pd, EINA_FALSE);

   return op;
}

EOLIAN static void
_efl_gfx_mapping_mapping_coord_absolute_set(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                                    int idx, double x, double y, double z)
{
   Gfx_Map_Op *op;

   EINA_SAFETY_ON_FALSE_RETURN((idx >= 0) && (idx < pd->cow->count));

   op = _gfx_mapping_op_add(eo_obj, pd, GFX_MAPPING_RAW_COORD, NULL, 0, 0, 0, EINA_FALSE);
   if (!op) return;

   op->raw_coord.idx = idx;
   op->raw_coord.x = x;
   op->raw_coord.y = y;
   op->raw_coord.z = z;
}

EOLIAN static void
_efl_gfx_mapping_mapping_color_set(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                           int idx, int r, int g, int b, int a)
{
   Gfx_Map_Op *op;

   EINA_SAFETY_ON_FALSE_RETURN((idx >= -1) && (idx < pd->cow->count));

   op = _gfx_mapping_op_add(eo_obj, pd, GFX_MAPPING_COLOR, NULL, 0, 0, 0, EINA_FALSE);
   if (!op) return;

   op->color.idx = idx;
   op->color.r = r;
   op->color.g = g;
   op->color.b = b;
   op->color.a = a;
}

EOLIAN static void
_efl_gfx_mapping_translate(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                       double dx, double dy, double dz)
{
   Gfx_Map_Op *op;

   op = _gfx_mapping_op_add(eo_obj, pd, GFX_MAPPING_TRANSLATE, NULL, 0, 0, 0, EINA_FALSE);
   if (!op) return;

   op->translate.dx = dx;
   op->translate.dy = dy;
   op->translate.dz = dz;
}

static inline void
_map_rotate(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
            double degrees, const Efl_Gfx_Entity *pivot, double cx, double cy,
            Eina_Bool absolute)
{
   Gfx_Map_Op *op;

   op = _gfx_mapping_op_add(eo_obj, pd, GFX_MAPPING_ROTATE_2D, pivot, cx, cy, 0, absolute);
   if (!op) return;

   op->rotate_2d.degrees = degrees;
}

EOLIAN static void
_efl_gfx_mapping_rotate(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                    double degrees, const Efl_Gfx_Entity *pivot, double cx, double cy)
{
   _map_rotate(eo_obj, pd, degrees, pivot, cx, cy, EINA_FALSE);
}

EOLIAN static void
_efl_gfx_mapping_rotate_absolute(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd, double degrees, double cx, double cy)
{
   _map_rotate(eo_obj, pd, degrees, NULL, cx, cy, EINA_TRUE);
}

static inline void
_map_rotate_3d(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
               double dx, double dy, double dz,
               const Efl_Gfx_Entity *pivot, double cx, double cy, double cz,
               Eina_Bool absolute)
{
   Gfx_Map_Op *op;

   op = _gfx_mapping_op_add(eo_obj, pd, GFX_MAPPING_ROTATE_3D, pivot, cx, cy, cz, absolute);
   if (!op) return;

   op->rotate_3d.dx = dx;
   op->rotate_3d.dy = dy;
   op->rotate_3d.dz = dz;
}

EOLIAN static void
_efl_gfx_mapping_rotate_3d(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                       double dx, double dy, double dz,
                       const Efl_Gfx_Entity *pivot, double cx, double cy, double cz)
{
   _map_rotate_3d(eo_obj, pd, dx, dy, dz, pivot, cx, cy, cz, EINA_FALSE);
}

EOLIAN static void
_efl_gfx_mapping_rotate_3d_absolute(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                                double dx, double dy, double dz, double cx, double cy, double cz)
{
   _map_rotate_3d(eo_obj, pd, dx, dy, dz, NULL, cx, cy, cz, EINA_TRUE);
}

static inline void
_map_rotate_quat(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                 double qx, double qy, double qz, double qw,
                 const Efl_Gfx_Entity *pivot, double cx, double cy, double cz,
                 Eina_Bool absolute)
{
   Gfx_Map_Op *op;

   op = _gfx_mapping_op_add(eo_obj, pd, GFX_MAPPING_ROTATE_QUAT, pivot, cx, cy, cz, absolute);
   if (!op) return;

   op->rotate_quat.qx = qx;
   op->rotate_quat.qy = qy;
   op->rotate_quat.qz = qz;
   op->rotate_quat.qw = qw;
}

EOLIAN static void
_efl_gfx_mapping_rotate_quat(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                         double qx, double qy, double qz, double qw,
                         const Efl_Gfx_Entity *pivot, double cx, double cy, double cz)
{
   _map_rotate_quat(eo_obj, pd, qx, qy, qz, qw, pivot, cx, cy, cz, EINA_FALSE);
}

EOLIAN static void
_efl_gfx_mapping_rotate_quat_absolute(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                                  double qx, double qy, double qz, double qw,
                                  double cx, double cy, double cz)
{
   _map_rotate_quat(eo_obj, pd, qx, qy, qz, qw, NULL, cx, cy, cz, EINA_TRUE);
}

static inline void
_map_zoom(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
          double zoomx, double zoomy,
          const Efl_Gfx_Entity *pivot, double cx, double cy,
          Eina_Bool absolute)
{
   Gfx_Map_Op *op;

   op = _gfx_mapping_op_add(eo_obj, pd, GFX_MAPPING_ZOOM, pivot, cx, cy, 0, absolute);
   if (!op) return;

   op->zoom.zx = zoomx;
   op->zoom.zy = zoomy;
}

EOLIAN static void
_efl_gfx_mapping_zoom(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                  double zoomx, double zoomy,
                  const Efl_Gfx_Entity *pivot, double cx, double cy)
{
   _map_zoom(eo_obj, pd, zoomx, zoomy, pivot, cx, cy, EINA_FALSE);
}

EOLIAN static void
_efl_gfx_mapping_zoom_absolute(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                           double zoomx, double zoomy, double cx, double cy)
{
   _map_zoom(eo_obj, pd, zoomx, zoomy, NULL, cx, cy, EINA_TRUE);
}

static inline void
_map_lighting_3d(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                  const Efl_Gfx_Entity *pivot, double lx, double ly, double lz,
                  int lr, int lg, int lb, int ar, int ag, int ab,
                  Eina_Bool absolute)
{
   Gfx_Map_Op *op;

   op = _gfx_mapping_op_add(eo_obj, pd, GFX_MAPPING_LIGHTING_3D, pivot, lx, ly, lz, absolute);
   if (!op) return;

   op->lighting_3d.lr = lr;
   op->lighting_3d.lg = lg;
   op->lighting_3d.lb = lb;
   op->lighting_3d.ar = ar;
   op->lighting_3d.ag = ag;
   op->lighting_3d.ab = ab;
}

EOLIAN static void
_efl_gfx_mapping_lighting_3d(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                          const Efl_Gfx_Entity *pivot, double lx, double ly, double lz,
                          int lr, int lg, int lb, int ar, int ag, int ab)
{
   _map_lighting_3d(eo_obj, pd, pivot, lx, ly, lz, lr, lg, lb, ar, ag, ab, EINA_FALSE);
}

EOLIAN static void
_efl_gfx_mapping_lighting_3d_absolute(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                                   double lx, double ly, double lz,
                                   int lr, int lg, int lb, int ar, int ag, int ab)
{
   _map_lighting_3d(eo_obj, pd, NULL, lx, ly, lz, lr, lg, lb, ar, ag, ab, EINA_TRUE);
}

static inline void
_map_perspective_3d(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                    const Efl_Gfx_Entity *pivot, double px, double py,
                    double z0, double foc,
                    Eina_Bool absolute)
{
   Gfx_Map_Op *op;

   if (foc <= 0.0)
     {
        ERR("Focal length must be greater than 0!");
        return;
     }

   op = _gfx_mapping_op_add(eo_obj, pd, GFX_MAPPING_PERSPECTIVE_3D, pivot, px, py, 0, absolute);
   if (!op) return;

   op->perspective_3d.z0 = z0;
   op->perspective_3d.foc = foc;
}

EOLIAN static void
_efl_gfx_mapping_perspective_3d(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                            const Efl_Gfx_Entity *pivot, double px, double py,
                            double z0, double foc)
{
   _map_perspective_3d(eo_obj, pd, pivot, px, py, z0, foc, EINA_FALSE);
}

EOLIAN static void
_efl_gfx_mapping_perspective_3d_absolute(Eo *eo_obj, Efl_Gfx_Mapping_Data *pd,
                                     double px, double py, double z0, double foc)
{
   _map_perspective_3d(eo_obj, pd, NULL, px, py, z0, foc, EINA_TRUE);
}

#include "canvas/efl_gfx_mapping.eo.c"
