/*
 * cairoblend.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 <assert.h>
#include <cairo.h>
#include <stdio.h>
#include <string.h>

#include "frei0r.h"
#include "frei0r/cairo.h"

typedef struct cairo_blend_instance
{
  unsigned int width;
  unsigned int height;
  double opacity;
  char *blend_mode;
} cairo_blend_instance_t;

int f0r_init()
{
  return 1;
}

void f0r_deinit()
{

}

void f0r_get_plugin_info(f0r_plugin_info_t* info)
{
  info->name = "cairoblend";
  info->author = "Janne Liljeblad";
  info->plugin_type = F0R_PLUGIN_TYPE_MIXER2;
  info->color_model = F0R_COLOR_MODEL_RGBA8888;
  info->frei0r_version = FREI0R_MAJOR_VERSION;
  info->major_version = 0;
  info->minor_version = 10; 
  info->num_params =  2; 
  info->explanation = "Composites second input on the first input with user-defined blend mode and opacity.";
}

void f0r_get_param_info(f0r_param_info_t* info, int param_index)
{
	switch(param_index) {
		case 0:
			info->name = "opacity";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "Opacity of composited image";
			break;
		case 1:
			info->name = "blend mode";
      info->type = F0R_PARAM_STRING;
			info->explanation = "Blend mode used to compose 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)
{
  // Validate inputs
  if (width == 0 || height == 0) {
    return NULL;
  }

  cairo_blend_instance_t* inst = (cairo_blend_instance_t*)calloc(1, sizeof(*inst));
  if (!inst) {
    return NULL;
  }

  inst->width = width;
  inst->height = height;

  inst->opacity = 1.0;

  const char* blend_val = NORMAL;
  inst->blend_mode  = (char*) malloc (strlen(blend_val) + 1);
  if (!inst->blend_mode) {
    free(inst);
    return NULL;
  }
  strcpy (inst->blend_mode, blend_val);

  return (f0r_instance_t)inst;
}

void f0r_destruct(f0r_instance_t instance)
{
  if (!instance) {
    return;
  }

  cairo_blend_instance_t* inst = (cairo_blend_instance_t*)instance;
  if (inst->blend_mode) {
    free(inst->blend_mode);
  }
  free(instance);
}

void f0r_set_param_value(f0r_instance_t instance, f0r_param_t param, int param_index)
{
	assert(instance);
  cairo_blend_instance_t* inst = (cairo_blend_instance_t*) instance;
  char* sval;
	switch(param_index) {
		case 0:
		  // Validate double parameter
		  if (param) {
			  inst->opacity = *((double*)param);
		  }
      break;
		case 1:
		  // Validate string parameter
		  if (param) {
			  sval = (*(char**)param);
			  if (sval) {
				  inst->blend_mode = (char*)realloc (inst->blend_mode, strlen(sval) + 1);
				  if (inst->blend_mode) {
					  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_blend_instance_t* inst = (cairo_blend_instance_t*)instance;

	switch(param_index) {
		case 0:
		  if (param) {
		    *((double*)param) = inst->opacity;
		  }
			break;
		case 1:
		  if (param && inst->blend_mode) {
        *((f0r_param_string *)param) = inst->blend_mode;
      } else if (param) {
        *((f0r_param_string *)param) = "";
      }
			break;
  }
}

void draw_composite(cairo_blend_instance_t* inst, unsigned char* out, unsigned char* src, double time)
{
  int w = inst->width;
  int h = inst->height;
  int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, w);

  // Validate inputs
  if (!inst || !out || !src || w <= 0 || h <= 0) {
    return;
  }

  cairo_surface_t* out_image = cairo_image_surface_create_for_data (out,
                                                                    CAIRO_FORMAT_ARGB32,
                                                                    w,
                                                                    h,
                                                                    stride);
  // Check if surface creation succeeded
  if (!out_image) {
    return;
  }

  cairo_t* cr = cairo_create (out_image);
  // Check if context creation succeeded
  if (!cr) {
    cairo_surface_destroy (out_image);
    return;
  }

  cairo_surface_t* src_image = cairo_image_surface_create_for_data ((unsigned char*)src,
                                                                     CAIRO_FORMAT_ARGB32,
                                                                     w,
                                                                     h,
                                                                     stride);
  // Check if surface creation succeeded
  if (!src_image) {
    cairo_destroy (cr);
    cairo_surface_destroy (out_image);
    return;
  }

  // Validate blend mode string
  if (inst->blend_mode) {
    // Set source, blend mode and draw with current opacity
    frei0r_cairo_set_operator(cr, inst->blend_mode);
  }

  cairo_set_source_surface (cr, src_image, 0, 0);
  cairo_paint_with_alpha (cr, inst->opacity);

  // Clean up in proper order
  cairo_surface_destroy (src_image);
  cairo_destroy (cr);
  cairo_surface_destroy (out_image);
}

void f0r_update(f0r_instance_t instance, double time,
		const uint32_t* inframe, uint32_t* outframe)
{
  // not impl. for mixers
}

void f0r_update2(f0r_instance_t instance, double time, const uint32_t* inframe1,
		 const uint32_t* inframe2, const uint32_t* inframe3, uint32_t* outframe)
{
  // Validate inputs
  if (!instance || !inframe1 || !inframe2 || !outframe) {
    return;
  }

  assert(instance);
  cairo_blend_instance_t* inst = (cairo_blend_instance_t*) instance;

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

  // Validate dimensions
  if (pixels <= 0) {
    return;
  }

  frei0r_cairo_premultiply_rgba2 (dst, out, pixels, -1);
  frei0r_cairo_premultiply_rgba (src, pixels, -1);
  draw_composite (inst, out, src, time);
  frei0r_cairo_unpremultiply_rgba (out, pixels);
}

