/*
 *			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"

/*stack used by CompositeTexture2D and CompositeTexture3D*/
typedef struct _composite_texture
{
	TextureHandler txh;
	/*the surface object handling the texture*/
	struct _visual_surface *surface;
	Bool is_pm, first;
	/*for 2D composite texture*/
	Fixed sx, sy;
} CompositeTextureStack;


static void DestroyCompositeTexture(SFNode *node)
{
	CompositeTextureStack *st = (CompositeTextureStack *) Node_GetPrivate(node);
	assert(!st->txh.data);
	texture_destroy(&st->txh);
	VS_Delete(st->surface);
	free(st);
}

static void Composite_GetPixelSize(SFNode *n, s32 *pw, s32 *ph)
{
	*pw = *ph = 0;
	if (!n) return;
	switch (Node_GetTag(n)) {
	case TAG_MPEG4_CompositeTexture3D:
		*pw = ((M_CompositeTexture3D*)n)->pixelWidth;
		*ph = ((M_CompositeTexture3D*)n)->pixelHeight;
		break;
	case TAG_MPEG4_CompositeTexture2D:
		*pw = ((M_CompositeTexture2D*)n)->pixelWidth;
		*ph = ((M_CompositeTexture2D*)n)->pixelHeight;
		break;
	}
	if (*pw<0) *pw = 0;
	if (*ph<0) *ph = 0;
}

static void Composite_SetPixelSize(SFNode *n, s32 pw, s32 ph)
{
	if (!n) return;
	switch (Node_GetTag(n)) {
	case TAG_MPEG4_CompositeTexture3D:
		((M_CompositeTexture3D*)n)->pixelWidth = pw;
		((M_CompositeTexture3D*)n)->pixelHeight = ph;
		break;
	case TAG_MPEG4_CompositeTexture2D:
		((M_CompositeTexture2D*)n)->pixelWidth = pw;
		((M_CompositeTexture2D*)n)->pixelHeight = ph;
		break;
	}
}

static Bool Composite_CheckBindables(SFNode *n, RenderEffect3D *eff, Bool force_check)
{
	SFNode *btop;
	Bool ret = 0;
	switch (Node_GetTag(n)) {
	case TAG_MPEG4_CompositeTexture3D: 
	{
		M_CompositeTexture3D*c3d = (M_CompositeTexture3D*)n;
		if (force_check || Node_GetDirty(c3d->background)) { Node_Render(c3d->background, eff); ret = 1; }
		btop = ChainGetEntry(eff->backgrounds, 0);
		if (btop != c3d->background) { 
			Node_Unregister(c3d->background, n);
			Node_Register(btop, n); 
			c3d->background = btop;
			Node_OnEventOutSTR(n, "background");
			ret = 1; 
		}
		if (force_check || Node_GetDirty(c3d->viewpoint)) { Node_Render(c3d->viewpoint, eff); ret = 1; }
		btop = ChainGetEntry(eff->viewpoints, 0);
		if (btop != c3d->viewpoint) { 
			Node_Unregister(c3d->viewpoint, n);
			Node_Register(btop, n); 
			c3d->viewpoint = btop; 
			Node_OnEventOutSTR(n, "viewpoint");
			ret = 1; 
		}
		
		if (force_check || Node_GetDirty(c3d->fog)) { Node_Render(c3d->fog, eff); ret = 1; }
		btop = ChainGetEntry(eff->fogs, 0);
		if (btop != c3d->fog) {
			Node_Unregister(c3d->fog, n);
			Node_Register(btop, n); 
			c3d->fog = btop;
			Node_OnEventOutSTR(n, "fog");
			ret = 1;
		}
		
		if (force_check || Node_GetDirty(c3d->navigationInfo)) { Node_Render(c3d->navigationInfo, eff); ret = 1; }
		btop = ChainGetEntry(eff->navigations, 0);
		if (btop != c3d->navigationInfo) {
			Node_Unregister(c3d->navigationInfo, n);
			Node_Register(btop, n); 
			c3d->navigationInfo = btop;
			Node_OnEventOutSTR(n, "navigationInfo");
			ret = 1;
		}
		return ret;
	}
	case TAG_MPEG4_CompositeTexture2D: 
	{
		M_CompositeTexture2D *c2d = (M_CompositeTexture2D*)n;
		if (force_check || Node_GetDirty(c2d->background)) { Node_Render(c2d->background, eff); ret = 1; }
		btop = ChainGetEntry(eff->backgrounds, 0);
		if (btop != c2d->background) {
			Node_Unregister(c2d->background, n);
			Node_Register(btop, n); 
			c2d->background = btop;
			Node_OnEventOutSTR(n, "background");
			ret = 1;
		}

		if (force_check || Node_GetDirty(c2d->viewport)) { Node_Render(c2d->viewport, eff); ret = 1; }
		btop = ChainGetEntry(eff->viewpoints, 0);
		if (btop != c2d->viewport) { 
			Node_Unregister(c2d->viewport, n);
			Node_Register(btop, n); 
			c2d->viewport = btop;
			Node_OnEventOutSTR(n, "viewport");
			ret = 1;
		}
		
		return ret;
	}
	}
	return 0;
}



static Chain *Composite_GetChildren(SFNode *n)
{
	switch (Node_GetTag(n)) {
	case TAG_MPEG4_CompositeTexture3D: return ((M_CompositeTexture3D*)n)->children;
	case TAG_MPEG4_CompositeTexture2D: return ((M_CompositeTexture2D*)n)->children;
	default: return NULL;
	}
}

static void UpdateCompositeTexture(TextureHandler *txh)
{
	s32 w, h;
	M4BBox box;
	u32 i, count;
	SFNode *child;
	RenderEffect3D *eff;
	Chain *children;
	Bool is_dirty;
	CompositeTextureStack *st = (CompositeTextureStack *) Node_GetPrivate(txh->owner);

	if (st->txh.hwtx && st->txh.compositor->reset_graphics) tx_delete(&st->txh);

	Composite_GetPixelSize(st->txh.owner, &w, &h);
	if (!w || !h) return;

	if (!st->txh.hwtx || ((s32) st->txh.width != w) || ( (s32) st->txh.height != h) ) {
		tx_delete(&st->txh);

		/*we don't use rect ext because of no support for texture transforms*/
		if (st->txh.compositor->hw_caps.npot_texture ) {
			st->txh.width = w;
			st->txh.height = h;
		} else {
			st->txh.width = 2;
			while (st->txh.width<(u32)w) st->txh.width*=2;
			st->txh.height = 2;
			while (st->txh.height<(u32)h) st->txh.height*=2;

			st->sx = INT2FIX(st->txh.width) / w;
			st->sy = INT2FIX(st->txh.height) / h;

			Composite_SetPixelSize(st->txh.owner, st->txh.width, st->txh.height);
		}

		/*generate texture*/
		st->txh.pixelformat = M4PF_RGBA;

		st->txh.stride = st->txh.width*4;
		st->txh.transparent = 1;
		st->surface->width = st->txh.width;
		st->surface->height = st->txh.height;

		tx_allocate(&st->txh);
		
		st->txh.data = malloc(sizeof(unsigned char) * st->txh.stride * st->txh.height);
		tx_setup_format(&st->txh);
		tx_set_image(&st->txh, 0);
		free(st->txh.data);
		st->txh.data = NULL;
	}

	children = Composite_GetChildren(st->txh.owner);
	/*note: we never clear the dirty state of the composite texture, so that any child invalidated
	doesn't invalidate the parent graph*/
	is_dirty = 0;
	count = ChainGetCount(children);
	for (i=0; i<count; i++) {
		child = ChainGetEntry(children, i);
		if (Node_GetDirty(child)) {
			is_dirty = 1;
			break;
		}
	}
	if (st->first) st->is_pm = SG_UsePixelMetrics(Node_GetParentGraph(txh->owner));

	eff = effect3d_new();
	eff->surface = st->surface;
	m4_mx_init(eff->model_matrix);
	m4_cmx_init(&eff->color_mat);
	eff->is_pixel_metrics = st->is_pm;
	VS_SetupEffects(st->surface, eff);
	eff->traversing_mode = TRAVERSE_GET_BOUNDS;

	/*children dirty, get bounds*/
	if (is_dirty) {
		/*store bbox for background2D*/
		box = eff->bbox;
		for (i=0; i<count; i++) {
			child = ChainGetEntry(children, i);
			Node_Render(child, eff);
		}
		eff->bbox = box;
	}

	if (!st->surface->camera.is_3D) m4_mx_add_scale(&eff->model_matrix, st->sx, st->sy, 1);

	/*check bindables*/
	if (Composite_CheckBindables(st->txh.owner, eff, st->first)) is_dirty = 1;
	st->first = 0;

	if (!is_dirty) {
		effect3d_delete(eff);
		return;
	}

	/*setup GL*/
	VS3D_Setup(st->surface);

	/*render*/
	VS_RootRenderChildren(eff, children);

	tx_copy_to_texture(&st->txh);
	effect3d_delete(eff);
}

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

Bool r3d_has_composite_texture(SFNode *appear)
{
	u32 tag;
	if (!appear) return 0;
	tag = Node_GetTag(appear);
	if ((tag==TAG_MPEG4_Appearance) || (tag==TAG_X3D_Appearance)) {
		M_Appearance *ap = (M_Appearance *)appear;
		if (!ap->texture) return 0;
		switch (Node_GetTag(((M_Appearance *)appear)->texture)) {
		case TAG_MPEG4_CompositeTexture2D:
		case TAG_MPEG4_CompositeTexture3D:
			return 1;
		}
	}
	return 0;
}

void R3D_CompositeAdjustScale(SFNode *node, Fixed *sx, Fixed *sy)
{
	switch (Node_GetTag(node)) {
	case TAG_MPEG4_CompositeTexture2D:
	case TAG_MPEG4_CompositeTexture3D:
	{
		CompositeTextureStack *st = (CompositeTextureStack *) Node_GetPrivate(node);
		(*sx) = m4_divfix(*sx, st->sx);
		(*sy) = m4_divfix(*sy, st->sy);
		break;
	}
	default:
		return;
	}
}


void R3D_InitCompositeTexture3D(Render3D *sr, SFNode *node)
{
	CompositeTextureStack *st = malloc(sizeof(CompositeTextureStack));
	memset(st, 0, sizeof(CompositeTextureStack));
	texture_setup(&st->txh, sr->compositor, node);
	st->txh.flags = TX_REPEAT_S | TX_REPEAT_T;
	st->first = 1;
	/*create composite surface*/
	st->surface = VS_New();
	st->surface->camera.is_3D = 1;
	camera_invalidate(&st->surface->camera);
	st->surface->render = sr;
	st->txh.update_texture_fcnt = UpdateCompositeTexture;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyCompositeTexture);
}


void R3D_InitCompositeTexture2D(Render3D *sr, SFNode *node)
{
	CompositeTextureStack *st = malloc(sizeof(CompositeTextureStack));
	memset(st, 0, sizeof(CompositeTextureStack));
	texture_setup(&st->txh, sr->compositor, node);
	st->txh.flags = TX_REPEAT_S | TX_REPEAT_T;
	st->first = 1;
	/*create composite surface*/
	st->surface = VS_New();
	st->surface->camera.is_3D = 0;
	camera_invalidate(&st->surface->camera);
	st->surface->render = sr;
	st->txh.update_texture_fcnt = UpdateCompositeTexture;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyCompositeTexture);
}


Bool r3d_handle_composite_event(Render3D *sr, M4UserEvent *ev)
{
	CompositeTextureStack *st;
	M4Matrix mx;
	RenderEffect3D *eff;
	Chain *children;
	Bool res;
	u32 i;
	SFVec3f txcoord;
	M_Appearance *ap = (M_Appearance *)sr->hit_info.appear;
	assert(ap && ap->texture);

	if (ev->event_type > M4E_LEFTUP) return 0;

	st = (CompositeTextureStack *) Node_GetPrivate(ap->texture);

	txcoord.x = sr->hit_info.hit_texcoords.x;
	txcoord.y = sr->hit_info.hit_texcoords.y;
	txcoord.z = 0;
	if (tx_get_transform(&st->txh, ap->textureTransform, &mx)) {
		/*tx coords are inverted when mapping, thus applying directly the matrix will give us the 
		untransformed coords*/
		m4_mx_apply_vec(&mx, &txcoord);
		while (txcoord.x<0) txcoord.x += FIX_ONE; while (txcoord.x>FIX_ONE) txcoord.x -= FIX_ONE;
		while (txcoord.y<0) txcoord.y += FIX_ONE; while (txcoord.y>FIX_ONE) txcoord.y -= FIX_ONE;
	}
	/*convert to tx space*/
	ev->mouse.x = FIX2INT(txcoord.x - FIX_ONE/2) * st->surface->width;
	ev->mouse.y = FIX2INT(txcoord.y - FIX_ONE/2) * st->surface->height;

	eff = effect3d_new();
	eff->surface = st->surface;
	eff->traversing_mode = TRAVERSE_RENDER;
	m4_mx_init(eff->model_matrix);
	m4_cmx_init(&eff->color_mat);
	eff->is_pixel_metrics = st->is_pm;
	VS_SetupEffects(st->surface, eff);

	children = Composite_GetChildren(st->txh.owner);
	for (i=0; i<ChainGetCount(children); i++) {
		SFNode *child = ChainGetEntry(children, i);
		SensorHandler *hsens = r3d_get_sensor_handler(child);
		if (hsens) ChainAddEntry(eff->sensors, hsens);
	}
	res = VS_ExecuteEvent(st->surface, eff, ev, children);
	effect3d_delete(eff);
	return res;
}



TextureHandler *R3D_GetTextureHandler(SFNode *n)
{
	if (!n) return NULL;
	switch (Node_GetTag(n)) {
	case TAG_MPEG4_CompositeTexture2D: return r3d_composite_get_texture(n);
	case TAG_MPEG4_CompositeTexture3D: return r3d_composite_get_texture(n);
	case TAG_MPEG4_LinearGradient: return r3d_lg_get_texture(n);
	case TAG_MPEG4_RadialGradient: return r3d_rg_get_texture(n);
/*	
	case TAG_MPEG4_MatteTexture: return matte_get_texture(n);
*/
	default: return texture_get_handler(n);
	}
}
