#define IMAGER_NO_CONTEXT
#include "imager.h"
#include "imageri.h"

IMAGER_STATIC_INLINE int
match_one_color(const i_sample_t *testme, const i_trim_colors_t *test) {
  if (test->c1.channel[0] > testme[0] || test->c2.channel[0] < testme[0])
    return 0;
  if (test->c1.channel[1] > testme[1] || test->c2.channel[1] < testme[1])
    return 0;
  if (test->c1.channel[2] > testme[2] || test->c2.channel[2] < testme[2])
    return 0;
  return 1;
}

IMAGER_STATIC_INLINE int
match_one_fcolor(const i_fsample_t *testme, const i_trim_colors_t *test) {
  if (test->cf1.channel[0] > testme[0] || test->cf2.channel[0] < testme[0])
    return 0;
  if (test->cf1.channel[1] > testme[1] || test->cf2.channel[1] < testme[1])
    return 0;
  if (test->cf1.channel[2] > testme[2] || test->cf2.channel[2] < testme[2])
    return 0;
  return 1;
}

IMAGER_STATIC_INLINE int
match_any_color(const i_sample_t *testme, const i_trim_colors_t *tests, int count) {
  int i;
  for (i = 0; i < count; ++i) {
    if (match_one_color(testme, tests+i))
      return 1;
  }
  return 0;
}

IMAGER_STATIC_INLINE int
match_any_fcolor(const i_fsample_t *testme, const i_trim_colors_t *tests, int count) {
  int i;
  for (i = 0; i < count; ++i) {
    if (match_one_fcolor(testme, tests+i))
      return 1;
  }
  return 0;
}

#!define MATCH_ANY_COLOR match_any_color match_any_fcolor

static const int gray_chans[4] = { 0, 0, 0, 1 };

#define TEST_COLOR(s) 					    \
  (color_count && MATCH_ANY_COLOR((s), colors, color_count) ||	\
   check_alpha && (s)[3] <= work_threshold)

static int
trim_rect_simple(i_img *im, double transp_threshold, int color_count,
		 const i_trim_colors_t *colors, i_img_dim *left, i_img_dim *top,
		 i_img_dim *right, i_img_dim *bottom) {
  const int color_chans = i_img_color_channels(im);
  const int *chans = color_chans == 1 ? gray_chans : NULL;
  const int has_alpha = i_img_has_alpha(im);
  const int check_alpha = has_alpha && transp_threshold < 1.0;
  const int chan_count = check_alpha ? 4 : 3;
  i_img_dim x, y;
#code im->bits <= 8
  IM_SAMPLE_T *samps = mymalloc(sizeof(IM_SAMPLE_T) * im->xsize * chan_count);
#if IM_EIGHT_BIT
  const IM_WORK_T work_threshold = floor(IM_SAMPLE_MAX * transp_threshold);
#else
  const IM_WORK_T work_threshold = transp_threshold;
#endif

  /* scan down from top */
  for (y = 0; y < im->ysize; ++y) {
    IM_SAMPLE_T *s;
    IM_GSAMP(im, 0, im->xsize, y, samps, chans, chan_count);
    for (x = 0, s = samps; x < im->xsize; ++x, s += chan_count) {
      if (!TEST_COLOR(s))
	break;
    }
    if (x < im->xsize)
      break;
  }
  *top = y;
  if (y < im->ysize) {
    /* scan from the bottom */
    for (y = im->ysize-1; y >= 0; --y) {
      IM_SAMPLE_T *s;
      IM_GSAMP(im, 0, im->xsize, y, samps, chans, chan_count);
      for (x = 0, s = samps; x < im->xsize; ++x, s += chan_count) {
	if (!TEST_COLOR(s))
	  break;
      }
      if (x < im->xsize)
	break;
    }
    *bottom = im->ysize - y - 1;
    
    /* we've trimmed top and bottom, now the sides */
    *left = *right = im->xsize;
    for (y = *top; y < im->ysize - *bottom && (*left || *right); ++y) {
      IM_SAMPLE_T *s;
      IM_GSAMP(im, 0, im->xsize, y, samps, chans, chan_count);
      for (x = 0, s = samps; x < *left; ++x, s += chan_count) {
	if (!TEST_COLOR(s)) {
	  *left = x;
	  break;
	}
      }
      for (x = im->xsize - 1, s = samps + chan_count * im->xsize;
	   x >= im->xsize - *right; --x, s -= chan_count) {
	if (!TEST_COLOR(s-chan_count)) {
	  *right = im->xsize - x - 1;
	  break;
	}
      }
    }
  }
  else {
    /* whole image can be trimmed */
    *left = im->xsize;
    *right = *bottom = 0;
  }
  myfree(samps);
#/code

  return 1;
}

int
i_trim_rect(i_img *im, double transp_threshold, int color_count, const i_trim_colors_t *colors,
	    i_img_dim *left, i_img_dim *top, i_img_dim *right, i_img_dim *bottom) {
  dIMCTXim(im);
  i_trim_colors_t *tcolors = NULL;
  i_trim_colors_t *tcolorp;
  const i_trim_colors_t *colorp;
  int tcolor_count = 0;
  int result;

  if (color_count) {
    tcolors = mymalloc(sizeof(i_trim_colors_t) * color_count);
    tcolorp = tcolors;
    /* convert 8-bit to float colors, or float colors to 8-bit depending on the
       image type.
    */
    if (im->bits <= 8) {
      int i, ch;
      for (i = 0, colorp = colors; i < color_count; ++i, ++colorp) {
	if (colorp->is_float) {
	  for (ch = 0; ch < 3; ++ch) {
	    tcolorp->c1.channel[ch] = ceil(colorp->cf1.channel[ch] * 255);
	    tcolorp->c2.channel[ch] = floor(colorp->cf2.channel[ch] * 255);
	  }
	}
	else {
	  *tcolorp = *colorp;
	}
	for (ch = 0; ch < 3; ++ch) {
	  if (tcolorp->c1.channel[ch] > tcolorp->c2.channel[ch])
	    break;
	}
	if (ch == 3) {
	  ++tcolorp, ++tcolor_count;
	}
      }
      /* TODO optimize where the image is greyscale to remove color ranges that don't
	 overlap the greyscale line
      */
    }
    else {
      int i, ch;
      for (i = 0, colorp = colors; i < color_count; ++i, ++colorp) {
	if (!colorp->is_float) {
	  for (ch = 0; ch < 3; ++ch) {
	    tcolorp->cf1.channel[ch] = colorp->c1.channel[ch] / 255.0;
	    tcolorp->cf2.channel[ch] = colorp->c2.channel[ch] / 255.0;
	  }
	}
	else {
	  *tcolorp = *colors;
	}
	for (ch = 0; ch < 3; ++ch) {
	  if (tcolorp->cf1.channel[ch] > tcolorp->cf2.channel[ch])
	    break;
	}
	if (ch == 3) {
	  ++tcolorp, ++tcolor_count;
	}
      }
      /* TODO optimize where the image is greyscale to remove color ranges that don't
	 overlap the greyscale line
      */
    }
  }
  
  i_clear_error();

  if (transp_threshold > 1.0 && tcolor_count == 0) {
    /* nothing to do */
    *left = *top = *right = *bottom = 0;
    result = 1;
  }
  else {
    result = trim_rect_simple(im, transp_threshold, tcolor_count, tcolors, left, top,
				right, bottom);
  }

  myfree(tcolors);
  return result;
}