/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / software 2D rasterizer plugin
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *   
 *  GPAC 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 *
 */


#include "m4_rend.h"

static void get_surface_world_matrix(M4Surface *_this, M4Matrix2D *mat)
{
	m4_mx2d_init(*mat);
	if (_this->center_coords) {
		m4_mx2d_add_scale(mat, FIX_ONE, -FIX_ONE);
		m4_mx2d_add_translation(mat, INT2FIX(_this->width / 2), INT2FIX(_this->height / 2));
	}
}

LPM4SURFACE m4_surface_new(Raster2D *_dr, Bool center_coords)
{
	M4Surface *_this;
	SAFEALLOC(_this, sizeof(M4Surface));
	if (_this) {
		_this->center_coords = center_coords;
		_this->texture_filter = M4_TEXTURE_FILTER_DEFAULT;
		_this->ftparams.source = &_this->ftoutline;
		_this->ftparams.user = _this;
		m4_gray_raster_new(_this->ftrast_mem, &_this->ftraster);
		m4_gray_raster_reset(_this->ftraster, _this->raster_pool, M4_FT_POOL_SIZE);
	}
	return _this;
}

void m4_surface_delete(LPM4SURFACE _this)
{
	M4Surface *surf = (M4Surface *)_this;

	if (surf->contours) free(surf->contours);
	if (surf->tags) free(surf->tags);
	if (surf->points) free(surf->points);
	if (surf->stencil_pix_run) free(surf->stencil_pix_run);
	m4_gray_raster_done(surf->ftraster);
	free(surf);
}

M4Err m4_surface_set_matrix(LPM4SURFACE _this, M4Matrix2D *mat)
{
	M4Matrix2D tmp;
	M4Surface *surf = (M4Surface *)_this;
	if (!surf) return M4BadParam;
	get_surface_world_matrix(surf, &surf->mat);
	if (!mat) return M4OK;
	m4_mx2d_init(tmp);
	m4_mx2d_add_matrix(&tmp, mat);
	m4_mx2d_add_matrix(&tmp, &surf->mat);
	m4_mx2d_copy(surf->mat, tmp);
	return M4OK;
}


M4Err m4_surface_attach_to_callbacks(LPM4SURFACE _this, M4RasterCallback *callbacks, u32 width, u32 height)
{
	M4Surface *surf = (M4Surface *)_this;
	if (!surf || !width || !height || !callbacks) return M4BadParam;
	if (!callbacks->cbk || !callbacks->fill_run_alpha || !callbacks->fill_run_no_alpha) return M4BadParam;

	surf->width = width;
	surf->height = height;
	if (surf->stencil_pix_run) free(surf->stencil_pix_run);
	SAFEALLOC(surf->stencil_pix_run , sizeof(u32) * (width+2));

	surf->raster_cbk = callbacks->cbk;
	surf->raster_fill_run_alpha = callbacks->fill_run_alpha;
	surf->raster_fill_run_no_alpha = callbacks->fill_run_no_alpha;
	m4_surface_set_matrix(surf, NULL);
	return M4OK;
}


M4Err m4_surface_attach_to_buffer(LPM4SURFACE _this, unsigned char *pixels, u32 width, u32 height, u32 stride, M4PixelFormat pixelFormat)
{
	u32 BPP;
	M4Surface *surf = (M4Surface *)_this;
	if (!surf || !pixels || (pixelFormat>M4PF_YUVA)) return M4BadParam;

	switch (pixelFormat) {
	case M4PF_RGB_555:
	case M4PF_RGB_565:
		BPP = 2;
		break;
	case M4PF_BGR_24:
	case M4PF_RGB_24:
		BPP = 3;
		break;
	case M4PF_RGB_32:
	case M4PF_ARGB:
		BPP = 4;
		break;
	case M4PF_BGR_32:
	default:
		return M4NotSupported;
	}
	surf->stride = stride;
	surf->width = width;
	if (surf->stencil_pix_run) free(surf->stencil_pix_run);
	SAFEALLOC(surf->stencil_pix_run , sizeof(u32) * (width+2));
	surf->height = height;
	surf->pixels = pixels;
	surf->pixelFormat = pixelFormat;
	surf->BPP = BPP;

	surf->raster_cbk = NULL;
	surf->raster_fill_run_alpha = NULL;
	surf->raster_fill_run_no_alpha = NULL;
	m4_surface_set_matrix(_this, NULL);
	return M4OK;
}


M4Err m4_surface_attach_to_texture(LPM4SURFACE _this, LPM4STENCIL sten)
{
	u32 BPP;
	M4Surface *surf = (M4Surface *)_this;
	M4Texture *tx = (M4Texture *) sten;;
	if (!surf || (tx->type != M4_STENCIL_TEXTURE)) return M4BadParam;

	switch (tx->pixel_format) {
	case M4PF_GREYSCALE:
		BPP = 1;
		break;
	case M4PF_ALPHAGREY:
	case M4PF_RGB_555:
	case M4PF_RGB_565:
		BPP = 2;
		break;
	case M4PF_BGR_24:
	case M4PF_RGB_24:
		BPP = 3;
		break;
	case M4PF_RGB_32:
	case M4PF_ARGB:
		BPP = 4;
		break;
	default:
		return M4NotSupported;
	}
	surf->stride = tx->stride;
	if (surf->stencil_pix_run) free(surf->stencil_pix_run);
	SAFEALLOC(surf->stencil_pix_run , sizeof(u32) * (tx->width+2));

	surf->width = tx->width;
	surf->height = tx->height;
	surf->pixels = tx->pixels;
	surf->pixelFormat = tx->pixel_format;
	surf->BPP = BPP;
	surf->raster_cbk = NULL;
	surf->raster_fill_run_alpha = NULL;
	surf->raster_fill_run_no_alpha = NULL;
	m4_surface_set_matrix(surf, NULL);
	return M4OK;
}

void m4_surface_detach(LPM4SURFACE _this)
{
	M4Surface *surf = (M4Surface *)_this;
	surf->raster_cbk = NULL;
	surf->raster_fill_run_alpha = NULL;
	surf->raster_fill_run_no_alpha = NULL;
}

M4Err m4_surface_clear(LPM4SURFACE _this, M4IRect *rc, u32 color)
{
	M4IRect clear;
	M4Surface *surf = (M4Surface *)_this;
	if (!surf) return M4BadParam;

	if (rc) {	
		s32 _x, _y;
		if (surf->center_coords) {
			_x = rc->x + surf->width / 2;
			_y = surf->height / 2 - rc->y;
		} else {
			_x = rc->x;
			_y = rc->y - rc->height;
		}

		clear.width = (u32) rc->width;
		if (_x>=0) {
			clear.x = (u32) _x;
		} else {
			if ( (s32) clear.width + _x < 0) return M4BadParam;
			clear.width += _x;
			clear.x = 0;
		}
		clear.height = (u32) rc->height;
		if (_y>=0) {
			clear.y = _y;
		} else {
			if ( (s32) clear.height + _y < 0) return M4BadParam;
			clear.height += _y;
			clear.y = 0;
		}
	} else {
		clear.x = clear.y = 0;
		clear.width = surf->width;
		clear.height = surf->height;
	}
	
	if (surf->raster_cbk) {
		return m4_surface_clear_user(surf, clear, color);
	}

	switch (surf->pixelFormat) {
	case M4PF_ARGB:
	case M4PF_RGB_32:
		return m4_surface_clear_argb(surf, clear, color);
	case M4PF_BGR_24:
		return m4_surface_clear_rgb(surf, clear, color);
	case M4PF_RGB_24:
		return m4_surface_clear_bgr(surf, clear, color);
	case M4PF_RGB_565:
		return m4_surface_clear_565(surf, clear, color);
	case M4PF_RGB_555:
		return m4_surface_clear_555(surf, clear, color);
	default:
		return M4BadParam;
	}
}

M4Err m4_surface_set_raster_level(LPM4SURFACE _this, M4RasterLevel RasterSetting)
{
	M4Surface *surf = (M4Surface *)_this;
	if (!surf) return M4BadParam;
	switch (RasterSetting) {
	case M4_RASTER_HIGH_QUALITY:
		surf->AALevel = 1;/*don't draw pixels with 0 alpha...*/
		surf->texture_filter = M4_TEXTURE_FILTER_HIGH_QUALITY;
		break;
	case M4_RASTER_MID:
		surf->AALevel = 90;
		surf->texture_filter = M4_TEXTURE_FILTER_HIGH_QUALITY;
		break;
	case M4_RASTER_HIGH_SPEED:
	default:
		surf->AALevel = 180;
		surf->texture_filter = M4_TEXTURE_FILTER_HIGH_SPEED;
		break;
	}
	return M4OK;
}


M4Err m4_surface_set_clipper(LPM4SURFACE _this , M4IRect *rc)
{
	M4Surface *surf = (M4Surface *)_this;
	if (!surf) return M4BadParam;
	if (rc) {
		surf->clipper = *rc;
		surf->useClipper = 1;
		/*clipper was given in BIFS like coords, we work with bottom-min for rect, (0,0) top-left of surface*/
		if (surf->center_coords) {
			surf->clipper.x += surf->width / 2;
			surf->clipper.y = surf->height / 2 - rc->y;
		} else {
			surf->clipper.y -= rc->height;
		}

		if (surf->clipper.x <=0) {
			if (surf->clipper.x + (s32) surf->clipper.width < 0) return M4BadParam;
			surf->clipper.width += surf->clipper.x;
			surf->clipper.x = 0;
		}
		if (surf->clipper.y <=0) {
			if (surf->clipper.y + (s32) surf->clipper.height < 0) return M4BadParam;
			surf->clipper.height += surf->clipper.y;
			surf->clipper.y = 0;
		}
		if (surf->clipper.x + surf->clipper.width > (s32) surf->width) {
			surf->clipper.width = surf->width - surf->clipper.x;
		}
		if (surf->clipper.y + surf->clipper.height > (s32) surf->height) {
			surf->clipper.height = surf->height - surf->clipper.y;
		}
	} else {
		surf->useClipper = 0;
	}
	return M4OK;
}


static M4Err m4_resizecontours(M4Surface *surf, u32 num)
{
	if (surf->contourlen < num) {
		if (surf->contours != NULL) free(surf->contours);
		surf->contours = malloc(sizeof (s16) * num);
		if (surf->contours == NULL) {
			surf->contourlen = 0;
			return M4OutOfMem;
		}
		surf->contourlen = num;
	}
	return M4OK;
}

static M4Err m4_resizepoints(M4Surface *surf, u32 num)
{
	if (surf->pointlen < num) {
		if (surf->points != NULL) free(surf->points);
		surf->points = NULL;

		if (surf->tags != NULL) free(surf->tags);
		surf->tags = NULL;

		surf->points = malloc(sizeof (M4_Vector) * num);
		if (surf->points == NULL) {
			surf->pointlen = 0;
			return M4OutOfMem;
		}
		surf->tags = malloc(sizeof(s8) * num);
		if (surf->tags == NULL) {
			surf->pointlen = 0;
			return M4OutOfMem;
		}
		surf->pointlen = num;
	}
	return M4OK;
}


static Bool setup_ft_callbacks(M4Surface *surf)
{
	u32 col, a;
	Bool use_const = 1;

	if (surf->sten->type == M4_STENCIL_SOLID) {
		col = surf->fill_col = ((M4Brush *)surf->sten)->color;
		a = M4C_A(surf->fill_col);
	} else {
		col = a = 0;
		use_const = 0;
	}
	
	if (surf->raster_cbk) {
		if (use_const) {
			if (!a) return 0;
			if (a!=0xFF) {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_user_fill_const_a;
			} else {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_user_fill_const;
			}
		} else {
			surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_user_fill_var;
		}
		return 1;
	}

	switch (surf->pixelFormat) {
	case M4PF_ARGB:
		if (use_const) {
			if (!a) return 0;
			if (a!=0xFF) {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_argb_fill_const_a;
			} else {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_argb_fill_const;
			}
		} else {
			surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_argb_fill_var;
		}
		break;

	case M4PF_RGB_32:
		if (use_const) {
			if (!a) return 0;
			if (a!=0xFF) {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_rgb32_fill_const_a;
			} else {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_rgb32_fill_const;
			}
		} else {
			surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_rgb32_fill_var;
		}
		break;
	case M4PF_RGB_24:
		if (use_const) {
			if (!a) return 0;
			if (a!=0xFF) {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_rgb_fill_const_a;
			} else {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_rgb_fill_const;
			}
		} else {
			surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_rgb_fill_var;
		}
		break;
	case M4PF_BGR_24:
		if (use_const) {
			if (!a) return 0;
			if (a!=0xFF) {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_bgr_fill_const_a;
			} else {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_bgr_fill_const;
			}
		} else {
			surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_bgr_fill_var;
		}
		break;
	case M4PF_RGB_565:
		if (use_const) {
			surf->fill_565 = M4C_TO_565(col);
			if (!a) return 0;
			if (a!=0xFF) {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_565_fill_const_a;
			} else {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_565_fill_const;
			}
		} else {
			surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_565_fill_var;
		}
		break;
	case M4PF_RGB_555:
		if (use_const) {
			surf->fill_555 = M4C_TO_555(col);
			if (!a) return 0;
			if (a!=0xFF) {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_555_fill_const_a;
			} else {
				surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_555_fill_const;
			}
		} else {
			surf->ftparams.gray_spans = (M4_Raster_Span_Func) m4_555_fill_var;
		}
		break;
	}
	return 1;
}

M4Err m4_surface_set_path(LPM4SURFACE _this, M4Path *gp)
{
	u32 n, i;
	M4Err e;
	Bool is_identity;
	M4Point2D pt;
	M4Surface *surf = (M4Surface *)_this;

	if (!surf) return M4BadParam;
	if (!gp) {
		surf->ftoutline.n_points = 0;
		surf->ftoutline.n_contours = 0;
		return M4OK;
	}

	n=0;

	if (gp->n_points > 32767) return M4OutOfMem;

	surf->ftoutline.n_points = gp->n_points;
	surf->ftoutline.n_contours = gp->n_contours;

	e = m4_resizepoints(surf, gp->n_points);
	if (e) return e;
	e = m4_resizecontours(surf, gp->n_contours);
	if (e) return e;

	surf->ftoutline.points = surf->points;
	surf->ftoutline.tags = surf->tags;
	surf->ftoutline.contours = surf->contours;

	n = 0;

	for (i=0; i<gp->n_contours; i++) surf->contours[i] = gp->contours[i];

	is_identity = m4_mx2d_is_identity(surf->mat);
	m4_path_get_bounds(gp, &surf->path_bounds);

	/*invert Y (ft uses min Y)*/
	surf->path_bounds.y -= surf->path_bounds.height;

	if (is_identity) {
		for (i=0; i<gp->n_points; i++) {
			pt = gp->points[i];
#ifdef M4_FIXED_POINT
			surf->points[i].x = pt.x;
			surf->points[i].y = pt.y;
#else
			/*move to 16.16 representation*/
			surf->points[i].x = (u32) (pt.x * 0x10000L);
			surf->points[i].y = (u32) (pt.y * 0x10000L);
#endif
			surf->tags[i] = gp->tags[i];
		}
	} else {
		for (i=0; i<gp->n_points; i++) {
			pt = gp->points[i];
			m4_mx2d_apply_point(&surf->mat, &pt);
#ifdef M4_FIXED_POINT
			surf->points[i].x = pt.x;
			surf->points[i].y = pt.y;
#else
			/*move to 16.16 representation*/
			surf->points[i].x = (u32) (pt.x * 0x10000L);
			surf->points[i].y = (u32) (pt.y * 0x10000L);
#endif
			surf->tags[i] = gp->tags[i];
		}
	}

	surf->ftoutline.flags = 0;
	if (gp->flags & M4_PATH_FILL_ZERO_NONZERO) surf->ftoutline.flags = M4_PATH_FILL_ZERO_NONZERO;
	return M4OK;
}


M4Err m4_surface_fill(LPM4SURFACE _this, LPM4STENCIL stencil)
{
	M4Rect rc;
	M4Matrix2D mat, st_mat;
	Bool restore_filter;
	M4Surface *surf = (M4Surface *)_this;
	M4Stencil *sten = (M4Stencil *)stencil;
	if (!surf || !stencil) return M4BadParam;
	if (!surf->ftoutline.n_points) return M4OK;
	surf->sten = sten;

	/*setup ft raster calllbacks*/
	if (!setup_ft_callbacks(surf)) return M4OK;

	get_surface_world_matrix(surf, &mat);

	restore_filter = 0;
	/*get path frame for texture convertion */
	if (sten->type != M4_STENCIL_SOLID) {
		rc = surf->path_bounds;
		m4_mx2d_apply_rect(&mat, &rc);
		rc.x = rc.y = 0;
		/*assign target frame and matrix*/
		sten->frame = rc;
		m4_mx2d_copy(sten->pmat, surf->mat);
		m4_mx2d_inverse(&sten->pmat);

		m4_mx2d_copy(st_mat, sten->smat);
		m4_mx2d_init(sten->smat);
		switch (sten->type) {
		case M4_STENCIL_TEXTURE:
			if (! ((M4Texture *)sten)->pixels) return M4BadParam;
			if (surf->center_coords) m4_mx2d_add_scale(&sten->smat, FIX_ONE, -FIX_ONE);
			m4_set_texture_active(sten);
			m4_mx2d_add_matrix(&sten->smat, &st_mat);
			m4_mx2d_add_matrix(&sten->smat, &mat);
			m4_mx2d_inverse(&sten->smat);
			bmp_init(sten);
			if (((M4Texture *)sten)->filter == M4_TEXTURE_FILTER_DEFAULT) {
				restore_filter = 1;
				((M4Texture *)sten)->filter = surf->texture_filter;
			}

			break;
		case M4_STENCIL_LINEAR_GRADIENT:
		{
			M4LinearGradient *lin = (M4LinearGradient *)sten;
			m4_mx2d_add_matrix(&sten->smat, &st_mat);
			m4_mx2d_add_matrix(&sten->smat, &mat);
			m4_mx2d_inverse(&sten->smat);
			/*and finalize matrix in gradient coord system*/
			m4_mx2d_add_matrix(&sten->smat, &lin->vecmat);
			m4_mx2d_add_scale(&sten->smat, INT2FIX(1<<M4GRADIENTSCALEBITS), INT2FIX(1<<M4GRADIENTSCALEBITS));

		}
			break;
		case M4_STENCIL_RADIAL_GRADIENT:
		{
			M4RadialGradient *rad = (M4RadialGradient*)sten;
			m4_mx2d_copy(sten->smat, st_mat);
			m4_mx2d_add_matrix(&sten->smat, &mat);
			m4_mx2d_inverse(&sten->smat);
			m4_mx2d_add_translation(&sten->smat, -rad->center.x, -rad->center.y);
			m4_mx2d_add_scale(&sten->smat, m4_divfix(FIX_ONE, rad->radius.x), m4_divfix(FIX_ONE, rad->radius.y));
			rad->d_f.x = m4_divfix(rad->focus.x - rad->center.x, rad->radius.x);
			rad->d_f.y = m4_divfix(rad->focus.y - rad->center.y, rad->radius.y);
			/*init*/
			m4_radial_init(rad);
		}
			break;
		}
	}

	if (surf->useClipper) {
		surf->ftparams.clip_box.xMin = surf->clipper.x;
		surf->ftparams.clip_box.yMin = surf->clipper.y;
		surf->ftparams.clip_box.xMax = (surf->clipper.x + surf->clipper.width);
		surf->ftparams.clip_box.yMax = (surf->clipper.y + surf->clipper.height);
	} else {
		surf->ftparams.clip_box.xMin = 0;
		surf->ftparams.clip_box.yMin = 0;
		surf->ftparams.clip_box.xMax = (surf->width);
		surf->ftparams.clip_box.yMax = (surf->height);
	}

	/*and call the raster*/
	m4_gray_raster_render(surf->ftraster, &surf->ftparams);

	/*restore stencil matrix*/
	if (sten->type != M4_STENCIL_SOLID) {
		m4_mx2d_copy(sten->smat, st_mat);
		if (restore_filter) ((M4Texture *)sten)->filter = M4_TEXTURE_FILTER_DEFAULT;
	}
	surf->sten = 0L;
	return M4OK;
}



