#ifndef _ROUNDED_RECT_
#define _ROUNDED_RECT_

#include "ellipse.glsl"
#include "rect.glsl"

#define RoundedRect vec4[3]

RoundedRect
rounded_rect_from_rect (Rect r)
{
  return RoundedRect (rect_bounds(r), vec4 (0.0), vec4 (0.0));
}

RoundedRect
rounded_rect_from_gsk (mat3x4 gsk_rounded_rect)
{
  return RoundedRect ((gsk_rounded_rect[0].xyxy + vec4 (0.0, 0.0, gsk_rounded_rect[0].zw)) * GSK_GLOBAL_SCALE.xyxy,
                      gsk_rounded_rect[1] * GSK_GLOBAL_SCALE.xxxx,
                      gsk_rounded_rect[2] * GSK_GLOBAL_SCALE.yyyy);
}

vec4
rounded_rect_bounds (RoundedRect r)
{
  return r[0];
}

vec4
rounded_rect_corner_widths (RoundedRect r)
{
  return r[1];
}

vec4
rounded_rect_corner_heights (RoundedRect r)
{
  return r[2];
}

vec2
rounded_rect_corner (RoundedRect r, const uint corner)
{
  return vec2 (
    rounded_rect_corner_widths (r)[corner],
    rounded_rect_corner_heights (r)[corner]
  );
}

float
rounded_rect_corner_width (RoundedRect r, const uint corner)
{
  return rounded_rect_corner_widths (r)[corner];
}

float
rounded_rect_corner_height (RoundedRect r, const uint corner)
{
  return rounded_rect_corner_heights (r)[corner];
}

float
rounded_rect_distance (RoundedRect r, vec2 p)
{
  float bounds_distance = rect_distance (Rect (rounded_rect_bounds (r)), p);
                      
  Ellipse tl = Ellipse (rounded_rect_bounds (r).xy + vec2( 1.,  1.)*rounded_rect_corner (r, TOP_LEFT),
                        rounded_rect_corner (r, TOP_LEFT));
  Ellipse tr = Ellipse (rounded_rect_bounds (r).zy + vec2(-1.,  1.)*rounded_rect_corner (r, TOP_RIGHT),
                        rounded_rect_corner (r, TOP_RIGHT));
  Ellipse br = Ellipse (rounded_rect_bounds (r).zw + vec2(-1., -1.)*rounded_rect_corner (r, BOTTOM_RIGHT),
                        rounded_rect_corner (r, BOTTOM_RIGHT));
  Ellipse bl = Ellipse (rounded_rect_bounds (r).xw + vec2( 1., -1.)*rounded_rect_corner (r, BOTTOM_LEFT),
                        rounded_rect_corner (r, BOTTOM_LEFT));

 vec4 distances = vec4(ellipse_distance (tl, p),
                       ellipse_distance (tr, p),
                       ellipse_distance (br, p),
                       ellipse_distance (bl, p));

  bvec4 is_out = bvec4(p.x < tl.center.x && p.y < tl.center.y,
                       p.x > tr.center.x && p.y < tr.center.y,
                       p.x > br.center.x && p.y > br.center.y,
                       p.x < bl.center.x && p.y > bl.center.y);
  distances = mix (vec4(bounds_distance), distances, is_out);

  vec2 max2 = max (distances.xy, distances.zw);
  return max (max2.x, max2.y);
}

RoundedRect
rounded_rect_shrink (RoundedRect r, vec4 amount)
{
  vec4 new_bounds = rounded_rect_bounds (r) + vec4(1.0,1.0,-1.0,-1.0) * amount.wxyz;
  vec4 new_widths = max (rounded_rect_corner_widths (r) - sign (rounded_rect_corner_widths (r)) * amount.wyyw, 0.0);
  vec4 new_heights = max (rounded_rect_corner_heights (r) - sign (rounded_rect_corner_heights (r)) * amount.xxzz, 0.0);
  new_widths = min (new_widths, new_bounds.z - new_bounds.x);
  new_heights = min (new_heights, new_bounds.w - new_bounds.y);
              
  return RoundedRect (new_bounds, new_widths, new_heights);
}

void
rounded_rect_scale (inout RoundedRect r,
                    vec2              scale)
{
  r = RoundedRect (
    rounded_rect_bounds (r) * scale.xyxy,
    rounded_rect_corner_widths (r) * scale.xxxx,
    rounded_rect_corner_heights (r) * scale.yyyy
  );
}

void
rounded_rect_offset (inout RoundedRect r,
                     vec2              offset)
{
  r = RoundedRect (
    rounded_rect_bounds (r) + offset.xyxy,
    rounded_rect_corner_widths (r),
    rounded_rect_corner_heights (r)
  );
}

bool
rounded_rect_is_slicable (RoundedRect r)
{
  vec2 size = rect_size (Rect (rounded_rect_bounds (r)));
  return any (lessThanEqual (rounded_rect_corner (r, TOP_LEFT) + rounded_rect_corner (r, BOTTOM_RIGHT), size))
      && any (lessThanEqual (rounded_rect_corner (r, BOTTOM_LEFT) + rounded_rect_corner (r, TOP_RIGHT), size));
}

Rect
rounded_rect_intersection_fallback_slice (RoundedRect outside,
                                          RoundedRect inside,
                                          uint        slice,
                                          bool        snap)
{
  Rect r;

  switch (slice)
    {
    default:
    case SLICE_TOP:
    case SLICE_RIGHT:
    case SLICE_BOTTOM:
    case SLICE_LEFT:
      return Rect (vec4 (0.0));

    case SLICE_TOP_LEFT:
      r = Rect (vec4 (rounded_rect_bounds (outside).xy, 0.5 * (rounded_rect_bounds (outside).xy + rounded_rect_bounds (outside).zw)));
      if (snap)
        r = rect_snap (r, gsk_rect_snap_new (GSK_SNAP_FLOOR, GSK_SNAP_CEIL, GSK_SNAP_CEIL, GSK_SNAP_FLOOR));
      return r;

    case SLICE_TOP_RIGHT:
      r = Rect (vec4 (0.5 * (rounded_rect_bounds (outside).x + rounded_rect_bounds (outside).z), rounded_rect_bounds (outside).y,
                      rounded_rect_bounds (outside).z, 0.5 * (rounded_rect_bounds (outside).y + rounded_rect_bounds (outside).w)));
      if (snap)
        r = rect_snap (r, gsk_rect_snap_new (GSK_SNAP_FLOOR, GSK_SNAP_CEIL, GSK_SNAP_CEIL, GSK_SNAP_CEIL));
      return r;

    case SLICE_BOTTOM_RIGHT:
      r = Rect (vec4 (0.5 * (rounded_rect_bounds (outside).xy + rounded_rect_bounds (outside).zw), rounded_rect_bounds (outside).zw));
      if (snap)
        r = rect_snap (r, gsk_rect_snap_new (GSK_SNAP_CEIL, GSK_SNAP_CEIL, GSK_SNAP_CEIL, GSK_SNAP_CEIL));
      return r;

    case SLICE_BOTTOM_LEFT:
      r = Rect (vec4 (rounded_rect_bounds (outside).x, 0.5 * (rounded_rect_bounds (outside).y + rounded_rect_bounds (outside).w),
                      0.5 * (rounded_rect_bounds (outside).x + rounded_rect_bounds (outside).z), rounded_rect_bounds (outside).w));
      if (snap)
        r = rect_snap (r, gsk_rect_snap_new (GSK_SNAP_CEIL, GSK_SNAP_CEIL, GSK_SNAP_CEIL, GSK_SNAP_FLOOR));
      return r;
    }
}

Rect
rounded_rect_intersection_slice (RoundedRect outside,
                                 RoundedRect inside,
                                 uint        slice,
                                 bool        snap)
{
  float x[4], y[4];

  if (!rounded_rect_is_slicable (outside) ||
      !rounded_rect_is_slicable (inside))
    {
      return rounded_rect_intersection_fallback_slice (outside, inside, slice, snap);
    }

  x[0] = rounded_rect_bounds (outside).x;
  x[3] = rounded_rect_bounds (outside).z;
  
  switch (slice)
    {
    case SLICE_TOP_LEFT:
    case SLICE_TOP:
    case SLICE_TOP_RIGHT:
      x[1] = max (rounded_rect_bounds (outside).x + rounded_rect_corner_width (outside, TOP_LEFT),
                  rounded_rect_bounds (inside).x + rounded_rect_corner_width (inside, TOP_LEFT));
      x[2] = min (rounded_rect_bounds (outside).z - rounded_rect_corner_width (outside, TOP_RIGHT),
                  rounded_rect_bounds (inside).z - rounded_rect_corner_width (inside, TOP_RIGHT));
      break;

    case SLICE_LEFT:
    case SLICE_RIGHT:
    default:
      x[1] = max (rounded_rect_bounds (outside).x, rounded_rect_bounds (inside).x);
      x[2] = max (rounded_rect_bounds (outside).z, rounded_rect_bounds (inside).z);

    case SLICE_BOTTOM_LEFT:
    case SLICE_BOTTOM:
    case SLICE_BOTTOM_RIGHT:
      x[1] = max (rounded_rect_bounds (outside).x + rounded_rect_corner_width (outside, BOTTOM_LEFT),
                  rounded_rect_bounds (inside).x + rounded_rect_corner_width (inside, BOTTOM_LEFT));
      x[2] = min (rounded_rect_bounds (outside).z - rounded_rect_corner_width (outside, BOTTOM_RIGHT),
                  rounded_rect_bounds (inside).z - rounded_rect_corner_width (inside, BOTTOM_RIGHT));
      break;
    }

  if (snap)
    {
      x[0] = floor (x[0]);
      x[1] = ceil (x[1]);
      x[2] = floor (x[2]);
      x[2] = max (x[1], x[2]);
      x[3] = ceil (x[3]);
    }

  y[0] = rounded_rect_bounds (outside).y;
  y[3] = rounded_rect_bounds (outside).w;
  
  switch (slice)
    {
    case SLICE_TOP_LEFT:
    case SLICE_LEFT:
    case SLICE_BOTTOM_LEFT:
      y[1] = max (rounded_rect_bounds (outside).y + rounded_rect_corner_height (outside, TOP_LEFT),
                  rounded_rect_bounds (inside).y + rounded_rect_corner_height (inside, TOP_LEFT));
      y[2] = min (rounded_rect_bounds (outside).w - rounded_rect_corner_height (outside, BOTTOM_LEFT),
                  rounded_rect_bounds (inside).w - rounded_rect_corner_height (inside, BOTTOM_LEFT));
      break;

    case SLICE_TOP:
    case SLICE_BOTTOM:
    default:
      y[1] = max (rounded_rect_bounds (outside).y, rounded_rect_bounds (inside).y);
      y[2] = max (rounded_rect_bounds (outside).w, rounded_rect_bounds (inside).w);

    case SLICE_TOP_RIGHT:
    case SLICE_RIGHT:
    case SLICE_BOTTOM_RIGHT:
      y[1] = max (rounded_rect_bounds (outside).y + rounded_rect_corner_height (outside, TOP_RIGHT),
                  rounded_rect_bounds (inside).y + rounded_rect_corner_height (inside, TOP_RIGHT));
      y[2] = min (rounded_rect_bounds (outside).w - rounded_rect_corner_height (outside, BOTTOM_RIGHT),
                  rounded_rect_bounds (inside).w - rounded_rect_corner_height (inside, BOTTOM_RIGHT));
      break;
    }

  if (snap)
    {
      y[0] = floor (y[0]);
      y[1] = ceil (y[1]);
      y[2] = floor (y[2]);
      y[2] = max (y[1], y[2]);
      y[3] = ceil (y[3]);
    }

  switch (slice)
    {
    case SLICE_TOP_LEFT:
      return Rect (vec4 (x[0], y[0], x[1], y[1]));
    case SLICE_TOP:
      return Rect (vec4 (x[1], y[0], x[2], y[1]));
    case SLICE_TOP_RIGHT:
      return Rect (vec4 (x[2], y[0], x[3], y[1]));
    case SLICE_LEFT:
      return Rect (vec4 (x[0], y[1], x[1], y[2]));
    case SLICE_RIGHT:
      return Rect (vec4 (x[2], y[1], x[3], y[2]));
    case SLICE_BOTTOM_LEFT:
      return Rect (vec4 (x[0], y[2], x[1], y[3]));
    case SLICE_BOTTOM:
      return Rect (vec4 (x[1], y[2], x[2], y[3]));
    case SLICE_BOTTOM_RIGHT:
      return Rect (vec4 (x[2], y[2], x[3], y[3]));
    default:
      return Rect (vec4 (0.0));
    }
}

#ifdef GSK_FRAGMENT_SHADER

float
rounded_rect_coverage (RoundedRect r, vec2 p)
{
  vec2 fw = fwidth (p);
  float distance_scale = max (fw.x, fw.y);
  float distance = rounded_rect_distance (r, p) / distance_scale;
  float coverage = 0.5 - distance;

  return clamp (coverage, 0.0, 1.0);
}

#endif

#endif
