/*
 *  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/>.
 */

#ifndef __BINIOU_BUFFER_8BITS_H
#define __BINIOU_BUFFER_8BITS_H

#include "utils.h"
#include "constants.h"
#include "point2d.h"

#define PIXEL_MINVAL       ((Pixel_t) (0))
#define PIXEL_MAXVAL       ((Pixel_t) (~0))
#define PIXEL_TRANSPARENT  (PIXEL_MINVAL)

/**
 * \typedef Pixel_t
 * \brief A pixel (color index in a RGBA colormap)
 */
typedef uint8_t Pixel_t;

typedef struct Line_s {
  short x1;
  short y1;
  short x2;
  short y2;
} Line_t;

/* Alias */
typedef struct Buffer8_s {
  Pixel_t *buffer;
} Buffer8_t;


Buffer8_t *Buffer8_new(void);
Buffer8_t *Buffer8_clone(const Buffer8_t *);
void Buffer8_delete(Buffer8_t *);


/* Pixel operations */
static inline Pixel_t
get_pixel_nc(const Buffer8_t *buf, const short x, const short y)
{
#ifdef DEBUG
  if ((x < MINX) || (y < MINY) || (x > MAXX) || (y > MAXY)) {
    if (NULL == getenv("LEBINIOU_NO_CRASH_ON_PIXEL_FUNCTIONS")) {
      xerror("want to get_pixel_nc(%d, %d)\n", x, y);
    } else {
      xdebug("want to get_pixel_nc(%d, %d)\n", x, y);
    }
  }
#endif /* DEBUG */

  return buf->buffer[y*WIDTH+x];
}


static inline void
set_pixel_nc(Buffer8_t *buf, const short x, const short y, const Pixel_t col)
{
#ifdef DEBUG
  if ((x < MINX) || (y < MINY) || (x > MAXX) || (y > MAXY)) {
    if (NULL == getenv("LEBINIOU_NO_CRASH_ON_PIXEL_FUNCTIONS")) {
      xerror("want to set_pixel_nc(%d, %d, %d)\n", x, y, col);
    } else {
      xdebug("want to set_pixel_nc(%d, %d, %d)\n", x, y, col);
    }
  }
#endif /* DEBUG */

  buf->buffer[y*WIDTH+x] = col;
}


static inline void
neg_pixel_nc(Buffer8_t *buf, const short x, const short y)
{
#ifdef DEBUG
  if ((x < MINX) || (y < MINY) || (x > MAXX) || (y > MAXY)) {
    if (NULL == getenv("LEBINIOU_NO_CRASH_ON_PIXEL_FUNCTIONS")) {
      xerror("want to neg_pixel_nc(%d, %d)\n", x, y);
    } else {
      xdebug("want to neg_pixel_nc(%d, %d)\n", x, y);
    }
  }
#endif /* DEBUG */

  buf->buffer[y*WIDTH+x] = (Pixel_t)(255-buf->buffer[y*WIDTH+x]);
}


static inline Pixel_t
get_pixel(const Buffer8_t *buff, const short x, const short y)
{
  /* if ((x>=MINX) && (x<=MAXX) && (y>=MINY) && (y<=MAXY)) */
  if (((unsigned)(x-MINX) < (WIDTH-MINX)) && ((unsigned)(y-MINY) < (HEIGHT-MINY))) {
    return get_pixel_nc(buff, x, y);
  } else {
    return 0;
  }
}


static inline void
set_pixel(Buffer8_t *buff, const short x, const short y, const Pixel_t col)
{
  /* if ((x>=MINX) && (x<=MAXX) && (y>=MINY) && (y<=MAXY)) */
  if (((unsigned)(x-MINX) < (WIDTH-MINX)) && ((unsigned)(y-MINY) < (HEIGHT-MINY))) {
    set_pixel_nc(buff, x, y, col);
  }
}


static inline void
neg_pixel(Buffer8_t *buff, const short x, const short y)
{
  /* if ((x>=MINX) && (x<=MAXX) && (y>=MINY) && (y<=MAXY)) */
  if (((unsigned)(x-MINX) < (WIDTH-MINX)) && ((unsigned)(y-MINY) < (HEIGHT-MINY))) {
    neg_pixel_nc(buff, x, y);
  }
}


/* Line operations */
static inline void
h_line_nc(Buffer8_t *buff, const short lig, const short start, const short end, const Pixel_t c)
{
#if 1
  int s, e, n;

  assert(lig >= MINY);
  assert(lig <= MAXY);
  assert(start >= MINX);
  assert(start <= MAXX);
  assert(end >= MINX);
  assert(end <= MAXX);

  if (start <= end) {
    s = start;
    e = end;
  } else {
    s = end;
    e = start;
  }

  n = e-s+1;
  memset((void *)&buff->buffer[lig*WIDTH+s], (int)c, n*sizeof(Pixel_t));
#else
  short i;

  for (i = start; i <= end; i++) {
    set_pixel_nc(buff, i, lig, c);
  }
#endif
}


static inline void
h_line(Buffer8_t *buff, const short lig, const short start, const short end, const Pixel_t c)
{
#if 1
  int s, e, n;

  assert(lig >= MINY);
  assert(lig <= MAXY);
  assert(start >= MINX);
  assert(start <= MAXX);
  assert(end >= MINX);
  assert(end <= MAXX);

  if (start <= end) {
    s = start;
    e = end;
  } else {
    s = end;
    e = start;
  }

  n = e-s+1;
  memset((void *)&buff->buffer[lig*WIDTH+s], (int)c, n*sizeof(Pixel_t));
#else
  short i;

  for (i = start; i <= end; i++) {
    set_pixel(buff, i, lig, c);
  }
#endif
}


static inline void
v_line_nc(Buffer8_t *buff, const short col, const short start, const short end, const Pixel_t c)
{
  short s, e, j;

  if (start <= end) {
    s = start;
    e = end;
  } else {
    s = end;
    e = start;
  }

  for (j = s; j <= e; j++) {
    set_pixel_nc(buff, col, j, c);
  }
}


static inline void
v_line(Buffer8_t *buff, const short col, const short start, const short end, const Pixel_t c)
{
  short s, e, j;

  if (start <= end) {
    s = start;
    e = end;
  } else {
    s = end;
    e = start;
  }

  for (j = s; j <= e; j++) {
    set_pixel(buff, col, j, c);
  }
}


static inline void
Buffer8_clear(Buffer8_t *buff)
{
  memset(buff->buffer, 0, BUFFSIZE*sizeof(Pixel_t));
}


static inline void
Buffer8_clear_border(Buffer8_t *buff)
{
  h_line_nc(buff, MINY, MINX, MAXX, PIXEL_MINVAL);
  h_line_nc(buff, MAXY, MINX, MAXX, PIXEL_MINVAL);
  v_line_nc(buff, MINX, MINY, MAXY, PIXEL_MINVAL);
  v_line_nc(buff, MAXX, MINY, MAXY, PIXEL_MINVAL);
}


static inline void
Buffer8_copy(const Buffer8_t *from, Buffer8_t *to)
{
  memcpy(to->buffer, from->buffer, BUFFSIZE*sizeof(Pixel_t));
}


static inline void
Buffer8_add(const Buffer8_t *from, Buffer8_t *to, const Pixel_t min)
{
  uint32_t i;
  for (i = 0; i < BUFFSIZE*sizeof(Pixel_t); i++)
    if (from->buffer[i] > min) {
      to->buffer[i] = from->buffer[i];
    }
}


void
Buffer8_color_bar(Buffer8_t *, const uint16_t);

static inline void
swap(short *a, short *b)
{
  short t;

  t = *a;
  *a = *b;
  *b = t;
}


static inline void
draw_box(Buffer8_t *buff, short x1, short y1, short x2, short y2, const Pixel_t c)
{
  if (x1 < 0) {
    x1 = 0;
  } else if (x1 > MAXX) {
    x1 = MAXX;
  }

  if (x2 < 0) {
    x2 = 0;
  } else if (x2 > MAXX) {
    x2 = MAXX;
  }

  if (y1 < 0) {
    y1 = 0;
  } else if (y1 > MAXY) {
    y1 = MAXY;
  }

  if (y2 < 0) {
    y2 = 0;
  } else if (y2 > MAXY) {
    y2 = MAXY;
  }

  if (x1 > x2) {
    swap(&x1, &x2);
  }
  if (y1 > y2) {
    swap(&y1, &y2);
  }

  h_line(buff, y1, x1, x2, c);
  h_line(buff, y2, x1, x2, c);
  v_line(buff, x1, y1, y2, c);
  v_line(buff, x2, y1, y2, c);
}


static inline void
draw_box_nc(Buffer8_t *buff, short x1, short y1, short x2, short y2, const Pixel_t c)
{
  if (x1 > x2) {
    swap(&x1, &x2);
  }
  if (y1 > y2) {
    swap(&y1, &y2);
  }

  h_line_nc(buff, y1, x1, x2, c);
  h_line_nc(buff, y2, x1, x2, c);
  v_line_nc(buff, x1, y1, y2, c);
  v_line_nc(buff, x2, y1, y2, c);
}


static inline void
draw_filled_box(Buffer8_t *b, short x1, short y1, short x2, short y2, const Pixel_t c)
{
  short j;

  if (x1 > x2) {
    swap(&x1, &x2);
  }
  if (y1 > y2) {
    swap(&y1, &y2);
  }

  for (j = y1; j <= y2; j++) {
    h_line(b, j, x1, x2, c);
  }
}


static inline void
draw_filled_box_nc(Buffer8_t *b, short x1, short y1, short x2, short y2, const Pixel_t c)
{
  short j;

  if (x1 > x2) {
    swap(&x1, &x2);
  }
  if (y1 > y2) {
    swap(&y1, &y2);
  }

  for (j = y1; j <= y2; j++) {
    h_line_nc(b, j, x1, x2, c);
  }
}



/* Kohen-Sutherland clipping */
#define KS_LEFT  0x01
#define KS_RIGHT 0x02
#define KS_ABOVE 0x04
#define KS_BELOW 0x08


static inline void
ks_region(short *reg, const short x, const short y)
{
  *reg = 0;

  if (x > MAXX) {
    *reg |= KS_RIGHT;
  } else if (x < MINX) {
    *reg |= KS_LEFT;
  }

  if (y > MAXY) {
    *reg |= KS_ABOVE;
  } else if (y < MINY) {
    *reg |= KS_BELOW;
  }
}


uint8_t ks_clip_line(short *, Point2d_t *, Point2d_t *, Point2d_t *, Point2d_t *);
void draw_line(Buffer8_t *, short, short, short, short, const Pixel_t);
void draw(Buffer8_t *, const Line_t *, const Pixel_t);


static inline void
Buffer8_init_mask_3x3(Buffer8_t *buff)
{
  short i, j;

  for (i = 1; i < MAXX; i++) {
    /* copy hor bottom */
    set_pixel_nc(buff, i, 0, get_pixel_nc(buff, i, MAXY - 1));
    /* copy hor top */
    set_pixel_nc(buff, i, MAXY, get_pixel_nc(buff, i, 1));
  }

  for (j = 1; j < MAXY; j++) {
    /* copy ver right */
    set_pixel_nc(buff, 0, j, get_pixel_nc(buff, MAXX - 1, j));
    /* copy ver left */
    set_pixel_nc(buff, MAXX, j, get_pixel_nc(buff, 1, j));
  }

  /* copy 4 corners */
  set_pixel_nc(buff, 0, 0, get_pixel_nc(buff, MAXX - 1, MAXY - 1));
  set_pixel_nc(buff, MAXX, 0, get_pixel_nc(buff, 1, MAXY - 1));
  set_pixel_nc(buff, 0, MAXY, get_pixel_nc(buff, MAXX - 1, 1));
  set_pixel_nc(buff, MAXX, MAXY, get_pixel_nc(buff, 1, 1));
}


static inline void
Buffer8_expand_border(Buffer8_t *buff)
{
  short i, j;

  /* ok we cheat a little little little bit to save 4 annoying corner pixels:
   * horizontally we set from MINX to MAXX
   * vertically we set from MINY+1 to MAXY-1
   */

  /* set bottom and top lines */
  /* we use +-2 since most blurs only act within that range */
  for (i = MINX; i <= MAXX; i++) {
    Pixel_t sum = (Pixel_t)((get_pixel_nc(buff, i, MINY+2)
                             + get_pixel_nc(buff, i, MAXY-2)) >> 1);
    set_pixel_nc(buff, i, MINY, sum);
    set_pixel_nc(buff, i, MAXY, sum);
  }

  /* set left and right */
  for (j = MINY+1; j < MAXY; j++) {
    Pixel_t sum = (Pixel_t)((get_pixel_nc(buff, MINX+2, j)
                             + get_pixel_nc(buff, MAXX-2, j)) >> 1);
    set_pixel_nc(buff, MINX, j, sum);
    set_pixel_nc(buff, MAXX, j, sum);
  }
}

void Buffer8_randomize(Buffer8_t *);

void Buffer8_bandpass(Buffer8_t *, const Buffer8_t *, const uint8_t, const uint8_t);
void Buffer8_overlay(Buffer8_t *, const Buffer8_t *);
void Buffer8_AND(Buffer8_t *, const Buffer8_t *);
void Buffer8_OR(Buffer8_t *, const Buffer8_t *);
void Buffer8_XOR(Buffer8_t *, const Buffer8_t *);
void Buffer8_average(Buffer8_t *, const Buffer8_t *);


static inline void
Buffer8_flip_v(Buffer8_t *buff)
{
  uint16_t j;

  for (j = 0; j < HHEIGHT; j++) {
    Pixel_t tmp[WIDTH];

    memcpy(tmp, &buff->buffer[j*WIDTH], WIDTH * sizeof(Pixel_t));
    memcpy(&buff->buffer[j*WIDTH], &buff->buffer[(MAXY-j)*WIDTH], WIDTH * sizeof(Pixel_t));
    memcpy(&buff->buffer[(MAXY-j)*WIDTH], tmp, WIDTH * sizeof(Pixel_t));
  }
}


static inline void
Buffer8_flip_h(Buffer8_t *buff)
{
  uint16_t j;

  for (j = 0; j < HEIGHT; j++) {
    uint16_t i;

    for (i = 0; i < HWIDTH; i++) {
      Pixel_t tmp;

      tmp = buff->buffer[j*WIDTH+i];
      buff->buffer[j*WIDTH+i] = buff->buffer[j*WIDTH+WIDTH-i-1];
      buff->buffer[j*WIDTH+WIDTH-i-1] = tmp;
    }
  }
}


void gray_scale(Pixel_t *, const uint16_t, const uint16_t, const Pixel_t *);
void init_gray8(void);
void Buffer8_YUV_to_full_gray(Buffer8_t *);
void Buffer8_substract_y(const Buffer8_t *, const Buffer8_t *, const Pixel_t, const Buffer8_t *);

#endif /* __BINIOU_BUFFER_8BITS_H */
