/* This file is an image processing operation for GEGL
 *
 * GEGL is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * GEGL 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with GEGL; if not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 1997 Lauri Alanko <la@iki.fi>
 * Copyright 2011 Robert Sasu (sasu.robert@gmail.com)
 */

#include "config.h"
#include <glib/gi18n-lib.h>

#ifdef GEGL_CHANT_PROPERTIES

gegl_chant_double (a1, _("(1,1) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (1,1)"))
gegl_chant_double (a2, _("(1,2) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (1,2)"))
gegl_chant_double (a3, _("(1,3) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (1,3)"))
gegl_chant_double (a4, _("(1,4) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (1,4)"))
gegl_chant_double (a5, _("(1,5) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (1,5)"))
gegl_chant_double (b1, _("(2,1) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (2,1)"))
gegl_chant_double (b2, _("(2,2) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (2,2)"))
gegl_chant_double (b3, _("(2,3) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (2,3)"))
gegl_chant_double (b4, _("(2,4) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (2,4)"))
gegl_chant_double (b5, _("(2,5) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (2,5)"))
gegl_chant_double (c1, _("(3,1) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (3,1)"))
gegl_chant_double (c2, _("(3,2) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (3,2)"))
gegl_chant_double (c3, _("(3,3) "), -G_MAXINT, G_MAXINT, 1.0,
                   _("Value of the element in position (3,3)"))
gegl_chant_double (c4, _("(3,4) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (3,4)"))
gegl_chant_double (c5, _("(3,5) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (3,5)"))
gegl_chant_double (d1, _("(4,1) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (4,1)"))
gegl_chant_double (d2, _("(4,2) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (4,2)"))
gegl_chant_double (d3, _("(4,3) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (4,3)"))
gegl_chant_double (d4, _("(4,4) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (4,4)"))
gegl_chant_double (d5, _("(4,5) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (4,5)"))
gegl_chant_double (e1, _("(5,1) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (5,1)"))
gegl_chant_double (e2, _("(5,2) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (5,2)"))
gegl_chant_double (e3, _("(5,3) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (5,3)"))
gegl_chant_double (e4, _("(5,4) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (5,4)"))
gegl_chant_double (e5, _("(5,5) "), -G_MAXINT, G_MAXINT, 0.0,
                   _("Value of the element in position (5,5)"))

gegl_chant_double (div, _("Divisor: "), -G_MAXINT, G_MAXINT, 1.0,
                   _("The value of the divisor"))
gegl_chant_double (off, _("Offset: "), -1.0, 1.0, 0.0,
                   _("The value of the offset"))

gegl_chant_boolean (norm, _("Normalize"), TRUE, _("Normalize or not"))

gegl_chant_boolean (red, _("Red"), TRUE, _("Red channel"))
gegl_chant_boolean (green, _("Green"), TRUE, _("Green channel"))
gegl_chant_boolean (blue, _("Blue"), TRUE, _("Blue channel"))
gegl_chant_boolean (alpha, _("Alpha"), TRUE, _("Alpha channel"))

gegl_chant_boolean (weight, _("Alpha-weighting"), TRUE, _("Alpha weighting"))

gegl_chant_string (border, _("Border"), "extend",
                   _("Type of border to choose. "
                     "Choices are extend, wrap, crop. "
                     "Default is extend"))

#else

#define GEGL_CHANT_TYPE_AREA_FILTER
#define GEGL_CHANT_C_FILE        "convolution-matrix.c"

#include "gegl-chant.h"
#include <math.h>
#include <stdio.h>

#define RESPONSE_RESET 1


#define BIG_MATRIX  /* toggle for 11x11 matrix code experimental*/
#undef BIG_MATRIX


#ifndef BIG_MATRIX
#define MATRIX_SIZE   (5)
#else
#define MATRIX_SIZE   (11)
#endif

#define HALF_WINDOW   (MATRIX_SIZE/2)
#define MATRIX_CELLS  (MATRIX_SIZE*MATRIX_SIZE)
#define DEST_ROWS     (MATRIX_SIZE/2 + 1)
#define CHANNELS      (5)
#define BORDER_MODES  (3)

static void
prepare (GeglOperation *operation)
{
  GeglOperationAreaFilter *op_area = GEGL_OPERATION_AREA_FILTER (operation);

  op_area->left = op_area->right = op_area->top = op_area->bottom = HALF_WINDOW;

  gegl_operation_set_format (operation, "output",
                             babl_format ("RGBA float"));
}

static void
make_matrix (GeglChantO  *o,
             gdouble    **matrix)
{
  matrix[0][0] = o->a1;
  matrix[0][1] = o->a2;
  matrix[0][2] = o->a3;
  matrix[0][3] = o->a4;
  matrix[0][4] = o->a5;

  matrix[1][0] = o->b1;
  matrix[1][1] = o->b2;
  matrix[1][2] = o->b3;
  matrix[1][3] = o->b4;
  matrix[1][4] = o->b5;

  matrix[2][0] = o->c1;
  matrix[2][1] = o->c2;
  matrix[2][2] = o->c3;
  matrix[2][3] = o->c4;
  matrix[2][4] = o->c5;

  matrix[3][0] = o->d1;
  matrix[3][1] = o->d2;
  matrix[3][2] = o->d3;
  matrix[3][3] = o->d4;
  matrix[3][4] = o->d5;

  matrix[4][0] = o->e1;
  matrix[4][1] = o->e2;
  matrix[4][2] = o->e3;
  matrix[4][3] = o->e4;
  matrix[4][4] = o->e5;
}

static void
normalize_o (GeglChantO  *o,
             gdouble    **matrix)
{
  gint      x, y;
  gboolean  valid = FALSE;
  gfloat    sum   = 0.0;

  for (y = 0; y < MATRIX_SIZE; y++)
    for (x = 0; x < MATRIX_SIZE; x++)
      {
        sum += matrix[x][y];
        if (matrix[x][y] != 0.0)
          valid = TRUE;
      }

  if (sum > 0)
    {
      o->off = 0.0;
      o->div = sum;
    }
  else if (sum < 0)
    {
      o->off = 1.0;
      o->div = -sum;
    }
  else
    {
      o->off = 0.5;
      o->div = 1;
    }

}

static void
convolve_pixel(gfloat               *src_buf,
               gfloat               *dst_buf,
               const GeglRectangle  *result,
               const GeglRectangle  *extended,
               const GeglRectangle  *boundary,
               gdouble             **matrix,
               GeglChantO           *o,
               GeglBuffer           *input,
               gint                  xx,
               gint                  yy,
               gdouble               matrixsum)
{
  gint    i, x, y, temp, s_x, s_y;
  gdouble sum;
  gfloat  color[4];
  gint    d_offset, s_offset;
  gint    half;

  gdouble alphasum  = 0.0;

  s_x = 0;
  s_y = 0;

  half = (MATRIX_SIZE / 2) + (MATRIX_SIZE % 2);

  d_offset = ((yy - result->y)*result->width * 4) + (xx - result->x) * 4;
  s_offset = (yy - result->y + HALF_WINDOW) * extended->width * 4 +
    (xx - result->x + HALF_WINDOW) * 4;

  for (i=0; i < 4; i++)
    {
      sum  = 0.0;
      if ((i==0 && o->red) || (i==1 && o->blue) || (i==2 && o->green)
          || (i==3 && o->alpha))
        {
          for (x=0;x < MATRIX_SIZE; x++)
            for (y=0; y < MATRIX_SIZE; y++)
              {
                if (!strcmp(o->border,"wrap"))
                  {
                    s_x = fmod (x+xx, boundary->width);
                    while (s_x < 0)
                      s_x +=boundary->width;

                    s_y = fmod (y+yy, boundary->height);
                    while (s_y < 0)
                      s_y +=boundary->width;
                  }
                else if (!strcmp(o->border,"extend"))
                  {
                    s_x = CLAMP (x+xx, 0, boundary->width);
                    s_y = CLAMP (y+yy, 0, boundary->height);
                  }
                temp = (s_y - extended->y) * extended->width * 4 +
                  (s_x - extended->x) * 4;

                if ((s_x >= extended->x && (s_x < extended->x + extended->width))
                    && (s_y >=extended->y && (s_y < extended->y + extended->height)))
                  {
                    if (i!=3 && o->weight)
                      sum += matrix[x][y] * src_buf[temp + i]
                        * src_buf[temp + 3];
                    else
                      sum += matrix[x][y] * src_buf[temp + i];

                    if (i==3)
                      alphasum += fabs (matrix[x][y] * src_buf[temp+i]);
                  }
                else
                  {
                    gfloat temp_color[4];
                    gegl_buffer_sample (input, s_x, s_y, NULL, temp_color,
                                        babl_format ("RGBA float"),
                                        GEGL_SAMPLER_NEAREST,
                                        GEGL_ABYSS_NONE);
                    if (i!=3 && o->weight)
                      sum += matrix[x][y] * temp_color[i]
                        * temp_color[3];
                    else
                      sum += matrix[x][y] * temp_color[i];

                    if (i==3)
                      alphasum += fabs (matrix[x][y] * temp_color[i]);
                  }
              }
          sum = sum / o->div;

          if (i==3 && o->weight)
            {
              if (alphasum != 0)
                sum = sum * matrixsum / alphasum;
              else sum = 0.0;
            }
          sum += o->off;

          color[i] = sum;
        }
      else
        color[i] = src_buf[s_offset + i];
    }

  for (i=0; i < 4; i++)
    dst_buf[d_offset + i] = color[i];
}



static GeglRectangle
get_effective_area (GeglOperation *operation)
{
  GeglRectangle  result = {0,0,0,0};
  GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (operation, "input");

  gegl_rectangle_copy(&result, in_rect);

  return result;
}

static gboolean
process (GeglOperation       *operation,
         GeglBuffer          *input,
         GeglBuffer          *output,
         const GeglRectangle *result,
         gint                 level)
{
  GeglChantO              *o       = GEGL_CHANT_PROPERTIES (operation);
  GeglOperationAreaFilter *op_area = GEGL_OPERATION_AREA_FILTER (operation);

  GeglRectangle   rect;
  GeglRectangle   boundary = get_effective_area (operation);
  gfloat         *src_buf;
  gfloat         *dst_buf;
  gdouble       **matrix;

  gchar         *type;
  gint           x, y;
  gdouble        matrixsum = 0.0;

  type = "RGBA float";

  matrix = g_new0 (gdouble*, MATRIX_SIZE);

  for (x=0; x < MATRIX_SIZE ;x++)
    matrix[x] = g_new0 (gdouble, MATRIX_SIZE);

  make_matrix (o, matrix);

  if (o->norm)
    normalize_o (o, matrix);

  for (x=0; x < MATRIX_SIZE; x++)
    for (y=0; y < MATRIX_SIZE; y++)
      matrixsum += fabs (matrix[x][y]);

  rect.x      = result->x - op_area->left;
  rect.width  = result->width + op_area->left + op_area->right;
  rect.y      = result->y - op_area->top;
  rect.height = result->height + op_area->top + op_area->bottom;

  src_buf = g_new0 (gfloat, rect.width * rect.height * 4);
  dst_buf = g_new0 (gfloat, result->width * result->height * 4);

  gegl_buffer_get (input, &rect, 1.0, babl_format (type), src_buf,
                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);

  /*fill src_buf with wrap pixels if it is the case*/

  if (o->div != 0)
    {
      for (y=result->y; y < result->height + result->y; y++)
        for (x=result->x; x < result->width + result->x; x++)
          convolve_pixel (src_buf, dst_buf, result, &rect, &boundary,
                          matrix, o, input, x, y, matrixsum);

      gegl_buffer_set (output, result, 0, babl_format (type),
                       dst_buf, GEGL_AUTO_ROWSTRIDE);
    }
  else
    gegl_buffer_set (output, &rect, 0, babl_format (type),
                     src_buf, GEGL_AUTO_ROWSTRIDE);



  g_free (src_buf);
  g_free (dst_buf);

  return TRUE;
}

static GeglRectangle
get_bounding_box (GeglOperation *operation)
{
  GeglRectangle  result = {0,0,0,0};
  GeglRectangle *in_rect;

  in_rect = gegl_operation_source_get_bounding_box (operation, "input");
  if (!in_rect)
    return result;

  return *in_rect;
}

static GeglRectangle
get_required_for_output (GeglOperation       *operation,
                         const gchar         *input_pad,
                         const GeglRectangle *roi)
{
  return get_bounding_box (operation);
}


static void
gegl_chant_class_init (GeglChantClass *klass)
{
  GeglOperationClass       *operation_class;
  GeglOperationFilterClass *filter_class;

  operation_class = GEGL_OPERATION_CLASS (klass);
  filter_class    = GEGL_OPERATION_FILTER_CLASS (klass);

  filter_class->process    = process;
  operation_class->prepare = prepare;
  operation_class->get_bounding_box        = get_bounding_box;
  operation_class->get_required_for_output = get_required_for_output;

  gegl_operation_class_set_keys (operation_class,
    "categories"  , "generic",
    "name"        , "gegl:convolution-matrix",
    "description" ,
    _("Creates image by manually set convolution matrix"),
    NULL);
}

#endif
