/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / 3D rendering 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 "render3d_nodes.h"


#define GRAD_TEXTURE_SIZE	128
#define GRAD_TEXTURE_HSIZE	64

/*linear/radial gradient*/
typedef struct
{
	TextureHandler txh;
	char *tx_data;
	Bool transparent;
	Bool no_rgb_support;
} GradientStack;

void GradientGetMatrix(SFNode *transform, M4Matrix2D *mat)
{
	m4_mx2d_init(*mat);
	if (transform) {
		switch (Node_GetTag(transform) ) {
		case TAG_MPEG4_Transform2D:
		{
			M_Transform2D *tr = (M_Transform2D *)transform;
			m4_mx2d_add_scale_at(mat, 0, 0, tr->scale.x, tr->scale.y, tr->scaleOrientation);
			m4_mx2d_add_rotation(mat, tr->center.x, tr->center.y, tr->rotationAngle);
			m4_mx2d_add_translation(mat, tr->translation.x, tr->translation.y);
		}
			break;
		case TAG_MPEG4_TransformMatrix2D:
		{
			M_TransformMatrix2D *tm = (M_TransformMatrix2D*)transform;
			m4_mx2d_init(*mat);
			mat->m[0] = tm->mxx; mat->m[1] = tm->mxy; mat->m[2] = tm->tx;
			mat->m[3] = tm->myx; mat->m[4] = tm->myy; mat->m[5] = tm->ty;
		}
			break;
		default:
			break;
		}
	}
}

static void DestroyLinearGradient(SFNode *node)
{
	GradientStack *st = (GradientStack *) Node_GetPrivate(node);
	texture_destroy(&st->txh);
	if (st->tx_data) free(st->tx_data);
	free(st);
}

static void UpdateLinearGradient(TextureHandler *txh)
{
	u32 i;
	SFVec2f start, end;
	u32 *cols;
	Fixed a;
	Bool const_a;
	M4Matrix2D mat;
	LPM4STENCIL stenc;
	LPM4SURFACE surf;
	LPM4STENCIL texture2D;
	M4Path *path;
	M4Err e;
	Bool transparent;
	M_LinearGradient *lg = (M_LinearGradient *) txh->owner;
	GradientStack *st = (GradientStack *) Node_GetPrivate(txh->owner);
	Raster2D *r2d = txh->compositor->r2d;

	if (!txh->hwtx) Node_SetDirty(txh->owner, 0);
	if (!Node_GetDirty(txh->owner)) return;
	Node_ClearDirty(txh->owner);

	if (!txh->hwtx) tx_allocate(txh);
	if (st->tx_data) {
		free(st->tx_data);
		st->tx_data = NULL;
	}

	if (lg->key.count<2) return;
	if (lg->key.count != lg->keyValue.count) return;

	start = lg->startPoint;
	end = lg->endPoint;

	transparent = (lg->opacity.count==1) ? (lg->opacity.vals[0]!=FIX_ONE) : 1;
	
	/*init our 2D graphics stuff*/
	texture2D = r2d->stencil_new(r2d, M4_STENCIL_TEXTURE);
	if (!texture2D) return;
	surf = r2d->surface_new(r2d, 1);
	if (!surf) {
		r2d->stencil_delete(texture2D);
		return;
	}

	if (st->no_rgb_support) transparent = 1;
	if (st->tx_data && (st->transparent != transparent)) {
		free(st->tx_data);
		st->tx_data = NULL;
	}
	
	if (transparent) {
		if (!st->tx_data) {
			SAFEALLOC(st->tx_data, sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*4);
		} else {
			memset(st->tx_data, 0, sizeof(char)*txh->stride*txh->height);
		}
		e = r2d->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, M4PF_ARGB, M4PF_ARGB, 1);
	} else {
		if (!st->tx_data) {
			SAFEALLOC(st->tx_data, sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*3);
		}
		e = r2d->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 3*GRAD_TEXTURE_SIZE, M4PF_RGB_24, M4PF_RGB_24, 1);
		/*try with ARGB (it actually is needed for GDIplus plugin since GDIplus cannot handle native RGB texture (it works in BGR)*/
		if (e) {
			/*remember for later use*/
			st->no_rgb_support = 1;
			transparent = 1;
			free(st->tx_data);
			SAFEALLOC(st->tx_data, sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*4);
			e = r2d->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, M4PF_ARGB, M4PF_ARGB, 1);
		}
	}
	st->transparent = transparent;

	if (e) {
		free(st->tx_data);
		r2d->stencil_delete(texture2D);
		r2d->surface_delete(surf);
		return;
	}
	e = r2d->surface_attach_to_texture(surf, texture2D);
	if (e) {
		r2d->stencil_delete(texture2D);
		r2d->surface_delete(surf);
		return;
	}

	/*create & setup gradient*/
	stenc = r2d->stencil_new(r2d, M4_STENCIL_LINEAR_GRADIENT);
	if (!stenc) {
		r2d->stencil_delete(texture2D);
		r2d->surface_delete(surf);
	}
	/*move line to object space*/
	start.x *= GRAD_TEXTURE_SIZE;
	end.x *= GRAD_TEXTURE_SIZE;
	start.y *= GRAD_TEXTURE_SIZE;
	end.y *= GRAD_TEXTURE_SIZE;
	r2d->stencil_set_linear_gradient(stenc, start.x, start.y, end.x, end.y, 0xFFFF0000, 0xFFFF00FF);
	const_a = (lg->opacity.count == 1) ? 1 : 0;
	cols = malloc(sizeof(u32) * lg->key.count);
	for (i=0; i<lg->key.count; i++) {
		a = (const_a ? lg->opacity.vals[0] : lg->opacity.vals[i]);
		cols[i] = M4C_ARGB_FIXED(a, lg->keyValue.vals[i].red, lg->keyValue.vals[i].green, lg->keyValue.vals[i].blue);
	}
	r2d->stencil_set_gradient_interpolation(stenc, lg->key.vals, cols, lg->key.count);
	free(cols);
	r2d->stencil_set_gradient_mode(stenc, lg->spreadMethod);

	/*fill surface*/
	path = m4_path_new();
	m4_path_add_move_to(path, -INT2FIX(GRAD_TEXTURE_HSIZE), -INT2FIX(GRAD_TEXTURE_HSIZE));
	m4_path_add_line_to(path, INT2FIX(GRAD_TEXTURE_HSIZE), -INT2FIX(GRAD_TEXTURE_HSIZE));
	m4_path_add_line_to(path, INT2FIX(GRAD_TEXTURE_HSIZE), INT2FIX(GRAD_TEXTURE_HSIZE));
	m4_path_add_line_to(path, -INT2FIX(GRAD_TEXTURE_HSIZE), INT2FIX(GRAD_TEXTURE_HSIZE));
	m4_path_close(path);

	/*add gradient transform*/
	GradientGetMatrix(lg->transform, &mat);
	/*move transform to object space*/
	mat.m[2] *= GRAD_TEXTURE_SIZE;
	mat.m[5] *= GRAD_TEXTURE_SIZE;
	/*translate to the center of the bounds*/
	m4_mx2d_add_translation(&mat, -INT2FIX(GRAD_TEXTURE_HSIZE), -INT2FIX(GRAD_TEXTURE_HSIZE));
	/*back to GL bottom->up order*/
	m4_mx2d_add_scale(&mat, FIX_ONE, -FIX_ONE);
	r2d->stencil_set_matrix(stenc, &mat);

	r2d->surface_set_raster_level(surf, M4_RASTER_HIGH_QUALITY);
	r2d->surface_set_path(surf, path);
	r2d->surface_fill(surf, stenc);
	r2d->stencil_delete(stenc);
	r2d->surface_delete(surf);
	r2d->stencil_delete(texture2D);
	m4_path_delete(path);

	txh->data = st->tx_data;
	txh->width = GRAD_TEXTURE_SIZE;
	txh->height = GRAD_TEXTURE_SIZE;
	txh->transparent = transparent;
	if (transparent) {
		u32 j;
		txh->stride = GRAD_TEXTURE_SIZE*4;

		/*back to RGBA texturing*/
		txh->pixelformat = M4PF_RGBA;
		for (i=0; i<txh->height; i++) {
			char *data = txh->data + i*txh->stride;
			for (j=0; j<txh->width; j++) {
				u32 val = *(u32 *) &data[4*j];
				data[4*j] = (val>>16) & 0xFF;
				data[4*j+1] = (val>>8) & 0xFF;
				data[4*j+2] = (val) & 0xFF;
				data[4*j+3] = (val>>24) & 0xFF;
			}
		}
	} else {
		txh->stride = GRAD_TEXTURE_SIZE*3;
		txh->pixelformat = M4PF_RGB_24;
	}
	R3D_SetTextureData(txh);
}

void R3D_InitLinearGradient(Render3D *sr, SFNode *node)
{
	GradientStack *st = malloc(sizeof(GradientStack));
	memset(st, 0, sizeof(GradientStack));
	texture_setup(&st->txh, sr->compositor, node);
	st->txh.update_texture_fcnt = UpdateLinearGradient;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyLinearGradient);
}

TextureHandler *r3d_lg_get_texture(SFNode *node)
{
	GradientStack *st = (GradientStack*) Node_GetPrivate(node);
	return &st->txh;
}


static void DestroyRadialGradient(SFNode *node)
{
	GradientStack *st = (GradientStack *) Node_GetPrivate(node);
	texture_destroy(&st->txh);
	if (st->tx_data) free(st->tx_data);
	free(st);
}

static void UpdateRadialGradient(TextureHandler *txh)
{
	u32 i;
	SFVec2f center, focal;
	u32 *cols;
	Fixed a, radius;
	Bool const_a;
	M4Matrix2D mat;
	LPM4STENCIL stenc;
	LPM4SURFACE surf;
	LPM4STENCIL texture2D;
	M4Path *path;
	M4Err e;
	Bool transparent;
	M_RadialGradient *rg = (M_RadialGradient*) txh->owner;
	GradientStack *st = (GradientStack *) Node_GetPrivate(txh->owner);
	Raster2D *r2d = txh->compositor->r2d;

	if (!txh->hwtx) Node_SetDirty(txh->owner, 0);
	if (!Node_GetDirty(txh->owner)) return;
	Node_ClearDirty(txh->owner);

	if (!txh->hwtx) tx_allocate(txh);
	if (st->tx_data) {
		free(st->tx_data);
		st->tx_data = NULL;
	}

	if (rg->key.count<2) return;
	if (rg->key.count != rg->keyValue.count) return;

	transparent = (rg->opacity.count==1) ? ((rg->opacity.vals[0]!=FIX_ONE) ? 1 : 0) : 1;
	
	/*init our 2D graphics stuff*/
	texture2D = r2d->stencil_new(r2d, M4_STENCIL_TEXTURE);
	if (!texture2D) return;
	surf = r2d->surface_new(r2d, 1);
	if (!surf) {
		r2d->stencil_delete(texture2D);
		return;
	}

	if (st->no_rgb_support) transparent = 1;
	if (st->tx_data && (st->transparent != transparent)) {
		free(st->tx_data);
		st->tx_data = NULL;
	}
	
	if (transparent) {
		if (!st->tx_data) {
			SAFEALLOC(st->tx_data, sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*4);
		} else {
			memset(st->tx_data, 0, sizeof(char)*txh->stride*txh->height);
		}
		e = r2d->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, M4PF_ARGB, M4PF_ARGB, 1);
	} else {
		if (!st->tx_data) {
			SAFEALLOC(st->tx_data, sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*3);
		}
		e = r2d->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 3*GRAD_TEXTURE_SIZE, M4PF_RGB_24, M4PF_RGB_24, 1);
		/*try with ARGB (it actually is needed for GDIplus plugin since GDIplus cannot handle native RGB texture (it works in BGR)*/
		if (e) {
			/*remember for later use*/
			st->no_rgb_support = 1;
			transparent = 1;
			free(st->tx_data);
			SAFEALLOC(st->tx_data, sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*4);
			e = r2d->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, M4PF_ARGB, M4PF_ARGB, 1);
		}
	}
	st->transparent = transparent;

	if (e) {
		free(st->tx_data);
		r2d->stencil_delete(texture2D);
		r2d->surface_delete(surf);
		return;
	}
	e = r2d->surface_attach_to_texture(surf, texture2D);
	if (e) {
		r2d->stencil_delete(texture2D);
		r2d->surface_delete(surf);
		return;
	}

	/*create & setup gradient*/
	stenc = r2d->stencil_new(r2d, M4_STENCIL_RADIAL_GRADIENT);
	if (!stenc) {
		r2d->stencil_delete(texture2D);
		r2d->surface_delete(surf);
	}

	center = rg->center;
	focal = rg->focalPoint;
	radius = rg->radius;

	/*move circle to object space*/
	center.x *= GRAD_TEXTURE_SIZE;
	center.y *= GRAD_TEXTURE_SIZE;
	focal.x *= GRAD_TEXTURE_SIZE;
	focal.y *= GRAD_TEXTURE_SIZE;
	radius *= GRAD_TEXTURE_SIZE;

	r2d->stencil_set_radial_gradient(stenc, center.x, center.y, focal.x, focal.y, radius, radius);

	const_a = (rg->opacity.count == 1) ? 1 : 0;
	cols = malloc(sizeof(u32) * rg->key.count);
	for (i=0; i<rg->key.count; i++) {
		a = (const_a ? rg->opacity.vals[0] : rg->opacity.vals[i]);
		cols[i] = M4C_ARGB_FIXED(a, rg->keyValue.vals[i].red, rg->keyValue.vals[i].green, rg->keyValue.vals[i].blue);
	}
	r2d->stencil_set_gradient_interpolation(stenc, rg->key.vals, cols, rg->key.count);
	free(cols);
	r2d->stencil_set_gradient_mode(stenc, rg->spreadMethod);

	/*fill surface*/
	path = m4_path_new();
	m4_path_add_move_to(path, -INT2FIX(GRAD_TEXTURE_HSIZE), -INT2FIX(GRAD_TEXTURE_HSIZE));
	m4_path_add_line_to(path, INT2FIX(GRAD_TEXTURE_HSIZE), -INT2FIX(GRAD_TEXTURE_HSIZE));
	m4_path_add_line_to(path, INT2FIX(GRAD_TEXTURE_HSIZE), INT2FIX(GRAD_TEXTURE_HSIZE));
	m4_path_add_line_to(path, -INT2FIX(GRAD_TEXTURE_HSIZE), INT2FIX(GRAD_TEXTURE_HSIZE));
	m4_path_close(path);

	/*add gradient transform*/
	GradientGetMatrix(rg->transform, &mat);
	/*move transform to object space*/
	mat.m[2] *= GRAD_TEXTURE_SIZE;
	mat.m[5] *= GRAD_TEXTURE_SIZE;
	/*translate to the center of the bounds*/
	m4_mx2d_add_translation(&mat, -INT2FIX(GRAD_TEXTURE_HSIZE), -INT2FIX(GRAD_TEXTURE_HSIZE));
	/*back to GL bottom->up order*/
	m4_mx2d_add_scale(&mat, FIX_ONE, -FIX_ONE);
	r2d->stencil_set_matrix(stenc, &mat);

	r2d->surface_set_raster_level(surf, M4_RASTER_HIGH_QUALITY);
	r2d->surface_set_path(surf, path);
	r2d->surface_fill(surf, stenc);
	r2d->stencil_delete(stenc);
	r2d->surface_delete(surf);
	r2d->stencil_delete(texture2D);
	m4_path_delete(path);

	txh->data = st->tx_data;
	txh->width = GRAD_TEXTURE_SIZE;
	txh->height = GRAD_TEXTURE_SIZE;
	txh->transparent = transparent;
	if (transparent) {
		u32 j;
		txh->stride = GRAD_TEXTURE_SIZE*4;

		/*back to RGBA texturing*/
		txh->pixelformat = M4PF_RGBA;
		for (i=0; i<txh->height; i++) {
			char *data = txh->data + i*txh->stride;
			for (j=0; j<txh->width; j++) {
				u32 val = *(u32 *) &data[4*j];
				data[4*j] = (val>>16) & 0xFF;
				data[4*j+1] = (val>>8) & 0xFF;
				data[4*j+2] = (val) & 0xFF;
				data[4*j+3] = (val>>24) & 0xFF;
			}
		}
	} else {
		txh->stride = GRAD_TEXTURE_SIZE*3;
		txh->pixelformat = M4PF_RGB_24;
	}
//	tx_set_blend_enable(txh, 1);
	R3D_SetTextureData(txh);
	return;
}

void R3D_InitRadialGradient(Render3D *sr, SFNode *node)
{
	GradientStack *st = malloc(sizeof(GradientStack));
	memset(st, 0, sizeof(GradientStack));
	texture_setup(&st->txh, sr->compositor, node);
	st->txh.update_texture_fcnt = UpdateRadialGradient;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyRadialGradient);
}

TextureHandler *r3d_rg_get_texture(SFNode *node)
{
	GradientStack *st = (GradientStack*) Node_GetPrivate(node);
	return &st->txh;
}
