/*
 *  ACM - Draw utilities module
 *  Copyright (C) 2007  Umberto Salsi
 *
 *  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; version 2 dated June, 1991.
 *
 *  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 <stdio.h>
#include <string.h>
#include <math.h>

#include "../util/memory.h"

#define draw_IMPORT
#include "draw.h"

typedef struct draw_Type {
	struct draw_Type *next;

	int segs_n;       /* segs[0..segs_n-1] currently used */
	int segs_size;    /* allocated segments, segs_size >= segs_n */
	Alib_Segment * segs;  /* pointer to the allocated segments */
} draw_data;

static draw_data *free_list = NULL;

#define SEGMENT2(a,b,c,d) \
 	seg->x1 = (int) (a); \
 	seg->y1 = (int) (b); \
 	seg->x2 = (int) (c); \
 	seg->y2 = (int) (d); \
 	seg++;


static void draw_destruct(void *p)
{
	draw_data *dd = p;
	if( dd == NULL )
		return;
	memory_dispose(dd->segs);
}


void draw_free(draw_data *dd)
{
	if( dd == NULL )
		return;

	dd->next = free_list;
	free_list = dd;
}


static void draw_cleanup()
{
	draw_data *dd;

	while( free_list != NULL ){
		dd = free_list;
		free_list = dd->next;
		memory_dispose(dd);
	}
}


draw_data * draw_new()
{
	draw_data *dd;

	if( free_list == NULL ){
		memory_registerCleanup(draw_cleanup);
		dd = memory_allocate(sizeof(draw_data), draw_destruct);
		dd->segs_size = 0;
		dd->segs = NULL;
	} else {
		dd = free_list;
		free_list = dd->next;
	}

	dd->next = NULL;
	dd->segs_n = 0;

	return dd;
}


static void required_segs(draw_data *dd, int n)
/*
	Expands dd so that at least n free segs be available.
*/
{
	if( dd->segs_size - dd->segs_n >= n )
		return;
	
	dd->segs_size = 2*dd->segs_size + 100;
	if( dd->segs_size - dd->segs_n < n )
		dd->segs_size = dd->segs_n + n;
	dd->segs = memory_realloc(dd->segs, dd->segs_size * sizeof(Alib_Segment));
}


void draw_segment(draw_data *dd, double x1, double y1, double x2, double y2)
{
	int i;
	Alib_Segment *seg;

	required_segs(dd, 1);
	i = dd->segs_n;
	seg = &dd->segs[i];
	SEGMENT2(x1 + 0.5, y1 + 0.5, x2 + 0.5, y2 + 0.5);
	dd->segs_n += 1;
}


void draw_rect(draw_data *dd, double x1, double y1, double x2, double y2)
{
	int i;
	Alib_Segment *seg;

	required_segs(dd, 4);
	i = dd->segs_n;
	seg = &dd->segs[i];

	/* Rounding: */
	x1 += 0.5;
	y1 += 0.5;
	x2 -= 0.5;
	y2 -= 0.5;

	SEGMENT2(x1, y1, x2, y1);
	SEGMENT2(x2, y1, x2, y2);
	SEGMENT2(x2, y2, x1, y2);
	SEGMENT2(x1, y2, x1, y1);
	dd->segs_n += 4;
}


#define MAX_CIRCLE_ERR 0.7

void draw_circle(draw_data *dd, double xo, double yo, double r)
{
	int i, j, steps;
	Alib_Segment *seg;
	double da, x1, y1, x2, y2, co, si;

	/* Rounding: */
	/* FIXME: improper rounding, as it should be -0.5 rather thatn +0.5 */
	xo += 0.5;
	yo += 0.5;
	r += 0.5;

	if( r < 1.0 )
		return;
	
	/* Angle step for max MAX_CIRCLE_ERR error: */
	da = 2.0 * acos((r-MAX_CIRCLE_ERR)/r);

	steps = (int) ceil(2.0 * M_PI / da);
	if( steps < 4 )
		steps = 4;
	steps = (steps + 3) * 4 / 4;
	da = 2.0 * M_PI / steps;

	co = cos(da);
	si = sin(da);

	x1 = 0.70710678 * r;
	y1 = 0.70710678 * r;

	required_segs(dd, steps);
	i = dd->segs_n;
	seg = &dd->segs[i];

	for( j = steps; j > 0; j-- ){
		x2 = co * x1 - si * y1;
		y2 = co * y1 + si * x1;
		SEGMENT2(xo + x1, yo + y1, xo + x2, yo + y2);
		x1 = x2;
		y1 = y2;
	}

	dd->segs_n += steps;
}


void draw_arc(draw_data *dd, double xo, double yo, double r, double a1, double a2)
{
	int i, j, steps;
	double a, da, co, si, x1, y1, x2, y2;
	Alib_Segment *seg;

	if( r < 1.0 )
		return;

	/***
	if( a1 < 0.0 )
		a1 = 2*M_PI + a1;

	if( a2 < 0.0 )
		a2 = 2*M_PI + a2;
	
	if( a1 > 2*M_PI )
		a1 = fmod(a1, 2*M_PI);

	if( a2 > 2*M_PI )
		a2 = fmod(a2, 2*M_PI);
	***/

	if( a1 > a2 ){
		a = a1;  a1 = a2;  a2 = a;
	}

	if( a1 == a2 )
		return;
	
	/* Rounding: */
	xo += 0.5;
	yo += 0.5;

	/* Angle step for max MAX_CIRCLE_ERR error: */
	da = 2.0 * acos((r-MAX_CIRCLE_ERR)/r);

	steps = (int) ceil((a2-a1) / da);
	if( steps < 1 )
		steps = 1;
	da = (a2-a1) / steps;

	co = cos(da);
	si = sin(da);
	x1 = r*cos(a1);
	y1 = r*sin(a1);
	required_segs(dd, steps);
	i = dd->segs_n;
	seg = &dd->segs[i];

	for( j = steps; j > 0; j-- ){
		x2 = co * x1 - si * y1;
		y2 = co * y1 + si * x1;
		SEGMENT2(xo + x1, yo + y1, xo + x2, yo + y2);
		x1 = x2;
		y1 = y2;
	}
	dd->segs_n += steps;
}


#define POINTER_APERTURE_COS 0.9397  /* cos(20 DEG) */
#define POINTER_APERTURE_SIN 0.3420  /* sin(20 DEG) */

void draw_pointer(draw_data *dd, double xo, double yo, double a, double l)
{
	double co, si, co1, si1, co2, si2, p0x, p0y, p1x, p1y, p2x, p2y;

	co = cos(a);
	si = sin(a);

	p0x = xo + l*co;
	p0y = yo + l*si;

	co1 = co*POINTER_APERTURE_COS - si*POINTER_APERTURE_SIN;
	si1 = co*POINTER_APERTURE_SIN + si*POINTER_APERTURE_COS;
	p1x = xo + 0.2*l*co1;
	p1y = yo + 0.2*l*si1;

	co2 = co*POINTER_APERTURE_COS + si*POINTER_APERTURE_SIN;
	si2 = -co*POINTER_APERTURE_SIN + si*POINTER_APERTURE_COS;
	p2x = xo + 0.2*l*co2;
	p2y = yo + 0.2*l*si2;

	draw_segment(dd, p0x, p0y,  p1x, p1y);
	draw_segment(dd, p0x, p0y,  p2x, p2y);
	draw_segment(dd, p1x, p1y,  p2x, p2y);
}


void draw_stroke(draw_data *dd, Viewport *v, Alib_Pixel color)
{
	if( dd->segs_n == 0 )
		return;

	VDrawSegments(v, dd->segs, dd->segs_n, color);
}


void draw_fill(draw_data *dd, Viewport *v, Alib_Pixel color)
{
	if( dd->segs_n == 0 )
		return;

	draw_stroke(dd, v, color);

	/* FIXME:
	FillPolygonNoClipping(v->w, dd->segs, dd->segs_n, z);
	*/
}


void draw_string_centered(Viewport *v, double xo, double yo, double fh,
	char *s, Alib_Pixel color)
{
	int fw, l, width, height;

	fw = VFontWidthPixels(v, (int) (fh+0.5));

	l = strlen(s);
	width = l * fw;
	height = (int)(fh + 0.5);

	VDrawStrokeString(v,
		(int) (xo - 0.5*width + 0.5),
		(int) (yo + 0.5*height + 0.5),
		s, l, (int)(fh + 0.5), color);
}


/* End of the draw module. */
