/*
 *  Copyright 1994-2022 Olivier Girondel
 *  Copyright 2019-2022 Laurent Marsac
 *
 *  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 "image_filter.h"
#include "constants.h"


/* optimized filters */
#define FILTER_BLUR1_3x3_SIZE 3
#define FILTER_BLUR1_3x3_SUM 8
static uint16_t filter_BLUR1_3x3[3*3] = {
  0, 1, 0,
  1, 4, 1,
  0, 1, 0
};

#define FILTER_BLUR2_3x3_SIZE 3
#define FILTER_BLUR2_3x3_SUM 8
static uint16_t filter_BLUR2_3x3[3*3] = {
  0, 1, 0,
  2, 2, 2,
  0, 1, 0
};

#define FILTER_BLUR3_3x3_SIZE 3
#define FILTER_BLUR3_3x3_SUM 8
static uint16_t filter_BLUR3_3x3[3*3] = {
  0, 2, 0,
  1, 2, 1,
  0, 2, 0
};

#define FILTER_BLUR4_3x3_SIZE 3
#define FILTER_BLUR4_3x3_SUM 32
static uint16_t filter_BLUR4_3x3[3*3] = {
  1, 2,  1,
  2, 16, 2,
  1, 2,  1
};

#define FILTER_VBLUR_3x3_SIZE 3
#define FILTER_VBLUR_3x3_SUM 10
static uint16_t filter_VBLUR_3x3[3*3] = {
  0, 2, 0,
  1, 4, 1,
  0, 2, 0
};

#define FILTER_HBLUR_3x3_SIZE 3
#define FILTER_HBLUR_3x3_SUM 10
static uint16_t filter_HBLUR_3x3[3*3] = {
  0, 1, 0,
  2, 4, 2,
  0, 1, 0
};

#define FILTER_DBLUR1_3x3_SIZE 3
#define FILTER_DBLUR1_3x3_SUM 10
static uint16_t filter_DBLUR1_3x3[3*3] = {
  2, 0, 1,
  0, 4, 0,
  1, 0, 2
};

#define FILTER_DBLUR2_3x3_SIZE 3
#define FILTER_DBLUR2_3x3_SUM 10
static uint16_t filter_DBLUR2_3x3[3*3] = {
  1, 0, 2,
  0, 4, 0,
  2, 0, 1
};

#define FILTER_BLUR_GAUSSIAN_3x3_SIZE 3
#define FILTER_BLUR_GAUSSIAN_3x3_SUM 16
static uint16_t filter_BLUR_GAUSSIAN_3x3[3*3] = {
  1, 2, 1,
  2, 4, 2,
  1, 2, 1
};


void
image_filter_average(Buffer8_t *dst, const Buffer8_t *src, enum FilterType type,
                     enum BorderMode borders, uint16_t size, uint16_t *filter)
{
  assert(NULL != dst);
  assert(NULL != src);

  uint16_t hsize = (size - 1) / 2;
  uint16_t sum_f = 0;

  /* optimized filters and generic version */
  switch (type) {
    case FT_BLUR1_3x3:
      filter = filter_BLUR1_3x3;
      size   = FILTER_BLUR1_3x3_SIZE;
      sum_f  = FILTER_BLUR1_3x3_SUM;

      for (uint16_t j = 1; j < MAXY; j++)
        for (uint16_t i = 1; i < MAXX; i++) {
          uint32_t sum = get_pixel_nc(src, i - 1, j);
          sum += get_pixel_nc(src, i, j - 1);
          sum += get_pixel_nc(src, i, j) * 4;
          sum += get_pixel_nc(src, i, j + 1);
          sum += get_pixel_nc(src, i + 1, j);

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / FILTER_BLUR1_3x3_SUM));
        }
      break;

    case FT_BLUR2_3x3:
      filter = filter_BLUR2_3x3;
      size   = FILTER_BLUR2_3x3_SIZE;
      sum_f  = FILTER_BLUR2_3x3_SUM;

      for (uint16_t j = 1; j < MAXY; j++)
        for (uint16_t i = 1; i < MAXX; i++) {
          uint32_t sum = get_pixel_nc(src, i - 1, j) * 2;
          sum += get_pixel_nc(src, i, j - 1);
          sum += get_pixel_nc(src, i, j) * 2;
          sum += get_pixel_nc(src, i, j + 1);
          sum += get_pixel_nc(src, i + 1, j) * 2;

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / FILTER_BLUR2_3x3_SUM));
        }
      break;

    case FT_BLUR3_3x3:
      filter = filter_BLUR3_3x3;
      size   = FILTER_BLUR3_3x3_SIZE;
      sum_f  = FILTER_BLUR3_3x3_SUM;

      for (uint16_t j = 1; j < MAXY; j++)
        for (uint16_t i = 1; i < MAXX; i++) {
          uint32_t sum = get_pixel_nc(src, i - 1, j);
          sum += get_pixel_nc(src, i, j - 1) * 2;
          sum += get_pixel_nc(src, i, j) * 2;
          sum += get_pixel_nc(src, i, j + 1) * 2;
          sum += get_pixel_nc(src, i + 1, j);

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / FILTER_BLUR3_3x3_SUM));
        }
      break;

    case FT_BLUR4_3x3:
      filter = filter_BLUR4_3x3;
      size   = FILTER_BLUR4_3x3_SIZE;
      sum_f  = FILTER_BLUR4_3x3_SUM;

      for (uint16_t j = 1; j < MAXY; j++)
        for (uint16_t i = 1; i < MAXX; i++) {
          uint32_t sum = get_pixel_nc(src, i - 1, j - 1);
          sum += get_pixel_nc(src, i - 1, j + 1);
          sum += get_pixel_nc(src, i + 1, j - 1);
          sum += get_pixel_nc(src, i + 1, j + 1);

          sum += get_pixel_nc(src, i - 1, j) * 2;
          sum += get_pixel_nc(src, i, j - 1) * 2;
          sum += get_pixel_nc(src, i, j) * 16;
          sum += get_pixel_nc(src, i, j + 1) * 2;
          sum += get_pixel_nc(src, i + 1, j) * 2;

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / FILTER_BLUR4_3x3_SUM));
        }
      break;

    case FT_VBLUR_3x3:
      filter = filter_VBLUR_3x3;
      size   = FILTER_VBLUR_3x3_SIZE;
      sum_f  = FILTER_VBLUR_3x3_SUM;

      for (uint16_t j = 1; j < MAXY; j++)
        for (uint16_t i = 1; i < MAXX; i++) {
          uint32_t sum = get_pixel_nc(src, i - 1, j);
          sum += get_pixel_nc(src, i, j - 1) * 2;
          sum += get_pixel_nc(src, i, j) * 4;
          sum += get_pixel_nc(src, i, j + 1) * 2;
          sum += get_pixel_nc(src, i + 1, j);

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / FILTER_VBLUR_3x3_SUM));
        }
      break;

    case FT_HBLUR_3x3:
      filter = filter_HBLUR_3x3;
      size   = FILTER_HBLUR_3x3_SIZE;
      sum_f  = FILTER_HBLUR_3x3_SUM;

      for (uint16_t j = 1; j < MAXY; j++)
        for (uint16_t i = 1; i < MAXX; i++) {
          uint32_t sum = get_pixel_nc(src, i - 1, j) * 2;
          sum += get_pixel_nc(src, i, j - 1) * 1;
          sum += get_pixel_nc(src, i, j) * 4;
          sum += get_pixel_nc(src, i, j + 1) * 1;
          sum += get_pixel_nc(src, i + 1, j) * 2;

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / FILTER_VBLUR_3x3_SUM));
        }
      break;

    case FT_DBLUR1_3x3:
      filter = filter_DBLUR1_3x3;
      size   = FILTER_DBLUR1_3x3_SIZE;
      sum_f  = FILTER_DBLUR1_3x3_SUM;

      for (uint16_t j = 1; j < MAXY; j++)
        for (uint16_t i = 1; i < MAXX; i++) {
          uint32_t sum = get_pixel_nc(src, i - 1, j - 1) * 2;
          sum += get_pixel_nc(src, i - 1, j + 1);
          sum += get_pixel_nc(src, i, j) * 4;
          sum += get_pixel_nc(src, i + 1, j + 1) * 2;
          sum += get_pixel_nc(src, i + 1, j - 1);

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / FILTER_VBLUR_3x3_SUM));
        }
      break;

    case FT_DBLUR2_3x3:
      filter = filter_DBLUR2_3x3;
      size   = FILTER_DBLUR2_3x3_SIZE;
      sum_f  = FILTER_DBLUR2_3x3_SUM;

      for (uint16_t j = 1; j < MAXY; j++)
        for (uint16_t i = 1; i < MAXX; i++) {
          uint32_t sum = get_pixel_nc(src, i - 1, j - 1);
          sum += get_pixel_nc(src, i - 1, j + 1) * 2;
          sum += get_pixel_nc(src, i, j) * 4;
          sum += get_pixel_nc(src, i + 1, j + 1);
          sum += get_pixel_nc(src, i + 1, j - 1) * 2;

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / FILTER_VBLUR_3x3_SUM));
        }
      break;


    case FT_BLUR_GAUSSIAN_3x3:
      filter = filter_BLUR_GAUSSIAN_3x3;
      size   = FILTER_BLUR_GAUSSIAN_3x3_SIZE;
      sum_f  = FILTER_BLUR_GAUSSIAN_3x3_SUM;

      for (uint16_t j = 1; j < MAXY; j++)
        for (uint16_t i = 1; i < MAXX; i++) {
          uint32_t sum = get_pixel_nc(src, i - 1, j) * 2;
          sum += get_pixel_nc(src, i, j - 1)* 2 ;
          sum += get_pixel_nc(src, i, j) * 4;
          sum += get_pixel_nc(src, i, j + 1) * 2;
          sum += get_pixel_nc(src, i + 1, j) * 2;
          sum += get_pixel_nc(src, i + 1, j + 1);
          sum += get_pixel_nc(src, i + 1, j - 1);
          sum += get_pixel_nc(src, i - 1, j + 1);
          sum += get_pixel_nc(src, i - 1, j - 1);

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / FILTER_BLUR_GAUSSIAN_3x3_SUM));
        }
      break;

    case FT_GENERIC:
      assert(NULL != filter);
      assert(size % 2 == 1);

      for (uint16_t a = 0; a < size; a++)
        for (uint16_t b = 0; b < size; b++) {
          sum_f += filter[size * a + b];
        }

      for (uint16_t j = hsize; j <= MAXY - hsize; j++)
        for (uint16_t i = hsize; i <= MAXX - hsize; i++) {
          uint32_t sum = 0;
          for (uint16_t a = 0; a < size; a++)
            for (uint16_t b = 0; b < size; b++)
              sum += get_pixel_nc(src, i - hsize + b, j - hsize + a) *
                     filter[size * a + b];

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_f));
        }
      break;
  }

  /* handle borders, for any filter type */
  hsize = (size - 1) / 2;

  switch (borders) {
    case BM_CLEAR:
      /* bottom */
      for (uint16_t j = 0; j < hsize; j++)
        for (uint16_t i = 0; i <= MAXX; i++) {
          set_pixel_nc(dst, i, j, (Pixel_t)0);
        }

      /* top */
      for (uint16_t j = MAXY - hsize + 1; j <= MAXY; j++)
        for (uint16_t i = 0; i <= MAXX; i++) {
          set_pixel_nc(dst, i, j, (Pixel_t)0);
        }

      for (uint16_t j = 0; j <= MAXY; j++) {
        /* left */
        for (uint16_t i = 0; i < hsize; i++) {
          set_pixel_nc(dst, i, j, (Pixel_t)0);
        }

        /* right */
        for (uint16_t i = MAXX - hsize + 1; i <= MAXX; i++) {
          set_pixel_nc(dst, i, j, (Pixel_t)0);
        }
      }
      break;

    /* blurring done using only real pixels arround (i,j) */
    case BM_LOCAL:
      /* left */
      for (uint16_t j = hsize; j <= MAXY - hsize; j++)
        for (uint16_t i = 0; i < hsize; i++) {
          uint32_t sum = 0, sum_fl = 0;

          for (uint16_t b = 0; b < size; b++) {
            short i2 = i - hsize + b;
            if (i2 >= 0)
              for (uint16_t a = 0; a < size; a++) {
                sum += get_pixel_nc(src, i2, j - hsize + a) *
                       filter[size * a + b];
                sum_fl += filter[size * a + b];
              }
          }

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_fl));
        }

      /* right */
      for (uint16_t j = hsize; j <= MAXY - hsize; j++)
        for (uint16_t i = MAXX - hsize + 1; i <= MAXX; i++) {
          uint32_t sum = 0, sum_fl = 0;

          for (uint16_t b = 0; b < size; b++) {
            short i2 = i - hsize + b;
            if (i2 <= MAXX)
              for (uint16_t a = 0; a < size; a++) {
                sum += get_pixel_nc(src, i2, j - hsize + a) *
                       filter[size * a + b];
                sum_fl += filter[size * a + b];
              }
          }

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_fl));
        }

      /* bottom */
      for (uint16_t j = 0; j < hsize; j++)
        for (uint16_t i = hsize; i <= MAXX - hsize; i++) {
          uint32_t sum = 0, sum_fl = 0;

          for (uint16_t a = 0; a < size; a++) {
            short j2 = j - hsize + a;
            if (j2 >= 0)
              for (uint16_t b = 0; b < size; b++) {
                sum += get_pixel_nc(src, i - hsize + b, j2) *
                       filter[size * a + b];
                sum_fl += filter[size * a + b];
              }
          }

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_fl));
        }

      /* top */
      for (uint16_t j = MAXY - hsize + 1; j <= MAXY; j++)
        for (uint16_t i = hsize; i <= MAXX - hsize; i++) {
          uint32_t sum = 0, sum_fl = 0;

          for (uint16_t a = 0; a < size; a++) {
            short j2 = j - hsize + a;
            if (j2 <= MAXY)
              for (uint16_t b = 0; b < size; b++) {
                sum += get_pixel_nc(src, i - hsize + b, j2) *
                       filter[size * a + b];
                sum_fl += filter[size * a + b];
              }
          }
          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_fl));
        }

      /* corners */
      for (uint16_t bx = 0, b0 = hsize; bx <= MAXX; bx += MAXX, b0 -= hsize)
        for (uint16_t by = 0, a0 = hsize; by <= MAXY; by += MAXY, a0 -= hsize) {
          uint32_t sum = 0, sum_fl = 0;
          for (uint16_t a = a0; a <= a0 + hsize; a++)
            for (uint16_t b = b0; b <= b0 + hsize; b++) {
              sum += get_pixel_nc(src, bx + b - hsize, by + a - hsize) *
                     filter[size * a + b];
              sum_fl += filter[size * a + b];
            }
          set_pixel_nc(dst, bx, by, (Pixel_t)(sum / sum_fl));
        }

      break;

    /* blurring done as if border were touching (left/right and top/bottom) */
    case BM_TOROIDAL:
      /* left */
      for (uint16_t j = hsize; j <= MAXY - hsize; j++)
        for (uint16_t i = 0; i < hsize; i++) {
          uint32_t sum = 0;

          for (uint16_t b = 0; b < size; b++) {
            short i2 = i - hsize + b;
            if (i2 < 0) {
              i2 = WIDTH + i2;
            }

            for (uint16_t a = 0; a < size; a++)
              sum += get_pixel_nc(src, i2, j - hsize + a) *
                     filter[size * a + b];
          }

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_f));
        }

      /* right */
      for (uint16_t j = hsize; j <= MAXY - hsize; j++)
        for (uint16_t i = MAXX - hsize + 1; i <= MAXX; i++) {
          uint32_t sum = 0;

          for (uint16_t b = 0; b < size; b++) {
            short i2 = i - hsize + b;
            if (i2 > MAXX) {
              i2 = i2 - MAXX - 1;
            }

            for (uint16_t a = 0; a < size; a++)
              sum += get_pixel_nc(src, i2, j - hsize + a) *
                     filter[size * a + b];
          }

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_f));
        }

      /* bottom */
      for (uint16_t j = 0; j < hsize; j++)
        for (uint16_t i = hsize; i <= MAXX - hsize; i++) {
          uint32_t sum = 0;

          for (uint16_t a = 0; a < size; a++) {
            short j2 = j - hsize + a;
            if (j2 < 0) {
              j2 = HEIGHT + j2;
            }

            for (uint16_t b = 0; b < size; b++)
              sum += get_pixel_nc(src, i - hsize + b, j2) *
                     filter[size * a + b];
          }

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_f));
        }

      /* top */
      for (uint16_t j = MAXY - hsize + 1; j <= MAXY; j++)
        for (uint16_t i = hsize; i <= MAXX - hsize; i++) {
          uint32_t sum = 0;

          for (uint16_t a = 0; a < size; a++) {
            short j2 = j - hsize + a;
            if (j2 > MAXY) {
              j2 = j2 - MAXY - 1;
            }

            for (uint16_t b = 0; b < size; b++)
              sum += get_pixel_nc(src, i - hsize + b, j2) *
                     filter[size * a + b];
          }

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_f));
        }

      /* corners */
      for (uint16_t i = 0; i <= MAXX; i += MAXX)
        for (uint16_t j = 0; j <= MAXY; j += MAXY) {
          uint32_t sum = 0;

          for (uint16_t a = 0; a < size; a++)
            for (uint16_t b = 0; b < size; b++) {
              uint16_t i2 = (i - hsize + b + WIDTH ) % WIDTH;
              uint16_t j2 = (j - hsize + a + HEIGHT) % HEIGHT;

              sum += get_pixel_nc(src, i2, j2) * filter[size * a + b];
            }

          set_pixel_nc(dst, i, j, (Pixel_t)(sum / sum_f));
        }

      break;

    case BM_NONE:
    default:
      break;
  }
}
