/* This file is part of GEGL
 *
 * GEGL 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 3 of the License, or (at your option) any later version.
 *
 * GEGL 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 GEGL; if not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2003-2007 Calvin Williamson, Øyvind Kolås.
 */

#include "config.h"

#include <string.h>
#include <glib-object.h>

#include "gegl.h"
#include "gegl-types-internal.h"
#include "gegl-utils.h"
#include "gegl-types-internal.h"

gint
_gegl_float_epsilon_zero (float value)
{
  return value > -GEGL_FLOAT_EPSILON && value < GEGL_FLOAT_EPSILON;
}

gint
_gegl_float_epsilon_equal (float v1, float v2)
{
  register float diff = v1 - v2;

  return diff > -GEGL_FLOAT_EPSILON && diff < GEGL_FLOAT_EPSILON;
}

GeglRectangle *
gegl_rectangle_new (gint           x,
                    gint           y,
                    guint          w,
                    guint          h)
{
  GeglRectangle *r = g_new (GeglRectangle, 1);

  r->x      = x;
  r->y      = y;
  r->width  = w;
  r->height = h;

  return r;
}

void
gegl_rectangle_set (GeglRectangle *r,
                    gint           x,
                    gint           y,
                    guint          w,
                    guint          h)
{
  r->x      = x;
  r->y      = y;
  r->width  = w;
  r->height = h;
}

void
gegl_rectangle_bounding_box (GeglRectangle       *dest,
                             const GeglRectangle *src1,
                             const GeglRectangle *src2)
{
  gboolean s1_has_area = src1->width && src1->height;
  gboolean s2_has_area = src2->width && src2->height;

  if (!s1_has_area && !s2_has_area)
    gegl_rectangle_set (dest, 0, 0, 0, 0);
  else if (!s1_has_area)
    gegl_rectangle_copy (dest, src2);
  else if (!s2_has_area)
    gegl_rectangle_copy (dest, src1);
  else
    {
      gint x1 = MIN (src1->x, src2->x);
      gint x2 = MAX (src1->x + src1->width, src2->x + src2->width);
      gint y1 = MIN (src1->y, src2->y);
      gint y2 = MAX (src1->y + src1->height, src2->y + src2->height);

      dest->x      = x1;
      dest->y      = y1;
      dest->width  = x2 - x1;
      dest->height = y2 - y1;
    }
}

gboolean
gegl_rectangle_intersect (GeglRectangle       *dest,
                          const GeglRectangle *src1,
                          const GeglRectangle *src2)
{
  gint x1, x2, y1, y2;

  x1 = MAX (src1->x, src2->x);
  x2 = MIN (src1->x + src1->width, src2->x + src2->width);

  if (x2 <= x1)
    {
      if (dest)
        gegl_rectangle_set (dest, 0, 0, 0, 0);
      return FALSE;
    }

  y1 = MAX (src1->y, src2->y);
  y2 = MIN (src1->y + src1->height, src2->y + src2->height);

  if (y2 <= y1)
    {
      if (dest)
        gegl_rectangle_set (dest, 0, 0, 0, 0);
      return FALSE;
    }

  if (dest)
    gegl_rectangle_set (dest, x1, y1, x2 - x1, y2 - y1);
  return TRUE;
}

gboolean
gegl_rectangle_subtract_bounding_box (GeglRectangle       *dest,
                                      const GeglRectangle *minuend,
                                      const GeglRectangle *subtrahend)
{
  gint mx1, mx2;
  gint my1, my2;

  gint sx1, sx2;
  gint sy1, sy2;

  mx1 = minuend->x;
  mx2 = minuend->x + minuend->width;
  my1 = minuend->y;
  my2 = minuend->y + minuend->height;

  sx1 = subtrahend->x;
  sx2 = subtrahend->x + subtrahend->width;
  sy1 = subtrahend->y;
  sy2 = subtrahend->y + subtrahend->height;

  if (sx1 <= mx1 && sx2 >= mx2)
    {
      if (sy1 <= my1) my1 = MAX (my1, sy2);
      if (sy2 >= my2) my2 = MIN (my2, sy1);
    }
  else if (sy1 <= my1 && sy2 >= my2)
    {
      if (sx1 <= mx1) mx1 = MAX (mx1, sx2);
      if (sx2 >= mx2) mx2 = MIN (mx2, sx1);
    }

  if (mx1 < mx2 && my1 < my2)
    {
      gegl_rectangle_set (dest, mx1, my1, mx2 - mx1, my2 - my1);
      return TRUE;
    }
  else
    {
      gegl_rectangle_set (dest, 0, 0, 0, 0);
      return FALSE;
    }
}

void
gegl_rectangle_copy (GeglRectangle       *to,
                     const GeglRectangle *from)
{
  to->x      = from->x;
  to->y      = from->y;
  to->width  = from->width;
  to->height = from->height;
}

gboolean
gegl_rectangle_contains (const GeglRectangle *r,
                         const GeglRectangle *s)
{
  g_return_val_if_fail (r && s, FALSE);

  if (s->x >= r->x &&
      s->y >= r->y &&
      (s->x + s->width) <= (r->x + r->width) &&
      (s->y + s->height) <= (r->y + r->height))
    return TRUE;
  else
    return FALSE;
}

gboolean
gegl_rectangle_equal (const GeglRectangle *r,
                      const GeglRectangle *s)
{
  g_return_val_if_fail (r && s, FALSE);

  if (r->x == s->x &&
      r->y == s->y &&
      r->width == s->width &&
      r->height == s->height)
    return TRUE;
  else
    return FALSE;
}

gboolean
gegl_rectangle_equal_coords (const GeglRectangle *r,
                             gint                 x,
                             gint                 y,
                             gint                 w,
                             gint                 h)
{
  g_return_val_if_fail (r, FALSE);

  if (r->x == x &&
      r->y == y &&
      r->width == w &&
      r->height == h)
    return TRUE;
  else
    return FALSE;
}

gboolean
gegl_rectangle_is_empty (const GeglRectangle *r)
{
  g_return_val_if_fail (r != NULL, FALSE);
  return r->width == 0 && r->height == 0;
}

GeglRectangle *
gegl_rectangle_dup (const GeglRectangle *rectangle)
{
  GeglRectangle *result = g_new (GeglRectangle, 1);

  *result = *rectangle;

  return result;
}

GeglRectangle
gegl_rectangle_infinite_plane (void)
{
  GeglRectangle infinite_plane_rect = {G_MININT / 2, G_MININT / 2, G_MAXINT, G_MAXINT};
  return infinite_plane_rect;
}

gboolean
gegl_rectangle_is_infinite_plane (const GeglRectangle *rectangle)
{
  return (rectangle->x      == G_MININT / 2 &&
          rectangle->y      == G_MININT / 2 &&
          rectangle->width  == G_MAXINT     &&
          rectangle->height == G_MAXINT);
}

void
gegl_rectangle_dump (const GeglRectangle *rectangle)
{
  g_print ("%d, %d, %d×%d\n",
           rectangle->x,
           rectangle->y,
           rectangle->width,
           rectangle->height);
}

GType
gegl_rectangle_get_type (void)
{
  static GType our_type = 0;

  if (our_type == 0)
    our_type = g_boxed_type_register_static (g_intern_static_string ("GeglRectangle"),
                                             (GBoxedCopyFunc) gegl_rectangle_dup,
                                             (GBoxedFreeFunc) g_free);
  return our_type;
}

#define GEGL_ALIGN 16
G_STATIC_ASSERT (GEGL_ALIGN <= G_MAXUINT8);

/* utility call that makes sure allocations are 16 byte aligned.
 * making RGBA float buffers have aligned access for pixels.
 */
gpointer gegl_malloc (gsize size)
{
  gchar *mem;
  gchar *ret;
  gint   offset;

  mem    = g_malloc (size + GEGL_ALIGN);
  offset = GEGL_ALIGN - GPOINTER_TO_UINT(mem) % GEGL_ALIGN;
  ret    = (gpointer)(mem + offset);

  /* store the offset to the real malloc one byte in front of this malloc */
  *(guint8*)(ret-1)=offset;
  return (gpointer) ret;
}

gpointer gegl_calloc (gsize size, int n_memb)
{
  gchar *ret = gegl_malloc (size * n_memb);
  memset (ret, 0, size * n_memb);
  return ret;
}

void
gegl_free (gpointer buf)
{
  g_assert (buf);
  g_free ((gchar*)buf - *((guint8*)buf -1));
}

void
gegl_memset_pattern (void * restrict       dst_ptr,
                     const void * restrict src_ptr,
                     gint                  pattern_size,
                     gint                  count)
{
  guchar       *dst = dst_ptr;
  const guchar *src = src_ptr;

  /* g_assert (pattern_size > 0 && count >= 0); */

  if (pattern_size == 1 || count == 0)
    {
      memset (dst, *src, count);
    }
  else
    {
      gsize block_size;
      gsize remaining_size;

      block_size = pattern_size,

      memcpy (dst, src, block_size);
      src  = dst;
      dst += block_size;

      remaining_size = (count - 1) * block_size;

      while (block_size < remaining_size)
        {
          memcpy (dst, src, block_size);
          dst += block_size;

          remaining_size -= block_size;

          /* limit the block size, so that we don't saturate the cache.
           * 
           * FIXME: optimal limit could use more benchmarking.
           */
          if (block_size <= 2048)
            block_size *= 2;
        }

      memcpy (dst, src, remaining_size);
    }
}
