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


typedef struct
{
	SFNode *owner;
	struct scene_renderer *compositor;
	GROUPINGNODESTACK
	Chain *backs;
	Chain *views;
	Bool first;
	M4Rect clip;
} Layer2DStack;


static void DestroyLayer2D(SFNode *node)
{
	Layer2DStack *st = (Layer2DStack *) Node_GetPrivate(node);
	DeleteGroupingNode((GroupingNode *)st);
	DeleteChain(st->backs);
	DeleteChain(st->views);
	free(st);
}


static void l2d_CheckBindables(SFNode *n, RenderEffect3D *eff, Bool force_render)
{
	SFNode *btop;
	M_Layer2D *l2d;
	l2d = (M_Layer2D *)n;
	if (force_render) Node_Render(l2d->background, eff);
	btop = ChainGetEntry(eff->backgrounds, 0);
	if (btop != l2d->background) { 
		Node_Unregister(l2d->background, n);
		Node_Register(btop, n); 
		l2d->background = btop;
		Node_OnEventOutSTR(n, "background");
	}
	if (force_render) Node_Render(l2d->viewport, eff);
	btop = ChainGetEntry(eff->viewpoints, 0);
	if (btop != l2d->viewport) { 
		Node_Unregister(l2d->viewport, n);
		Node_Register(btop, n); 
		l2d->viewport = btop;
		Node_OnEventOutSTR(n, "viewport");
	}
}

static void RenderLayer2D(SFNode *node, void *rs)
{
	Chain *oldb, *oldv, *oldf, *oldn;
	SFNode *viewport;
	Chain *node_list_backup;
	SFNode *back;
	M_Layer2D *l = (M_Layer2D *)node;
	Layer2DStack *st = (Layer2DStack *) Node_GetPrivate(node);
	RenderEffect3D *eff = (RenderEffect3D *) rs;

	/*layers can only be rendered in a 2D context*/
	if (eff->camera->is_3D) return;

	/*layer2D maintains its own stacks*/
	oldb = eff->backgrounds;
	oldv = eff->viewpoints;
	oldf = eff->fogs;
	oldn = eff->navigations;
	eff->backgrounds = st->backs;
	eff->viewpoints = st->views;
	eff->fogs = eff->navigations = NULL;

	l2d_CheckBindables(node, eff, st->first);

	back = NULL;
	if (ChainGetCount(st->backs)) back = ChainGetEntry(st->backs, 0);

	viewport = NULL;
	if (ChainGetCount(st->views)) viewport = ChainGetEntry(st->views, 0);

	if (Node_GetDirty(node)) {
		/*recompute children bounds (otherwise culling will fail)*/
		if (Node_GetDirty(node) && SG_CHILD_DIRTY) 
			grouping_traverse((GroupingNode *)st, eff, NULL);
		
		/*and override group bounds*/
		R3D_GetSurfaceSizeInfo(eff, &st->clip.width, &st->clip.height);
		/*setup bounds in local coord system*/
		if (l->size.x>=0) st->clip.width = l->size.x;
		if (l->size.y>=0) st->clip.height = l->size.y;
		m4_rect_center(&st->clip, st->clip.width, st->clip.height);
		
		m4_bbox_from_rect(&st->bbox, &st->clip);
	}
	
	/*drawing a layer means drawing all subelements as a whole (no depth sorting with parents)*/
	if (eff->traversing_mode==TRAVERSE_RENDER) {
		M4Rect prev_clipper;
		Bool had_clip;

		eff->layer_clipper = R3D_UpdateClipper(eff, st->clip, &had_clip, &prev_clipper, 1);

		VS3D_PushMatrix(eff->surface);
		/*setup clipping*/
		VS3D_SetClipper2D(eff->surface, eff->layer_clipper);
		
		/*apply background BEFORE viewport*/
		if (back) {
			eff->traversing_mode = TRAVERSE_RENDER_BINDABLE;
			eff->bbox = st->bbox;
			Node_Render(back, eff);
		}

		/*sort all children without transform, and use current transform when flushing contexts*/
		m4_mx_init(eff->model_matrix);

		/*apply viewport*/
		if (viewport) {
			eff->traversing_mode = TRAVERSE_RENDER_BINDABLE;
			m4_bbox_from_rect(&eff->bbox, &st->clip);
			Node_Render(viewport, eff);
			VS3D_MultMatrix(eff->surface, eff->model_matrix.m);
		}

		node_list_backup = eff->surface->alpha_nodes_to_draw;
		eff->surface->alpha_nodes_to_draw = NewChain();
		eff->traversing_mode = TRAVERSE_SORT;
		/*reset cull flag*/
		eff->cull_flag = 0;
		grouping_traverse((GroupingNode *)st, eff, NULL);

		VS_FlushContexts(eff->surface, eff);

		assert(!ChainGetCount(eff->surface->alpha_nodes_to_draw));
		DeleteChain(eff->surface->alpha_nodes_to_draw);
		eff->surface->alpha_nodes_to_draw = node_list_backup;

		
		VS3D_PopMatrix(eff->surface);

		VS3D_ResetClipper2D(eff->surface);

		eff->has_layer_clip = had_clip;
		if (had_clip) {
			eff->layer_clipper = prev_clipper;
			VS3D_SetClipper2D(eff->surface, eff->layer_clipper);
		}

	} else if (eff->traversing_mode==TRAVERSE_SORT) {
		VS_RegisterContext(eff, node, &st->bbox, 0);
	}
	/*check picking - we must fall in our 2D clipper*/
	else if (eff->traversing_mode==TRAVERSE_PICK) {
		if (R3D_PickInClipper(eff, &st->clip)) 
			grouping_traverse((GroupingNode *)st, eff, NULL);
	} else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->bbox;
	}
	
	group_reset_children((GroupingNode*)st);

	/*restore effect*/
	eff->backgrounds = oldb;
	eff->viewpoints = oldv;
	eff->fogs = oldf;
	eff->navigations = oldn;

	/*in case we missed bindables*/
	if (st->first) {
		st->first = 0;
		SR_Invalidate(st->compositor, NULL);
	}
}

void R3D_InitLayer2D(Render3D *sr, SFNode *node)
{
	Layer2DStack *stack = malloc(sizeof(Layer2DStack));
	SetupGroupingNode((GroupingNode*)stack, sr->compositor, node, ((M_Layer2D *)node)->children);
	stack->backs = NewChain();
	stack->views = NewChain();
	stack->first = 1;

	Node_SetPrivate(node, stack);
	Node_SetPreDestroyFunction(node, DestroyLayer2D);
	Node_SetRenderFunction(node, RenderLayer2D);
}


typedef struct
{
	SFNode *owner;
	struct scene_renderer *compositor;
	GROUPINGNODESTACK
	Chain *backs;
	Chain *views;
	Chain *navinfos;
	Chain *fogs;
	M4Camera cam;
	Bool first;
	M4Rect clip;
} Layer3DStack;


static void DestroyLayer3D(SFNode *node)
{
	Layer3DStack *st = (Layer3DStack *) Node_GetPrivate(node);
	Render3D *sr = (Render3D *)st->compositor->visual_renderer->user_priv;
	DeleteGroupingNode((GroupingNode *)st);
	
	BindableStackDelete(st->backs);
	BindableStackDelete(st->views);
	BindableStackDelete(st->fogs);
	BindableStackDelete(st->navinfos);
	if (sr->active_layer == node) sr->active_layer = NULL;
	free(st);
}

static void l3d_CheckBindables(SFNode *n, RenderEffect3D *eff, Bool force_render)
{
	SFNode *btop;
	u32 mode;
	M_Layer3D *l3d;
	l3d = (M_Layer3D *)n;

	mode = eff->traversing_mode;
	eff->traversing_mode = TRAVERSE_GET_BOUNDS;

	if (force_render) Node_Render(l3d->background, eff);
	btop = ChainGetEntry(eff->backgrounds, 0);
	if (btop != l3d->background) { 
		Node_Unregister(l3d->background, n);
		Node_Register(btop, n); 
		l3d->background = btop;
		Node_OnEventOutSTR(n, "background");
	}
	if (force_render) Node_Render(l3d->viewpoint, eff);
	btop = ChainGetEntry(eff->viewpoints, 0);
	if (btop != l3d->viewpoint) { 
		Node_Unregister(l3d->viewpoint, n);
		Node_Register(btop, n); 
		l3d->viewpoint = btop;
		Node_OnEventOutSTR(n, "viewpoint");
	}
	if (force_render) Node_Render(l3d->navigationInfo, eff);
	btop = ChainGetEntry(eff->navigations, 0);
	if (btop != l3d->navigationInfo) { 
		Node_Unregister(l3d->navigationInfo, n);
		Node_Register(btop, n); 
		l3d->navigationInfo = btop;
		Node_OnEventOutSTR(n, "navigationInfo");
	}
	if (force_render) Node_Render(l3d->fog, eff);
	btop = ChainGetEntry(eff->fogs, 0);
	if (btop != l3d->fog) { 
		Node_Unregister(l3d->fog, n);
		Node_Register(btop, n); 
		l3d->fog = btop;
		Node_OnEventOutSTR(n, "fog");
	}
	eff->traversing_mode = mode;
}


static void RenderLayer3D(SFNode *node, void *rs)
{
	Chain *oldb, *oldv, *oldf, *oldn;
	M4Rect rc;
	u32 cur_lights;
	Fixed sw, sh;
	Chain *node_list_backup;
	M4BBox bbox_backup;
	M4Matrix model_backup;
	M4Camera *prev_cam;
	M_Layer3D *l = (M_Layer3D *)node;
	Layer3DStack *st = (Layer3DStack *) Node_GetPrivate(node);
	RenderEffect3D *eff = (RenderEffect3D *) rs;

	/*layers can only be rendered in a 2D context*/
	if (eff->camera->is_3D) return;

	if (Node_GetDirty(node)) {

		/*main surface in pixel metrics, use output width*/
		if (eff->is_pixel_metrics && (eff->surface->render->surface==eff->surface)) {
			st->clip.width = INT2FIX(eff->surface->render->out_width);
			st->clip.height = INT2FIX(eff->surface->render->out_height);
		} else {
			R3D_GetSurfaceSizeInfo(eff, &st->clip.width, &st->clip.height);
		}
		/*setup bounds in local coord system*/
		if (l->size.x>=0) st->clip.width = l->size.x;
		if (l->size.y>=0) st->clip.height = l->size.y;
		m4_rect_center(&st->clip, st->clip.width, st->clip.height);
		m4_bbox_from_rect(&st->bbox, &st->clip);
	}

	switch (eff->traversing_mode) {
	case TRAVERSE_SORT:
		VS_RegisterContext(eff, node, &st->bbox, 0);
		return;
	case TRAVERSE_GET_BOUNDS:
		eff->bbox = st->bbox;
		return;
	case TRAVERSE_RENDER:
	case TRAVERSE_PICK:
		break;
	default:
		return;
	}

	/*layer3D maintains its own stacks*/
	oldb = eff->backgrounds;
	oldv = eff->viewpoints;
	oldf = eff->fogs;
	oldn = eff->navigations;
	eff->backgrounds = st->backs;
	eff->viewpoints = st->views;
	eff->fogs = st->fogs;
	eff->navigations = st->navinfos;

	prev_cam = eff->camera;
	eff->camera = &st->cam;

	/*check bindables*/
	l3d_CheckBindables(node, eff, st->first);

	/*compute viewport in surface coordinate*/
	rc = st->clip;
	m4_mx_apply_rect(&eff->model_matrix, &rc);
	if (eff->surface->render->surface==eff->surface) {
		m4_mx_init(model_backup);
		m4_mx_add_scale(&model_backup, eff->surface->render->scale_x, eff->surface->render->scale_y, FIX_ONE);
		m4_mx_apply_rect(&model_backup, &rc);
		sw = INT2FIX(eff->surface->render->compositor->width);
		sh = INT2FIX(eff->surface->render->compositor->height);
	} else {
		sw = INT2FIX(eff->surface->width);
		sh = INT2FIX(eff->surface->height);
	}

	st->cam.vp = rc;
	st->cam.vp.x += sw/2;
	st->cam.vp.y -= st->cam.vp.height;
	st->cam.vp.y += sh/2;
	if (st->cam.vp.x<0) { st->cam.vp.width += st->cam.vp.x; st->cam.vp.x = 0; }
	if (st->cam.vp.y<0) { st->cam.vp.height += st->cam.vp.y; st->cam.vp.y = 0; }
	if (st->cam.vp.width>sw) st->cam.vp.width = sw;
	if (st->cam.vp.height>sh) st->cam.vp.height = sh;

	eff->camera->width = eff->camera->vp.width;
	eff->camera->height = eff->camera->vp.height;

	if (!eff->is_pixel_metrics) {
		if (eff->camera->height > eff->camera->width) {
			eff->camera->height = 2 * m4_divfix(eff->camera->height, eff->camera->width);
			eff->camera->width = 2*FIX_ONE;
		} else {
			eff->camera->width = 2 * m4_divfix(eff->camera->width, eff->camera->height);
			eff->camera->height = 2*FIX_ONE;
		}
	}
	bbox_backup = eff->bbox;
	m4_mx_copy(model_backup, eff->model_matrix);

	/*setup surface bounds*/
	eff->bbox.max_edge.x = eff->camera->width/2;
	eff->bbox.min_edge.x = -eff->bbox.max_edge.x;
	eff->bbox.max_edge.y = eff->camera->height/2;
	eff->bbox.min_edge.y = -eff->bbox.max_edge.y;
	eff->bbox.max_edge.z = eff->bbox.min_edge.z = 0;
	eff->bbox.is_set = 1;


	/*recompute children bounds (otherwise culling will fail)*/
	if (Node_GetDirty(node) && SG_CHILD_DIRTY) {
		u32 mode_back = eff->traversing_mode;
		eff->traversing_mode = TRAVERSE_GET_BOUNDS;
		grouping_traverse((GroupingNode *)st, eff, NULL);
		eff->traversing_mode = mode_back;
	}
	Node_ClearDirtyFlag(node, SG_NODE_DIRTY);


	VS3D_SetMatrixMode(eff->surface, MAT_PROJECTION);
	VS3D_PushMatrix(eff->surface);
	VS3D_SetMatrixMode(eff->surface, MAT_MODELVIEW);
	VS3D_PushMatrix(eff->surface);

	/*drawing a layer means drawing all subelements as a whole (no depth sorting with parents)*/
	if (eff->traversing_mode==TRAVERSE_RENDER) {

		cur_lights = eff->surface->num_lights;
		/*this will init projection. Note that we're binding the viewpoint in the current pixelMetrics context
		even if the viewpoint was declared in an inline below*/
		VS_InitRender(eff);

		VS_DoCollisions(eff, l->children);
		eff->traversing_mode = TRAVERSE_SORT;

		/*shortcut node list*/
		node_list_backup = eff->surface->alpha_nodes_to_draw;
		eff->surface->alpha_nodes_to_draw = NewChain();

		/*reset cull flag*/
		eff->cull_flag = 0;
		grouping_traverse((GroupingNode *)st, eff, NULL);

		VS_FlushContexts(eff->surface, eff);

		DeleteChain(eff->surface->alpha_nodes_to_draw);
		eff->surface->alpha_nodes_to_draw = node_list_backup;

		while (cur_lights < eff->surface->num_lights) {
			VS3D_RemoveLastLight(eff->surface);
		}

	}
	/*check picking - we must fall in our 2D clipper except when mvt is grabbed on layer*/
	else if (eff->traversing_mode==TRAVERSE_PICK) {
		M4Ray prev_r;
		SFVec3f start, end;
		SFVec4f res;
		Fixed in_x, in_y;
		Bool do_pick = 0;
		if (eff->surface->render->active_layer==node) {
			do_pick = (eff->surface->render->is_grabbed || eff->surface->render->nav_is_grabbed) ? 1 : 0;
		}
		if (!do_pick && !ChainGetCount(eff->surface->render->sensors)) do_pick = R3D_PickInClipper(eff, &st->clip);
		if (!do_pick) goto l3d_exit;

		prev_r = eff->ray;

		R3D_Get2DPlaneIntersection(&eff->ray, &start);

		m4_mx_inverse(&eff->model_matrix);
		m4_mx_apply_vec(&eff->model_matrix, &start);


		if (eff->surface->render->surface==eff->surface) {
			start.x = m4_mulfix(start.x, eff->surface->render->scale_x);
			start.y = m4_mulfix(start.y, eff->surface->render->scale_y);
		}

		VS_SetupProjection(eff);
		in_x = 2 * m4_divfix(start.x, st->cam.width);
		in_y = 2 * m4_divfix(start.y, st->cam.height);
			
		res.x = in_x; res.y = in_y; res.z = -FIX_ONE; res.q = FIX_ONE;
		m4_mx_apply_vec_4x4(&st->cam.unprojection, &res);
		if (!res.q) goto l3d_exit;
		start.x = m4_divfix(res.x, res.q);
		start.y = m4_divfix(res.y, res.q);
		start.z = m4_divfix(res.z, res.q);

		res.x = in_x; res.y = in_y; res.z = FIX_ONE; res.q = FIX_ONE;
		m4_mx_apply_vec_4x4(&st->cam.unprojection, &res);
		if (!res.q) goto l3d_exit;
		end.x = m4_divfix(res.x, res.q);
		end.y = m4_divfix(res.y, res.q);
		end.z = m4_divfix(res.z, res.q);
		eff->ray = m4_ray(start, end);
		grouping_traverse((GroupingNode *)st, eff, NULL);
		eff->ray = prev_r;

		/*we're the first layer being traversed, store info if navigation allowed*/
		if (!eff->collect_layer) {
			if (eff->camera->navigate_mode || (eff->camera->navigation_flags & NAV_ANY))
				eff->collect_layer = node;
		}
	}

l3d_exit:
	VS3D_SetMatrixMode(eff->surface, MAT_PROJECTION);
	VS3D_PopMatrix(eff->surface);
	VS3D_SetMatrixMode(eff->surface, MAT_MODELVIEW);
	VS3D_PopMatrix(eff->surface);
	/*restore camera*/
	eff->camera = prev_cam;
	VS3D_SetViewport(eff->surface, eff->camera->vp);

	group_reset_children((GroupingNode*)st);

	/*restore effect*/
	eff->backgrounds = oldb;
	eff->viewpoints = oldv;
	eff->fogs = oldf;
	eff->navigations = oldn;
	eff->bbox = bbox_backup;
	m4_mx_copy(eff->model_matrix, model_backup);

	/*in case we missed bindables*/
	if (st->first) {
		st->first = 0;
		SR_Invalidate(st->compositor, NULL);
	}
}

void R3D_InitLayer3D(Render3D *sr, SFNode *node)
{
	Layer3DStack *stack;
	SAFEALLOC(stack, sizeof(Layer3DStack));
	SetupGroupingNode((GroupingNode*)stack, sr->compositor, node, ((M_Layer3D *)node)->children);
	stack->backs = NewChain();
	stack->views = NewChain();
	stack->fogs = NewChain();
	stack->navinfos = NewChain();
	stack->first = 1;
	stack->cam.is_3D = 1;
	camera_invalidate(&stack->cam);


	Node_SetPrivate(node, stack);
	Node_SetPreDestroyFunction(node, DestroyLayer3D);
	Node_SetRenderFunction(node, RenderLayer3D);
}

M4Camera *l3d_get_camera(SFNode *node)
{
	Layer3DStack *st = (Layer3DStack *) Node_GetPrivate(node);
	return &st->cam;
}

void l3d_bind_camera(SFNode *node, Bool do_bind, u32 nav_value)
{
	Layer3DStack *st = (Layer3DStack *) Node_GetPrivate(node);
	SFNode *n = ChainGetEntry(st->navinfos, 0);
	if (n) Bindable_SetSetBind(n, do_bind);
	else st->cam.navigate_mode = nav_value;
}
