/*
 *			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
	
	SFNode *last_geom;
	M4PathIterator *iter;
} PathLayoutStack;


static void DestroyPathLayout(SFNode *node)
{
	PathLayoutStack *st = (PathLayoutStack*)Node_GetPrivate(node);
	DeleteGroupingNode((GroupingNode *)st);
	if (st->iter) m4_path_delete_iterator(st->iter);
	free(st);
}

static void RenderPathLayout(SFNode *node, void *rs)
{
	u32 i, count, minor, major, int_bck;
	Fixed length, offset, length_after_point;
	Bool res;
	u32 mode_bckup;
	ChildGroup *cg;
	M4Matrix mat;
	M4Matrix2D mx2d;
	GroupingNode *parent_bck;
	PathLayoutStack *gr = (PathLayoutStack*) Node_GetPrivate(node);
	M_PathLayout *pl = (M_PathLayout *)node;
	RenderEffect3D *eff = (RenderEffect3D *) rs;
	
	if (!pl->geometry) return;
	
	/*only low-level primitives allowed*/
	switch (Node_GetTag((SFNode *) pl->geometry)) {
	case TAG_MPEG4_Rectangle: return;
	case TAG_MPEG4_Circle: return;
	case TAG_MPEG4_Ellipse: return;
	}

	/*store effect*/
	m4_mx_copy(mat, eff->model_matrix);
	parent_bck = eff->parent;

	m4_mx_init(eff->model_matrix);
	eff->parent = NULL;

	/*check geom changes*/
	if ((pl->geometry != gr->last_geom) || Node_GetDirty(pl->geometry)) {
		if (gr->iter) m4_path_delete_iterator(gr->iter);
		gr->iter = NULL;

		int_bck = eff->trav_flags;
		mode_bckup = eff->traversing_mode;
		eff->traversing_mode = TRAVERSE_GET_BOUNDS;
		eff->trav_flags |= TF_SWITCHED_OFF;
		Node_Render((SFNode *)pl->geometry, eff);
		eff->traversing_mode = mode_bckup;
		eff->trav_flags = int_bck;
	}

	if (!gr->iter) {
		/*get the drawable*/
		stack2D *dr = (stack2D *) Node_GetPrivate( (SFNode *) pl->geometry);
		/*init iteration*/
		if (!dr || !dr->path) return;
		gr->iter = m4_path_new_iterator(dr->path);
		if (!gr->iter) return;
	}

	eff->parent = (GroupingNode *) gr;
	int_bck = eff->text_split_mode;
	eff->text_split_mode = 2;
	mode_bckup = eff->traversing_mode;
	eff->traversing_mode = TRAVERSE_GET_BOUNDS;
	grouping_traverse((GroupingNode *) gr, eff, NULL);
	eff->text_split_mode = int_bck;
	eff->traversing_mode = mode_bckup;

	/*restore effect*/
	m4_mx_copy(eff->model_matrix, mat);
	eff->parent = parent_bck;

	count = ChainGetCount(gr->groups);
	
	length = m4_pathiterator_get_length(gr->iter);
	/*place all children*/
	offset = m4_mulfix(length, pl->pathOffset);

	major = pl->alignment.count ? pl->alignment.vals[0] : 0;
	minor = (pl->alignment.count==2) ? pl->alignment.vals[1] : 0;

	if (pl->wrapMode==1) {
		while (offset<0) offset += length;
	}

	for (i=0; i<count; i++) {
		cg = ChainGetEntry(gr->groups, i);
		if (cg->original.width>length) break;

		/*first set our center and baseline*/
		m4_mx2d_init(mx2d);

		/*major align*/
		switch (major) {
		case 2:
			if (cg->is_text_group) m4_mx2d_add_translation(&mx2d, -cg->original.x - cg->original.width, 0);
			else m4_mx2d_add_translation(&mx2d, -cg->original.width/2, 0);
			length_after_point = 0;
			break;
		case 1:
			length_after_point = cg->original.width/2;
			if (cg->is_text_group) m4_mx2d_add_translation(&mx2d, -cg->original.x - cg->original.width / 2, 0);
			break;
		default:
		case 0:
			if (cg->is_text_group) m4_mx2d_add_translation(&mx2d, cg->original.x, 0);
			else m4_mx2d_add_translation(&mx2d, cg->original.width/2, 0);
			length_after_point = cg->original.width;
			break;
		}

		/*if wrapping and out of path, restart*/
		if ((pl->wrapMode==1) && (offset+length_after_point>=length)) {
			offset += length_after_point;
			offset -= length;
			i--;
			continue;
		}
		/*if not wrapping and not yet in path skip */
		if (!pl->wrapMode && (offset+length_after_point < 0)) {
			child_render_done_complex(cg, (RenderEffect3D *)rs, NULL);
			goto next;
		}

		/*minor justify*/
		switch (minor) {
		/*top alignment*/
		case 3:
			if (cg->is_text_group) 
				m4_mx2d_add_translation(&mx2d, 0, -1 * cg->ascent);
			else 
				m4_mx2d_add_translation(&mx2d, 0, -1 * cg->original.height / 2);
			
			break;
		/*baseline*/
		case 1:
			/*move to bottom align if not text*/
			if (!cg->is_text_group) 
				m4_mx2d_add_translation(&mx2d, 0, cg->original.height / 2);
			break;
		/*middle*/
		case 2:
			/*if text use (asc+desc) /2 as line height since glyph height differ*/
			if (cg->is_text_group) 
				m4_mx2d_add_translation(&mx2d, 0, cg->descent - (cg->ascent + cg->descent) / 2);
			break;
		/*bottomline alignment*/
		case 0:
		default:
			if (cg->is_text_group)
				m4_mx2d_add_translation(&mx2d, 0, cg->descent);
			else
				m4_mx2d_add_translation(&mx2d, 0, cg->original.height / 2);
			
			break;
		}
		res = m4_pathiterator_get_transform_at_offset(gr->iter, offset, (Bool) (pl->wrapMode==2), &mx2d, 1, length_after_point);
		if (!res) break;

		child_render_done_complex(cg, (RenderEffect3D *)rs, &mx2d);

next:
		if (i+1<count) {
			ChildGroup *cg_next = ChainGetEntry(gr->groups, i+1);

			/*update offset according to major alignment */
			switch (major) {
			case 2:
				if (cg_next->is_text_group) offset += m4_mulfix(pl->spacing , cg_next->original.x);
				offset += m4_mulfix(pl->spacing , cg_next->original.width);
				break;
			case 1:
				if (cg->is_text_group) offset += m4_mulfix(pl->spacing, cg->original.x) / 2;
				offset += m4_mulfix(pl->spacing, cg->original.width) / 2;
				offset += cg_next->original.width / 2;
				break;
			default:
			case 0:
				if (cg->is_text_group) offset += m4_mulfix(pl->spacing, cg->original.x);
				offset += m4_mulfix(pl->spacing , cg->original.width);
				break;
			}
		}
		/*wrap*/
		if ((pl->wrapMode==1) && (offset>=length)) offset-=length;
	}

	/*undrawn nodes*/
	for (;i<count; i++) {
		cg = ChainGetEntry(gr->groups, i);
		child_render_done_complex(cg, (RenderEffect3D *)rs, NULL);
	}

	group_reset_children((GroupingNode *) gr);
}

void R3D_InitPathLayout(Render3D *sr, SFNode *node)
{
	PathLayoutStack *stack = malloc(sizeof(PathLayoutStack));
	memset(stack, 0, sizeof(PathLayoutStack));
	SetupGroupingNode((GroupingNode*)stack, sr->compositor, node, ((M_PathLayout *)node)->children);
	Node_SetPrivate(node, stack);
	Node_SetPreDestroyFunction(node, DestroyPathLayout);
	Node_SetRenderFunction(node, RenderPathLayout);
}

