The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "tickit.h"

static inline int minint(int a, int b) { return a < b ? a : b; }
static inline int maxint(int a, int b) { return a > b ? a : b; }

void tickit_rect_init_sized(TickitRect *rect, int top, int left, int lines, int cols)
{
  rect->top   = top;
  rect->left  = left;
  rect->lines = lines;
  rect->cols  = cols;
}

void tickit_rect_init_bounded(TickitRect *rect, int top, int left, int bottom, int right)
{
  rect->top   = top;
  rect->left  = left;
  rect->lines = bottom - top;
  rect->cols  = right - left;
}

void tickit_rect_translate(TickitRect *rect, int downward, int rightward)
{
  rect->top += downward;
  rect->left += rightward;
}

bool tickit_rect_intersect(TickitRect *dst, const TickitRect *a, const TickitRect *b)
{
  int top    = maxint(a->top, b->top);
  int bottom = minint(tickit_rect_bottom(a), tickit_rect_bottom(b));

  if(top >= bottom)
    return false;

  int left  = maxint(a->left, b->left);
  int right = minint(tickit_rect_right(a), tickit_rect_right(b));

  if(left >= right)
    return false;

  tickit_rect_init_bounded(dst, top, left, bottom, right);
  return true;
}

bool tickit_rect_intersects(const TickitRect *a, const TickitRect *b)
{
  return a->top < tickit_rect_bottom(b) &&
         b->top < tickit_rect_bottom(a) &&
         a->left < tickit_rect_right(b) &&
         b->left < tickit_rect_right(a);
}

bool tickit_rect_contains(const TickitRect *large, const TickitRect *small)
{
  return (small->top                >= large->top               ) &&
         (tickit_rect_bottom(small) <= tickit_rect_bottom(large)) &&
         (small->left               >= large->left              ) &&
         (tickit_rect_right(small)  <= tickit_rect_right(large) );
}

int tickit_rect_add(TickitRect ret[3], const TickitRect *a, const TickitRect *b)
{
  int a_bottom = tickit_rect_bottom(a);
  int a_right  = tickit_rect_right(a);
  int b_bottom = tickit_rect_bottom(b);
  int b_right  = tickit_rect_right(b);

  if(a->left > b_right  || b->left > a_right ||
      a->top  > b_bottom || b->top  > a_bottom) {
    ret[0] = *a;
    ret[1] = *b;
    return 2;
  }

  int rows[4];
  rows[0] = a->top;
  rows[1] = b->top;
  rows[2] = a_bottom;
  rows[3] = b_bottom;

  /* Since we know top < bottom for each rect individually we can do this
   * better than a plain sort
   */
  if(rows[0] > rows[1]) { int tmp = rows[0]; rows[0] = rows[1]; rows[1] = tmp; } // tops now in order
  if(rows[2] > rows[3]) { int tmp = rows[2]; rows[2] = rows[3]; rows[3] = tmp; } // bottoms now in order
  if(rows[1] > rows[2]) { int tmp = rows[1]; rows[1] = rows[2]; rows[2] = tmp; } // all now in order

  int rects = 0;
  for(int i = 0; i < 3; i++ ) {
    int this_top    = rows[i];
    int this_bottom = rows[i+1];

    // Skip non-unique
    if(this_top == this_bottom)
      continue;

    int has_a = this_top >= a->top && this_bottom <= a_bottom;
    int has_b = this_top >= b->top && this_bottom <= b_bottom;

    int this_left =  (has_a && has_b) ? minint(a->left, b->left) :
                     (has_a)          ? a->left                  :
                                        b->left;

    int this_right = (has_a && has_b) ? maxint(a_right, b_right) :
                     (has_a)          ? a_right                  :
                                        b_right;

    if(rects && ret[rects-1].left == this_left && ret[rects-1].cols == this_right - this_left) {
      ret[rects-1].lines = this_bottom - ret[rects-1].top;
    }
    else {
      tickit_rect_init_bounded(ret + rects, this_top, this_left, this_bottom, this_right);
      rects++;
    }
  }

  return rects;
}

int tickit_rect_subtract(TickitRect ret[5], const TickitRect *orig, const TickitRect *hole)
{
  if(tickit_rect_contains(hole, orig))
    return 0;

  if(!tickit_rect_intersects(hole, orig)) {
    ret[0] = *orig;
    return 1;
  }

  int rects = 0;

  int orig_right = tickit_rect_right(orig);
  int hole_right = tickit_rect_right(hole);

  if(orig->top < hole->top) {
    tickit_rect_init_bounded(ret + rects, orig->top, orig->left, hole->top, orig_right);
    rects++;
  }

  int orig_bottom = tickit_rect_bottom(orig);
  int hole_bottom = tickit_rect_bottom(hole);

  int mid_top    = maxint(orig->top,   hole->top);
  int mid_bottom = minint(orig_bottom, hole_bottom);

  if(orig->left < hole->left) {
    tickit_rect_init_bounded(ret + rects, mid_top, orig->left, mid_bottom, hole->left);
    rects++;
  }

  if(orig_right > hole_right) {
    tickit_rect_init_bounded(ret + rects, mid_top, hole_right, mid_bottom, orig_right);
    rects++;
  }

  if(orig_bottom > hole_bottom) {
    tickit_rect_init_bounded(ret + rects, hole_bottom, orig->left, orig_bottom, orig_right);
    rects++;
  }

  return rects;
}