/*
 * Cogl
 *
 * A Low Level GPU Graphics and Utilities API
 *
 * Copyright (C) 2007,2008,2009,2010,2013 Intel Corporation.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Authors:
 *  Ivan Leben    <ivan@openedhand.com>
 *  Øyvind Kolås  <pippin@linux.intel.com>
 *  Neil Roberts  <neil@linux.intel.com>
 *  Robert Bragg  <robert@linux.intel.com>
 */

#include "cogl-config.h"

#include "cogl-util.h"
#include "cogl-object.h"
#include "cogl-context-private.h"
#include "cogl-journal-private.h"
#include "cogl-pipeline-private.h"
#include "cogl-framebuffer-private.h"
#include "cogl-primitive-private.h"
#include "cogl-texture-private.h"
#include "cogl-primitives-private.h"
#include "cogl-private.h"
#include "cogl-attribute-private.h"
#include "cogl1-context.h"
#include "tesselator/tesselator.h"

#include "cogl-path/cogl-path.h"
#include "cogl-path-private.h"
#include "cogl-gtype-private.h"

#include <string.h>
#include <math.h>

#define _COGL_MAX_BEZ_RECURSE_DEPTH 16

static void _cogl_path_free (CoglPath *path);

static void _cogl_path_build_fill_attribute_buffer (CoglPath *path);
static CoglPrimitive *_cogl_path_get_fill_primitive (CoglPath *path);
static void _cogl_path_build_stroke_attribute_buffer (CoglPath *path);

COGL_OBJECT_DEFINE (Path, path);
COGL_GTYPE_DEFINE_CLASS (Path, path);

static void
_cogl_path_data_clear_vbos (CoglPathData *data)
{
  int i;

  if (data->fill_attribute_buffer)
    {
      cogl_object_unref (data->fill_attribute_buffer);
      cogl_object_unref (data->fill_vbo_indices);

      for (i = 0; i < COGL_PATH_N_ATTRIBUTES; i++)
        cogl_object_unref (data->fill_attributes[i]);

      data->fill_attribute_buffer = NULL;
    }

  if (data->fill_primitive)
    {
      cogl_object_unref (data->fill_primitive);
      data->fill_primitive = NULL;
    }

  if (data->stroke_attribute_buffer)
    {
      cogl_object_unref (data->stroke_attribute_buffer);

      for (i = 0; i < data->stroke_n_attributes; i++)
        cogl_object_unref (data->stroke_attributes[i]);

      g_free (data->stroke_attributes);

      data->stroke_attribute_buffer = NULL;
    }
}

static void
_cogl_path_data_unref (CoglPathData *data)
{
  if (--data->ref_count <= 0)
    {
      _cogl_path_data_clear_vbos (data);

      g_array_free (data->path_nodes, TRUE);

      g_slice_free (CoglPathData, data);
    }
}

static void
_cogl_path_modify (CoglPath *path)
{
  /* This needs to be called whenever the path is about to be modified
     to implement copy-on-write semantics */

  /* If there is more than one path using the data then we need to
     copy the data instead */
  if (path->data->ref_count != 1)
    {
      CoglPathData *old_data = path->data;

      path->data = g_slice_dup (CoglPathData, old_data);
      path->data->path_nodes = g_array_new (FALSE, FALSE,
                                            sizeof (CoglPathNode));
      g_array_append_vals (path->data->path_nodes,
                           old_data->path_nodes->data,
                           old_data->path_nodes->len);

      path->data->fill_attribute_buffer = NULL;
      path->data->fill_primitive = NULL;
      path->data->stroke_attribute_buffer = NULL;
      path->data->ref_count = 1;

      _cogl_path_data_unref (old_data);
    }
  else
    /* The path is altered so the vbos will now be invalid */
    _cogl_path_data_clear_vbos (path->data);
}

void
cogl2_path_set_fill_rule (CoglPath *path,
                          CoglPathFillRule fill_rule)
{
  g_return_if_fail (cogl_is_path (path));

  if (path->data->fill_rule != fill_rule)
    {
      _cogl_path_modify (path);

      path->data->fill_rule = fill_rule;
    }
}

CoglPathFillRule
cogl2_path_get_fill_rule (CoglPath *path)
{
  g_return_val_if_fail (cogl_is_path (path), COGL_PATH_FILL_RULE_NON_ZERO);

  return path->data->fill_rule;
}

static void
_cogl_path_add_node (CoglPath *path,
                     gboolean new_sub_path,
		     float x,
		     float y)
{
  CoglPathNode new_node;
  CoglPathData *data;

  _cogl_path_modify (path);

  data = path->data;

  new_node.x = x;
  new_node.y = y;
  new_node.path_size = 0;

  if (new_sub_path || data->path_nodes->len == 0)
    data->last_path = data->path_nodes->len;

  g_array_append_val (data->path_nodes, new_node);

  g_array_index (data->path_nodes, CoglPathNode, data->last_path).path_size++;

  if (data->path_nodes->len == 1)
    {
      data->path_nodes_min.x = data->path_nodes_max.x = x;
      data->path_nodes_min.y = data->path_nodes_max.y = y;
    }
  else
    {
      if (x < data->path_nodes_min.x)
        data->path_nodes_min.x = x;
      if (x > data->path_nodes_max.x)
        data->path_nodes_max.x = x;
      if (y < data->path_nodes_min.y)
        data->path_nodes_min.y = y;
      if (y > data->path_nodes_max.y)
        data->path_nodes_max.y = y;
    }

  /* Once the path nodes have been modified then we'll assume it's no
     longer a rectangle. cogl2_path_rectangle will set this back to
     TRUE if this has been called from there */
  data->is_rectangle = FALSE;
}

static void
_cogl_path_stroke_nodes (CoglPath *path,
                         CoglFramebuffer *framebuffer,
                         CoglPipeline *pipeline)
{
  CoglPathData *data;
  CoglPipeline *copy = NULL;
  unsigned int path_start;
  int path_num = 0;
  CoglPathNode *node;

  g_return_if_fail (cogl_is_path (path));
  g_return_if_fail (cogl_is_framebuffer (framebuffer));
  g_return_if_fail (cogl_is_pipeline (pipeline));

  data = path->data;

  if (data->path_nodes->len == 0)
    return;

  if (cogl_pipeline_get_n_layers (pipeline) != 0)
    {
      copy = cogl_pipeline_copy (pipeline);
      _cogl_pipeline_prune_to_n_layers (copy, 0);
      pipeline = copy;
    }

  _cogl_path_build_stroke_attribute_buffer (path);

  for (path_start = 0;
       path_start < data->path_nodes->len;
       path_start += node->path_size)
    {
      CoglPrimitive *primitive;

      node = &g_array_index (data->path_nodes, CoglPathNode, path_start);

      primitive =
        cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_LINE_STRIP,
                                            node->path_size,
                                            &data->stroke_attributes[path_num],
                                            1);
      cogl_primitive_draw (primitive, framebuffer, pipeline);
      cogl_object_unref (primitive);

      path_num++;
    }

  if (copy)
    cogl_object_unref (copy);
}

void
_cogl_path_get_bounds (CoglPath *path,
                       float *min_x,
                       float *min_y,
                       float *max_x,
                       float *max_y)
{
  CoglPathData *data = path->data;

  if (data->path_nodes->len == 0)
    {
      *min_x = 0.0f;
      *min_y = 0.0f;
      *max_x = 0.0f;
      *max_y = 0.0f;
    }
  else
    {
      *min_x = data->path_nodes_min.x;
      *min_y = data->path_nodes_min.y;
      *max_x = data->path_nodes_max.x;
      *max_y = data->path_nodes_max.y;
    }
}

static void
_cogl_path_fill_nodes_with_clipped_rectangle (CoglPath *path,
                                              CoglFramebuffer *framebuffer,
                                              CoglPipeline *pipeline)
{
  /* We need at least three stencil bits to combine clips */
  if (_cogl_framebuffer_get_stencil_bits (framebuffer) >= 3)
    {
      static gboolean seen_warning = FALSE;

      if (!seen_warning)
        {
          g_warning ("Paths can not be filled using materials with "
                     "sliced textures unless there is a stencil "
                     "buffer");
          seen_warning = TRUE;
        }
    }

  cogl_framebuffer_push_path_clip (framebuffer, path);
  cogl_framebuffer_draw_rectangle (framebuffer,
                                   pipeline,
                                   path->data->path_nodes_min.x,
                                   path->data->path_nodes_min.y,
                                   path->data->path_nodes_max.x,
                                   path->data->path_nodes_max.y);
  cogl_framebuffer_pop_clip (framebuffer);
}

static gboolean
validate_layer_cb (CoglPipelineLayer *layer, void *user_data)
{
  gboolean *needs_fallback = user_data;
  CoglTexture *texture = _cogl_pipeline_layer_get_texture (layer);

  /* If any of the layers of the current pipeline contain sliced
   * textures or textures with waste then it won't work to draw the
   * path directly. Instead we fallback to pushing the path as a clip
   * on the clip-stack and drawing the path's bounding rectangle
   * instead.
   */

  if (texture != NULL && (cogl_texture_is_sliced (texture) ||
                          !_cogl_texture_can_hardware_repeat (texture)))
    *needs_fallback = TRUE;

  return !*needs_fallback;
}

static void
_cogl_path_fill_nodes (CoglPath *path,
                       CoglFramebuffer *framebuffer,
                       CoglPipeline *pipeline,
                       CoglDrawFlags flags)
{
  if (path->data->path_nodes->len == 0)
    return;

  /* If the path is a simple rectangle then we can divert to using
     cogl_framebuffer_draw_rectangle which should be faster because it
     can go through the journal instead of uploading the geometry just
     for two triangles */
  if (path->data->is_rectangle && flags == 0)
    {
      float x_1, y_1, x_2, y_2;

      _cogl_path_get_bounds (path, &x_1, &y_1, &x_2, &y_2);
      cogl_framebuffer_draw_rectangle (framebuffer,
                                       pipeline,
                                       x_1, y_1,
                                       x_2, y_2);
    }
  else
    {
      gboolean needs_fallback = FALSE;
      CoglPrimitive *primitive;

      _cogl_pipeline_foreach_layer_internal (pipeline,
                                             validate_layer_cb,
                                             &needs_fallback);
      if (needs_fallback)
        {
          _cogl_path_fill_nodes_with_clipped_rectangle (path,
                                                        framebuffer,
                                                        pipeline);
          return;
        }

      primitive = _cogl_path_get_fill_primitive (path);

      _cogl_primitive_draw (primitive, framebuffer, pipeline, flags);
    }
}

void
cogl2_path_move_to (CoglPath *path,
                    float x,
                    float y)
{
  CoglPathData *data;

  g_return_if_fail (cogl_is_path (path));

  _cogl_path_add_node (path, TRUE, x, y);

  data = path->data;

  data->path_start.x = x;
  data->path_start.y = y;

  data->path_pen = data->path_start;
}

void
cogl2_path_rel_move_to (CoglPath *path,
                        float x,
                        float y)
{
  CoglPathData *data;

  g_return_if_fail (cogl_is_path (path));

  data = path->data;

  cogl2_path_move_to (path,
                      data->path_pen.x + x,
                      data->path_pen.y + y);
}

void
cogl2_path_line_to (CoglPath *path,
                    float x,
                    float y)
{
  CoglPathData *data;

  g_return_if_fail (cogl_is_path (path));

  _cogl_path_add_node (path, FALSE, x, y);

  data = path->data;

  data->path_pen.x = x;
  data->path_pen.y = y;
}

void
cogl2_path_rel_line_to (CoglPath *path,
                        float x,
                        float y)
{
  CoglPathData *data;

  g_return_if_fail (cogl_is_path (path));

  data = path->data;

  cogl2_path_line_to (path,
                      data->path_pen.x + x,
                      data->path_pen.y + y);
}

void
cogl2_path_close (CoglPath *path)
{
  g_return_if_fail (cogl_is_path (path));

  _cogl_path_add_node (path, FALSE, path->data->path_start.x,
                       path->data->path_start.y);

  path->data->path_pen = path->data->path_start;
}

void
cogl2_path_line (CoglPath *path,
                 float x_1,
	         float y_1,
	         float x_2,
	         float y_2)
{
  cogl2_path_move_to (path, x_1, y_1);
  cogl2_path_line_to (path, x_2, y_2);
}

void
cogl2_path_polyline (CoglPath *path,
                     const float *coords,
                     int num_points)
{
  int c = 0;

  g_return_if_fail (cogl_is_path (path));

  cogl2_path_move_to (path, coords[0], coords[1]);

  for (c = 1; c < num_points; ++c)
    cogl2_path_line_to (path, coords[2*c], coords[2*c+1]);
}

void
cogl2_path_polygon (CoglPath *path,
                    const float *coords,
                    int num_points)
{
  cogl2_path_polyline (path, coords, num_points);
  cogl2_path_close (path);
}

void
cogl2_path_rectangle (CoglPath *path,
                      float x_1,
                      float y_1,
                      float x_2,
                      float y_2)
{
  gboolean is_rectangle;

  /* If the path was previously empty and the rectangle isn't mirrored
     then we'll record that this is a simple rectangle path so that we
     can optimise it */
  is_rectangle = (path->data->path_nodes->len == 0 &&
                  x_2 >= x_1 &&
                  y_2 >= y_1);

  cogl2_path_move_to (path, x_1, y_1);
  cogl2_path_line_to (path, x_2, y_1);
  cogl2_path_line_to (path, x_2, y_2);
  cogl2_path_line_to (path, x_1, y_2);
  cogl2_path_close (path);

  path->data->is_rectangle = is_rectangle;
}

gboolean
_cogl_path_is_rectangle (CoglPath *path)
{
  return path->data->is_rectangle;
}

static void
_cogl_path_arc (CoglPath *path,
                float center_x,
	        float center_y,
                float radius_x,
                float radius_y,
                float angle_1,
                float angle_2,
                float angle_step,
                unsigned int move_first)
{
  float a = 0x0;
  float cosa = 0x0;
  float sina = 0x0;
  float px = 0x0;
  float py = 0x0;

  /* Fix invalid angles */

  if (angle_1 == angle_2 || angle_step == 0x0)
    return;

  if (angle_step < 0x0)
    angle_step = -angle_step;

  /* Walk the arc by given step */

  a = angle_1;
  while (a != angle_2)
    {
      cosa = cosf (a * (G_PI/180.0));
      sina = sinf (a * (G_PI/180.0));

      px = center_x + (cosa * radius_x);
      py = center_y + (sina * radius_y);

      if (a == angle_1 && move_first)
	cogl2_path_move_to (path, px, py);
      else
	cogl2_path_line_to (path, px, py);

      if (G_LIKELY (angle_2 > angle_1))
        {
          a += angle_step;
          if (a > angle_2)
            a = angle_2;
        }
      else
        {
          a -= angle_step;
          if (a < angle_2)
            a = angle_2;
        }
    }

  /* Make sure the final point is drawn */

  cosa = cosf (angle_2 * (G_PI/180.0));
  sina = sinf (angle_2 * (G_PI/180.0));

  px = center_x + (cosa * radius_x);
  py = center_y + (sina * radius_y);

  cogl2_path_line_to (path, px, py);
}

void
cogl2_path_arc (CoglPath *path,
                float center_x,
                float center_y,
                float radius_x,
                float radius_y,
                float angle_1,
                float angle_2)
{
  float angle_step = 10;

  g_return_if_fail (cogl_is_path (path));

  /* it is documented that a move to is needed to create a freestanding
   * arc
   */
  _cogl_path_arc (path,
                  center_x, center_y,
	          radius_x, radius_y,
	          angle_1, angle_2,
	          angle_step, 0 /* no move */);
}


static void
_cogl_path_rel_arc (CoglPath *path,
                    float center_x,
                    float center_y,
                    float radius_x,
                    float radius_y,
                    float angle_1,
                    float angle_2,
                    float angle_step)
{
  CoglPathData *data;

  data = path->data;

  _cogl_path_arc (path,
                  data->path_pen.x + center_x,
	          data->path_pen.y + center_y,
	          radius_x, radius_y,
	          angle_1, angle_2,
	          angle_step, 0 /* no move */);
}

void
cogl2_path_ellipse (CoglPath *path,
                    float center_x,
                    float center_y,
                    float radius_x,
                    float radius_y)
{
  float angle_step = 10;

  g_return_if_fail (cogl_is_path (path));

  /* FIXME: if shows to be slow might be optimized
   * by mirroring just a quarter of it */

  _cogl_path_arc (path,
                  center_x, center_y,
	          radius_x, radius_y,
	          0, 360,
	          angle_step, 1 /* move first */);

  cogl2_path_close (path);
}

void
cogl2_path_round_rectangle (CoglPath *path,
                            float x_1,
                            float y_1,
                            float x_2,
                            float y_2,
                            float radius,
                            float arc_step)
{
  float inner_width = x_2 - x_1 - radius * 2;
  float inner_height = y_2 - y_1 - radius * 2;

  g_return_if_fail (cogl_is_path (path));

  cogl2_path_move_to (path, x_1, y_1 + radius);
  _cogl_path_rel_arc (path,
                      radius, 0,
                      radius, radius,
                      180,
                      270,
                      arc_step);

  cogl2_path_line_to (path,
                     path->data->path_pen.x + inner_width,
                     path->data->path_pen.y);
  _cogl_path_rel_arc (path,
                      0, radius,
                      radius, radius,
                      -90,
                      0,
                      arc_step);

  cogl2_path_line_to (path,
                     path->data->path_pen.x,
                     path->data->path_pen.y + inner_height);

  _cogl_path_rel_arc (path,
                      -radius, 0,
                      radius, radius,
                      0,
                      90,
                      arc_step);

  cogl2_path_line_to (path,
                     path->data->path_pen.x - inner_width,
                     path->data->path_pen.y);
  _cogl_path_rel_arc (path,
                      0, -radius,
                      radius, radius,
                      90,
                      180,
                      arc_step);

  cogl2_path_close (path);
}

static void
_cogl_path_bezier3_sub (CoglPath *path,
                        CoglBezCubic *cubic)
{
  CoglBezCubic cubics[_COGL_MAX_BEZ_RECURSE_DEPTH];
  CoglBezCubic *cleft;
  CoglBezCubic *cright;
  CoglBezCubic *c;
  floatVec2 dif1;
  floatVec2 dif2;
  floatVec2 mm;
  floatVec2 c1;
  floatVec2 c2;
  floatVec2 c3;
  floatVec2 c4;
  floatVec2 c5;
  int cindex;

  /* Put first curve on stack */
  cubics[0] = *cubic;
  cindex =  0;

  while (cindex >= 0)
    {
      c = &cubics[cindex];


      /* Calculate distance of control points from their
       * counterparts on the line between end points */
      dif1.x = (c->p2.x * 3) - (c->p1.x * 2) - c->p4.x;
      dif1.y = (c->p2.y * 3) - (c->p1.y * 2) - c->p4.y;
      dif2.x = (c->p3.x * 3) - (c->p4.x * 2) - c->p1.x;
      dif2.y = (c->p3.y * 3) - (c->p4.y * 2) - c->p1.y;

      if (dif1.x < 0)
        dif1.x = -dif1.x;
      if (dif1.y < 0)
        dif1.y = -dif1.y;
      if (dif2.x < 0)
        dif2.x = -dif2.x;
      if (dif2.y < 0)
        dif2.y = -dif2.y;


      /* Pick the greatest of two distances */
      if (dif1.x < dif2.x) dif1.x = dif2.x;
      if (dif1.y < dif2.y) dif1.y = dif2.y;

      /* Cancel if the curve is flat enough */
      if (dif1.x + dif1.y <= 1.0 ||
	  cindex == _COGL_MAX_BEZ_RECURSE_DEPTH-1)
	{
	  /* Add subdivision point (skip last) */
	  if (cindex == 0)
            return;

	  _cogl_path_add_node (path, FALSE, c->p4.x, c->p4.y);

	  --cindex;

          continue;
	}

      /* Left recursion goes on top of stack! */
      cright = c; cleft = &cubics[++cindex];

      /* Subdivide into 2 sub-curves */
      c1.x = ((c->p1.x + c->p2.x) / 2);
      c1.y = ((c->p1.y + c->p2.y) / 2);
      mm.x = ((c->p2.x + c->p3.x) / 2);
      mm.y = ((c->p2.y + c->p3.y) / 2);
      c5.x = ((c->p3.x + c->p4.x) / 2);
      c5.y = ((c->p3.y + c->p4.y) / 2);

      c2.x = ((c1.x + mm.x) / 2);
      c2.y = ((c1.y + mm.y) / 2);
      c4.x = ((mm.x + c5.x) / 2);
      c4.y = ((mm.y + c5.y) / 2);

      c3.x = ((c2.x + c4.x) / 2);
      c3.y = ((c2.y + c4.y) / 2);

      /* Add left recursion to stack */
      cleft->p1 = c->p1;
      cleft->p2 = c1;
      cleft->p3 = c2;
      cleft->p4 = c3;

      /* Add right recursion to stack */
      cright->p1 = c3;
      cright->p2 = c4;
      cright->p3 = c5;
      cright->p4 = c->p4;
    }
}

void
cogl2_path_curve_to (CoglPath *path,
                     float x_1,
                     float y_1,
                     float x_2,
                     float y_2,
                     float x_3,
                     float y_3)
{
  CoglBezCubic cubic;

  g_return_if_fail (cogl_is_path (path));

  /* Prepare cubic curve */
  cubic.p1 = path->data->path_pen;
  cubic.p2.x = x_1;
  cubic.p2.y = y_1;
  cubic.p3.x = x_2;
  cubic.p3.y = y_2;
  cubic.p4.x = x_3;
  cubic.p4.y = y_3;

  /* Run subdivision */
  _cogl_path_bezier3_sub (path, &cubic);

  /* Add last point */
  _cogl_path_add_node (path, FALSE, cubic.p4.x, cubic.p4.y);
  path->data->path_pen = cubic.p4;
}

void
cogl2_path_rel_curve_to (CoglPath *path,
                         float x_1,
                         float y_1,
                         float x_2,
                         float y_2,
                         float x_3,
                         float y_3)
{
  CoglPathData *data;

  g_return_if_fail (cogl_is_path (path));

  data = path->data;

  cogl2_path_curve_to (path,
                       data->path_pen.x + x_1,
                       data->path_pen.y + y_1,
                       data->path_pen.x + x_2,
                       data->path_pen.y + y_2,
                       data->path_pen.x + x_3,
                       data->path_pen.y + y_3);
}

CoglPath *
cogl2_path_new (void)
{
  CoglPath *path;
  CoglPathData *data;

  _COGL_GET_CONTEXT (ctx, NULL);

  path = g_slice_new (CoglPath);
  data = path->data = g_slice_new (CoglPathData);

  data->ref_count = 1;
  data->context = ctx;
  data->fill_rule = COGL_PATH_FILL_RULE_EVEN_ODD;
  data->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode));
  data->last_path = 0;
  data->fill_attribute_buffer = NULL;
  data->stroke_attribute_buffer = NULL;
  data->fill_primitive = NULL;
  data->is_rectangle = FALSE;

  return _cogl_path_object_new (path);
}

CoglPath *
cogl_path_copy (CoglPath *old_path)
{
  CoglPath *new_path;

  g_return_val_if_fail (cogl_is_path (old_path), NULL);

  new_path = g_slice_new (CoglPath);
  new_path->data = old_path->data;
  new_path->data->ref_count++;

  return _cogl_path_object_new (new_path);
}

static void
_cogl_path_free (CoglPath *path)
{
  _cogl_path_data_unref (path->data);
  g_slice_free (CoglPath, path);
}

/* If second order beziers were needed the following code could
 * be re-enabled:
 */
#if 0

static void
_cogl_path_bezier2_sub (CoglPath *path,
                        CoglBezQuad *quad)
{
  CoglBezQuad quads[_COGL_MAX_BEZ_RECURSE_DEPTH];
  CoglBezQuad *qleft;
  CoglBezQuad *qright;
  CoglBezQuad *q;
  floatVec2 mid;
  floatVec2 dif;
  floatVec2 c1;
  floatVec2 c2;
  floatVec2 c3;
  int qindex;

  /* Put first curve on stack */
  quads[0] = *quad;
  qindex =  0;

  /* While stack is not empty */
  while (qindex >= 0)
    {

      q = &quads[qindex];

      /* Calculate distance of control point from its
       * counterpart on the line between end points */
      mid.x = ((q->p1.x + q->p3.x) / 2);
      mid.y = ((q->p1.y + q->p3.y) / 2);
      dif.x = (q->p2.x - mid.x);
      dif.y = (q->p2.y - mid.y);
      if (dif.x < 0) dif.x = -dif.x;
      if (dif.y < 0) dif.y = -dif.y;

      /* Cancel if the curve is flat enough */
      if (dif.x + dif.y <= 1.0 ||
          qindex == _COGL_MAX_BEZ_RECURSE_DEPTH - 1)
	{
	  /* Add subdivision point (skip last) */
	  if (qindex == 0) return;
	  _cogl_path_add_node (path, FALSE, q->p3.x, q->p3.y);
	  --qindex; continue;
	}

      /* Left recursion goes on top of stack! */
      qright = q; qleft = &quads[++qindex];

      /* Subdivide into 2 sub-curves */
      c1.x = ((q->p1.x + q->p2.x) / 2);
      c1.y = ((q->p1.y + q->p2.y) / 2);
      c3.x = ((q->p2.x + q->p3.x) / 2);
      c3.y = ((q->p2.y + q->p3.y) / 2);
      c2.x = ((c1.x + c3.x) / 2);
      c2.y = ((c1.y + c3.y) / 2);

      /* Add left recursion onto stack */
      qleft->p1 = q->p1;
      qleft->p2 = c1;
      qleft->p3 = c2;

      /* Add right recursion onto stack */
      qright->p1 = c2;
      qright->p2 = c3;
      qright->p3 = q->p3;
    }
}

void
cogl_path_curve2_to (CoglPath *path,
                     float x_1,
                     float y_1,
                     float x_2,
                     float y_2)
{
  CoglBezQuad quad;

  /* Prepare quadratic curve */
  quad.p1 = path->data->path_pen;
  quad.p2.x = x_1;
  quad.p2.y = y_1;
  quad.p3.x = x_2;
  quad.p3.y = y_2;

  /* Run subdivision */
  _cogl_path_bezier2_sub (&quad);

  /* Add last point */
  _cogl_path_add_node (FALSE, quad.p3.x, quad.p3.y);
  path->data->path_pen = quad.p3;
}

void
cogl_rel_curve2_to (CoglPath *path,
                    float x_1,
                    float y_1,
                    float x_2,
                    float y_2)
{
  CoglPathData *data;

  g_return_if_fail (cogl_is_path (path));

  data = path->data;

  cogl_path_curve2_to (data->path_pen.x + x_1,
                       data->path_pen.y + y_1,
                       data->path_pen.x + x_2,
                       data->path_pen.y + y_2);
}

#endif

typedef struct _CoglPathTesselator CoglPathTesselator;
typedef struct _CoglPathTesselatorVertex CoglPathTesselatorVertex;

struct _CoglPathTesselator
{
  GLUtesselator *glu_tess;
  GLenum primitive_type;
  int vertex_number;
  /* Array of CoglPathTesselatorVertex. This needs to grow when the
     combine callback is called */
  GArray *vertices;
  /* Array of integers for the indices into the vertices array. Each
     element will either be uint8_t, uint16_t or uint32_t depending on
     the number of vertices */
  GArray *indices;
  CoglIndicesType indices_type;
  /* Indices used to split fans and strips */
  int index_a, index_b;
};

struct _CoglPathTesselatorVertex
{
  float x, y, s, t;
};

static void
_cogl_path_tesselator_begin (GLenum type,
                             CoglPathTesselator *tess)
{
  g_assert (type == GL_TRIANGLES ||
            type == GL_TRIANGLE_FAN ||
            type == GL_TRIANGLE_STRIP);

  tess->primitive_type = type;
  tess->vertex_number = 0;
}

static CoglIndicesType
_cogl_path_tesselator_get_indices_type_for_size (int n_vertices)
{
  if (n_vertices <= 256)
    return COGL_INDICES_TYPE_UNSIGNED_BYTE;
  else if (n_vertices <= 65536)
    return COGL_INDICES_TYPE_UNSIGNED_SHORT;
  else
    return COGL_INDICES_TYPE_UNSIGNED_INT;
}

static void
_cogl_path_tesselator_allocate_indices_array (CoglPathTesselator *tess)
{
  switch (tess->indices_type)
    {
    case COGL_INDICES_TYPE_UNSIGNED_BYTE:
      tess->indices = g_array_new (FALSE, FALSE, sizeof (uint8_t));
      break;

    case COGL_INDICES_TYPE_UNSIGNED_SHORT:
      tess->indices = g_array_new (FALSE, FALSE, sizeof (uint16_t));
      break;

    case COGL_INDICES_TYPE_UNSIGNED_INT:
      tess->indices = g_array_new (FALSE, FALSE, sizeof (uint32_t));
      break;
    }
}

static void
_cogl_path_tesselator_add_index (CoglPathTesselator *tess, int vertex_index)
{
  switch (tess->indices_type)
    {
    case COGL_INDICES_TYPE_UNSIGNED_BYTE:
      {
        uint8_t val = vertex_index;
        g_array_append_val (tess->indices, val);
      }
      break;

    case COGL_INDICES_TYPE_UNSIGNED_SHORT:
      {
        uint16_t val = vertex_index;
        g_array_append_val (tess->indices, val);
      }
      break;

    case COGL_INDICES_TYPE_UNSIGNED_INT:
      {
        uint32_t val = vertex_index;
        g_array_append_val (tess->indices, val);
      }
      break;
    }
}

static void
_cogl_path_tesselator_vertex (void *vertex_data,
                              CoglPathTesselator *tess)
{
  int vertex_index;

  vertex_index = GPOINTER_TO_INT (vertex_data);

  /* This tries to convert all of the primitives into GL_TRIANGLES
     with indices to share vertices */
  switch (tess->primitive_type)
    {
    case GL_TRIANGLES:
      /* Directly use the vertex */
      _cogl_path_tesselator_add_index (tess, vertex_index);
      break;

    case GL_TRIANGLE_FAN:
      if (tess->vertex_number == 0)
        tess->index_a = vertex_index;
      else if (tess->vertex_number == 1)
        tess->index_b = vertex_index;
      else
        {
          /* Create a triangle with the first vertex, the previous
             vertex and this vertex */
          _cogl_path_tesselator_add_index (tess, tess->index_a);
          _cogl_path_tesselator_add_index (tess, tess->index_b);
          _cogl_path_tesselator_add_index (tess, vertex_index);
          /* Next time we will use this vertex as the previous
             vertex */
          tess->index_b = vertex_index;
        }
      break;

    case GL_TRIANGLE_STRIP:
      if (tess->vertex_number == 0)
        tess->index_a = vertex_index;
      else if (tess->vertex_number == 1)
        tess->index_b = vertex_index;
      else
        {
          _cogl_path_tesselator_add_index (tess, tess->index_a);
          _cogl_path_tesselator_add_index (tess, tess->index_b);
          _cogl_path_tesselator_add_index (tess, vertex_index);
          if (tess->vertex_number & 1)
            tess->index_b = vertex_index;
          else
            tess->index_a = vertex_index;
        }
      break;

    default:
      g_assert_not_reached ();
    }

  tess->vertex_number++;
}

static void
_cogl_path_tesselator_end (CoglPathTesselator *tess)
{
  tess->primitive_type = GL_FALSE;
}

static void
_cogl_path_tesselator_combine (double coords[3],
                               void *vertex_data[4],
                               float weight[4],
                               void **out_data,
                               CoglPathTesselator *tess)
{
  CoglPathTesselatorVertex *vertex;
  CoglIndicesType new_indices_type;
  int i;

  /* Add a new vertex to the array */
  g_array_set_size (tess->vertices, tess->vertices->len + 1);
  vertex = &g_array_index (tess->vertices,
                           CoglPathTesselatorVertex,
                           tess->vertices->len - 1);
  /* The data is just the index to the vertex */
  *out_data = GINT_TO_POINTER (tess->vertices->len - 1);
  /* Set the coordinates of the new vertex */
  vertex->x = coords[0];
  vertex->y = coords[1];
  /* Generate the texture coordinates as the weighted average of the
     four incoming coordinates */
  vertex->s = 0.0f;
  vertex->t = 0.0f;
  for (i = 0; i < 4; i++)
    {
      CoglPathTesselatorVertex *old_vertex =
        &g_array_index (tess->vertices, CoglPathTesselatorVertex,
                        GPOINTER_TO_INT (vertex_data[i]));
      vertex->s += old_vertex->s * weight[i];
      vertex->t += old_vertex->t * weight[i];
    }

  /* Check if we've reached the limit for the data type of our indices */
  new_indices_type =
    _cogl_path_tesselator_get_indices_type_for_size (tess->vertices->len);
  if (new_indices_type != tess->indices_type)
    {
      CoglIndicesType old_indices_type = new_indices_type;
      GArray *old_vertices = tess->indices;

      /* Copy the indices to an array of the new type */
      tess->indices_type = new_indices_type;
      _cogl_path_tesselator_allocate_indices_array (tess);

      switch (old_indices_type)
        {
        case COGL_INDICES_TYPE_UNSIGNED_BYTE:
          for (i = 0; i < old_vertices->len; i++)
            _cogl_path_tesselator_add_index (tess,
                                             g_array_index (old_vertices,
                                                            uint8_t, i));
          break;

        case COGL_INDICES_TYPE_UNSIGNED_SHORT:
          for (i = 0; i < old_vertices->len; i++)
            _cogl_path_tesselator_add_index (tess,
                                             g_array_index (old_vertices,
                                                            uint16_t, i));
          break;

        case COGL_INDICES_TYPE_UNSIGNED_INT:
          for (i = 0; i < old_vertices->len; i++)
            _cogl_path_tesselator_add_index (tess,
                                             g_array_index (old_vertices,
                                                            uint32_t, i));
          break;
        }

      g_array_free (old_vertices, TRUE);
    }
}

static void
_cogl_path_build_fill_attribute_buffer (CoglPath *path)
{
  CoglPathTesselator tess;
  unsigned int path_start = 0;
  CoglPathData *data = path->data;
  int i;

  /* If we've already got a vbo then we don't need to do anything */
  if (data->fill_attribute_buffer)
    return;

  tess.primitive_type = FALSE;

  /* Generate a vertex for each point on the path */
  tess.vertices = g_array_new (FALSE, FALSE, sizeof (CoglPathTesselatorVertex));
  g_array_set_size (tess.vertices, data->path_nodes->len);
  for (i = 0; i < data->path_nodes->len; i++)
    {
      CoglPathNode *node =
        &g_array_index (data->path_nodes, CoglPathNode, i);
      CoglPathTesselatorVertex *vertex =
        &g_array_index (tess.vertices, CoglPathTesselatorVertex, i);

      vertex->x = node->x;
      vertex->y = node->y;

      /* Add texture coordinates so that a texture would be drawn to
         fit the bounding box of the path and then cropped by the
         path */
      if (data->path_nodes_min.x == data->path_nodes_max.x)
        vertex->s = 0.0f;
      else
        vertex->s = ((node->x - data->path_nodes_min.x)
                     / (data->path_nodes_max.x - data->path_nodes_min.x));
      if (data->path_nodes_min.y == data->path_nodes_max.y)
        vertex->t = 0.0f;
      else
        vertex->t = ((node->y - data->path_nodes_min.y)
                     / (data->path_nodes_max.y - data->path_nodes_min.y));
    }

  tess.indices_type =
    _cogl_path_tesselator_get_indices_type_for_size (data->path_nodes->len);
  _cogl_path_tesselator_allocate_indices_array (&tess);

  tess.glu_tess = gluNewTess ();

  if (data->fill_rule == COGL_PATH_FILL_RULE_EVEN_ODD)
    gluTessProperty (tess.glu_tess, GLU_TESS_WINDING_RULE,
                     GLU_TESS_WINDING_ODD);
  else
    gluTessProperty (tess.glu_tess, GLU_TESS_WINDING_RULE,
                     GLU_TESS_WINDING_NONZERO);

  /* All vertices are on the xy-plane */
  gluTessNormal (tess.glu_tess, 0.0, 0.0, 1.0);

  gluTessCallback (tess.glu_tess, GLU_TESS_BEGIN_DATA,
                   (GCallback) _cogl_path_tesselator_begin);
  gluTessCallback (tess.glu_tess, GLU_TESS_VERTEX_DATA,
                   (GCallback) _cogl_path_tesselator_vertex);
  gluTessCallback (tess.glu_tess, GLU_TESS_END_DATA,
                   (GCallback) _cogl_path_tesselator_end);
  gluTessCallback (tess.glu_tess, GLU_TESS_COMBINE_DATA,
                   (GCallback) _cogl_path_tesselator_combine);

  gluTessBeginPolygon (tess.glu_tess, &tess);

  while (path_start < data->path_nodes->len)
    {
      CoglPathNode *node =
        &g_array_index (data->path_nodes, CoglPathNode, path_start);

      gluTessBeginContour (tess.glu_tess);

      for (i = 0; i < node->path_size; i++)
        {
          double vertex[3] = { node[i].x, node[i].y, 0.0 };
          gluTessVertex (tess.glu_tess, vertex,
                         GINT_TO_POINTER (i + path_start));
        }

      gluTessEndContour (tess.glu_tess);

      path_start += node->path_size;
    }

  gluTessEndPolygon (tess.glu_tess);

  gluDeleteTess (tess.glu_tess);

  data->fill_attribute_buffer =
    cogl_attribute_buffer_new (data->context,
                               sizeof (CoglPathTesselatorVertex) *
                               tess.vertices->len,
                               tess.vertices->data);
  g_array_free (tess.vertices, TRUE);

  data->fill_attributes[0] =
    cogl_attribute_new (data->fill_attribute_buffer,
                        "cogl_position_in",
                        sizeof (CoglPathTesselatorVertex),
                        G_STRUCT_OFFSET (CoglPathTesselatorVertex, x),
                        2, /* n_components */
                        COGL_ATTRIBUTE_TYPE_FLOAT);
  data->fill_attributes[1] =
    cogl_attribute_new (data->fill_attribute_buffer,
                        "cogl_tex_coord0_in",
                        sizeof (CoglPathTesselatorVertex),
                        G_STRUCT_OFFSET (CoglPathTesselatorVertex, s),
                        2, /* n_components */
                        COGL_ATTRIBUTE_TYPE_FLOAT);

  data->fill_vbo_indices = cogl_indices_new (data->context,
                                             tess.indices_type,
                                             tess.indices->data,
                                             tess.indices->len);
  data->fill_vbo_n_indices = tess.indices->len;
  g_array_free (tess.indices, TRUE);
}

static CoglPrimitive *
_cogl_path_get_fill_primitive (CoglPath *path)
{
  if (path->data->fill_primitive)
    return path->data->fill_primitive;

  _cogl_path_build_fill_attribute_buffer (path);

  path->data->fill_primitive =
    cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_TRIANGLES,
                                        path->data->fill_vbo_n_indices,
                                        path->data->fill_attributes,
                                        COGL_PATH_N_ATTRIBUTES);
  cogl_primitive_set_indices (path->data->fill_primitive,
                              path->data->fill_vbo_indices,
                              path->data->fill_vbo_n_indices);

  return path->data->fill_primitive;
}

static CoglClipStack *
_cogl_clip_stack_push_from_path (CoglClipStack *stack,
                                 CoglPath *path,
                                 CoglMatrixEntry *modelview_entry,
                                 CoglMatrixEntry *projection_entry,
                                 const float *viewport)
{
  float x_1, y_1, x_2, y_2;

  _cogl_path_get_bounds (path, &x_1, &y_1, &x_2, &y_2);

  /* If the path is a simple rectangle then we can divert to pushing a
     rectangle clip instead which usually won't involve the stencil
     buffer */
  if (_cogl_path_is_rectangle (path))
    return _cogl_clip_stack_push_rectangle (stack,
                                            x_1, y_1,
                                            x_2, y_2,
                                            modelview_entry,
                                            projection_entry,
                                            viewport);
  else
    {
      CoglPrimitive *primitive = _cogl_path_get_fill_primitive (path);

      return _cogl_clip_stack_push_primitive (stack,
                                              primitive,
                                              x_1, y_1, x_2, y_2,
                                              modelview_entry,
                                              projection_entry,
                                              viewport);
    }
}

void
cogl_framebuffer_push_path_clip (CoglFramebuffer *framebuffer,
                                 CoglPath *path)
{
  CoglMatrixEntry *modelview_entry =
    _cogl_framebuffer_get_modelview_entry (framebuffer);
  CoglMatrixEntry *projection_entry =
    _cogl_framebuffer_get_projection_entry (framebuffer);
  /* XXX: It would be nicer if we stored the private viewport as a
   * vec4 so we could avoid this redundant copy. */
  float viewport[] = {
      framebuffer->viewport_x,
      framebuffer->viewport_y,
      framebuffer->viewport_width,
      framebuffer->viewport_height
  };

  framebuffer->clip_stack =
    _cogl_clip_stack_push_from_path (framebuffer->clip_stack,
                                     path,
                                     modelview_entry,
                                     projection_entry,
                                     viewport);

  if (framebuffer->context->current_draw_buffer == framebuffer)
    framebuffer->context->current_draw_buffer_changes |=
      COGL_FRAMEBUFFER_STATE_CLIP;
}

static void
_cogl_path_build_stroke_attribute_buffer (CoglPath *path)
{
  CoglPathData *data = path->data;
  CoglBuffer *buffer;
  unsigned int n_attributes = 0;
  unsigned int path_start;
  CoglPathNode *node;
  floatVec2 *buffer_p;
  unsigned int i;

  /* If we've already got a cached vbo then we don't need to do anything */
  if (data->stroke_attribute_buffer)
    return;

  data->stroke_attribute_buffer =
    cogl_attribute_buffer_new_with_size (data->context,
                                         data->path_nodes->len *
                                         sizeof (floatVec2));

  buffer = COGL_BUFFER (data->stroke_attribute_buffer);
  buffer_p = _cogl_buffer_map_for_fill_or_fallback (buffer);

  /* Copy the vertices in and count the number of sub paths. Each sub
     path will form a separate attribute so we can paint the disjoint
     line strips */
  for (path_start = 0;
       path_start < data->path_nodes->len;
       path_start += node->path_size)
    {
      node = &g_array_index (data->path_nodes, CoglPathNode, path_start);

      for (i = 0; i < node->path_size; i++)
        {
          buffer_p[path_start + i].x = node[i].x;
          buffer_p[path_start + i].y = node[i].y;
        }

      n_attributes++;
    }

  _cogl_buffer_unmap_for_fill_or_fallback (buffer);

  data->stroke_attributes = g_new (CoglAttribute *, n_attributes);

  /* Now we can loop the sub paths again to create the attributes */
  for (i = 0, path_start = 0;
       path_start < data->path_nodes->len;
       i++, path_start += node->path_size)
    {
      node = &g_array_index (data->path_nodes, CoglPathNode, path_start);

      data->stroke_attributes[i] =
        cogl_attribute_new (data->stroke_attribute_buffer,
                            "cogl_position_in",
                            sizeof (floatVec2),
                            path_start * sizeof (floatVec2),
                            2, /* n_components */
                            COGL_ATTRIBUTE_TYPE_FLOAT);
    }

  data->stroke_n_attributes = n_attributes;
}

void
cogl_framebuffer_fill_path (CoglFramebuffer *framebuffer,
                            CoglPipeline *pipeline,
                            CoglPath *path)
{
  g_return_if_fail (cogl_is_framebuffer (framebuffer));
  g_return_if_fail (cogl_is_pipeline (pipeline));
  g_return_if_fail (cogl_is_path (path));

  _cogl_path_fill_nodes (path, framebuffer, pipeline, 0 /* flags */);
}

void
cogl_framebuffer_stroke_path (CoglFramebuffer *framebuffer,
                              CoglPipeline *pipeline,
                              CoglPath *path)
{
  g_return_if_fail (cogl_is_framebuffer (framebuffer));
  g_return_if_fail (cogl_is_pipeline (pipeline));
  g_return_if_fail (cogl_is_path (path));

  _cogl_path_stroke_nodes (path, framebuffer, pipeline);
}
