/* Swfdec
 * Copyright (C) 2008 Benjamin Otte <otte@gnome.org>
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "swfdec_blur_filter.h"

#include <math.h>

#include "swfdec_debug.h"

G_DEFINE_TYPE (SwfdecBlurFilter, swfdec_blur_filter, SWFDEC_TYPE_FILTER)

static void
swfdec_blur_filter_clone (SwfdecFilter *dfilter, SwfdecFilter *sfilter)
{
  SwfdecBlurFilter *dest = SWFDEC_BLUR_FILTER (dfilter);
  SwfdecBlurFilter *source = SWFDEC_BLUR_FILTER (sfilter);

  dest->x = source->x;
  dest->y = source->y;
  dest->quality = source->quality;
}

static void
swfdec_blur_filter_create_convolution_matrix (SwfdecBlurFilter *blur,
    double xscale, double yscale)
{
  guint x, y, w, h;
  double div, blurx, blury;

  if (blur->matrix && xscale == blur->xscale && yscale == blur->yscale)
    return;

  blurx = MAX (blur->x * xscale, 1);
  blury = MAX (blur->y * yscale, 1);
  w = ceil ((blurx - 1) / 2);
  w = w * 2 + 1;
  h = ceil ((blury - 1) / 2);
  h = h * 2 + 1;

  blur->matrix = swfdec_convolution_matrix_new (w, h);
  blur->xscale = xscale;
  blur->yscale = yscale;

  div = 1.0 / (blurx * blury);
  for (y = 0; y < h; y++) {
    double val = div;
    if (y == 0 || y == w - 1) {
      val *= (1 - (h - MAX (blury, 1)) / 2);
    }
    for (x = 0; x < w; x++) {
      if (x == 0 || x == w - 1) {
	swfdec_convolution_matrix_set (blur->matrix, x, y,
	    val * (1 - (w - MAX (blurx, 1)) / 2));
      } else {
	swfdec_convolution_matrix_set (blur->matrix, x, y, val);
      }
    }
  }
}

static void
swfdec_blur_filter_get_rectangle (SwfdecFilter *filter, SwfdecRectangle *dest,
    double xscale, double yscale, const SwfdecRectangle *source)
{
  SwfdecBlurFilter *blur = SWFDEC_BLUR_FILTER (filter);
  double blurx, blury;
  guint w, h;

  blurx = MAX (blur->x * xscale, 1);
  blury = MAX (blur->y * yscale, 1);
  w = ceil ((blurx - 1) / 2);
  w *= blur->quality;
  h = ceil ((blury - 1) / 2);
  h *= blur->quality;

  dest->x = source->x - w;
  dest->y = source->y - h;
  dest->width = source->width + 2 * w;
  dest->height = source->height + 2 * h;
}

static cairo_pattern_t *
swfdec_blur_filter_apply (SwfdecFilter *filter, cairo_pattern_t *pattern,
    double xscale, double yscale, const SwfdecRectangle *rect)
{
  SwfdecBlurFilter *blur = SWFDEC_BLUR_FILTER (filter);
  cairo_surface_t *a, *b;
  guint i, j, x, y;
  guint8 *adata, *bdata;
  guint astride, bstride;
  cairo_t *cr;

  if (blur->x <= 1.0 && blur->y <= 1.0)
    return cairo_pattern_reference (pattern);

  swfdec_blur_filter_create_convolution_matrix (blur, xscale, yscale);
  x = swfdec_convolution_matrix_get_width (blur->matrix) / 2;
  y = swfdec_convolution_matrix_get_height (blur->matrix) / 2;

  /* FIXME: make this work in a single pass (requires smarter matrix construction) */
  a = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
      rect->width + 2 * x * blur->quality,
      rect->height + 2 * y * blur->quality);
  cairo_surface_set_device_offset (a, 
      - (double) rect->x + x * blur->quality, - (double) rect->y + y * blur->quality);
  b = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
      rect->width + 2 * x * blur->quality,
      rect->height + 2 * y * blur->quality);
  cairo_surface_set_device_offset (b, 
      - (double) rect->x + x * blur->quality, - (double) rect->y + y * blur->quality);

  cr = cairo_create (b);
  cairo_set_source (cr, pattern);
  cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
  cairo_fill (cr);
  cairo_destroy (cr);
  cairo_surface_flush (b);

  adata = cairo_image_surface_get_data (a);
  astride = cairo_image_surface_get_stride (a);
  bdata = cairo_image_surface_get_data (b);
  bstride = cairo_image_surface_get_stride (b);
  for (i = 1, j = blur->quality - 1; i <= blur->quality; i++, j--) {
    swfdec_convolution_matrix_apply (blur->matrix,
	rect->width + 2 * x * i, rect->height + 2 * y * i,
	adata + 4 * x * j + astride * y * j, astride,
	bdata + 4 * x * j + bstride * y * j, bstride);
    i++;
    j--;
    if (i > blur->quality) {
      cairo_surface_destroy (b);
      goto out;
    }
    swfdec_convolution_matrix_apply (blur->matrix,
	rect->width + 2 * x * i, rect->height + 2 * y * i,
	bdata + 4 * x * j + bstride * y * j, bstride,
	adata + 4 * x * j + astride * y * j, astride);
  }

  cairo_surface_destroy (a);
  a = b;
out:
  cairo_surface_mark_dirty (a);
  pattern = cairo_pattern_create_for_surface (a);
  cairo_surface_destroy (a);

  return pattern;
}

static void
swfdec_blur_filter_dispose (GObject *object)
{
  SwfdecBlurFilter *blur = SWFDEC_BLUR_FILTER (object);

  if (blur->matrix) {
    swfdec_convolution_matrix_free (blur->matrix);
    blur->matrix = NULL;
  }

  G_OBJECT_CLASS (swfdec_blur_filter_parent_class)->dispose (object);
}

static void
swfdec_blur_filter_class_init (SwfdecBlurFilterClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  SwfdecFilterClass *filter_class = SWFDEC_FILTER_CLASS (klass);

  object_class->dispose = swfdec_blur_filter_dispose;

  filter_class->clone = swfdec_blur_filter_clone;
  filter_class->get_rectangle = swfdec_blur_filter_get_rectangle;
  filter_class->apply = swfdec_blur_filter_apply;
}

static void
swfdec_blur_filter_init (SwfdecBlurFilter *filter)
{
  filter->x = 4;
  filter->y = 4;
  filter->quality = 1;
}

void
swfdec_blur_filter_invalidate (SwfdecBlurFilter *blur)
{
  g_return_if_fail (SWFDEC_IS_BLUR_FILTER (blur));

  if (blur->matrix) {
    swfdec_convolution_matrix_free (blur->matrix);
    blur->matrix = NULL;
  }
}

