/*
 * Cogl
 *
 * An object oriented GL/GLES Abstraction/Utility Layer
 *
 * Copyright (C) 2009 Intel Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *
 * Authors:
 *  Neil Roberts   <neil@linux.intel.com>
 */

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

#include <glib.h>

#include "cogl-util.h"
#include "cogl-rectangle-map.h"
#include "cogl-debug.h"

/* Implements a data structure which keeps track of unused
   sub-rectangles within a larger rectangle using a binary tree
   structure. The algorithm for this is based on the description here:

   http://www.blackpawn.com/texts/lightmaps/default.html
*/

#if defined (COGL_ENABLE_DEBUG) && defined (HAVE_CAIRO)

/* The cairo header is only used for debugging to generate an image of
   the atlas */
#include <cairo.h>

static void _cogl_rectangle_map_dump_image (CoglRectangleMap *map);

#endif /* COGL_ENABLE_DEBUG && HAVE_CAIRO */

typedef struct _CoglRectangleMapNode       CoglRectangleMapNode;
typedef struct _CoglRectangleMapStackEntry CoglRectangleMapStackEntry;

typedef void (* CoglRectangleMapInternalForeachCb) (CoglRectangleMapNode *node,
                                                    void *data);

typedef enum
{
  COGL_RECTANGLE_MAP_BRANCH,
  COGL_RECTANGLE_MAP_FILLED_LEAF,
  COGL_RECTANGLE_MAP_EMPTY_LEAF
} CoglRectangleMapNodeType;

struct _CoglRectangleMap
{
  CoglRectangleMapNode *root;

  unsigned int n_rectangles;

  unsigned int space_remaining;

  GDestroyNotify value_destroy_func;

  /* Stack used for walking the structure. This is only used during
     the lifetime of a single function call but it is kept here as an
     optimisation to avoid reallocating it every time it is needed */
  GArray *stack;
};

struct _CoglRectangleMapNode
{
  CoglRectangleMapNodeType type;

  CoglRectangleMapEntry rectangle;

  unsigned int largest_gap;

  CoglRectangleMapNode *parent;

  union
  {
    /* Fields used when this is a branch */
    struct
    {
      CoglRectangleMapNode *left;
      CoglRectangleMapNode *right;
    } branch;

    /* Field used when this is a filled leaf */
    void *data;
  } d;
};

struct _CoglRectangleMapStackEntry
{
  /* The node to search */
  CoglRectangleMapNode *node;
  /* Index of next branch of this node to explore. Basically either 0
     to go left or 1 to go right */
  gboolean next_index;
};

static CoglRectangleMapNode *
_cogl_rectangle_map_node_new (void)
{
  return g_slice_new (CoglRectangleMapNode);
}

static void
_cogl_rectangle_map_node_free (CoglRectangleMapNode *node)
{
  g_slice_free (CoglRectangleMapNode, node);
}

CoglRectangleMap *
_cogl_rectangle_map_new (unsigned int width,
                         unsigned int height,
                         GDestroyNotify value_destroy_func)
{
  CoglRectangleMap *map = g_new (CoglRectangleMap, 1);
  CoglRectangleMapNode *root = _cogl_rectangle_map_node_new ();

  root->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
  root->parent = NULL;
  root->rectangle.x = 0;
  root->rectangle.y = 0;
  root->rectangle.width = width;
  root->rectangle.height = height;
  root->largest_gap = width * height;

  map->root = root;
  map->n_rectangles = 0;
  map->value_destroy_func = value_destroy_func;
  map->space_remaining = width * height;

  map->stack = g_array_new (FALSE, FALSE, sizeof (CoglRectangleMapStackEntry));

  return map;
}

static void
_cogl_rectangle_map_stack_push (GArray *stack,
                                CoglRectangleMapNode *node,
                                gboolean next_index)
{
  CoglRectangleMapStackEntry *new_entry;

  g_array_set_size (stack, stack->len + 1);

  new_entry = &g_array_index (stack, CoglRectangleMapStackEntry,
                              stack->len - 1);

  new_entry->node = node;
  new_entry->next_index = next_index;
}

static void
_cogl_rectangle_map_stack_pop (GArray *stack)
{
  g_array_set_size (stack, stack->len - 1);
}

static CoglRectangleMapStackEntry *
_cogl_rectangle_map_stack_get_top (GArray *stack)
{
  return &g_array_index (stack, CoglRectangleMapStackEntry,
                         stack->len - 1);
}

static CoglRectangleMapNode *
_cogl_rectangle_map_node_split_horizontally (CoglRectangleMapNode *node,
                                             unsigned int left_width)
{
  /* Splits the node horizontally (according to emacs' definition, not
     vim) by converting it to a branch and adding two new leaf
     nodes. The leftmost branch will have the width left_width and
     will be returned. If the node is already just the right size it
     won't do anything */

  CoglRectangleMapNode *left_node, *right_node;

  if (node->rectangle.width == left_width)
    return node;

  left_node = _cogl_rectangle_map_node_new ();
  left_node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
  left_node->parent = node;
  left_node->rectangle.x = node->rectangle.x;
  left_node->rectangle.y = node->rectangle.y;
  left_node->rectangle.width = left_width;
  left_node->rectangle.height = node->rectangle.height;
  left_node->largest_gap = (left_node->rectangle.width *
                            left_node->rectangle.height);
  node->d.branch.left = left_node;

  right_node = _cogl_rectangle_map_node_new ();
  right_node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
  right_node->parent = node;
  right_node->rectangle.x = node->rectangle.x + left_width;
  right_node->rectangle.y = node->rectangle.y;
  right_node->rectangle.width = node->rectangle.width - left_width;
  right_node->rectangle.height = node->rectangle.height;
  right_node->largest_gap = (right_node->rectangle.width *
                             right_node->rectangle.height);
  node->d.branch.right = right_node;

  node->type = COGL_RECTANGLE_MAP_BRANCH;

  return left_node;
}

static CoglRectangleMapNode *
_cogl_rectangle_map_node_split_vertically (CoglRectangleMapNode *node,
                                           unsigned int top_height)
{
  /* Splits the node vertically (according to emacs' definition, not
     vim) by converting it to a branch and adding two new leaf
     nodes. The topmost branch will have the height top_height and
     will be returned. If the node is already just the right size it
     won't do anything */

  CoglRectangleMapNode *top_node, *bottom_node;

  if (node->rectangle.height == top_height)
    return node;

  top_node = _cogl_rectangle_map_node_new ();
  top_node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
  top_node->parent = node;
  top_node->rectangle.x = node->rectangle.x;
  top_node->rectangle.y = node->rectangle.y;
  top_node->rectangle.width = node->rectangle.width;
  top_node->rectangle.height = top_height;
  top_node->largest_gap = (top_node->rectangle.width *
                           top_node->rectangle.height);
  node->d.branch.left = top_node;

  bottom_node = _cogl_rectangle_map_node_new ();
  bottom_node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
  bottom_node->parent = node;
  bottom_node->rectangle.x = node->rectangle.x;
  bottom_node->rectangle.y = node->rectangle.y + top_height;
  bottom_node->rectangle.width = node->rectangle.width;
  bottom_node->rectangle.height = node->rectangle.height - top_height;
  bottom_node->largest_gap = (bottom_node->rectangle.width *
                              bottom_node->rectangle.height);
  node->d.branch.right = bottom_node;

  node->type = COGL_RECTANGLE_MAP_BRANCH;

  return top_node;
}

#ifdef COGL_ENABLE_DEBUG

static unsigned int
_cogl_rectangle_map_verify_recursive (CoglRectangleMapNode *node)
{
  /* This is just used for debugging the data structure. It
     recursively walks the tree to verify that the largest gap values
     all add up */

  switch (node->type)
    {
    case COGL_RECTANGLE_MAP_BRANCH:
      {
        int sum =
          _cogl_rectangle_map_verify_recursive (node->d.branch.left) +
          _cogl_rectangle_map_verify_recursive (node->d.branch.right);
        g_assert (node->largest_gap ==
                  MAX (node->d.branch.left->largest_gap,
                       node->d.branch.right->largest_gap));
        return sum;
      }

    case COGL_RECTANGLE_MAP_EMPTY_LEAF:
      g_assert (node->largest_gap ==
                node->rectangle.width * node->rectangle.height);
      return 0;

    case COGL_RECTANGLE_MAP_FILLED_LEAF:
      g_assert (node->largest_gap == 0);
      return 1;
    }

  return 0;
}

static unsigned int
_cogl_rectangle_map_get_space_remaining_recursive (CoglRectangleMapNode *node)
{
  /* This is just used for debugging the data structure. It
     recursively walks the tree to verify that the remaining space
     value adds up */

  switch (node->type)
    {
    case COGL_RECTANGLE_MAP_BRANCH:
      {
        CoglRectangleMapNode *l = node->d.branch.left;
        CoglRectangleMapNode *r = node->d.branch.right;

        return (_cogl_rectangle_map_get_space_remaining_recursive (l) +
                _cogl_rectangle_map_get_space_remaining_recursive (r));
      }

    case COGL_RECTANGLE_MAP_EMPTY_LEAF:
      return node->rectangle.width * node->rectangle.height;

    case COGL_RECTANGLE_MAP_FILLED_LEAF:
      return 0;
    }

  return 0;
}

static void
_cogl_rectangle_map_verify (CoglRectangleMap *map)
{
  unsigned int actual_n_rectangles =
    _cogl_rectangle_map_verify_recursive (map->root);
  unsigned int actual_space_remaining =
    _cogl_rectangle_map_get_space_remaining_recursive (map->root);

  g_assert_cmpuint (actual_n_rectangles, ==, map->n_rectangles);
  g_assert_cmpuint (actual_space_remaining, ==, map->space_remaining);
}

#endif /* COGL_ENABLE_DEBUG */

gboolean
_cogl_rectangle_map_add (CoglRectangleMap *map,
                         unsigned int width,
                         unsigned int height,
                         void *data,
                         CoglRectangleMapEntry *rectangle)
{
  unsigned int rectangle_size = width * height;
  /* Stack of nodes to search in */
  GArray *stack = map->stack;
  CoglRectangleMapNode *found_node = NULL;

  /* Zero-sized rectangles break the algorithm for removing rectangles
     so we'll disallow them */
  _COGL_RETURN_VAL_IF_FAIL (width > 0 && height > 0, FALSE);

  /* Start with the root node */
  g_array_set_size (stack, 0);
  _cogl_rectangle_map_stack_push (stack, map->root, FALSE);

  /* Depth-first search for an empty node that is big enough */
  while (stack->len > 0)
    {
      CoglRectangleMapStackEntry *stack_top;
      CoglRectangleMapNode *node;
      int next_index;

      /* Pop an entry off the stack */
      stack_top = _cogl_rectangle_map_stack_get_top (stack);
      node = stack_top->node;
      next_index = stack_top->next_index;
      _cogl_rectangle_map_stack_pop (stack);

      /* Regardless of the type of the node, there's no point
         descending any further if the new rectangle won't fit within
         it */
      if (node->rectangle.width >= width &&
          node->rectangle.height >= height &&
          node->largest_gap >= rectangle_size)
        {
          if (node->type == COGL_RECTANGLE_MAP_EMPTY_LEAF)
            {
              /* We've found a node we can use */
              found_node = node;
              break;
            }
          else if (node->type == COGL_RECTANGLE_MAP_BRANCH)
            {
              if (next_index)
                /* Try the right branch */
                _cogl_rectangle_map_stack_push (stack,
                                                node->d.branch.right,
                                                0);
              else
                {
                  /* Make sure we remember to try the right branch once
                     we've finished descending the left branch */
                  _cogl_rectangle_map_stack_push (stack,
                                                  node,
                                                  1);
                  /* Try the left branch */
                  _cogl_rectangle_map_stack_push (stack,
                                                  node->d.branch.left,
                                                  0);
                }
            }
        }
    }

  if (found_node)
    {
      CoglRectangleMapNode *node;

      /* Split according to whichever axis will leave us with the
         largest space */
      if (found_node->rectangle.width - width >
          found_node->rectangle.height - height)
        {
          found_node =
            _cogl_rectangle_map_node_split_horizontally (found_node, width);
          found_node =
            _cogl_rectangle_map_node_split_vertically (found_node, height);
        }
      else
        {
          found_node =
            _cogl_rectangle_map_node_split_vertically (found_node, height);
          found_node =
            _cogl_rectangle_map_node_split_horizontally (found_node, width);
        }

      found_node->type = COGL_RECTANGLE_MAP_FILLED_LEAF;
      found_node->d.data = data;
      found_node->largest_gap = 0;
      if (rectangle)
        *rectangle = found_node->rectangle;

      /* Walk back up the tree and update the stored largest gap for
         the node's sub tree */
      for (node = found_node->parent; node; node = node->parent)
        {
          /* This node is a parent so it should always be a branch */
          g_assert (node->type == COGL_RECTANGLE_MAP_BRANCH);

          node->largest_gap = MAX (node->d.branch.left->largest_gap,
                                   node->d.branch.right->largest_gap);
        }

      /* There is now an extra rectangle in the map */
      map->n_rectangles++;
      /* and less space */
      map->space_remaining -= rectangle_size;

#ifdef COGL_ENABLE_DEBUG
      if (G_UNLIKELY (COGL_DEBUG_ENABLED (COGL_DEBUG_DUMP_ATLAS_IMAGE)))
        {
#ifdef HAVE_CAIRO
          _cogl_rectangle_map_dump_image (map);
#endif
          /* Dumping the rectangle map is really slow so we might as well
             verify the space remaining here as it is also quite slow */
          _cogl_rectangle_map_verify (map);
        }
#endif

      return TRUE;
    }
  else
    return FALSE;
}

void
_cogl_rectangle_map_remove (CoglRectangleMap *map,
                            const CoglRectangleMapEntry *rectangle)
{
  CoglRectangleMapNode *node = map->root;
  unsigned int rectangle_size = rectangle->width * rectangle->height;

  /* We can do a binary-chop down the search tree to find the rectangle */
  while (node->type == COGL_RECTANGLE_MAP_BRANCH)
    {
      CoglRectangleMapNode *left_node = node->d.branch.left;

      /* If and only if the rectangle is in the left node then the x,y
         position of the rectangle will be within the node's
         rectangle */
      if (rectangle->x < left_node->rectangle.x + left_node->rectangle.width &&
          rectangle->y < left_node->rectangle.y + left_node->rectangle.height)
        /* Go left */
        node = left_node;
      else
        /* Go right */
        node = node->d.branch.right;
    }

  /* Make sure we found the right node */
  if (node->type != COGL_RECTANGLE_MAP_FILLED_LEAF ||
      node->rectangle.x != rectangle->x ||
      node->rectangle.y != rectangle->y ||
      node->rectangle.width != rectangle->width ||
      node->rectangle.height != rectangle->height)
    /* This should only happen if someone tried to remove a rectangle
       that was not in the map so something has gone wrong */
    g_return_if_reached ();
  else
    {
      /* Convert the node back to an empty node */
      if (map->value_destroy_func)
        map->value_destroy_func (node->d.data);
      node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;
      node->largest_gap = rectangle_size;

      /* Walk back up the tree combining branch nodes that have two
         empty leaves back into a single empty leaf */
      for (node = node->parent; node; node = node->parent)
        {
          /* This node is a parent so it should always be a branch */
          g_assert (node->type == COGL_RECTANGLE_MAP_BRANCH);

          if (node->d.branch.left->type == COGL_RECTANGLE_MAP_EMPTY_LEAF &&
              node->d.branch.right->type == COGL_RECTANGLE_MAP_EMPTY_LEAF)
            {
              _cogl_rectangle_map_node_free (node->d.branch.left);
              _cogl_rectangle_map_node_free (node->d.branch.right);
              node->type = COGL_RECTANGLE_MAP_EMPTY_LEAF;

              node->largest_gap = (node->rectangle.width *
                                   node->rectangle.height);
            }
          else
            break;
        }

      /* Reduce the amount of space remaining in all of the parents
         further up the chain */
      for (; node; node = node->parent)
        node->largest_gap = MAX (node->d.branch.left->largest_gap,
                                 node->d.branch.right->largest_gap);

      /* There is now one less rectangle */
      g_assert (map->n_rectangles > 0);
      map->n_rectangles--;
      /* and more space */
      map->space_remaining += rectangle_size;
    }

#ifdef COGL_ENABLE_DEBUG
  if (G_UNLIKELY (COGL_DEBUG_ENABLED (COGL_DEBUG_DUMP_ATLAS_IMAGE)))
    {
#ifdef HAVE_CAIRO
      _cogl_rectangle_map_dump_image (map);
#endif
      /* Dumping the rectangle map is really slow so we might as well
         verify the space remaining here as it is also quite slow */
      _cogl_rectangle_map_verify (map);
    }
#endif
}

unsigned int
_cogl_rectangle_map_get_width (CoglRectangleMap *map)
{
  return map->root->rectangle.width;
}

unsigned int
_cogl_rectangle_map_get_height (CoglRectangleMap *map)
{
  return map->root->rectangle.height;
}

unsigned int
_cogl_rectangle_map_get_remaining_space (CoglRectangleMap *map)
{
  return map->space_remaining;
}

unsigned int
_cogl_rectangle_map_get_n_rectangles (CoglRectangleMap *map)
{
  return map->n_rectangles;
}

static void
_cogl_rectangle_map_internal_foreach (CoglRectangleMap *map,
                                      CoglRectangleMapInternalForeachCb func,
                                      void *data)
{
  /* Stack of nodes to search in */
  GArray *stack = map->stack;

  /* Start with the root node */
  g_array_set_size (stack, 0);
  _cogl_rectangle_map_stack_push (stack, map->root, 0);

  /* Iterate all nodes depth-first */
  while (stack->len > 0)
    {
      CoglRectangleMapStackEntry *stack_top =
        _cogl_rectangle_map_stack_get_top (stack);
      CoglRectangleMapNode *node = stack_top->node;

      switch (node->type)
        {
        case COGL_RECTANGLE_MAP_BRANCH:
          if (stack_top->next_index == 0)
            {
              /* Next time we come back to this node, go to the right */
              stack_top->next_index = 1;

              /* Explore the left branch next */
              _cogl_rectangle_map_stack_push (stack,
                                              node->d.branch.left,
                                              0);
            }
          else if (stack_top->next_index == 1)
            {
              /* Next time we come back to this node, stop processing it */
              stack_top->next_index = 2;

              /* Explore the right branch next */
              _cogl_rectangle_map_stack_push (stack,
                                              node->d.branch.right,
                                              0);
            }
          else
            {
              /* We're finished with this node so we can call the callback */
              func (node, data);
              _cogl_rectangle_map_stack_pop (stack);
            }
          break;

        default:
          /* Some sort of leaf node, just call the callback */
          func (node, data);
          _cogl_rectangle_map_stack_pop (stack);
          break;
        }
    }

  /* The stack should now be empty */
  g_assert (stack->len == 0);
}

typedef struct _CoglRectangleMapForeachClosure
{
  CoglRectangleMapCallback callback;
  void *data;
} CoglRectangleMapForeachClosure;

static void
_cogl_rectangle_map_foreach_cb (CoglRectangleMapNode *node, void *data)
{
  CoglRectangleMapForeachClosure *closure = data;

  if (node->type == COGL_RECTANGLE_MAP_FILLED_LEAF)
    closure->callback (&node->rectangle, node->d.data, closure->data);
}

void
_cogl_rectangle_map_foreach (CoglRectangleMap *map,
                             CoglRectangleMapCallback callback,
                             void *data)
{
  CoglRectangleMapForeachClosure closure;

  closure.callback = callback;
  closure.data = data;

  _cogl_rectangle_map_internal_foreach (map,
                                        _cogl_rectangle_map_foreach_cb,
                                        &closure);
}

static void
_cogl_rectangle_map_free_cb (CoglRectangleMapNode *node, void *data)
{
  CoglRectangleMap *map = data;

  if (node->type == COGL_RECTANGLE_MAP_FILLED_LEAF && map->value_destroy_func)
    map->value_destroy_func (node->d.data);

  _cogl_rectangle_map_node_free (node);
}

void
_cogl_rectangle_map_free (CoglRectangleMap *map)
{
  _cogl_rectangle_map_internal_foreach (map,
                                        _cogl_rectangle_map_free_cb,
                                        map);

  g_array_free (map->stack, TRUE);

  g_free (map);
}

#if defined (COGL_ENABLE_DEBUG) && defined (HAVE_CAIRO)

static void
_cogl_rectangle_map_dump_image_cb (CoglRectangleMapNode *node, void *data)
{
  cairo_t *cr = data;

  if (node->type == COGL_RECTANGLE_MAP_FILLED_LEAF ||
      node->type == COGL_RECTANGLE_MAP_EMPTY_LEAF)
    {
      /* Fill the rectangle using a different colour depending on
         whether the rectangle is used */
      if (node->type == COGL_RECTANGLE_MAP_FILLED_LEAF)
        cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
      else
        cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);

      cairo_rectangle (cr,
                       node->rectangle.x,
                       node->rectangle.y,
                       node->rectangle.width,
                       node->rectangle.height);

      cairo_fill_preserve (cr);

      /* Draw a white outline around the rectangle */
      cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
      cairo_stroke (cr);
    }
}

static void
_cogl_rectangle_map_dump_image (CoglRectangleMap *map)
{
  /* This dumps a png to help visualize the map. Each leaf rectangle
     is drawn with a white outline. Unused leaves are filled in black
     and used leaves are blue */

  cairo_surface_t *surface =
    cairo_image_surface_create (CAIRO_FORMAT_RGB24,
                                _cogl_rectangle_map_get_width (map),
                                _cogl_rectangle_map_get_height (map));
  cairo_t *cr = cairo_create (surface);

  _cogl_rectangle_map_internal_foreach (map,
                                        _cogl_rectangle_map_dump_image_cb,
                                        cr);

  cairo_destroy (cr);

  cairo_surface_write_to_png (surface, "cogl-rectangle-map-dump.png");

  cairo_surface_destroy (surface);
}

#endif /* COGL_ENABLE_DEBUG && HAVE_CAIRO */
