#include "clutter-build-config.h"

#include <math.h>

#include "clutter-types.h"
#include "clutter-interval.h"
#include "clutter-private.h"
#include "clutter-actor-box-private.h"

/**
 * clutter_actor_box_new:
 * @x_1: X coordinate of the top left point
 * @y_1: Y coordinate of the top left point
 * @x_2: X coordinate of the bottom right point
 * @y_2: Y coordinate of the bottom right point
 *
 * Allocates a new [struct@ActorBox] using the passed coordinates
 * for the top left and bottom right points.
 *
 * This function is the logical equivalent of:
 *
 * ```c
 *   clutter_actor_box_init (clutter_actor_box_alloc (),
 *                           x_1, y_1,
 *                           x_2, y_2);
 * ```
 *
 * Return value: (transfer full): the newly allocated #ClutterActorBox.
 *   Use [method@ActorBox.free] to free the resources
 */
ClutterActorBox *
clutter_actor_box_new (gfloat x_1,
                       gfloat y_1,
                       gfloat x_2,
                       gfloat y_2)
{
  return clutter_actor_box_init (clutter_actor_box_alloc (),
                                 x_1, y_1,
                                 x_2, y_2);
}

/**
 * clutter_actor_box_alloc:
 *
 * Allocates a new [struct@ActorBox].
 *
 * Return value: (transfer full): the newly allocated #ClutterActorBox.
 *   Use [method@ActorBox.free] to free its resources
 */
ClutterActorBox *
clutter_actor_box_alloc (void)
{
  return g_new0 (ClutterActorBox, 1);
}

/**
 * clutter_actor_box_init:
 * @box: a #ClutterActorBox
 * @x_1: X coordinate of the top left point
 * @y_1: Y coordinate of the top left point
 * @x_2: X coordinate of the bottom right point
 * @y_2: Y coordinate of the bottom right point
 *
 * Initializes @box with the given coordinates.
 *
 * Return value: (transfer none): the initialized #ClutterActorBox
 */
ClutterActorBox *
clutter_actor_box_init (ClutterActorBox *box,
                        gfloat           x_1,
                        gfloat           y_1,
                        gfloat           x_2,
                        gfloat           y_2)
{
  g_return_val_if_fail (box != NULL, NULL);

  box->x1 = x_1;
  box->y1 = y_1;
  box->x2 = x_2;
  box->y2 = y_2;

  return box;
}

/**
 * clutter_actor_box_init_rect:
 * @box: a #ClutterActorBox
 * @x: X coordinate of the origin
 * @y: Y coordinate of the origin
 * @width: width of the box
 * @height: height of the box
 *
 * Initializes @box with the given origin and size.
 */
void
clutter_actor_box_init_rect (ClutterActorBox *box,
                             gfloat           x,
                             gfloat           y,
                             gfloat           width,
                             gfloat           height)
{
  g_return_if_fail (box != NULL);

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

/**
 * clutter_actor_box_copy:
 * @box: a #ClutterActorBox
 *
 * Copies @box
 *
 * Return value: a newly allocated copy of #ClutterActorBox. Use
 *   [method@ActorBox.free] to free the allocated resources
 */
ClutterActorBox *
clutter_actor_box_copy (const ClutterActorBox *box)
{
  if (G_LIKELY (box != NULL))
    return g_memdup2 (box, sizeof (ClutterActorBox));

  return NULL;
}

/**
 * clutter_actor_box_free:
 * @box: a #ClutterActorBox
 *
 * Frees a #ClutterActorBox allocated using [ctor@ActorBox.new]
 * or [method@ActorBox.copy].
 */
void
clutter_actor_box_free (ClutterActorBox *box)
{
  if (G_LIKELY (box != NULL))
    g_free (box);
}

/**
 * clutter_actor_box_equal:
 * @box_a: a #ClutterActorBox
 * @box_b: a #ClutterActorBox
 *
 * Checks @box_a and @box_b for equality
 *
 * Return value: %TRUE if the passed #ClutterActorBox are equal
 */
gboolean
clutter_actor_box_equal (const ClutterActorBox *box_a,
                         const ClutterActorBox *box_b)
{
  g_return_val_if_fail (box_a != NULL && box_b != NULL, FALSE);

  if (box_a == box_b)
    return TRUE;

  return box_a->x1 == box_b->x1 && box_a->y1 == box_b->y1 &&
         box_a->x2 == box_b->x2 && box_a->y2 == box_b->y2;
}

/**
 * clutter_actor_box_get_x:
 * @box: a #ClutterActorBox
 *
 * Retrieves the X coordinate of the origin of @box
 *
 * Return value: the X coordinate of the origin
 */
gfloat
clutter_actor_box_get_x (const ClutterActorBox *box)
{
  g_return_val_if_fail (box != NULL, 0.);

  return box->x1;
}

/**
 * clutter_actor_box_get_y:
 * @box: a #ClutterActorBox
 *
 * Retrieves the Y coordinate of the origin of @box
 *
 * Return value: the Y coordinate of the origin
 */
gfloat
clutter_actor_box_get_y (const ClutterActorBox *box)
{
  g_return_val_if_fail (box != NULL, 0.);

  return box->y1;
}

/**
 * clutter_actor_box_get_width:
 * @box: a #ClutterActorBox
 *
 * Retrieves the width of the @box
 *
 * Return value: the width of the box
 */
gfloat
clutter_actor_box_get_width (const ClutterActorBox *box)
{
  g_return_val_if_fail (box != NULL, 0.);

  return box->x2 - box->x1;
}

/**
 * clutter_actor_box_get_height:
 * @box: a #ClutterActorBox
 *
 * Retrieves the height of the @box
 *
 * Return value: the height of the box
 */
gfloat
clutter_actor_box_get_height (const ClutterActorBox *box)
{
  g_return_val_if_fail (box != NULL, 0.);

  return box->y2 - box->y1;
}

/**
 * clutter_actor_box_get_origin:
 * @box: a #ClutterActorBox
 * @x: (out) (allow-none): return location for the X coordinate, or %NULL
 * @y: (out) (allow-none): return location for the Y coordinate, or %NULL
 *
 * Retrieves the origin of @box
 */
void
clutter_actor_box_get_origin (const ClutterActorBox *box,
                              gfloat                *x,
                              gfloat                *y)
{
  g_return_if_fail (box != NULL);

  if (x)
    *x = box->x1;

  if (y)
    *y = box->y1;
}

/**
 * clutter_actor_box_get_size:
 * @box: a #ClutterActorBox
 * @width: (out) (allow-none): return location for the width, or %NULL
 * @height: (out) (allow-none): return location for the height, or %NULL
 *
 * Retrieves the size of @box
 */
void
clutter_actor_box_get_size (const ClutterActorBox *box,
                            gfloat                *width,
                            gfloat                *height)
{
  g_return_if_fail (box != NULL);

  if (width)
    *width = box->x2 - box->x1;

  if (height)
    *height = box->y2 - box->y1;
}

/**
 * clutter_actor_box_get_area:
 * @box: a #ClutterActorBox
 *
 * Retrieves the area of @box
 *
 * Return value: the area of a #ClutterActorBox, in pixels
 */
gfloat
clutter_actor_box_get_area (const ClutterActorBox *box)
{
  g_return_val_if_fail (box != NULL, 0.);

  return (box->x2 - box->x1) * (box->y2 - box->y1);
}

/**
 * clutter_actor_box_contains:
 * @box: a #ClutterActorBox
 * @x: X coordinate of the point
 * @y: Y coordinate of the point
 *
 * Checks whether a point with @x, @y coordinates is contained
 * within @box
 *
 * Return value: %TRUE if the point is contained by the #ClutterActorBox
 */
gboolean
clutter_actor_box_contains (const ClutterActorBox *box,
                            gfloat                 x,
                            gfloat                 y)
{
  g_return_val_if_fail (box != NULL, FALSE);

  return (x > box->x1 && x < box->x2) &&
         (y > box->y1 && y < box->y2);
}

/**
 * clutter_actor_box_from_vertices:
 * @box: a #ClutterActorBox
 * @verts: (array fixed-size=4): array of four #graphene_point3d_t
 *
 * Calculates the bounding box represented by the four vertices; for details
 * of the vertex array see [method@Actor.get_abs_allocation_vertices].
 */
void
clutter_actor_box_from_vertices (ClutterActorBox          *box,
                                 const graphene_point3d_t  verts[])
{
  gfloat x_1, x_2, y_1, y_2;

  g_return_if_fail (box != NULL);
  g_return_if_fail (verts != NULL);

  /* 4-way min/max */
  x_1 = verts[0].x;
  y_1 = verts[0].y;

  if (verts[1].x < x_1)
    x_1 = verts[1].x;

  if (verts[2].x < x_1)
    x_1 = verts[2].x;

  if (verts[3].x < x_1)
    x_1 = verts[3].x;

  if (verts[1].y < y_1)
    y_1 = verts[1].y;

  if (verts[2].y < y_1)
    y_1 = verts[2].y;

  if (verts[3].y < y_1)
    y_1 = verts[3].y;

  x_2 = verts[0].x;
  y_2 = verts[0].y;

  if (verts[1].x > x_2)
    x_2 = verts[1].x;

  if (verts[2].x > x_2)
    x_2 = verts[2].x;

  if (verts[3].x > x_2)
    x_2 = verts[3].x;

  if (verts[1].y > y_2)
    y_2 = verts[1].y;

  if (verts[2].y > y_2)
    y_2 = verts[2].y;

  if (verts[3].y > y_2)
    y_2 = verts[3].y;

  box->x1 = x_1;
  box->x2 = x_2;
  box->y1 = y_1;
  box->y2 = y_2;
}

/**
 * clutter_actor_box_interpolate:
 * @initial: the initial #ClutterActorBox
 * @final: the final #ClutterActorBox
 * @progress: the interpolation progress
 * @result: (out): return location for the interpolation
 *
 * Interpolates between @initial and @final `ClutterActorBox`es
 * using @progress
 */
void
clutter_actor_box_interpolate (const ClutterActorBox *initial,
                               const ClutterActorBox *final,
                               gdouble                progress,
                               ClutterActorBox       *result)
{
  g_return_if_fail (initial != NULL);
  g_return_if_fail (final != NULL);
  g_return_if_fail (result != NULL);

  result->x1 = initial->x1 + (final->x1 - initial->x1) * progress;
  result->y1 = initial->y1 + (final->y1 - initial->y1) * progress;
  result->x2 = initial->x2 + (final->x2 - initial->x2) * progress;
  result->y2 = initial->y2 + (final->y2 - initial->y2) * progress;
}

/**
 * clutter_actor_box_clamp_to_pixel:
 * @box: (inout): the #ClutterActorBox to clamp
 *
 * Clamps the components of @box to the nearest integer
 */
void
clutter_actor_box_clamp_to_pixel (ClutterActorBox *box)
{
  g_return_if_fail (box != NULL);

  box->x1 = floorf (box->x1);
  box->y1 = floorf (box->y1);
  box->x2 = ceilf (box->x2);
  box->y2 = ceilf (box->y2);
}

/**
 * clutter_actor_box_union:
 * @a: (in): the first #ClutterActorBox
 * @b: (in): the second #ClutterActorBox
 * @result: (out): the #ClutterActorBox representing a union
 *   of @a and @b
 *
 * Unions the two boxes @a and @b and stores the result in @result.
 */
void
clutter_actor_box_union (const ClutterActorBox *a,
                         const ClutterActorBox *b,
                         ClutterActorBox       *result)
{
  g_return_if_fail (a != NULL);
  g_return_if_fail (b != NULL);
  g_return_if_fail (result != NULL);

  result->x1 = MIN (a->x1, b->x1);
  result->y1 = MIN (a->y1, b->y1);

  result->x2 = MAX (a->x2, b->x2);
  result->y2 = MAX (a->y2, b->y2);
}

static gboolean
clutter_actor_box_progress (const GValue *a,
                            const GValue *b,
                            gdouble       factor,
                            GValue       *retval)
{
  ClutterActorBox res = { 0, };

  clutter_actor_box_interpolate (g_value_get_boxed (a),
                                 g_value_get_boxed (b),
                                 factor,
                                 &res);

  g_value_set_boxed (retval, &res);

  return TRUE;
}

/**
 * clutter_actor_box_set_origin:
 * @box: a #ClutterActorBox
 * @x: the X coordinate of the new origin
 * @y: the Y coordinate of the new origin
 *
 * Changes the origin of @box, maintaining the size of the #ClutterActorBox.
 */
void
clutter_actor_box_set_origin (ClutterActorBox *box,
                              gfloat           x,
                              gfloat           y)
{
  gfloat width, height;

  g_return_if_fail (box != NULL);

  width = box->x2 - box->x1;
  height = box->y2 - box->y1;

  clutter_actor_box_init_rect (box, x, y, width, height);
}

/**
 * clutter_actor_box_set_size:
 * @box: a #ClutterActorBox
 * @width: the new width
 * @height: the new height
 *
 * Sets the size of @box, maintaining the origin of the #ClutterActorBox.
 */
void
clutter_actor_box_set_size (ClutterActorBox *box,
                            gfloat           width,
                            gfloat           height)
{
  g_return_if_fail (box != NULL);

  box->x2 = box->x1 + width;
  box->y2 = box->y1 + height;
}

void
_clutter_actor_box_enlarge_for_effects (ClutterActorBox *box)
{
  float width, height;

  if (clutter_actor_box_get_area (box) == 0.0)
    return;

  /* The aim here is that for a given rectangle defined with floating point
   * coordinates we want to determine a stable quantized size in pixels
   * that doesn't vary due to the original box's sub-pixel position.
   *
   * The reason this is important is because effects will use this
   * API to determine the size of offscreen framebuffers and so for
   * a fixed-size object that may be animated across the screen we
   * want to make sure that the stage paint-box has an equally stable
   * size so that effects aren't made to continuously re-allocate
   * a corresponding fbo.
   *
   * The other thing we consider is that the calculation of this box is
   * subject to floating point precision issues that might be slightly
   * different to the precision issues involved with actually painting the
   * actor, which might result in painting slightly leaking outside the
   * user's calculated paint-volume. For this we simply aim to pad out the
   * paint-volume by at least half a pixel all the way around.
   */
  width = box->x2 - box->x1;
  height = box->y2 - box->y1;
  width = CLUTTER_NEARBYINT (width);
  height = CLUTTER_NEARBYINT (height);
  /* XXX: NB the width/height may now be up to 0.5px too small so we
   * must also pad by 0.25px all around to account for this. In total we
   * must padd by at least 0.75px around all sides. */

  /* XXX: The furthest that we can overshoot the bottom right corner by
   * here is 1.75px in total if you consider that the 0.75 padding could
   * just cross an integer boundary and so ceil will effectively add 1.
   */
  box->x2 = ceilf (box->x2 + 0.75);
  box->y2 = ceilf (box->y2 + 0.75);

  /* Now we redefine the top-left relative to the bottom right based on the
   * rounded width/height determined above + a constant so that the overall
   * size of the box will be stable and not dependent on the box's
   * position.
   *
   * Adding 3px to the width/height will ensure we cover the maximum of
   * 1.75px padding on the bottom/right and still ensure we have > 0.75px
   * padding on the top/left.
   */
  box->x1 = box->x2 - width - 3;
  box->y1 = box->y2 - height - 3;
}

/**
 * clutter_actor_box_scale:
 * @box: a #ClutterActorBox
 * @scale: scale factor for resizing this box
 *
 * Rescale the @box by provided @scale factor.
 */
void
clutter_actor_box_scale (ClutterActorBox *box,
                         gfloat           scale)
{
  g_return_if_fail (box != NULL);

  box->x1 *= scale;
  box->x2 *= scale;
  box->y1 *= scale;
  box->y2 *= scale;
}

/**
 * clutter_actor_box_is_initialized:
 * @box: a #ClutterActorBox
 *
 * Checks if @box has been initialized, a #ClutterActorBox is uninitialized
 * if it has a size of -1 at an origin of 0, 0.
 *
 * Returns: %TRUE if the box is uninitialized, %FALSE if it isn't
 */
gboolean
clutter_actor_box_is_initialized (ClutterActorBox *box)
{
  gboolean x1_uninitialized, x2_uninitialized;
  gboolean y1_uninitialized, y2_uninitialized;

  g_return_val_if_fail (box != NULL, TRUE);

  x1_uninitialized = isinf (box->x1);
  x2_uninitialized = isinf (box->x2) && signbit (box->x2);
  y1_uninitialized = isinf (box->y1);
  y2_uninitialized = isinf (box->y2) && signbit (box->y2);

  return !x1_uninitialized || !x2_uninitialized ||
         !y1_uninitialized || !y2_uninitialized;
}

G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterActorBox, clutter_actor_box,
                               clutter_actor_box_copy,
                               clutter_actor_box_free,
                               CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_actor_box_progress));
