/* $Id$
 *
 * Copyright © 2004 Keith Packard
 *
 * This library is free software; you can redistribute it and/or
 * modify it either under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation
 * (the "LGPL") or, at your option, under the terms of the Mozilla
 * Public License Version 1.1 (the "MPL"). If you do not alter this
 * notice, a recipient may use your version of this file under either
 * the MPL or the LGPL.
 *
 * You should have received a copy of the LGPL along with this library
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 * You should have received a copy of the MPL along with this library
 * in the file COPYING-MPL-1.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
 * the specific language governing rights and limitations.
 *
 * The Original Code is the cairo graphics library.
 *
 * The Initial Developer of the Original Code is Keith Packard
 *
 * Contributor(s):
 *      Keith Packard <keithp@keithp.com>
 */

if (!Command::valid_name ((string[]) { "Cairo" }))
    Foreign::load ("libcairo-5c.so.0");

extend namespace Cairo {
    
    public typedef void (real x, real y) move_to_func_t;
    public typedef void (real x, real y) line_to_func_t;
    public typedef void (real x1, real y1, real x2, real y2, real x3, real y3) curve_to_func_t;
    public typedef void () close_path_func_t;
    public typedef struct { 
	real hue, saturation, value;
    } hsv_color_t;
    
    public real width (cairo_t cr) = 
	Surface::width (get_target (cr));
    
    public real height (cairo_t cr) = 
	Surface::height (get_target (cr));
    
    public file open_event (cairo_t cr) =
	Surface::open_event (get_target (cr));

    public cairo_t new (int args...) {
	int	w = dim(args) > 0 ? args[0] : 0;
	int	h = dim(args) > 1 ? args[1] : 0;
	string	name = (dim (argv) > 0) ? argv[0] : "nickle";
	cairo_t	cr = create (Surface::create_window (name, w, h));
	
        set_source_rgba (cr, 1, 1, 1, 1);
	paint (cr);
	set_source_rgba (cr, 0, 0, 0, 1);
	return cr;
    }

    public cairo_t new_window (string name, int args...) {
	int	    w = dim(args) > 0 ? args[0] : 0;
	int	    h = dim(args) > 1 ? args[1] : 0;
	surface_t   surface = Surface::create_window (name, w, h);
	cairo_t	    cr = create (surface);
	
	set_source_rgba (cr, 1, 1, 1, 1);
	paint (cr);
	set_source_rgba (cr, 0, 0, 0, 1);
	return cr;
    }

    public cairo_t new_image (int width, int height)
    {
	surface_t   surface = Image::surface_create (Image::format_t.ARGB, width, height);
	cairo_t	    cr = create (surface);
	
	set_source_rgba (cr, 0, 0, 0, 0);
	set_operator (cr, operator_t.SOURCE);
	paint (cr);
	set_operator (cr, operator_t.OVER);
	set_source_rgba (cr, 0, 0, 0, 1);
	return cr;
    }
    
    public void write_to_png (cairo_t cr, string filename)
    {
	surface_t   surface = get_target (cr);
	Surface::write_to_png (surface, filename);
    }
    
    public cairo_t new_pdf (string filename, 
			   real width, real height)
    {
	surface_t   surface = Pdf::surface_create (filename, width, height);
	cairo_t	    cr = create (surface);
	
	set_source_rgba (cr, 1, 1, 1, 1);
	paint (cr);
	set_source_rgba (cr, 0, 0, 0, 1);
	return cr;
    }
    
    public cairo_t new_svg (string filename, 
			   real width, real height)
    {
	surface_t   surface = Svg::surface_create (filename, width, height);
	cairo_t	    cr = create (surface);
	return cr;
    }
    
    public cairo_t dup (cairo_t cr)
    {
	return create (get_target (cr));
    }

    real[3] to_hsv(real r, real g, real b)
    {
	real minimum = min (r, g, b);
	real maximum = max (r, g, b);
	real v = maximum;
	real s = (maximum == 0) ? 0 : (maximum - minimum) / maximum;
	real h = 0;
	if (s != 0)
	{
	    switch (maximum) {
	    case r:	h =       (g - b) / (maximum - minimum);  break;
	    case g:	h = 2.0 + (b - r) / (maximum - minimum);  break;
	    case b:	h = 4.0 + (r - g) / (maximum - minimum);  break;
	    }
	    h = h / 6;
	}
	return (real[3]) { h, s, v };
    }

    /* convert hsv to rgb */

    real[3] from_hsv(real h, real s, real v)
    {
	if (v == 0.0)
	    return (real[3]) { 0 ... };
	else if (s == 0.0) {
	    return (real[3]) { v ... };
	} else {
	    real h6 = (h * 6) % 6;
	    int  i = floor (h6);
	    real f = h6 - i;
	    real p = v * (1 - s);
	    real q = v * (1 - (s * f));
	    real t = v * (1 - (s * (1 - f)));

	    switch(i) {
		default:return (real[3]) { v, t, p };
	    case 1: return (real[3]) { q, v, p };
	    case 2: return (real[3]) { p, v, t };
	    case 3: return (real[3]) { p, q, v };
	    case 4: return (real[3]) { t, p, v };
	    case 5: return (real[3]) { v, p, q };
	    }
	}
    }

    public rgb_color_t hsv_to_rgb (hsv_color_t hsv)
    {
	real[3] rgb = from_hsv (hsv.hue, hsv.saturation, hsv.value);
	return (rgb_color_t) { red = rgb[0], green = rgb[1], blue = rgb[2] };
    }
    
    public hsv_color_t rgb_to_hsv (rgb_color_t rgb)
    {
	real[3] hsv = to_hsv (rgb.red, rgb.green, rgb.blue);
	return (hsv_color_t) { hue = hsv[0], saturation = hsv[1], value = hsv[2] };
    }
    
    public void set_source_hsv (cairo_t cr, real h, real s, real v)
	/*
	 * Set color using HSV specification
	 *  H: hue	    0 = red, 0.{3} = green, 0.{6} = blue
	 *  S: satuation    0..1
	 *  V: value	    0..1
	 */
    {
	set_source_rgb (cr, from_hsv (h, s, v) ...);
    }

    public namespace Matrix {
	matrix_t multiply_scalar (matrix_t a, real scalar) = 
	    (matrix_t) { 
		xx = a.xx * scalar, yx = a.yx * scalar,
		xy = a.xy * scalar, yy = a.yy * scalar,
		x0 = a.x0 * scalar, y0 = a.y0 * scalar };
		

	public matrix_t identity () =
	    (matrix_t) { 
		xx = 1, yx = 0,
		xy = 0, yy = 1,
		x0 = 0, y0 = 0 };

	public real determinant (matrix_t m) = 
	    m.xx * m.yy - m.yx * m.xy;
	
	/* This function isn't a correct adjoint in that the implicit 1 in the
	 homogeneous result should actually be ad-bc instead. But, since this
	 adjoint is only used in the computation of the inverse, which
	 divides by det (A)=ad-bc anyway, everything works out in the end. */
	matrix_t adjoint (matrix_t m) = 
	    (matrix_t) {
		xx = m.yy,  yx = -m.xy,
		xy = -m.yx, yy = m.xx,
		x0 = m.yx * m.y0 - m.yy * m.x0,
		y0 = m.xy * m.x0 - m.xx * m.y0 };
	
	/* inv (A) = 1/det (A) * adj (A) */
	public matrix_t invert (matrix_t a) =
	    multiply_scalar (adjoint (a), 1 / determinant (a));

	public matrix_t multiply (matrix_t a, matrix_t b) =
	    (matrix_t) {
		xx = a.xx * b.xx + a.yx * b.xy,
		yx = a.xx * b.yx + a.yx * b.yy,
		xy = a.xy * b.xx + a.yy * b.xy,
		yy = a.xy * b.yx + a.yy * b.yy,
		x0 = a.x0 * b.xx + a.y0 * b.xy + b.x0,
		y0 = a.x0 * b.yx + a.y0 * b.yy + b.y0 };

	public matrix_t translate (matrix_t m, real tx, real ty) =
	    multiply ((matrix_t) { 
		xx = 1,  yx = 0,
		xy = 0,  yy = 1,
		x0 = tx, y0 = ty }, m);
	
	public matrix_t scale (matrix_t m, real sx, real sy) =
	    multiply ((matrix_t) { 
		xx = sx, yx =  0,
		xy =  0, yy = sy,
		x0 =  0, y0 =  0 }, m);
	
	public matrix_t rotate (matrix_t m, real a) =
	    multiply ((matrix_t) { 
		xx = (real c = cos(a)), yx = (real s = sin(a)),
		xy = -s,                yy = c,
		x0 = 0,                 y0 = 0 }, m);

	public point_t point (matrix_t m, point_t p) =
	    (point_t) { 
		x = m.xx * p.x + m.yx * p.y + m.x0,
		y = m.xy * p.x + m.yy * p.y + m.y0
	    };
	
	public point_t distance (matrix_t m, point_t p) =
	    (point_t) { 
		x = m.xx * p.x + m.yx * p.y,
		y = m.xy * p.x + m.yy * p.y
	    };
	
     }	
}
