/*
 *			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"
#include "grouping.h"

void group_start_child(GroupingNode *group, SFNode *n)
{
	ChildGroup *cg;
	if (!n) {
		ChildGroup *cg_prev = ChainGetEntry(group->groups, ChainGetCount(group->groups)-1);
		n = cg_prev ? cg_prev->child : NULL;
	}
	if (!n) return;
	cg = malloc(sizeof(ChildGroup));
	memset(cg, 0, sizeof(ChildGroup));
	cg->child = n;
	ChainAddEntry(group->groups, cg);
}

void group_end_child(GroupingNode *group, M4BBox *bounds)
{
	ChildGroup *cg = ChainGetEntry(group->groups, ChainGetCount(group->groups)-1);
	if (!cg) return;
	/*don't override splitted text info*/
	if (cg->is_text_group) return;
	m4_rect_from_bbox(&cg->original, bounds);
	cg->final = cg->original;
}

void group_end_text_child(GroupingNode *group, M4Rect *bounds, Fixed ascent, Fixed descent, u32 split_text_idx)
{
	ChildGroup *cg = ChainGetEntry(group->groups, ChainGetCount(group->groups)-1);
	if (!cg) return;
	cg->split_text_idx = split_text_idx;
	cg->is_text_group = 1;
	cg->ascent = ascent;
	cg->descent = descent;
	cg->final = cg->original = *bounds;
}


void group_reset_children(GroupingNode *group)
{
	while (ChainGetCount(group->groups)) {
		ChildGroup *cg = ChainGetEntry(group->groups, 0);
		ChainDeleteEntry(group->groups, 0);
		free(cg);
	}
}



/*This is the generic routine for child traversing*/
void grouping_traverse(GroupingNode *group, RenderEffect3D *eff, u32 *positions)
{
	u32 i, count, mode_back;
	Bool split_text_backup, is_parent, get_bounds, do_lights;
	DLightContext *dl;
	Chain *sensor_backup;
	SFNode *child;
	SensorHandler *hsens;

	is_parent = (eff->parent == group) ? 1 : 0;
	count = ChainGetCount(group->children);

	if (Node_GetDirty(group->owner) & SG_CHILD_DIRTY) {
		/*need to recompute bounds*/
		if (eff->traversing_mode!=TRAVERSE_GET_BOUNDS) {
			/*traverse subtree to recompute bounds*/
			mode_back = eff->traversing_mode;
			eff->traversing_mode=TRAVERSE_GET_BOUNDS;
			grouping_traverse(group, eff, positions);
			eff->traversing_mode = mode_back;
		}
		/*we're recomputing bounds*/
		else {
			u32 ntag = Node_GetTag(group->owner);
			/*rebuild sensors/lights*/
			if (group->sensors) DeleteChain(group->sensors);
			group->sensors = NewChain();
			if (group->lights) DeleteChain(group->lights);
			group->lights = NewChain();

			/*special case for anchor which is a parent node acting as a sensor*/
			if ((ntag==TAG_MPEG4_Anchor) || (ntag==TAG_X3D_Anchor)) {
				SensorHandler *r3d_anchor_get_handler(SFNode *n);
				hsens = r3d_anchor_get_handler(group->owner);
				if (hsens) ChainAddEntry(group->sensors, hsens);
			}
			for (i=0; i<count; i++) {
				child = ChainGetEntry(group->children, positions ? positions[i] : i);
				hsens = r3d_get_sensor_handler(child);
				if (hsens) ChainAddEntry(group->sensors, hsens);
				else if (r3d_is_light(child, 0)) ChainAddEntry(group->lights, child);
			}	

			if (!ChainGetCount(group->sensors)) {
				DeleteChain(group->sensors);
				group->sensors = NULL;
			}
			if (!ChainGetCount(group->lights)) {
				DeleteChain(group->lights);
				group->lights = NULL;
			}

			/*now here is the trick: ExternProtos may not be loaded at this point, in which case we can't
			perform proper culling. Unloaded ExternProto signal themselves by invalidating their parent
			graph to get a new Render(). We must therefore reset the CHILD_DIRTY flag before computing 
			bounds otherwise we'll never re-invalidate the subgraph anymore*/
			Node_ClearDirtyFlag(group->owner, SG_CHILD_DIRTY);
		}
	}
	/*not parent (eg form, layout...) sub-tree not dirty and getting bounds, direct copy */
	else if (!is_parent && (eff->traversing_mode==TRAVERSE_GET_BOUNDS) && Node_IsFirstCyclicRender(group->owner)) {
		eff->bbox = group->bbox;
		Node_ClearDirty(group->owner);
		return;
	}

	Node_ClearDirtyFlag(group->owner, SG_NODE_DIRTY);

	mode_back=eff->cull_flag;
	/*if culling not disabled*/
	if (!group->dont_cull &&
		/*for geometry AND lights*/
		(eff->traversing_mode==TRAVERSE_SORT) 
		/*do cull*/
		&& !node_cull(eff, &group->bbox, 0)) {

		eff->cull_flag = mode_back;
		return;
	}
	

	/*turn on global lights*/
	if (group->lights && (eff->traversing_mode==TRAVERSE_LIGHTING)) {
		u32 lcount = ChainGetCount(group->lights);
		for (i=0; i<lcount; i++) {
			SFNode *n = ChainGetEntry(group->lights, i);
			/*don't render local lights*/
			if (r3d_is_light(n, 1)) continue;
			Node_Render(n, eff);
		}
	}

	/*picking*/
	sensor_backup = NULL;
	if (group->sensors && (eff->traversing_mode==TRAVERSE_PICK)) {
		u32 scount;
		/*reset sensor stack if any sensors at this level*/
		sensor_backup = eff->sensors;
		eff->sensors = NewChain();

		/*add sensor(s) to effects*/
		scount = ChainGetCount(group->sensors);
		for (i=0; i <scount; i++) {
			SensorHandler *hsens = ChainGetEntry(group->sensors, i);
			ChainAddEntry(eff->sensors, hsens);
		}
	}

	/*turn on local lights*/
	do_lights = 0;
	if (group->lights && (eff->traversing_mode==TRAVERSE_SORT)) {
		u32 lcount;
		do_lights = 1;
		eff->traversing_mode = TRAVERSE_RENDER;
		eff->local_light_on = 1;

		lcount = ChainGetCount(group->lights);
		for (i=0; i<lcount; i++) {
			SFNode *n = ChainGetEntry(group->lights, i);
			if (r3d_is_light(n, 1)) {
				/*store lights for alpha render*/
				dl = malloc(sizeof(DLightContext));
				dl->dlight = n;
				memcpy(&dl->light_matrix, &eff->model_matrix, sizeof(M4Matrix));
				ChainAddEntry(eff->local_lights, dl);
				/*and turn them on for non-alpha render*/
				Node_Render(dl->dlight, eff);
			}
		}
		eff->traversing_mode = TRAVERSE_SORT;
	}
	
	get_bounds = (!is_parent && (eff->traversing_mode==TRAVERSE_GET_BOUNDS)) ? 1 : 0;

	if (get_bounds || is_parent) {
		split_text_backup = eff->text_split_mode;
		if (!is_parent && (count>1)) eff->text_split_mode = 0;
		group->dont_cull = group->bbox.is_set = eff->bbox.is_set = 0;
		
		for (i=0; i<count; i++) {
			child = ChainGetEntry(group->children, positions ? positions[i] : i);
			if (is_parent) group_start_child(group, child);
			Node_Render(child, eff);
			if (is_parent) group_end_child(group, &eff->bbox);
			else if (get_bounds) {
				if (eff->trav_flags & TF_DONT_CULL) {
					group->dont_cull = 1;
					eff->trav_flags &= ~TF_DONT_CULL;
				} else if (eff->bbox.is_set) {
					m4_bbox_union(&group->bbox, &eff->bbox);
				}
				eff->bbox.is_set = 0;
			}
		} 
		eff->bbox = group->bbox;
		if (group->dont_cull) eff->trav_flags |= TF_DONT_CULL;
		eff->text_split_mode = split_text_backup;
	} else {
		if (positions) {
			for (i=0; i<count; i++) {
				child = ChainGetEntry(group->children, positions[i]);
				Node_Render(child, eff);
			} 
		} else {
			for (i=0; i<count; i++) {
				child = ChainGetEntry(group->children, i);
				Node_Render(child, eff);
			} 
		}
	}
	eff->cull_flag = mode_back;

	if (sensor_backup) {
		/*destroy current effect list and restore previous*/
		DeleteChain(eff->sensors);
		eff->sensors = sensor_backup;
	}

	/*remove dlights*/
	if (do_lights) {
		u32 lcount;
		eff->traversing_mode = TRAVERSE_RENDER;
		eff->local_light_on = 0;
		while ( (lcount = ChainGetCount(eff->local_lights)) ) {
			dl = ChainGetEntry(eff->local_lights, lcount-1);
			ChainDeleteEntry(eff->local_lights, lcount-1);
			Node_Render(dl->dlight, eff);
			free(dl);
		}
		/*and back to sort mode*/
		eff->traversing_mode = TRAVERSE_SORT;
	}
}

/*final drawing of each group*/
void child_render_done(ChildGroup *cg, RenderEffect3D *eff)
{
	M4Matrix mx, m4_mx_bckup;
	m4_mx_init(mx);
	m4_mx_add_translation(&mx, cg->final.x - cg->original.x, cg->final.y - cg->original.y, 0);

	m4_mx_copy(m4_mx_bckup, eff->model_matrix);
	m4_mx_add_translation(&eff->model_matrix, cg->final.x - cg->original.x, cg->final.y - cg->original.y, 0);

	eff->split_text_idx = cg->split_text_idx;
	if (eff->traversing_mode==TRAVERSE_SORT) {
		VS3D_PushMatrix(eff->surface);
		VS3D_MultMatrix(eff->surface, mx.m);
	}
	Node_Render(cg->child, eff);
	if (eff->traversing_mode==TRAVERSE_SORT) VS3D_PopMatrix(eff->surface);
	eff->split_text_idx = 0;
	m4_mx_copy(eff->model_matrix, m4_mx_bckup);
}

void child_render_done_complex(ChildGroup *cg, RenderEffect3D *eff, M4Matrix2D *mat2D)
{
	M4Matrix mx, m4_mx_bckup;
	if (!mat2D) return;
	m4_mx_from_mx2d(&mx, mat2D);
	m4_mx_copy(m4_mx_bckup, eff->model_matrix);
	m4_mx_add_matrix(&eff->model_matrix, &mx);
	eff->split_text_idx = cg->split_text_idx;
	if (eff->traversing_mode==TRAVERSE_SORT) {
		VS3D_PushMatrix(eff->surface);
		VS3D_MultMatrix(eff->surface, mx.m);
	}
	Node_Render(cg->child, eff);
	if (eff->traversing_mode==TRAVERSE_SORT) VS3D_PopMatrix(eff->surface);
	eff->split_text_idx = 0;
	m4_mx_copy(eff->model_matrix, m4_mx_bckup);
}


void SetupGroupingNode(GroupingNode *group, LPSCENERENDER sr, SFNode *node, Chain *children)
{
	memset(group, 0, sizeof(GroupingNode));
	traversable_setup(group, node, sr);
	group->groups = NewChain();
	group->children = children;
}

void DeleteGroupingNode(GroupingNode *group)
{
	/*just in case*/
	group_reset_children(group);
	DeleteChain(group->groups);
	if (group->sensors) DeleteChain(group->sensors);
	group->sensors = NULL;
	if (group->lights) DeleteChain(group->lights);
	group->lights = NULL;
}

void DestroyBaseGrouping(SFNode *node)
{
	GroupingNode *group = (GroupingNode *)Node_GetPrivate(node);
	DeleteGroupingNode(group);
	free(group);
}

void NewGroupingNodeStack(LPSCENERENDER sr, SFNode *node, Chain *children)
{
	GroupingNode *st = malloc(sizeof(GroupingNode));
	if (!st) return;
	SetupGroupingNode(st, sr, node, children);
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyBaseGrouping);
}

