/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  lebiniou 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

#include <libswscale/swscale.h>
#include "buffer_8bits.h"
#include "brandom.h"

/*!
 * \brief Create a pixel buffer
 */
Buffer8_t *
Buffer8_new(void)
{
  Buffer8_t *buff = xcalloc(1, sizeof(Buffer8_t));

  buff->buffer = xcalloc(BUFFSIZE, sizeof(Pixel_t));

  return buff;
}


Buffer8_t *
Buffer8_clone(const Buffer8_t *src)
{
  Buffer8_t *buff = xmalloc(sizeof(Buffer8_t));

  buff->buffer = xcalloc(BUFFSIZE, sizeof(Pixel_t));
  Buffer8_copy(src, buff);

  return buff;
}


void
Buffer8_delete(Buffer8_t *buff)
{
  if (NULL != buff) {
    xfree(buff->buffer);
    xfree(buff);
  }
}


inline uint8_t
ks_clip_line(short *exists, Point2d_t *p0, Point2d_t *q0, Point2d_t *p, Point2d_t *q)
{
  short x1 = (short)p->x, x2 = (short)q->x, y1 = (short)p->y, y2 = (short)q->y;
  short reg1, reg2;
  short outside = 0;
  *exists = 1;

  ks_region(&reg1, x1, y1);
  ks_region(&reg2, x2, y2);

  if (reg1) {
    ++outside;
  }
  if (reg2) {
    ++outside;
  }

  if (!outside) {
    return 0;
  }

  while (reg1 | reg2) {
    if (reg1 & reg2) {
      *exists = 0;
      return 1;
    }

    if (!reg1) {
      swap(&reg1, &reg2);
      swap(&x1, &x2);
      swap(&y1, &y2);
    }

    if (reg1 & KS_LEFT) {
      y1 += (short)(((y2-y1)*(MINX-x1))/(float)(x2-x1));
      x1 = MINX;
    } else if (reg1 & KS_RIGHT) {
      y1 += (short)(((y2-y1)*(MAXX-x1))/(float)(x2-x1));
      x1 = MAXX;
    } else if (reg1 & KS_ABOVE) {
      x1 += (short)(((x2-x1)*(MAXY-y1))/(float)(y2-y1));
      y1 = MAXY;
    } else if (reg1 & KS_BELOW) {
      x1 += (short)(((x2-x1)*(MINY-y1))/(float)(y2-y1));
      y1 = MINY;
    }

    ks_region(&reg1, x1, y1);
  }

  p0->x = x1;
  p0->y = y1;
  q0->x = x2;
  q0->y = y2;

  return 1;
}


void
draw_line(Buffer8_t *buff, short x1, short y1, short x2, short y2, const Pixel_t c)
{
  short exists = 0;
  Point2d_t p, q, p0, q0;
  short dx, dy, mod;
  char  sgn_dy;

  if ((x1 == x2) && (y1 == y2)) {
    set_pixel(buff, x1, y1, c);
    return;
  };

  p.x = x1;
  p.y = y1;
  q.x = x2;
  q.y = y2;

  if (!ks_clip_line (&exists, &p0, &q0, &p, &q)) {
  } else if (exists) {
    x1 = (short)q0.x;
    y1 = (short)q0.y;
    x2 = (short)p0.x;
    y2 = (short)p0.y;
  } else {
    return;
  }

  /* Bresenham Algorithm */
  if (x1 > x2) {
    swap(&x1, &x2);
    swap(&y1, &y2);
  }

  dx = x2 - x1;

  if (y2 > y1) {
    dy = y2 - y1;
    sgn_dy = 1;
  } else {
    dy = y1 - y2;
    sgn_dy = -1;
  }

  if (dy <= dx) {
    for (mod = -((dx + 1) >> 1); ; x1++, mod += dy) {
      if (mod >/*=*/ 0) {
        y1 += sgn_dy, mod -= dx;
      }

      set_pixel_nc(buff, x1, y1, c);

      if (x1 == x2) {
        return;
      }
    }
  } else {
    for (mod = -((dy + 1) >> 1); ; y1 += sgn_dy, mod += dx) {
      if (mod >/*=*/ 0) {
        x1++, mod -= dy;
      }

      set_pixel_nc(buff, x1, y1, c);

      if (y1 == y2) {
        return;
      }
    }
  }
}


void
draw(Buffer8_t *buff, const Line_t *l, const Pixel_t c)
{
  draw_line(buff, l->x1, l->y1, l->x2, l->y2, c);
}


void
Buffer8_color_bar(Buffer8_t *buff, const uint16_t height)
{
  int i;

  for (i = 0; i < WIDTH; i++) {
    Pixel_t color = (Pixel_t)((float)i / (float)WIDTH * 255.0);
    draw_line(buff, i, 0, i, height, color);
  }
}


void
Buffer8_randomize(Buffer8_t *buff)
{
  Pixel_t *p = buff->buffer;

  for ( ; p < buff->buffer + BUFFSIZE*sizeof(Pixel_t); p++) {
    *p = b_rand_uint32_range(0, 255);
  }
}


void
Buffer8_bandpass(Buffer8_t *s1, const Buffer8_t *s2, const uint8_t min, const uint8_t max)
{
  /* Take pixels from s2 if they are in [min..max] */
  Pixel_t *d = s1->buffer;
  const Pixel_t *s = s2->buffer;

  for ( ; d < s1->buffer + BUFFSIZE*sizeof(Pixel_t); d++, s++) {
    *d = (Pixel_t)(((*s >= min) && (*s <= max)) ? *s : *d);
  }
}


void
Buffer8_overlay(Buffer8_t *s1, const Buffer8_t *s2)
{
  /* Take pixels from s2 if they are != 0 */
  Pixel_t *d = s1->buffer;
  const Pixel_t *s = s2->buffer;

  for ( ; d < s1->buffer + BUFFSIZE*sizeof(Pixel_t); d++, s++) {
    *d = (Pixel_t)((*s) ? *s : *d);
  }
}


void
Buffer8_AND(Buffer8_t *dst, const Buffer8_t *src)
{
  /* AND pixels from dst with pixels from src */
  Pixel_t *d = dst->buffer;
  const Pixel_t *s = src->buffer;

  for ( ; d < dst->buffer + BUFFSIZE*sizeof(Pixel_t); d++, s++) {
    *d &= *s;
  }
}


void
Buffer8_OR(Buffer8_t *dst, const Buffer8_t *src)
{
  /* OR pixels from dst with pixels from src */
  Pixel_t *d = dst->buffer;
  const Pixel_t *s = src->buffer;

  for ( ; d < dst->buffer + BUFFSIZE*sizeof(Pixel_t); d++, s++) {
    *d |= *s;
  }
}


void
Buffer8_XOR(Buffer8_t *dst, const Buffer8_t *src)
{
  /* XOR pixels from dst with pixels from src */
  Pixel_t *d = dst->buffer;
  const Pixel_t *s = src->buffer;

  for ( ; d < dst->buffer + BUFFSIZE*sizeof(Pixel_t); d++, s++) {
    *d ^= *s;
  }
}


void
Buffer8_average(Buffer8_t *s1, const Buffer8_t *s2)
{
  /* mix pixels from s2 with pixels from s1 */
  Pixel_t *d = s1->buffer;
  const Pixel_t *s = s2->buffer;

  for ( ; d < s1->buffer + BUFFSIZE*sizeof(Pixel_t); d++, s++) {
    *d = (Pixel_t)((uint16_t)*d + (uint16_t)*s) >> 1;
  }
}


static uint8_t gray8[256];

void
init_gray8(void)
{
  // rescale Y values (16..235) to full 0..255
  // not all values are used but we want to avoid substracting 16
  // on each pixel :)
  memset(&gray8, 0, 256 * sizeof(uint8_t));
  for (uint8_t i = 16; i <= 235; ++i) {
    gray8[i] = (uint8_t)((i - 16.0) / (235 - 16) * 255);
  }
}


void
Buffer8_YUV_to_full_gray(Buffer8_t *buff)
{
  for (uint32_t i = 0; i < BUFFSIZE; ++i) {
    buff->buffer[i] = gray8[buff->buffer[i]];
  }
}


void
gray_scale(Pixel_t *grey_to, const uint16_t width, const uint16_t height, const Pixel_t *grey_data)
{
  int srcStride[4]= {0,0,0,0};
  int dstStride[4]= {0,0,0,0};
  const uint8_t *srcSlice[4]= {NULL,NULL,NULL,NULL};
  uint8_t *dstSlice[4]= {NULL,NULL,NULL,NULL};
  struct SwsContext *sws_context = NULL;
  int ret;

  sws_context = sws_getContext(width, height, AV_PIX_FMT_GRAY8,
                               WIDTH, HEIGHT, AV_PIX_FMT_GRAY8,
#ifdef __NetBSD__
                               SWS_BILINEAR, NULL, NULL, NULL);
#else
                               SWS_FAST_BILINEAR, NULL, NULL, NULL);
#endif
  if (NULL == sws_context) {
    xerror("sws_getContext\n");
  }

  srcStride[0] = width;
  dstStride[0] = WIDTH;
  srcSlice[0] = (const uint8_t *)grey_data;
  dstSlice[0] = (uint8_t *)grey_to;

  ret = sws_scale(sws_context,
                  srcSlice,
                  srcStride,
                  0,
                  height,
                  dstSlice,
                  dstStride);
  if (ret < 0) {
    xerror("sws_scale\n");
  }

  sws_freeContext(sws_context);
}


void
Buffer8_substract_y(const Buffer8_t *buff1, const Buffer8_t *buff2, const Pixel_t threshold, const Buffer8_t *dst)
{
  Pixel_t *b1 = buff1->buffer;
  Pixel_t *b2 = buff2->buffer;
  Pixel_t *d = dst->buffer;

  /* let's do the simple form for now (check original code below) */
  /* TODO: optimize */
  for (; d < dst->buffer+(BUFFSIZE*sizeof(Pixel_t)); b1++, b2++, d++) {
    *d = (abs(*b1-*b2) > threshold) ? 255 : 0;
  }

#if 0
  unsigned char *image_bgsubtract_y(RGB32 *src) {
    int i;
    int R, G, B;
    RGB32 *p;
    short *q;
    unsigned char *r;
    int v;

    p = src;
    q = (short *)background;
    r = diff;
    for (i=0; i<video_area; i++) {
      R = ((*p)&0xff0000)>>(16-1);
      G = ((*p)&0xff00)>>(8-2);
      B = (*p)&0xff;
      v = (R + G + B) - (int)(*q);
      *r = ((v + y_threshold)>>24) | ((y_threshold - v)>>24);

      p++;
      q++;
      r++;
    }

    return diff;
    /* The origin of subtraction function is;
     * diff(src, dest) = (abs(src - dest) > threshold) ? 0xff : 0;
     *
     * This functions is transformed to;
     * (threshold > (src - dest) > -threshold) ? 0 : 0xff;
     *
     * (v + threshold)>>24 is 0xff when v is less than -threshold.
     * (v - threshold)>>24 is 0xff when v is less than threshold.
     * So, ((v + threshold)>>24) | ((threshold - v)>>24) will become 0xff when
     * abs(src - dest) > threshold.
     */
  }
#endif
}
