/*
 * cairogradient.c
 * Copyright 2012 Janne Liljeblad 
 *
 * This file is a Frei0r plugin.
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <cairo.h>
#include <stdio.h>
#include <math.h>

#include "frei0r.h"
#include "frei0r_cairo.h"

typedef struct cairo_gradient_instance
{
  unsigned int width;
  unsigned int height;
  char *pattern;
	f0r_param_color_t start_color;
	double start_opacity;
	f0r_param_color_t end_color;
	double end_opacity;
  double start_x;
  double start_y;
  double end_x;
  double end_y;
  char *end_point;
  double offset;
  char *blend_mode;
} cairo_gradient_instance_t;

int f0r_init()
{
  return 1;
}

void f0r_deinit()
{

}

void f0r_get_plugin_info(f0r_plugin_info_t* info)
{
  info->name = "cairogradient";
  info->author = "Janne Liljeblad";
  info->plugin_type = F0R_PLUGIN_TYPE_FILTER;
  info->color_model = F0R_COLOR_MODEL_RGBA8888;
  info->frei0r_version = FREI0R_MAJOR_VERSION;
  info->major_version = 1; 
  info->minor_version = 0;
  info->num_params = 11; 
  info->explanation = "Draws a gradient on top of image. Filter is given gradient start and end points, colors and opacities.";
}

void f0r_get_param_info(f0r_param_info_t* info, int param_index)
{
	switch(param_index) {
		case 0:
			info->name = "pattern";
      info->type = F0R_PARAM_STRING;
			info->explanation = "Linear or radial gradient";// Accepted values: 'gradient_linear' and 'gradient_radial
			break;
		case 1:
			info->name = "start color";
			info->type = F0R_PARAM_COLOR;
			info->explanation = "First color of the gradient";
			break;
		case 2:
			info->name = "start opacity";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "Opacity of the first color of the gradient";
      break;
		case 3:
			info->name = "end color";
			info->type = F0R_PARAM_COLOR;
			info->explanation = "Second color of the gradient";
			break;
		case 4:
			info->name = "end opacity";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "Opacity of the second color of the gradient";
			break;
		case 5:
			info->name = "start x";
      info->type = F0R_PARAM_DOUBLE;
			info->explanation = "X position of the start point of the gradient";
			break;
		case 6:
			info->name = "start y";
      info->type = F0R_PARAM_DOUBLE;
			info->explanation = "Y position of the start point of the gradient";
			break;
    case 7:
			info->name = "end x";
      info->type = F0R_PARAM_DOUBLE;
			info->explanation = "X position of the end point of the gradient";
			break;
    case 8:
			info->name = "end y";
      info->type = F0R_PARAM_DOUBLE;
			info->explanation = "Y position of the end point of the gradient";
			break;
    case 9:
			info->name = "offset";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "Position of first color in the line connecting gradient ends, really useful only for radial gradient";
			break;
    case 10:
			info->name = "blend mode";
      info->type = F0R_PARAM_STRING;
			info->explanation = "Blend mode used to compose gradient on image. Accepted values: 'normal', 'add', 'saturate', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'colordodge', 'colorburn', 'hardlight', 'softlight', 'difference', 'exclusion', 'hslhue', 'hslsaturation', 'hslcolor', 'hslluminosity'";
			break;
	}  
}

f0r_instance_t f0r_construct(unsigned int width, unsigned int height)
{
  cairo_gradient_instance_t* inst = (cairo_gradient_instance_t*) calloc (1, sizeof(*inst));
  inst->width = width; 
  inst->height = height;

	const char* pattern_val = GRADIENT_LINEAR;
  inst->pattern = (char*) malloc (strlen(pattern_val) + 1);
	strcpy (inst->pattern, pattern_val);

  inst->start_color.r = 0.0;
  inst->start_color.g = 0.0;
  inst->start_color.b = 0.0;
  inst->start_opacity = 0.5;
  inst->end_color.r = 1.0;
  inst->end_color.g = 1.0;
  inst->end_color.b = 1.0;
  inst->end_opacity = 0.5;
  inst->start_x = 0.5;
  inst->start_y = 0.0;
  inst->end_x = 0.5;
  inst->end_y = 1.0;
  inst->offset = 0.0;

	const char* blend_val = NORMAL;
  inst->blend_mode  = (char*) malloc (strlen(blend_val) + 1 );
	strcpy (inst->blend_mode, blend_val);

  return (f0r_instance_t)inst;
}

void f0r_destruct(f0r_instance_t instance)
{
  cairo_gradient_instance_t* inst = (cairo_gradient_instance_t*)instance;
  free(inst->blend_mode);
  free(inst->pattern);
  free(instance);
}

void f0r_set_param_value(f0r_instance_t instance, f0r_param_t param, int param_index)
{
	assert(instance);
  cairo_gradient_instance_t* inst = (cairo_gradient_instance_t*)instance;
  char* sval;
	switch(param_index) {
		case 0:
			sval = (*(char**)param);
			inst->pattern = (char*)realloc (inst->pattern, strlen(sval) + 1);
			strcpy( inst->pattern, sval );
      break;
		case 1:
			inst->start_color = *((f0r_param_color_t*)param);
      break;
		case 2:
			inst->start_opacity = *((double*)param);
      break;
		case 3:
			inst->end_color = *((f0r_param_color_t*)param);
      break;
		case 4:
			inst->end_opacity = *((double*)param);
      break;
		case 5:
			inst->start_x = *((double*)param);
      break;
		case 6:
			inst->start_y = *((double*)param);
      break;
		case 7:
			inst->end_x = *((double*)param);
      break;
		case 8:
			inst->end_y = *((double*)param);
      break;
		case 9:
			inst->offset = *((double*)param);
      break;
		case 10:
			sval = (*(char**)param);
			inst->blend_mode = (char*) realloc (inst->blend_mode, strlen(sval) + 1);
			strcpy (inst->blend_mode, sval);
      break;
  }
}

void f0r_get_param_value(f0r_instance_t instance, f0r_param_t param, int param_index)
{
	assert(instance);
  cairo_gradient_instance_t* inst = (cairo_gradient_instance_t*)instance;

	switch(param_index) {
		case 0:
      *((f0r_param_string *)param) = inst->pattern;
			break;
		case 1:
			*((f0r_param_color_t*)param) = inst->start_color;
			break;
		case 2:
			*((double*)param) = inst->start_opacity;
			break;
		case 3:
			*((f0r_param_color_t*)param) = inst->end_color;
			break;
		case 4:
			*((double*)param) = inst->end_opacity;
			break;
		case 5:
			*((double*)param) = inst->start_x;
			break;
		case 6:
			*((double*)param) = inst->start_y;
			break;
		case 7:
			*((double*)param) = inst->end_x;
			break;
		case 8:
			*((double*)param) = inst->end_y;
			break;
		case 9:
			*((double*)param) = inst->offset;
			break;
		case 10:
      *((f0r_param_string *)param) = inst->blend_mode;
			break;
	}
}

void draw_gradient(cairo_gradient_instance_t* inst, unsigned char* dst, const unsigned char* src, double time)
{
  int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32,  inst->width);
  cairo_surface_t *surface = cairo_image_surface_create_for_data (dst, CAIRO_FORMAT_ARGB32, inst->width, inst->height, stride);
  cairo_t *cr = cairo_create (surface);

  cairo_surface_t* src_image = cairo_image_surface_create_for_data ((unsigned char*)src, 
                                              CAIRO_FORMAT_ARGB32, inst->width, inst->height, stride);

  cairo_set_source_surface (cr, src_image, 0, 0);
  cairo_paint (cr);

  cairo_pattern_t *pat;
  double sx = inst->start_x;
  double sy = inst->start_y;
  double ex = inst->end_x;
  double ey = inst->end_y;
  if (strcmp(inst->pattern, GRADIENT_RADIAL) == 0)
  {


    double distance_x = (sx - ex) * inst->width;
    double distance_y = (sy - ey) * inst->height;
    double distance = sqrt( (distance_x * distance_x) + (distance_y * distance_y));
    pat = cairo_pattern_create_radial (sx * inst->width,
                                       sy * inst->height,
                                       0.0,
                                       sx * inst->width,
                                       sy * inst->height,
                                       distance);
  }
  else
  {
    pat = cairo_pattern_create_linear ( sx * inst->width,
                                        sy * inst->height,
                                        ex * inst->width,
                                        ey * inst->height);
  }

  freior_cairo_set_color_stop_rgba_LITTLE_ENDIAN (pat,
                                                  1.0,
                                                  inst->start_color.r,
                                                  inst->start_color.g,
                                                  inst->start_color.b,
                                                  inst->start_opacity);
  freior_cairo_set_color_stop_rgba_LITTLE_ENDIAN (pat,
                                                  inst->offset,                                    
                                                  inst->end_color.r,
                                                  inst->end_color.g,
                                                  inst->end_color.b,
                                                  inst->end_opacity);


  cairo_set_source (cr, pat);
  frei0r_cairo_set_operator(cr, inst->blend_mode);
  cairo_rectangle (cr, 0, 0, inst->width, inst->height);

  cairo_fill (cr);

  cairo_pattern_destroy (pat);
  cairo_destroy (cr);
  cairo_surface_destroy (surface);
  cairo_surface_destroy (src_image);  
}

void f0r_update(f0r_instance_t instance, double time,
		const uint32_t* inframe, uint32_t* outframe)
{
  assert(instance);
  cairo_gradient_instance_t* inst = (cairo_gradient_instance_t*)instance;

  unsigned char* dst = (unsigned char*)outframe;
  unsigned char* src = (unsigned char*)inframe;
  int pixels = inst->width * inst->height;

  // Clear the destination
  memset(dst, 0, pixels * sizeof(uint32_t));

  frei0r_cairo_premultiply_rgba (src, pixels, -1);
  draw_gradient(inst, dst, src, time);
  frei0r_cairo_unpremultiply_rgba (dst, pixels);
}

