/*
    Copyright (C) 2009-2010 Samsung Electronics
    Copyright (C) 2009-2010 ProFUSION embedded systems

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "config.h"
#include "ewk_tiled_backing_store.h"

#define _GNU_SOURCE
#include "ewk_tiled_private.h"
#include <Ecore.h>
#include <Eina.h>
#include <errno.h>
#include <math.h>
#include <stdio.h> // XXX REMOVE ME LATER
#include <stdlib.h>
#include <string.h>

#define IDX(col, row, rowspan) (col + (row * rowspan))

#if !defined(MIN)
# define MIN(a, b) ((a < b) ? a : b)
#endif

#if !defined(MAX)
# define MAX(a, b) ((a > b) ? a : b)
#endif

typedef enum _Ewk_Tiled_Backing_Store_Pre_Render_Priority Ewk_Tiled_Backing_Store_Pre_Render_Priority;
typedef struct _Ewk_Tiled_Backing_Store_Data Ewk_Tiled_Backing_Store_Data;
typedef struct _Ewk_Tiled_Backing_Store_Item Ewk_Tiled_Backing_Store_Item;
typedef struct _Ewk_Tiled_Backing_Store_Pre_Render_Request Ewk_Tiled_Backing_Store_Pre_Render_Request;

enum _Ewk_Tiled_Backing_Store_Pre_Render_Priority {
    PRE_RENDER_PRIORITY_LOW = 0, /**< Append the request to the list */
    PRE_RENDER_PRIORITY_HIGH     /**< Prepend the request to the list */
};

struct _Ewk_Tiled_Backing_Store_Item {
    EINA_INLIST;
    Ewk_Tile *tile;
    struct {
        Evas_Coord x, y, w, h;
    } geometry;
    Eina_Bool smooth_scale;
};

struct _Ewk_Tiled_Backing_Store_Pre_Render_Request {
    EINA_INLIST;
    unsigned long col, row;
    float zoom;
};

struct _Ewk_Tiled_Backing_Store_Data {
    Evas_Object_Smart_Clipped_Data base;
    Evas_Object *self;
    Evas_Object *contents_clipper;
    struct {
        Eina_Inlist **items;
        Evas_Coord x, y, w, h;
        long cols, rows;
        struct {
            Evas_Coord w, h;
            float zoom;
            Eina_Bool zoom_weak_smooth_scale:1;
        } tile;
        struct {
            struct {
                Evas_Coord x, y;
            } cur, old, base, zoom_center;
        } offset;
    } view;
    Evas_Colorspace cspace;
    struct {
        Ewk_Tile_Matrix *matrix;
        struct {
            unsigned long col, row;
        } base;
        struct {
            unsigned long cols, rows;
        } cur, old;
        Evas_Coord width, height;
    } model;
    struct {
        Eina_Bool (*cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *area);
        void *data;
        Eina_Bool process_entire_queue;
        Eina_Inlist *pre_render_requests;
        Ecore_Idler *idler;
        Eina_Bool disabled;
        Eina_Bool suspend:1;
    } render;
    struct {
        void *(*pre_cb)(void *data, Evas_Object *o);
        void *pre_data;
        void *(*post_cb)(void *data, void *pre_data, Evas_Object *o);
        void *post_data;
    } process;
    struct {
        Eina_Bool any:1;
        Eina_Bool pos:1;
        Eina_Bool size:1;
        Eina_Bool model:1;
        Eina_Bool offset:1;
    } changed;
#ifdef DEBUG_MEM_LEAKS
    Ecore_Event_Handler *sig_usr;
#endif
};

static Evas_Smart_Class _parent_sc = EVAS_SMART_CLASS_INIT_NULL;
int _ewk_tiled_log_dom = -1;

#define PRIV_DATA_GET_OR_RETURN(obj, ptr, ...)                       \
    Ewk_Tiled_Backing_Store_Data *ptr = evas_object_smart_data_get(obj); \
    if (!ptr) {                                                      \
        CRITICAL("no private data in obj=%p", obj);                  \
        return __VA_ARGS__;                                          \
    }

static void _ewk_tiled_backing_store_fill_renderers(Ewk_Tiled_Backing_Store_Data *priv);
static inline void _ewk_tiled_backing_store_view_dbg(const Ewk_Tiled_Backing_Store_Data *priv);
static inline void _ewk_tiled_backing_store_changed(Ewk_Tiled_Backing_Store_Data *priv);

static inline void _ewk_tiled_backing_store_updates_process(Ewk_Tiled_Backing_Store_Data *priv)
{
    void *data = NULL;

    /* Do not process updates. Note that we still want to get updates requests
     * in the queue in order to not miss any updates after the render is
     * resumed.
     */
    if (priv->render.suspend || !evas_object_visible_get(priv->self))
        return;

    if (priv->process.pre_cb)
        data = priv->process.pre_cb(priv->process.pre_data, priv->self);

    ewk_tile_matrix_updates_process(priv->model.matrix);

    if (priv->process.post_cb)
        priv->process.post_cb(priv->process.post_data, data, priv->self);
}

static int _ewk_tiled_backing_store_flush(void *data)
{
    Ewk_Tiled_Backing_Store_Data *priv = data;
    Ewk_Tile_Unused_Cache *tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix);

    if (tuc) {
        DBG("flush unused tile cache.");
        ewk_tile_unused_cache_auto_flush(tuc);
    } else
        ERR("no cache?!");

    return 0;
}

static Ewk_Tile *_ewk_tiled_backing_store_tile_new(Ewk_Tiled_Backing_Store_Data *priv, unsigned long col, unsigned long row, float zoom)
{
    Ewk_Tile *t;
    Evas *evas = evas_object_evas_get(priv->self);
    if (!evas) {
        CRITICAL("evas_object_evas_get failed!");
        return NULL;
    }

    t = ewk_tile_matrix_tile_new
        (priv->model.matrix, evas, col, row, zoom);

    if (!t) {
        CRITICAL("ewk_tile_matrix_tile_new failed!");
        return NULL;
    }

    return t;
}

static void _ewk_tiled_backing_store_item_move(Ewk_Tiled_Backing_Store_Item *it, Evas_Coord x, Evas_Coord y)
{
    it->geometry.x = x;
    it->geometry.y = y;

    if (it->tile)
        evas_object_move(it->tile->image, x, y);
}

static void _ewk_tiled_backing_store_item_resize(Ewk_Tiled_Backing_Store_Item *it, Evas_Coord w, Evas_Coord h)
{
    it->geometry.w = w;
    it->geometry.h = h;

    if (it->tile) {
        evas_object_resize(it->tile->image, w, h);
        evas_object_image_fill_set(it->tile->image, 0, 0, w, h);
    }
}

static void _ewk_tiled_backing_store_tile_associate(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tile *t, Ewk_Tiled_Backing_Store_Item *it)
{
    if (it->tile)
        CRITICAL("it->tile=%p, but it should be NULL!", it->tile);
    it->tile = t;
    evas_object_move(it->tile->image, it->geometry.x, it->geometry.y);
    evas_object_resize(it->tile->image, it->geometry.w, it->geometry.h);
    evas_object_image_fill_set
        (it->tile->image, 0, 0, it->geometry.w, it->geometry.h);
    evas_object_image_smooth_scale_set(it->tile->image, it->smooth_scale);

    if (!ewk_tile_visible_get(t))
        evas_object_smart_member_add(t->image, priv->self);

    ewk_tile_show(t);
}

static void _ewk_tiled_backing_store_tile_dissociate(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it, double last_used)
{
    Ewk_Tile_Unused_Cache *tuc;
    ewk_tile_hide(it->tile);
    if (!ewk_tile_visible_get(it->tile))
        evas_object_smart_member_del(it->tile->image);
    ewk_tile_matrix_tile_put(priv->model.matrix, it->tile, last_used);
    tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix);
    ewk_tile_unused_cache_auto_flush(tuc);

    it->tile = NULL;
}

static void _ewk_tiled_backing_store_tile_dissociate_all(Ewk_Tiled_Backing_Store_Data *priv)
{
    Eina_Inlist *it;
    Ewk_Tiled_Backing_Store_Item *item;
    int i;
    double last_used = ecore_loop_time_get();

    for (i = 0; i < priv->view.rows; i++) {
        it = priv->view.items[i];
        EINA_INLIST_FOREACH(it, item)
            if (item->tile)
                _ewk_tiled_backing_store_tile_dissociate(priv, item, last_used);
    }
}

static inline Eina_Bool _ewk_tiled_backing_store_pre_render_request_add(Ewk_Tiled_Backing_Store_Data *priv, unsigned long col, unsigned long row, float zoom, Ewk_Tiled_Backing_Store_Pre_Render_Priority priority)
{
    Ewk_Tiled_Backing_Store_Pre_Render_Request *r;

    MALLOC_OR_OOM_RET(r, sizeof(*r), EINA_FALSE);

    if (priority == PRE_RENDER_PRIORITY_HIGH)
        priv->render.pre_render_requests = eina_inlist_prepend
            (priv->render.pre_render_requests, EINA_INLIST_GET(r));
    else
        priv->render.pre_render_requests = eina_inlist_append
            (priv->render.pre_render_requests, EINA_INLIST_GET(r));

    r->col = col;
    r->row = row;
    r->zoom = zoom;

    return EINA_TRUE;
}

static inline void _ewk_tiled_backing_store_pre_render_request_del(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Pre_Render_Request *r)
{
    priv->render.pre_render_requests = eina_inlist_remove
        (priv->render.pre_render_requests, EINA_INLIST_GET(r));
    free(r);
}

static inline Ewk_Tiled_Backing_Store_Pre_Render_Request *_ewk_tiled_backing_store_pre_render_request_first(const Ewk_Tiled_Backing_Store_Data *priv)
{
    return EINA_INLIST_CONTAINER_GET(
        priv->render.pre_render_requests,
        Ewk_Tiled_Backing_Store_Pre_Render_Request);
}

static void _ewk_tiled_backing_store_pre_render_request_flush(Ewk_Tiled_Backing_Store_Data *priv)
{
    Eina_Inlist **pl = &priv->render.pre_render_requests;
    while (*pl) {
        Ewk_Tiled_Backing_Store_Pre_Render_Request *r;
        r = _ewk_tiled_backing_store_pre_render_request_first(priv);
        *pl = eina_inlist_remove(*pl, *pl);
        free(r);
    }
}

static void _ewk_tiled_backing_store_pre_render_request_clear(Ewk_Tiled_Backing_Store_Data *priv)
{
    Eina_Inlist **pl = &priv->render.pre_render_requests;
    Eina_Inlist *iter = *pl, *tmp;
    while (iter) {
        Ewk_Tiled_Backing_Store_Pre_Render_Request *r =
            EINA_INLIST_CONTAINER_GET(
                iter, Ewk_Tiled_Backing_Store_Pre_Render_Request);
        tmp = iter->next;
        *pl = eina_inlist_remove(*pl, iter);
        iter = tmp;
        free(r);
    }
}

/* assumes priv->process.pre_cb was called if required! */
static void _ewk_tiled_backing_store_pre_render_request_process_single(Ewk_Tiled_Backing_Store_Data *priv)
{
    Ewk_Tiled_Backing_Store_Pre_Render_Request *req;
    Eina_Rectangle area;
    Ewk_Tile_Matrix *tm = priv->model.matrix;
    Ewk_Tile *t;
    Ewk_Tile_Unused_Cache *tuc;
    unsigned long col, row;
    float zoom;
    double last_used = ecore_loop_time_get();

    req = _ewk_tiled_backing_store_pre_render_request_first(priv);
    if (!req)
        return;

    col = req->col;
    row = req->row;
    zoom = req->zoom;

    if (ewk_tile_matrix_tile_exact_exists(tm, col, row, zoom)) {
        DBG("no pre-render required for tile %lu,%lu @ %f.", col, row, zoom);
        goto end;
    }

    t = _ewk_tiled_backing_store_tile_new(priv, col, row, zoom);
    if (!t)
        goto end;

    area.x = 0;
    area.y = 0;
    area.w = priv->view.tile.w;
    area.h = priv->view.tile.h;

    priv->render.cb(priv->render.data, t, &area);
    evas_object_image_data_update_add(
        t->image,
        area.x, area.y, area.w, area.h);
    ewk_tile_matrix_tile_updates_clear(tm, t);

    ewk_tile_matrix_tile_put(tm, t, last_used);

end:
    _ewk_tiled_backing_store_pre_render_request_del(priv, req);
    tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix);
    ewk_tile_unused_cache_auto_flush(tuc);
}

static Eina_Bool _ewk_tiled_backing_store_item_process_idler_cb(void *data)
{
    Ewk_Tiled_Backing_Store_Data *priv = data;

    if (priv->process.pre_cb)
        data = priv->process.pre_cb(priv->process.pre_data, priv->self);

    _ewk_tiled_backing_store_pre_render_request_process_single(priv);

    if (priv->process.post_cb)
        priv->process.post_cb(priv->process.post_data, data, priv->self);

    if (!priv->render.pre_render_requests) {
        priv->render.idler = NULL;
        return EINA_FALSE;
    }

    return EINA_TRUE;
}

static inline void _ewk_tiled_backing_store_item_process_idler_stop(Ewk_Tiled_Backing_Store_Data *priv)
{
    if (!priv->render.idler)
        return;

    ecore_idler_del(priv->render.idler);
    priv->render.idler = NULL;
}

static inline void _ewk_tiled_backing_store_item_process_idler_start(Ewk_Tiled_Backing_Store_Data *priv)
{
    if (priv->render.idler)
        return;
    priv->render.idler = ecore_idler_add(
        _ewk_tiled_backing_store_item_process_idler_cb, priv);
}

static Eina_Bool _ewk_tiled_backing_store_disable_render(Ewk_Tiled_Backing_Store_Data *priv)
{
    if (priv->render.suspend)
        return EINA_TRUE;

    priv->render.suspend = EINA_TRUE;
    _ewk_tiled_backing_store_item_process_idler_stop(priv);
    return EINA_TRUE;
}

static Eina_Bool _ewk_tiled_backing_store_enable_render(Ewk_Tiled_Backing_Store_Data *priv)
{
    if (!priv->render.suspend)
        return EINA_TRUE;

    priv->render.suspend = EINA_FALSE;

    _ewk_tiled_backing_store_fill_renderers(priv);
    _ewk_tiled_backing_store_item_process_idler_start(priv);

    return EINA_TRUE;
}

static inline Eina_Bool _ewk_tiled_backing_store_item_fill(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it, unsigned long col, unsigned long row)
{
    unsigned long m_col = priv->model.base.col + col;
    unsigned long m_row = priv->model.base.row + row;
    double last_used = ecore_loop_time_get();

    if (m_col >= priv->model.cur.cols || m_row >= priv->model.cur.rows) {
        if (it->tile)
            _ewk_tiled_backing_store_tile_dissociate(priv, it, last_used);
    } else {
        Ewk_Tile *t;
        const float zoom = priv->view.tile.zoom;

        if (it->tile) {
            Ewk_Tile *old = it->tile;
            if (old->row != m_row || old->col != m_col || old->zoom != zoom)
                _ewk_tiled_backing_store_tile_dissociate(priv, it, last_used);
            else if (old->row == m_row && old->col == m_col && old->zoom == zoom)
                goto end;
        }

        t = ewk_tile_matrix_tile_exact_get(priv->model.matrix, m_col, m_row, zoom);

        if (!t) {
            /* NOTE: it never returns NULL if it->tile was set! */
            if (it->tile) {
                CRITICAL("it->tile=%p, but it should be NULL!", it->tile);
                _ewk_tiled_backing_store_tile_dissociate(priv, it,
                                                         last_used);
            }

            /* Do not add new requests to the render queue */
            if (!priv->render.suspend) {
                t = _ewk_tiled_backing_store_tile_new(priv, m_col, m_row, zoom);
                if (!t)
                    return EINA_FALSE;
                _ewk_tiled_backing_store_tile_associate(priv, t, it);
            }
        } else if (t != it->tile) {
            if (it->tile)
                _ewk_tiled_backing_store_tile_dissociate(priv,
                                                             it, last_used);
            _ewk_tiled_backing_store_tile_associate(priv, t, it);
        }

      end:

        return EINA_TRUE;
    }

    return EINA_TRUE;
}

static Ewk_Tiled_Backing_Store_Item *_ewk_tiled_backing_store_item_add(Ewk_Tiled_Backing_Store_Data *priv, unsigned long col, unsigned long row)
{
    Ewk_Tiled_Backing_Store_Item *it;
    Evas_Coord x, y, tw, th;

    DBG("o=%p", priv->self);

    MALLOC_OR_OOM_RET(it, sizeof(*it), NULL);

    tw = priv->view.tile.w;
    th = priv->view.tile.h;
    x = priv->view.offset.base.x + priv->view.x + tw  *col;
    y = priv->view.offset.base.y + priv->view.y + th  *row;

    it->tile = NULL;

    it->smooth_scale = priv->view.tile.zoom_weak_smooth_scale;
    _ewk_tiled_backing_store_item_move(it, x, y);
    _ewk_tiled_backing_store_item_resize(it, tw, th);
    if (!_ewk_tiled_backing_store_item_fill(priv, it, col, row)) {
        free(it);
        return NULL;
    }

    return it;
}

static void _ewk_tiled_backing_store_item_del(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tiled_Backing_Store_Item *it)
{
    if (it->tile) {
        double last_used = ecore_loop_time_get();
        _ewk_tiled_backing_store_tile_dissociate(priv, it, last_used);
    }

    free(it);
}

static void _ewk_tiled_backing_store_item_smooth_scale_set(Ewk_Tiled_Backing_Store_Item *it, Eina_Bool smooth_scale)
{
    if (it->smooth_scale == smooth_scale)
        return;

    if (it->tile)
        evas_object_image_smooth_scale_set(it->tile->image, smooth_scale);
}

static inline void _ewk_tiled_backing_store_changed(Ewk_Tiled_Backing_Store_Data *priv)
{
    if (priv->changed.any)
        return;
    evas_object_smart_changed(priv->self);
    priv->changed.any = EINA_TRUE;
}

static void _ewk_tiled_backing_store_view_cols_end_del(Ewk_Tiled_Backing_Store_Data *priv, Eina_Inlist **p_row, unsigned int count)
{
    Eina_Inlist *n;
    unsigned int i;

    if (!count)
        return;

    n = (*p_row)->last;

    for (i = 0; i < count; i++) {
        Ewk_Tiled_Backing_Store_Item *it;
        it = EINA_INLIST_CONTAINER_GET(n, Ewk_Tiled_Backing_Store_Item);
        n = n->prev;
        *p_row = eina_inlist_remove(*p_row, EINA_INLIST_GET(it));
        _ewk_tiled_backing_store_item_del(priv, it);
    }
}

static Eina_Bool _ewk_tiled_backing_store_view_cols_end_add(Ewk_Tiled_Backing_Store_Data *priv, Eina_Inlist **p_row, unsigned int base_col, unsigned int count)
{
    unsigned int i, r = p_row - priv->view.items;

    for (i = 0; i < count; i++, base_col++) {
        Ewk_Tiled_Backing_Store_Item *it;

        it = _ewk_tiled_backing_store_item_add(priv, base_col, r);
        if (!it) {
            CRITICAL("failed to add column %u of %u in row %u.", i, count, r);
            _ewk_tiled_backing_store_view_cols_end_del(priv, p_row, i);
            return EINA_FALSE;
        }

        *p_row = eina_inlist_append(*p_row, EINA_INLIST_GET(it));
    }
    return EINA_TRUE;
}

static void _ewk_tiled_backing_store_view_row_del(Ewk_Tiled_Backing_Store_Data *priv, Eina_Inlist *row)
{
    while (row) {
        Ewk_Tiled_Backing_Store_Item *it;
        it = EINA_INLIST_CONTAINER_GET(row, Ewk_Tiled_Backing_Store_Item);
        row = row->next;
        _ewk_tiled_backing_store_item_del(priv, it);
    }
}

static void _ewk_tiled_backing_store_view_rows_range_del(Ewk_Tiled_Backing_Store_Data *priv, Eina_Inlist **start, Eina_Inlist **end)
{
    for (; start < end; start++) {
        _ewk_tiled_backing_store_view_row_del(priv, *start);
        *start = NULL;
    }
}

static void _ewk_tiled_backing_store_view_rows_all_del(Ewk_Tiled_Backing_Store_Data *priv)
{
    Eina_Inlist **start;
    Eina_Inlist **end;

    start = priv->view.items;
    end = priv->view.items + priv->view.rows;
    _ewk_tiled_backing_store_view_rows_range_del(priv, start, end);

    free(priv->view.items);
    priv->view.items = NULL;
    priv->view.cols = 0;
    priv->view.rows = 0;
}

static void _ewk_tiled_backing_store_render(void *data, Ewk_Tile *t, const Eina_Rectangle *area)
{
    Ewk_Tiled_Backing_Store_Data *priv = data;

    INF("TODO %p (visible? %d) [%lu,%lu] %d,%d + %dx%d",
        t, t->visible, t->col, t->row, area->x, area->y, area->w, area->h);

    if (!t->visible)
        return;

    if (priv->view.tile.w != t->w || priv->view.tile.h != t->h)
        return; // todo: remove me later, don't even flag as dirty!

    EINA_SAFETY_ON_NULL_RETURN(priv->render.cb);
    if (!priv->render.cb(priv->render.data, t, area))
        return;

    evas_object_image_data_update_add(t->image, area->x, area->y, area->w, area->h);
}

static inline void _ewk_tiled_backing_store_model_matrix_create(Ewk_Tiled_Backing_Store_Data *priv, Ewk_Tile_Unused_Cache *tuc)
{
    if (priv->model.matrix) {
        _ewk_tiled_backing_store_view_rows_all_del(priv);

        priv->changed.offset = EINA_FALSE;
        priv->changed.size = EINA_TRUE;

        ewk_tile_matrix_free(priv->model.matrix);
    }

    priv->model.matrix = ewk_tile_matrix_new(tuc, priv->model.cur.cols, priv->model.cur.rows, priv->cspace, _ewk_tiled_backing_store_render, priv);
}

static void _ewk_tiled_backing_store_smart_member_del(Evas_Object *o, Evas_Object *member)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    if (!priv->contents_clipper)
        return;
    evas_object_clip_unset(member);
    if (!evas_object_clipees_get(priv->contents_clipper))
        evas_object_hide(priv->contents_clipper);
}

static void _ewk_tiled_backing_store_smart_member_add(Evas_Object *o, Evas_Object *member)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    if (!priv->contents_clipper)
        return;
    evas_object_clip_set(member, priv->contents_clipper);
    if (evas_object_visible_get(o))
        evas_object_show(priv->contents_clipper);
}

#ifdef DEBUG_MEM_LEAKS
static void _ewk_tiled_backing_store_mem_dbg(Ewk_Tiled_Backing_Store_Data *priv)
{
    static int run = 0;

    run++;

    printf("\n--- BEGIN DEBUG TILED BACKING STORE MEMORY [%d] --\n"
           "t=%0.2f, obj=%p, priv=%p, view.items=%p, matrix=%p\n",
           run, ecore_loop_time_get(),
           priv->self, priv, priv->view.items, priv->model.matrix);

    ewk_tile_matrix_dbg(priv->model.matrix);
    ewk_tile_accounting_dbg();

    printf("--- END DEBUG TILED BACKING STORE MEMORY [%d] --\n\n", run);
}

static Eina_Bool _ewk_tiled_backing_store_sig_usr(void *data, int type, void *event)
{
    Ecore_Event_Signal_User *sig = (Ecore_Event_Signal_User*)event;
    Ewk_Tiled_Backing_Store_Data *priv = (Ewk_Tiled_Backing_Store_Data*)data;

    if (sig->number == 2) {
        Ewk_Tile_Unused_Cache *tuc;
        tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix);
        ewk_tile_unused_cache_auto_flush(tuc);
    }

    _ewk_tiled_backing_store_view_dbg(priv);
    _ewk_tiled_backing_store_mem_dbg(priv);
    return EINA_TRUE;
}
#endif

static void _ewk_tiled_backing_store_smart_add(Evas_Object *o)
{
    Ewk_Tiled_Backing_Store_Data *priv;

    DBG("o=%p", o);

    CALLOC_OR_OOM_RET(priv, sizeof(*priv));

    priv->self = o;
    priv->view.tile.zoom = 1.0;
    priv->view.tile.w = DEFAULT_TILE_W;
    priv->view.tile.h = DEFAULT_TILE_H;
    priv->view.offset.cur.x = 0;
    priv->view.offset.cur.y = 0;
    priv->view.offset.old.x = 0;
    priv->view.offset.old.y = 0;
    priv->view.offset.base.x = 0;
    priv->view.offset.base.y = 0;

    priv->model.base.col = 0;
    priv->model.base.row = 0;
    priv->model.cur.cols = 1;
    priv->model.cur.rows = 1;
    priv->model.old.cols = 0;
    priv->model.old.rows = 0;
    priv->model.width = 0;
    priv->model.height = 0;
    priv->render.process_entire_queue = EINA_TRUE;
    priv->render.suspend = EINA_FALSE;
    priv->cspace = EVAS_COLORSPACE_ARGB8888; // TODO: detect it.

    evas_object_smart_data_set(o, priv);
    _parent_sc.add(o);

    priv->contents_clipper = evas_object_rectangle_add(
        evas_object_evas_get(o));
    evas_object_move(priv->contents_clipper, 0, 0);
    evas_object_resize(priv->contents_clipper,
                       priv->model.width, priv->model.height);
    evas_object_color_set(priv->contents_clipper, 255, 255, 255, 255);
    evas_object_show(priv->contents_clipper);
    evas_object_smart_member_add(priv->contents_clipper, o);

    _ewk_tiled_backing_store_model_matrix_create(priv, NULL);
    evas_object_move(priv->base.clipper, 0, 0);
    evas_object_resize(priv->base.clipper, 0, 0);
    evas_object_clip_set(priv->contents_clipper, priv->base.clipper);

#ifdef DEBUG_MEM_LEAKS
    priv->sig_usr = ecore_event_handler_add
        (ECORE_EVENT_SIGNAL_USER, _ewk_tiled_backing_store_sig_usr, priv);
#endif
}

static void _ewk_tiled_backing_store_smart_del(Evas_Object *o)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    DBG("o=%p", o);
    Ewk_Tile_Unused_Cache *tuc;

    tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix);
    ewk_tile_unused_cache_unlock_area(tuc);

    _ewk_tiled_backing_store_flush(priv);

    _ewk_tiled_backing_store_pre_render_request_flush(priv);
    _ewk_tiled_backing_store_item_process_idler_stop(priv);
    _ewk_tiled_backing_store_view_rows_all_del(priv);

#ifdef DEBUG_MEM_LEAKS
    _ewk_tiled_backing_store_mem_dbg(priv);
    if (priv->sig_usr)
        priv->sig_usr = ecore_event_handler_del(priv->sig_usr);
#endif

    ewk_tile_matrix_free(priv->model.matrix);
    evas_object_smart_member_del(priv->contents_clipper);
    evas_object_del(priv->contents_clipper);

    _parent_sc.del(o);

#ifdef DEBUG_MEM_LEAKS
    printf("\nIMPORTANT: TILED BACKING STORE DELETED (may be real leaks)\n");
    ewk_tile_accounting_dbg();
#endif
}

static void _ewk_tiled_backing_store_smart_move(Evas_Object *o, Evas_Coord x, Evas_Coord y)
{
    DBG("o=%p, new pos: %dx%d", o, x, y);

    PRIV_DATA_GET_OR_RETURN(o, priv);

    if (priv->changed.pos)
        return;

    if (priv->view.x == x && priv->view.y == y)
        return;

    priv->changed.pos = EINA_TRUE;
    _ewk_tiled_backing_store_changed(priv);
}

static void _ewk_tiled_backing_store_smart_resize(Evas_Object *o, Evas_Coord w, Evas_Coord h)
{
    DBG("o=%p, new size: %dx%d", o, w, h);

    PRIV_DATA_GET_OR_RETURN(o, priv);

    if (priv->changed.size)
        return;

    if (priv->view.w == w && priv->view.h == h)
        return;

    priv->changed.size = EINA_TRUE;
    _ewk_tiled_backing_store_changed(priv);
}

static void _ewk_tiled_backing_store_recalc_renderers(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord w, Evas_Coord h, Evas_Coord tw, Evas_Coord th)
{
    long cols, rows, old_rows, old_cols;
    INF("o=%p, new size: %dx%d", priv->self, w, h);

    cols = 1 + (int)ceil((float)w / (float)tw);
    rows = 1 + (int)ceil((float)h / (float)th);

    INF("o=%p new grid size cols: %ld, rows: %ld, was %ld, %ld",
        priv->self, cols, rows, priv->view.cols, priv->view.rows);

    if (priv->view.cols == cols && priv->view.rows == rows)
        return;

    old_cols = priv->view.cols;
    old_rows = priv->view.rows;

    if (rows < old_rows) {
        Eina_Inlist **start, **end;
        start = priv->view.items + rows;
        end = priv->view.items + old_rows;
        _ewk_tiled_backing_store_view_rows_range_del(priv, start, end);
    }
    REALLOC_OR_OOM_RET(priv->view.items, sizeof(Eina_Inlist*) * (int)rows);
    priv->view.rows = rows;
    priv->view.cols = cols;
    if (rows > old_rows) {
        Eina_Inlist **start, **end;
        start = priv->view.items + old_rows;
        end = priv->view.items + rows;
        for (; start < end; start++) {
            Eina_Bool r;
            *start = NULL;
            r = _ewk_tiled_backing_store_view_cols_end_add
                (priv, start, 0, cols);
            if (!r) {
                CRITICAL("failed to allocate %ld columns", cols);
                _ewk_tiled_backing_store_view_rows_range_del
                    (priv, priv->view.items + old_rows, start);
                priv->view.rows = old_rows;
                return;
            }
        }
    }

    if (cols != old_cols) {
        Eina_Inlist **start, **end;
        int todo = cols - old_cols;
        start = priv->view.items;
        end = start + MIN(old_rows, rows);
        if (todo > 0) {
            for (; start < end; start++) {
                Eina_Bool r;
                r = _ewk_tiled_backing_store_view_cols_end_add
                    (priv, start, old_cols, todo);
                if (!r) {
                    CRITICAL("failed to allocate %d columns!", todo);

                    for (start--; start >= priv->view.items; start--)
                        _ewk_tiled_backing_store_view_cols_end_del(priv, start, todo);
                    if (rows > old_rows) {
                        start = priv->view.items + old_rows;
                        end = priv->view.items + rows;
                        for (; start < end; start++)
                            _ewk_tiled_backing_store_view_cols_end_del(priv, start, todo);
                    }
                    return;
                }
            }
        } else if (todo < 0) {
            todo = -todo;
            for (; start < end; start++)
                _ewk_tiled_backing_store_view_cols_end_del(priv, start, todo);
        }
    }

    _ewk_tiled_backing_store_fill_renderers(priv);
}

static void _ewk_tiled_backing_store_smart_calculate_size(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord w, Evas_Coord h)
{
    evas_object_resize(priv->base.clipper, w, h);

    priv->view.w = w;
    priv->view.h = h;

    _ewk_tiled_backing_store_recalc_renderers(
        priv, w, h, priv->view.tile.w, priv->view.tile.h);
}

// TODO: remove me later.
static inline void _ewk_tiled_backing_store_view_dbg(const Ewk_Tiled_Backing_Store_Data *priv)
{
    Eina_Inlist **start, **end;
    printf("tiles=%2ld,%2ld  model=%2ld,%2ld [%dx%d] base=%+3ld,%+4ld offset=%+4d,%+4d old=%+4d,%+4d base=%+3d,%+3d\n",
           priv->view.cols, priv->view.rows,
           priv->model.cur.cols, priv->model.cur.rows,
           priv->model.width, priv->model.height,
           priv->model.base.col, priv->model.base.row,
           priv->view.offset.cur.x, priv->view.offset.cur.y,
           priv->view.offset.old.x, priv->view.offset.old.y,
           priv->view.offset.base.x, priv->view.offset.base.y);

    start = priv->view.items;
    end = priv->view.items + priv->view.rows;
    for (; start < end; start++) {
        const Ewk_Tiled_Backing_Store_Item *it;

        EINA_INLIST_FOREACH(*start, it) {
            printf(" %+4d,%+4d ", it->geometry.x, it->geometry.y);

            if (!it->tile)
                printf("            ;");
            else
                printf("%8p %lu,%lu;", it->tile, it->tile->col, it->tile->row);
        }
        printf("\n");
    }
    printf("---\n");
}

/**
 * @internal
 * Move top row down as last.
 *
 * The final result is visually the same, but logically the top that
 * went out of screen is now at bottom and filled with new model items.
 *
 * This is worth just when @a count is smaller than @c
 * priv->view.rows, after that one is refilling the whole matrix so it
 * is better to trigger full refill.
 *
 * @param count the number of times to repeat the process.
 */
static void _ewk_tiled_backing_store_view_wrap_up(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, unsigned int count)
{
    unsigned int last_row = priv->view.rows - 1;
    Evas_Coord tw = priv->view.tile.w;
    Evas_Coord th = priv->view.tile.h;
    Evas_Coord off_y = priv->view.offset.base.y + count * th;
    Evas_Coord oy = y + (last_row - count + 1) * th + off_y;
    Eina_Inlist **itr_start, **itr_end;

    itr_start = priv->view.items;
    itr_end = itr_start + last_row;

    for (; count > 0; count--) {
        Eina_Inlist **itr;
        Eina_Inlist *tmp = *itr_start;
        Ewk_Tiled_Backing_Store_Item *it;
        Evas_Coord ox = x + priv->view.offset.base.x;
        int c = 0;

        for (itr = itr_start; itr < itr_end; itr++)
            *itr = *(itr + 1);
        *itr = tmp;

        priv->model.base.row++;
        EINA_INLIST_FOREACH(tmp, it) {
            _ewk_tiled_backing_store_item_move(it, ox, oy);
            ox += tw;
            _ewk_tiled_backing_store_item_fill(priv, it, c, last_row);
            c++;
        }
        oy += th;
    }
    priv->view.offset.base.y = off_y;
}

/**
 * @internal
 * Move bottom row up as first.
 *
 * The final result is visually the same, but logically the bottom that
 * went out of screen is now at top and filled with new model items.
 *
 * This is worth just when @a count is smaller than @c
 * priv->view.rows, after that one is refilling the whole matrix so it
 * is better to trigger full refill.
 *
 * @param count the number of times to repeat the process.
 */
static void _ewk_tiled_backing_store_view_wrap_down(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, unsigned int count)
{
    Evas_Coord tw = priv->view.tile.w;
    Evas_Coord th = priv->view.tile.h;
    Evas_Coord off_y = priv->view.offset.base.y - count * th;
    Evas_Coord oy = y + off_y + (count - 1) * th;
    Eina_Inlist **itr_start, **itr_end;

    itr_start = priv->view.items + priv->view.rows - 1;
    itr_end = priv->view.items;

    for (; count > 0; count--) {
        Eina_Inlist **itr;
        Eina_Inlist *tmp = *itr_start;
        Ewk_Tiled_Backing_Store_Item *it;
        Evas_Coord ox = x + priv->view.offset.base.x;
        int c = 0;

        for (itr = itr_start; itr > itr_end; itr--)
            *itr = *(itr - 1);
        *itr = tmp;

        priv->model.base.row--;
        EINA_INLIST_FOREACH(tmp, it) {
            _ewk_tiled_backing_store_item_move(it, ox, oy);
            ox += tw;
            _ewk_tiled_backing_store_item_fill(priv, it, c, 0);
            c++;
        }
        oy -= th;
    }
    priv->view.offset.base.y = off_y;
}

/**
 * @internal
 * Move left-most (first) column right as last (right-most).
 *
 * The final result is visually the same, but logically the first col that
 * went out of screen is now at last and filled with new model items.
 *
 * This is worth just when @a count is smaller than @c
 * priv->view.cols, after that one is refilling the whole matrix so it
 * is better to trigger full refill.
 *
 * @param count the number of times to repeat the process.
 */
static void _ewk_tiled_backing_store_view_wrap_left(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, unsigned int count)
{
    unsigned int r, last_col = priv->view.cols - 1;
    Evas_Coord tw = priv->view.tile.w;
    Evas_Coord th = priv->view.tile.h;
    Evas_Coord off_x = priv->view.offset.base.x + count * tw;
    Evas_Coord oy = y + priv->view.offset.base.y;
    Eina_Inlist **itr;
    Eina_Inlist **itr_end;

    itr = priv->view.items;
    itr_end = itr + priv->view.rows;
    r = 0;

    priv->model.base.col += count;

    for (; itr < itr_end; itr++, r++) {
        Evas_Coord ox = x + (last_col - count + 1) * tw + off_x;
        unsigned int i, c = last_col - count + 1;

        for (i = 0; i < count; i++, c++, ox += tw) {
            Ewk_Tiled_Backing_Store_Item *it;
            it = EINA_INLIST_CONTAINER_GET(*itr, Ewk_Tiled_Backing_Store_Item);
            *itr = eina_inlist_demote(*itr, *itr);

            _ewk_tiled_backing_store_item_move(it, ox, oy);
            _ewk_tiled_backing_store_item_fill(priv, it, c, r);
        }
        oy += th;
    }

    priv->view.offset.base.x = off_x;
}

/**
 * @internal
 * Move right-most (last) column left as first (left-most).
 *
 * The final result is visually the same, but logically the last col that
 * went out of screen is now at first and filled with new model items.
 *
 * This is worth just when @a count is smaller than @c
 * priv->view.cols, after that one is refilling the whole matrix so it
 * is better to trigger full refill.
 *
 * @param count the number of times to repeat the process.
 */
static void _ewk_tiled_backing_store_view_wrap_right(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, unsigned int count)
{
    unsigned int r;
    Evas_Coord tw = priv->view.tile.w;
    Evas_Coord th = priv->view.tile.h;
    Evas_Coord off_x = priv->view.offset.base.x - count * tw;
    Evas_Coord oy = y + priv->view.offset.base.y;
    Eina_Inlist **itr, **itr_end;

    itr = priv->view.items;
    itr_end = itr + priv->view.rows;
    r = 0;

    priv->model.base.col -= count;

    for (; itr < itr_end; itr++, r++) {
        Evas_Coord ox = x + (count - 1) * tw + off_x;
        unsigned int i, c = count - 1;

        for (i = 0; i < count; i++, c--, ox -= tw) {
            Ewk_Tiled_Backing_Store_Item *it;
            it = EINA_INLIST_CONTAINER_GET((*itr)->last, Ewk_Tiled_Backing_Store_Item);
            *itr = eina_inlist_promote(*itr, (*itr)->last);

            _ewk_tiled_backing_store_item_move(it, ox, oy);
            _ewk_tiled_backing_store_item_fill(priv, it, c, r);
        }
        oy += th;
    }

    priv->view.offset.base.x = off_x;
}

static void _ewk_tiled_backing_store_view_refill(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y, int step_x, int step_y)
{
    Eina_Inlist **itr, **itr_end;
    Evas_Coord base_ox, oy, tw, th;
    unsigned int r;

    evas_object_move(priv->base.clipper, x, y);

    tw = priv->view.tile.w;
    th = priv->view.tile.h;

    base_ox = x + priv->view.offset.base.x;
    oy = y + priv->view.offset.base.y;

    itr = priv->view.items;
    itr_end = itr + priv->view.rows;
    r = 0;

    priv->model.base.col -= step_x;
    priv->model.base.row -= step_y;

    for (; itr < itr_end; itr++, r++) {
        Ewk_Tiled_Backing_Store_Item *it;
        Evas_Coord ox = base_ox;
        unsigned int c = 0;
        EINA_INLIST_FOREACH(*itr, it) {
            _ewk_tiled_backing_store_item_fill(priv, it, c, r);
            _ewk_tiled_backing_store_item_move(it, ox, oy);
            c++;
            ox += tw;
        }
        oy += th;
    }
}

static void _ewk_tiled_backing_store_view_pos_apply(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y)
{
    Eina_Inlist **itr, **itr_end;
    Evas_Coord base_ox, oy, tw, th;

    evas_object_move(priv->base.clipper, x, y);

    tw = priv->view.tile.w;
    th = priv->view.tile.h;

    base_ox = x + priv->view.offset.base.x;
    oy = y + priv->view.offset.base.y;

    itr = priv->view.items;
    itr_end = itr + priv->view.rows;
    for (; itr < itr_end; itr++) {
        Ewk_Tiled_Backing_Store_Item *it;
        Evas_Coord ox = base_ox;
        EINA_INLIST_FOREACH(*itr, it) {
            _ewk_tiled_backing_store_item_move(it, ox, oy);
            ox += tw;
        }
        oy += th;
    }
}

static void _ewk_tiled_backing_store_smart_calculate_offset_force(Ewk_Tiled_Backing_Store_Data *priv)
{
    Evas_Coord dx = priv->view.offset.cur.x - priv->view.offset.old.x;
    Evas_Coord dy = priv->view.offset.cur.y - priv->view.offset.old.y;
    Evas_Coord tw, th;
    int step_y, step_x;

    INF("o=%p, offset: %+4d, %+4d (%+4d, %+4d)",
        priv->self, dx, dy, priv->view.offset.cur.x, priv->view.offset.cur.y);

    tw = priv->view.tile.w;
    th = priv->view.tile.h;

    long new_col = -priv->view.offset.cur.x / tw;
    step_x = priv->model.base.col - new_col;
    long new_row = -priv->view.offset.cur.y / th;
    step_y = priv->model.base.row - new_row;

    priv->view.offset.old.x = priv->view.offset.cur.x;
    priv->view.offset.old.y = priv->view.offset.cur.y;
    evas_object_move(
        priv->contents_clipper,
        priv->view.offset.cur.x + priv->view.x,
        priv->view.offset.cur.y + priv->view.y);

    priv->view.offset.base.x += dx - step_x * tw;
    priv->view.offset.base.y += dy - step_y * th;

    _ewk_tiled_backing_store_view_refill
        (priv, priv->view.x, priv->view.y, step_x, step_y);
}

static void _ewk_tiled_backing_store_smart_calculate_offset(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y)
{
    Evas_Coord dx = priv->view.offset.cur.x - priv->view.offset.old.x;
    Evas_Coord dy = priv->view.offset.cur.y - priv->view.offset.old.y;
    Evas_Coord tw, th;
    int step_y, step_x;

    INF("o=%p, offset: %+4d, %+4d (%+4d, %+4d)",
        priv->self, dx, dy, priv->view.offset.cur.x, priv->view.offset.cur.y);

    if (!dx && !dy)
        return;

    tw = priv->view.tile.w;
    th = priv->view.tile.h;

    long new_col = -priv->view.offset.cur.x / tw;
    step_x = priv->model.base.col - new_col;
    long new_row = -priv->view.offset.cur.y / th;
    step_y = priv->model.base.row - new_row;

    priv->view.offset.old.x = priv->view.offset.cur.x;
    priv->view.offset.old.y = priv->view.offset.cur.y;
    evas_object_move(
        priv->contents_clipper,
        priv->view.offset.cur.x + priv->view.x,
        priv->view.offset.cur.y + priv->view.y);

    if ((step_x < 0 && step_x <= -priv->view.cols)
        || (step_x > 0 && step_x >= priv->view.cols)
        || (step_y < 0 && step_y <= -priv->view.rows)
        || (step_y > 0 && step_y >= priv->view.rows)) {

        priv->view.offset.base.x += dx - step_x * tw;
        priv->view.offset.base.y += dy - step_y * th;

        _ewk_tiled_backing_store_view_refill
            (priv, priv->view.x, priv->view.y, step_x, step_y);
        return;
    }

    priv->view.offset.base.x += dx;
    priv->view.offset.base.y += dy;

    if (step_y < 0)
        _ewk_tiled_backing_store_view_wrap_up(priv, x, y, -step_y);
    else if (step_y > 0)
        _ewk_tiled_backing_store_view_wrap_down(priv, x, y, step_y);

    if (step_x < 0)
        _ewk_tiled_backing_store_view_wrap_left(priv, x, y, -step_x);
    else if (step_x > 0)
        _ewk_tiled_backing_store_view_wrap_right(priv, x, y, step_x);

    _ewk_tiled_backing_store_view_pos_apply(priv, x, y);
}

static void _ewk_tiled_backing_store_smart_calculate_pos(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y)
{
    _ewk_tiled_backing_store_view_pos_apply(priv, x, y);
    priv->view.x = x;
    priv->view.y = y;
    evas_object_move(
        priv->contents_clipper,
        priv->view.offset.cur.x + priv->view.x,
        priv->view.offset.cur.y + priv->view.y);
}

static void _ewk_tiled_backing_store_fill_renderers(Ewk_Tiled_Backing_Store_Data *priv)
{
    Eina_Inlist *it;
    Ewk_Tiled_Backing_Store_Item *item;
    int i, j;

    for (i = 0; i < priv->view.rows; i++) {
        it = priv->view.items[i];
        j = 0;
        EINA_INLIST_FOREACH(it, item)
            _ewk_tiled_backing_store_item_fill(priv, item, j++, i);
    }
}

static void _ewk_tiled_backing_store_smart_calculate(Evas_Object *o)
{
    Evas_Coord x, y, w, h;

    evas_object_geometry_get(o, &x, &y, &w, &h);
    DBG("o=%p at %d,%d + %dx%d", o, x, y, w, h);

    PRIV_DATA_GET_OR_RETURN(o, priv);

    priv->changed.any = EINA_FALSE;

    ewk_tile_matrix_freeze(priv->model.matrix);

    if (!priv->render.suspend && priv->changed.model) {
        unsigned long cols, rows;

        cols = priv->model.width / priv->view.tile.w + 1;
        rows = priv->model.height / priv->view.tile.h + 1;

        priv->model.old.cols = priv->model.cur.cols;
        priv->model.old.rows = priv->model.cur.rows;
        priv->model.cur.cols = cols;
        priv->model.cur.rows = rows;
        if (priv->model.old.cols > cols)
            cols = priv->model.old.cols;
        if (priv->model.old.rows > rows)
            rows = priv->model.old.rows;
        ewk_tile_matrix_resize(priv->model.matrix, cols, rows);
    }

    if (priv->changed.pos && (priv->view.x != x || priv->view.y != y)) {
        _ewk_tiled_backing_store_smart_calculate_pos(priv, x, y);
        priv->changed.pos = EINA_FALSE;
    }
    if (priv->changed.offset) {
        _ewk_tiled_backing_store_smart_calculate_offset(priv, x, y);
        priv->changed.offset = EINA_FALSE;
    }

    if (priv->changed.size) {
        _ewk_tiled_backing_store_smart_calculate_size(priv, w, h);
        priv->changed.size = EINA_FALSE;
    }

    if (!priv->render.suspend && priv->changed.model) {
        Eina_Rectangle rect;
        rect.x = 0;
        rect.y = 0;
        rect.w = priv->model.width;
        rect.h = priv->model.height;
        _ewk_tiled_backing_store_fill_renderers(priv);
        ewk_tile_matrix_resize(priv->model.matrix,
                           priv->model.cur.cols,
                           priv->model.cur.rows);
        priv->changed.model = EINA_FALSE;
        evas_object_resize(priv->contents_clipper, priv->model.width, priv->model.height);
        _ewk_tiled_backing_store_smart_calculate_offset_force(priv);

        /* Make sure we do not miss any important repaint by
         * repainting the whole viewport */
        const Eina_Rectangle r =
            { 0, 0, priv->model.width, priv->model.height };
        ewk_tile_matrix_update(priv->model.matrix, &r,
                               priv->view.tile.zoom);
    }

    ewk_tile_matrix_thaw(priv->model.matrix);

    _ewk_tiled_backing_store_updates_process(priv);

    if (priv->view.offset.base.x > 0
        || priv->view.offset.base.x <= - priv->view.tile.w
        || priv->view.offset.base.y > 0
        || priv->view.offset.base.y <= - priv->view.tile.h)
        ERR("incorrect base offset %+4d,%+4d, tile=%dx%d, cur=%+4d,%+4d\n",
            priv->view.offset.base.x, priv->view.offset.base.y,
            priv->view.tile.w, priv->view.tile.h,
            priv->view.offset.cur.x, priv->view.offset.cur.y);

}

Evas_Object *ewk_tiled_backing_store_add(Evas *e)
{
    static Evas_Smart *smart = NULL;

    if (_ewk_tiled_log_dom < 0)
        _ewk_tiled_log_dom = eina_log_domain_register("Ewk_Tiled_Backing_Store", NULL);

    if (!smart) {
        static Evas_Smart_Class sc =
            EVAS_SMART_CLASS_INIT_NAME_VERSION("Ewk_Tiled_Backing_Store");

        evas_object_smart_clipped_smart_set(&sc);
        _parent_sc = sc;

        sc.add = _ewk_tiled_backing_store_smart_add;
        sc.del = _ewk_tiled_backing_store_smart_del;
        sc.resize = _ewk_tiled_backing_store_smart_resize;
        sc.move = _ewk_tiled_backing_store_smart_move;
        sc.calculate = _ewk_tiled_backing_store_smart_calculate;
        sc.member_add = _ewk_tiled_backing_store_smart_member_add;
        sc.member_del = _ewk_tiled_backing_store_smart_member_del;

        smart = evas_smart_class_new(&sc);
    }

    return evas_object_smart_add(e, smart);
}

void ewk_tiled_backing_store_render_cb_set(Evas_Object *o, Eina_Bool (*cb)(void *data, Ewk_Tile *t, const Eina_Rectangle *area), const void *data)
{
    EINA_SAFETY_ON_NULL_RETURN(cb);
    PRIV_DATA_GET_OR_RETURN(o, priv);
    priv->render.cb = cb;
    priv->render.data = (void*)data;
}

Ewk_Tile_Unused_Cache *ewk_tiled_backing_store_tile_unused_cache_get(const Evas_Object *o)
{
    PRIV_DATA_GET_OR_RETURN(o, priv, NULL);
    return ewk_tile_matrix_unused_cache_get(priv->model.matrix);
}

void ewk_tiled_backing_store_tile_unused_cache_set(Evas_Object *o, Ewk_Tile_Unused_Cache *tuc)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);

    if (ewk_tile_matrix_unused_cache_get(priv->model.matrix) == tuc)
        return;

    _ewk_tiled_backing_store_model_matrix_create(priv, tuc);
}

static Eina_Bool _ewk_tiled_backing_store_scroll_full_offset_set_internal(Ewk_Tiled_Backing_Store_Data *priv, Evas_Coord x, Evas_Coord y)
{
    /* TODO: check offset go out of bounds, clamp */
    if (priv->render.disabled)
        return EINA_FALSE;

    priv->view.offset.cur.x = x;
    priv->view.offset.cur.y = y;

    priv->changed.offset = EINA_TRUE;
    _ewk_tiled_backing_store_changed(priv);

    return EINA_TRUE;
}

Eina_Bool ewk_tiled_backing_store_scroll_full_offset_set(Evas_Object *o, Evas_Coord x, Evas_Coord y)
{
    DBG("o=%p, x=%d, y=%d", o, x, y);

    PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE);
    if (x == priv->view.offset.cur.x && y == priv->view.offset.cur.y)
        return EINA_TRUE;

    return _ewk_tiled_backing_store_scroll_full_offset_set_internal(priv, x, y);
}

Eina_Bool ewk_tiled_backing_store_scroll_full_offset_add(Evas_Object *o, Evas_Coord dx, Evas_Coord dy)
{
    DBG("o=%p, dx=%d, dy=%d", o, dx, dy);

    PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE);
    if (!dx && !dy)
        return EINA_TRUE;

    return _ewk_tiled_backing_store_scroll_full_offset_set_internal
        (priv, priv->view.offset.cur.x + dx, priv->view.offset.cur.y + dy);
}

static Eina_Bool _ewk_tiled_backing_store_zoom_set_internal(Ewk_Tiled_Backing_Store_Data *priv, float *zoom, Evas_Coord cx, Evas_Coord cy, Evas_Coord *offx, Evas_Coord *offy)
{
    *offx = priv->view.offset.cur.x;
    *offy = priv->view.offset.cur.y;

    if (fabsf(priv->view.tile.zoom - *zoom) < ZOOM_STEP_MIN) {
        DBG("ignored as zoom difference is < %f: %f",
            (double)ZOOM_STEP_MIN, fabsf(priv->view.tile.zoom - *zoom));
        return EINA_TRUE;
    }

    _ewk_tiled_backing_store_pre_render_request_flush(priv);
    Evas_Coord tw, th;

    *zoom = ROUNDED_ZOOM(priv->view.tile.w, *zoom);

    tw = priv->view.tile.w;
    th = priv->view.tile.h;

    float scale = *zoom / priv->view.tile.zoom;

    priv->view.tile.zoom = *zoom;
    // todo: check cx [0, w]...
    priv->view.offset.zoom_center.x = cx;
    priv->view.offset.zoom_center.y = cy;


    if (!priv->view.w || !priv->view.h) {
        priv->view.offset.base.x = 0;
        priv->view.offset.base.y = 0;
        return EINA_TRUE;
    }
    Eina_Inlist **itr, **itr_end;
    Ewk_Tiled_Backing_Store_Item *it;

    Evas_Coord new_x = cx + (priv->view.offset.cur.x - cx) * scale;
    Evas_Coord new_y = cy + (priv->view.offset.cur.y - cy) * scale;
    Evas_Coord bx = cx + (priv->view.offset.base.x - cx) * scale;
    Evas_Coord by = cy + (priv->view.offset.base.y - cy) * scale;

    Evas_Coord model_width = priv->model.width * scale;
    Evas_Coord model_height = priv->model.height * scale;

    if (model_width < priv->view.w || new_x >= 0)
        new_x = 0;
    else if (-new_x + priv->view.w >= model_width)
        new_x = -model_width + priv->view.w;

    if (model_height < priv->view.h || new_y >= 0)
        new_y = 0;
    else if (-new_y + priv->view.h >= model_height)
        new_y = -model_height + priv->view.h;

    bx = new_x % tw;
    priv->model.base.col = - new_x / tw;
    by = new_y % th;
    priv->model.base.row = - new_y / th;

    priv->changed.size = EINA_TRUE;
    priv->changed.model = EINA_TRUE;
    _ewk_tiled_backing_store_changed(priv);

    priv->view.offset.cur.x = new_x;
    priv->view.offset.cur.y = new_y;
    priv->view.offset.base.x = bx;
    priv->view.offset.base.y = by;

    priv->view.offset.old.x = priv->view.offset.cur.x;
    priv->view.offset.old.y = priv->view.offset.cur.y;
    *offx = priv->view.offset.cur.x;
    *offy = priv->view.offset.cur.y;

    evas_object_move(
        priv->contents_clipper,
        new_x + priv->view.x,
        new_y + priv->view.y);

    _ewk_tiled_backing_store_fill_renderers(priv);

    Evas_Coord oy = priv->view.offset.base.y + priv->view.y;
    Evas_Coord base_ox = priv->view.x + priv->view.offset.base.x;

    itr = priv->view.items;
    itr_end = itr + priv->view.rows;

    for (; itr < itr_end; itr++) {
        Evas_Coord ox = base_ox;
        Eina_Inlist *lst = *itr;

        EINA_INLIST_FOREACH(lst, it) {
            _ewk_tiled_backing_store_item_move(it, ox, oy);
            _ewk_tiled_backing_store_item_resize(it, tw, th);
            ox += tw;
        }
        oy += th;
    }

    return EINA_TRUE;
}

Eina_Bool ewk_tiled_backing_store_zoom_set(Evas_Object *o, float *zoom, Evas_Coord cx, Evas_Coord cy, Evas_Coord *offx, Evas_Coord *offy)
{
    DBG("o=%p, zoom=%f", o, (double)*zoom);

    PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE);

    return _ewk_tiled_backing_store_zoom_set_internal(priv, zoom, cx, cy, offx, offy);
}

Eina_Bool ewk_tiled_backing_store_zoom_weak_set(Evas_Object *o, float zoom, Evas_Coord cx, Evas_Coord cy)
{
    DBG("o=%p, zoom=%f", o, (double)zoom);
    PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE);
    if (!priv->view.w || !priv->view.h)
        return EINA_FALSE;
    Eina_Inlist **itr, **itr_end;
    Ewk_Tiled_Backing_Store_Item *it;
    Evas_Coord tw, th;
    Eina_Bool recalc = EINA_FALSE;

    float scale = zoom / priv->view.tile.zoom;

    tw = TILE_SIZE_AT_ZOOM(priv->view.tile.w, scale);
    scale = TILE_ZOOM_AT_SIZE(tw, priv->view.tile.w);
    th = TILE_SIZE_AT_ZOOM(priv->view.tile.h, scale);
    zoom = scale * priv->view.tile.zoom;

    Evas_Coord model_width = priv->model.width * scale;
    Evas_Coord model_height = priv->model.height * scale;

    evas_object_resize(priv->contents_clipper, model_width, model_height);

    int vrows = ceil((float)priv->view.h / (float)th) + 1;
    int vcols = ceil((float)priv->view.w / (float)tw) + 1;
    Evas_Coord new_x = cx + (priv->view.offset.cur.x - cx) * scale;
    Evas_Coord new_y = cy + (priv->view.offset.cur.y - cy) * scale;
    Evas_Coord bx = new_x % tw;
    Evas_Coord by = new_y % th;
    unsigned long base_row = -new_y / th;
    unsigned long base_col = -new_x / tw;

    if (base_row != priv->model.base.row || base_col != priv->model.base.col) {
        priv->model.base.row = base_row;
        priv->model.base.col = base_col;
        recalc = EINA_TRUE;
    }

    if (vrows > priv->view.rows || vcols > priv->view.cols)
        recalc = EINA_TRUE;

    if (recalc) {
        Evas_Coord w, h;
        evas_object_geometry_get(o, NULL, NULL, &w, &h);
        _ewk_tiled_backing_store_recalc_renderers(priv, w, h, tw, th);
        _ewk_tiled_backing_store_fill_renderers(priv);
        _ewk_tiled_backing_store_updates_process(priv);
    }

    Evas_Coord base_ox = bx + priv->view.x;
    Evas_Coord oy = by + priv->view.y;

    evas_object_move(priv->contents_clipper,
                     new_x + priv->view.x,
                     new_y + priv->view.y);

    itr = priv->view.items;
    itr_end = itr + priv->view.rows;

    for (; itr < itr_end; itr++) {
        Evas_Coord ox = base_ox;
        Eina_Inlist *lst = *itr;

        EINA_INLIST_FOREACH(lst, it) {
            _ewk_tiled_backing_store_item_move(it, ox, oy);
            _ewk_tiled_backing_store_item_resize(it, tw, th);
            ox += tw;
        }
        oy += th;
    }

    return EINA_TRUE;
}

void ewk_tiled_backing_store_fix_offsets(Evas_Object *o, Evas_Coord w, Evas_Coord h)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    Eina_Inlist **itr, **itr_end;
    Ewk_Tiled_Backing_Store_Item *it;
    Evas_Coord new_x = priv->view.offset.cur.x;
    Evas_Coord new_y = priv->view.offset.cur.y;
    Evas_Coord bx = priv->view.offset.base.x;
    Evas_Coord by = priv->view.offset.base.y;
    Evas_Coord tw = priv->view.tile.w;
    Evas_Coord th = priv->view.tile.h;

    if (-new_x > w) {
        new_x = -w;
        bx = new_x % tw;
        priv->model.base.col = -new_x / tw;
    }

    if (-new_y > h) {
        new_y = -h;
        by = new_y % th;
        priv->model.base.row = -new_y / th;
    }

    if (bx >= 0 || bx <= -2 * priv->view.tile.w) {
        bx = new_x % tw;
        priv->model.base.col = -new_x / tw;
    }

    if (by >= 0 || by <= -2 * priv->view.tile.h) {
        by = new_y % th;
        priv->model.base.row = -new_y / th;
    }

    priv->view.offset.cur.x = new_x;
    priv->view.offset.cur.y = new_y;
    priv->view.offset.old.x = new_x;
    priv->view.offset.old.y = new_y;
    priv->view.offset.base.x = bx;
    priv->view.offset.base.y = by;
    evas_object_move(priv->contents_clipper,
                     new_x + priv->view.x,
                     new_y + priv->view.y);

    Evas_Coord oy = priv->view.offset.base.y + priv->view.y;
    Evas_Coord base_ox = priv->view.x + priv->view.offset.base.x;

    itr = priv->view.items;
    itr_end = itr + priv->view.rows;

    for (; itr < itr_end; itr++) {
        Evas_Coord ox = base_ox;
        Eina_Inlist *lst = *itr;

        EINA_INLIST_FOREACH(lst, it) {
            _ewk_tiled_backing_store_item_move(it, ox, oy);
            _ewk_tiled_backing_store_item_resize(it, tw, th);
            ox += tw;
        }
        oy += th;
    }
}

void ewk_tiled_backing_store_zoom_weak_smooth_scale_set(Evas_Object *o, Eina_Bool smooth_scale)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    Eina_Inlist **itr, **itr_end;

    itr = priv->view.items;
    itr_end = itr + priv->view.rows;
    priv->view.tile.zoom_weak_smooth_scale = smooth_scale;

    for (; itr< itr_end; itr++) {
        Ewk_Tiled_Backing_Store_Item *it;
        EINA_INLIST_FOREACH(*itr, it)
            if (it->tile)
                _ewk_tiled_backing_store_item_smooth_scale_set
                    (it, smooth_scale);
    }
}

Eina_Bool ewk_tiled_backing_store_update(Evas_Object *o, const Eina_Rectangle *update)
{
    PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE);

    if (priv->render.disabled)
        return EINA_FALSE;

    return ewk_tile_matrix_update(priv->model.matrix, update,
                                  priv->view.tile.zoom);
}

void ewk_tiled_backing_store_updates_process_pre_set(Evas_Object *o, void *(*cb)(void *data, Evas_Object *o), const void *data)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    priv->process.pre_cb = cb;
    priv->process.pre_data = (void*)data;
}

void ewk_tiled_backing_store_updates_process_post_set(Evas_Object *o, void *(*cb)(void *data, void *pre_data, Evas_Object *o), const void *data)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    priv->process.post_cb = cb;
    priv->process.post_data = (void*)data;
}

void ewk_tiled_backing_store_updates_process(Evas_Object *o)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    _ewk_tiled_backing_store_updates_process(priv);
}

void ewk_tiled_backing_store_updates_clear(Evas_Object *o)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);

    ewk_tile_matrix_updates_clear(priv->model.matrix);
}

void ewk_tiled_backing_store_contents_resize(Evas_Object *o, Evas_Coord width, Evas_Coord height)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);

    if (width == priv->model.width && height == priv->model.height)
        return;

    priv->model.width = width;
    priv->model.height = height;
    priv->changed.model = EINA_TRUE;

    DBG("width,height=%d, %d", width, height);
    _ewk_tiled_backing_store_changed(priv);
}

void ewk_tiled_backing_store_disabled_update_set(Evas_Object *o, Eina_Bool value)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);

    if (value != priv->render.disabled)
        priv->render.disabled = value;
}

void ewk_tiled_backing_store_flush(Evas_Object *o)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    Ewk_Tile_Unused_Cache *tuc = NULL;

    priv->view.offset.cur.x = 0;
    priv->view.offset.cur.y = 0;
    priv->view.offset.old.x = 0;
    priv->view.offset.old.y = 0;
    priv->view.offset.base.x = 0;
    priv->view.offset.base.y = 0;
    priv->model.base.col = 0;
    priv->model.base.row = 0;
    priv->model.cur.cols = 1;
    priv->model.cur.rows = 1;
    priv->model.old.cols = 0;
    priv->model.old.rows = 0;
    priv->changed.size = EINA_TRUE;

#ifdef DEBUG_MEM_LEAKS
    printf("\nFLUSHED BACKING STORE, STATUS BEFORE DELETING TILE MATRIX:\n");
    _ewk_tiled_backing_store_mem_dbg(priv);
#endif

    _ewk_tiled_backing_store_pre_render_request_flush(priv);
    _ewk_tiled_backing_store_tile_dissociate_all(priv);
    tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix);
    ewk_tile_unused_cache_clear(tuc);

#ifdef DEBUG_MEM_LEAKS
    printf("\nFLUSHED BACKING STORE, STATUS AFTER RECREATING TILE MATRIX:\n");
    _ewk_tiled_backing_store_mem_dbg(priv);
#endif
}

Eina_Bool ewk_tiled_backing_store_pre_render_region(Evas_Object *o, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h, float zoom)
{
    PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE);
    Eina_Tile_Grid_Slicer slicer;
    const Eina_Tile_Grid_Info *info;
    Evas_Coord tw, th;
    Ewk_Tile_Unused_Cache *tuc;

    tw = priv->view.tile.w;
    th = priv->view.tile.h;
    zoom = ROUNDED_ZOOM(priv->view.tile.w, zoom);

    if (!eina_tile_grid_slicer_setup(&slicer, x, y, w, h, tw, th)) {
        ERR("could not setup grid slicer for %d,%d+%dx%d tile=%dx%d",
            x, y, w, h, tw, th);
        return EINA_FALSE;
    }

    while (eina_tile_grid_slicer_next(&slicer, &info)) {
        const unsigned long c = info->col;
        const unsigned long r = info->row;
        if (!_ewk_tiled_backing_store_pre_render_request_add(priv, c, r, zoom, PRE_RENDER_PRIORITY_LOW))
            break;
    }

    _ewk_tiled_backing_store_item_process_idler_start(priv);

    tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix);
    ewk_tile_unused_cache_lock_area(tuc, x, y, w, h, zoom);
    return EINA_TRUE;
}

Eina_Bool ewk_tiled_backing_store_pre_render_relative_radius(Evas_Object *o, unsigned int n, float zoom)
{
    PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE);
    unsigned long start_row, end_row, start_col, end_col, i, j, w, h;
    Ewk_Tile_Unused_Cache *tuc;

    INF("priv->model.base.row =%ld, n=%u priv->view.rows=%lu",
            priv->model.base.row, n, priv->view.rows);
    start_row = (long)priv->model.base.row - n;
    start_col = (long)priv->model.base.col - n;
    end_row = MIN(priv->model.cur.rows - 1,
                  priv->model.base.row + priv->view.rows + n - 1);
    end_col = MIN(priv->model.cur.cols - 1,
                  priv->model.base.col + priv->view.cols + n - 1);

    INF("start_row=%lu, end_row=%lu, start_col=%lu, end_col=%lu",
         start_row, end_row, start_col, end_col);

    zoom = ROUNDED_ZOOM(priv->view.tile.w, zoom);

    for (i = start_row; i <= end_row; i++)
        for (j = start_col; j <= end_col; j++)
            if (!_ewk_tiled_backing_store_pre_render_request_add(priv, j, i, zoom, PRE_RENDER_PRIORITY_LOW))
                goto start_processing;

start_processing:
    _ewk_tiled_backing_store_item_process_idler_start(priv);

    tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix);
    h = (end_row - start_row + 1) * TILE_SIZE_AT_ZOOM(priv->view.tile.h, zoom);
    w = (end_col - start_col + 1) * TILE_SIZE_AT_ZOOM(priv->view.tile.w, zoom);
    ewk_tile_unused_cache_lock_area(tuc,
            start_col * TILE_SIZE_AT_ZOOM(priv->view.tile.w, zoom),
            start_row * TILE_SIZE_AT_ZOOM(priv->view.tile.h, zoom), w, h, zoom);

    return EINA_TRUE;
}

void ewk_tiled_backing_store_pre_render_cancel(Evas_Object *o)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    Ewk_Tile_Unused_Cache *tuc;

    _ewk_tiled_backing_store_pre_render_request_clear(priv);

    tuc = ewk_tile_matrix_unused_cache_get(priv->model.matrix);
    ewk_tile_unused_cache_unlock_area(tuc);
}

Eina_Bool ewk_tiled_backing_store_disable_render(Evas_Object *o)
{
    PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE);
    return _ewk_tiled_backing_store_disable_render(priv);
}

Eina_Bool ewk_tiled_backing_store_enable_render(Evas_Object *o)
{
    PRIV_DATA_GET_OR_RETURN(o, priv, EINA_FALSE);
    _ewk_tiled_backing_store_changed(priv);
    return _ewk_tiled_backing_store_enable_render(priv);
}

/**
 * Set the process_entire_queue flag of the renderer idler.
 *
 *
 * @param o the tiled backing store object
 * @param value EINA_TRUE if we want to process all the request of our queue. EINA_FALSE otherwise.
 */
void ewk_tiled_backing_store_process_entire_queue_set(Evas_Object *o, Eina_Bool value)
{
    PRIV_DATA_GET_OR_RETURN(o, priv);
    if (priv->render.process_entire_queue != value)
        priv->render.process_entire_queue = value;
}
