/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2023
 *					All rights reserved
 *
 *  This file is part of GPAC / Scene Compositor sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "nodes_stacks.h"
#include "mpeg4_grouping.h"
#include "visual_manager.h"

#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR)

typedef struct
{
	s32 last_switch;
} SwitchStack;

static void TraverseSwitch(GF_Node *node, void *rs, Bool is_destroy)
{
	GF_ChildNodeItem *l;
	u32 i;
	Bool prev_switch;
	GF_ChildNodeItem *children;
	s32 whichChoice;
	GF_Node *child;
	SwitchStack *st = (SwitchStack *)gf_node_get_private(node);
	GF_TraverseState *tr_state;
	tr_state = (GF_TraverseState *)rs;
	children = NULL;

	whichChoice = -1;
	if (is_destroy) {
		gf_sc_check_focus_upon_destroy(node);
		gf_free(st);
		return;
	}
	/*WARNING: X3D/MPEG4 NOT COMPATIBLE*/
	switch (gf_node_get_tag(node)) {
	case TAG_MPEG4_Switch:
		children = ((M_Switch *)node)->choice;
		whichChoice = ((M_Switch *)node)->whichChoice;
		break;
#ifndef GPAC_DISABLE_X3D
	case TAG_X3D_Switch:
		children = ((X_Switch *)node)->children;
		whichChoice = ((X_Switch *)node)->whichChoice;
		break;
#endif
	}

	if (tr_state->traversing_mode!=TRAVERSE_GET_BOUNDS) {
		prev_switch = tr_state->switched_off;
		/*check changes in choice field*/
		if ((gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) || (st->last_switch != whichChoice) ) {
			tr_state->switched_off = 1;
			i=0;
			l = children;
			while (l) {
				//			if ((s32) i!=whichChoice) gf_node_traverse(l->node, tr_state);
				if ((s32) i == st->last_switch) gf_node_traverse(l->node, tr_state);
				l = l->next;
				i++;
			}
			tr_state->switched_off = 0;
			st->last_switch = whichChoice;
		}

		gf_node_dirty_clear(node, 0);

		/*no need to check for sensors since a sensor is active for the whole parent group, that is for switch itself
		CSQ: switch cannot be used to switch sensors, too bad...*/
		tr_state->switched_off = prev_switch;
	}

	if (!children) return;
	if (whichChoice==-2) {
#ifndef GPAC_DISABLE_3D
		if (tr_state->visual->autostereo_type) {
			u32 idx;
			u32 count = gf_node_list_get_count(children);
			/*this should be a bit more subtle (reusing views if missing, ...)...*/
			idx = tr_state->visual->current_view % count;

			child = (GF_Node*)gf_node_list_get_child(children, idx);
			gf_node_traverse(child, tr_state);
			return;
		} else
#endif //GPAC_DISABLE_3D
		{
			/*fallback to first view*/
			whichChoice=0;
		}
	}
	if (whichChoice>=0) {
		child = (GF_Node*)gf_node_list_get_child(children, whichChoice);
		gf_node_traverse(child, tr_state);
	}
}

void compositor_init_switch(GF_Compositor *compositor, GF_Node *node)
{
	SwitchStack *st = (SwitchStack *)gf_malloc(sizeof(SwitchStack));
	st->last_switch = -1;
	gf_node_set_private(node, st);
	gf_node_set_callback_function(node, TraverseSwitch);
}


/*transform2D*/
typedef struct
{
	GROUPING_MPEG4_STACK_2D
	GF_Matrix2D mat;
	u8 is_identity;
	u8 is_null;
	u8 is_untransform;
} Transform2DStack;

static void traverse_transform(GF_Node *node, Transform2DStack *stack, GF_TraverseState *tr_state)
{
	if (stack->is_null) return;

	/*note we don't clear dirty flag, this is done in traversing*/
	if (stack->is_identity) {
		group_2d_traverse(node, (GroupingNode2D *)stack, tr_state);
	}
#ifndef GPAC_DISABLE_3D
	else if (tr_state->visual->type_3d) {
		GF_Matrix mx_bckup;
		gf_mx_copy(mx_bckup, tr_state->model_matrix);

		gf_mx_add_matrix_2d(&tr_state->model_matrix, &stack->mat);
		group_2d_traverse(node, (GroupingNode2D *)stack, tr_state);
		gf_mx_copy(tr_state->model_matrix, mx_bckup);
	}
#endif
	else {
		GF_Matrix2D bckup;
		gf_mx2d_copy(bckup, tr_state->transform);
		gf_mx2d_pre_multiply(&tr_state->transform, &stack->mat);

		group_2d_traverse(node, (GroupingNode2D *)stack, tr_state);

		gf_mx2d_copy(tr_state->transform, bckup);
	}

	if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
		gf_mx2d_apply_rect(&stack->mat, &tr_state->bounds);
	}
}

void TraverseUntransformEx(GF_Node *node, void *rs, GroupingNode2D *grp);

static void TraverseTransform2D(GF_Node *node, void *rs, Bool is_destroy)
{
	M_Transform2D *tr = (M_Transform2D *)node;
	Transform2DStack *ptr = (Transform2DStack *)gf_node_get_private(node);
	GF_TraverseState *tr_state;

	if (is_destroy) {
		gf_sc_check_focus_upon_destroy(node);
		group_2d_destroy(node, (GroupingNode2D*)ptr);
		gf_free(ptr);
		return;
	}

	tr_state = (GF_TraverseState *) rs;

	if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {
		gf_mx2d_init(ptr->mat);
		ptr->is_identity = 1;
		if ((tr->scale.x != FIX_ONE) || (tr->scale.y != FIX_ONE)) {
			gf_mx2d_add_scale_at(&ptr->mat, tr->scale.x, tr->scale.y, 0, 0, tr->scaleOrientation);
			ptr->is_identity = 0;
		}
		if (tr->rotationAngle) {
			gf_mx2d_add_rotation(&ptr->mat, tr->center.x, tr->center.y, tr->rotationAngle);
			ptr->is_identity = 0;
		}
		if (tr->translation.x || tr->translation.y) {
			ptr->is_identity = 0;
			gf_mx2d_add_translation(&ptr->mat, tr->translation.x, tr->translation.y);
		}
		gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
		ptr->is_null = (!tr->scale.x || !tr->scale.y) ? 1 : 0;
	}

	if (ptr->is_untransform) {
		TraverseUntransformEx(node, tr_state, (GroupingNode2D *)ptr);
	} else {
		traverse_transform(node, ptr, tr_state);
	}
}
void transform2d_set_untransform(GF_Node *node)
{
	Transform2DStack *stack;
	if (gf_node_get_tag(node)!=TAG_MPEG4_Transform2D) return;
	stack = (Transform2DStack *)gf_node_get_private(node);
	stack->is_untransform = 1;
}

void compositor_init_transform2d(GF_Compositor *compositor, GF_Node *node)
{
	Transform2DStack *stack;
	GF_SAFEALLOC(stack, Transform2DStack);
	if (!stack) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate transform2d stack\n"));
		return;
	}

	gf_mx2d_init(stack->mat);
	stack->is_identity = 1;
	gf_node_set_private(node, stack);
	gf_node_set_callback_function(node, TraverseTransform2D);
}

void tr_mx2d_get_matrix(GF_Node *n, GF_Matrix2D *mat)
{
	M_TransformMatrix2D *tr = (M_TransformMatrix2D*)n;
	gf_mx2d_init(*mat);
	mat->m[0] = tr->mxx;
	mat->m[1] = tr->mxy;
	mat->m[2] = tr->tx;
	mat->m[3] = tr->myx;
	mat->m[4] = tr->myy;
	mat->m[5] = tr->ty;
}


/*TransformMatrix2D*/
static void TraverseTransformMatrix2D(GF_Node *node, void *rs, Bool is_destroy)
{
	Transform2DStack *ptr = (Transform2DStack *) gf_node_get_private(node);
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;

	if (is_destroy) {
		gf_sc_check_focus_upon_destroy(node);
		group_2d_destroy(node, (GroupingNode2D*)ptr);
		gf_free(ptr);
		return;
	}

	if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {
		M_TransformMatrix2D *tr = (M_TransformMatrix2D*)node;
		tr_mx2d_get_matrix(node, &ptr->mat);
		if ((tr->mxx==FIX_ONE) && (tr->mxy==0) && (tr->tx==0)
		        && (tr->myx==0) && (tr->myy==FIX_ONE) && (tr->ty==0) )
			ptr->is_identity = 1;
		else
			ptr->is_identity = 0;

		ptr->is_null = ( (!ptr->mat.m[0] && !ptr->mat.m[1]) || (!ptr->mat.m[3] && !ptr->mat.m[4]) ) ? 1 : 0;
		gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
	}
	traverse_transform(node, ptr, tr_state);
}


void compositor_init_transformmatrix2d(GF_Compositor *compositor, GF_Node *node)
{
	Transform2DStack *stack;
	GF_SAFEALLOC(stack, Transform2DStack);
	if (!stack) return;

	gf_mx2d_init(stack->mat);
	gf_node_set_private(node, stack);
	gf_node_set_callback_function(node, TraverseTransformMatrix2D);
}


typedef struct
{
	GROUPING_MPEG4_STACK_2D
	GF_ColorMatrix cmat;
} ColorTransformStack;

/*ColorTransform*/
static void TraverseColorTransform(GF_Node *node, void *rs, Bool is_destroy)
{
	Bool c_changed;
	M_ColorTransform *tr = (M_ColorTransform *)node;
	ColorTransformStack *ptr = (ColorTransformStack  *)gf_node_get_private(node);
	GF_TraverseState *tr_state = (GF_TraverseState *) rs;
	Bool prev_inv;

	if (is_destroy) {
		gf_sc_check_focus_upon_destroy(node);
		group_2d_destroy(node, (GroupingNode2D*)ptr);
		gf_free(ptr);
		return;
	}
	if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
		group_2d_traverse(node, (GroupingNode2D *) ptr, tr_state);
		return;
	}

	prev_inv = tr_state->invalidate_all;
	c_changed = 0;
	if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {
		gf_cmx_set(&ptr->cmat,
		           tr->mrr , tr->mrg, tr->mrb, tr->mra, tr->tr,
		           tr->mgr , tr->mgg, tr->mgb, tr->mga, tr->tg,
		           tr->mbr, tr->mbg, tr->mbb, tr->mba, tr->tb,
		           tr->mar, tr->mag, tr->mab, tr->maa, tr->ta);
		c_changed = 1;
		gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
	}

	if ((tr_state->traversing_mode==TRAVERSE_SORT)
	        && !tr->maa && !tr->mar && !tr->mag && !tr->mab && !tr->ta)
		return;

	/*if modified redraw all nodes*/
	if (c_changed) tr_state->invalidate_all = 1;

	/*note we don't clear dirty flag, this is done in traversing*/
	if (ptr->cmat.identity) {
		group_2d_traverse(node, (GroupingNode2D *) ptr, tr_state);
	} else {
		GF_ColorMatrix gf_cmx_bck;
		gf_cmx_copy(&gf_cmx_bck, &tr_state->color_mat);
		gf_cmx_multiply(&tr_state->color_mat, &ptr->cmat);


		group_2d_traverse(node, (GroupingNode2D *) ptr, tr_state);
		/*restore traversing state*/
		gf_cmx_copy(&tr_state->color_mat, &gf_cmx_bck);
	}
	tr_state->invalidate_all = prev_inv;
}

void compositor_init_colortransform(GF_Compositor *compositor, GF_Node *node)
{
	ColorTransformStack *stack;
	GF_SAFEALLOC(stack, ColorTransformStack);
	if (!stack) return;

	gf_cmx_init(&stack->cmat);
	gf_node_set_private(node, stack);
	gf_node_set_callback_function(node, TraverseColorTransform);
}


struct og_pos
{
	Fixed priority;
	u32 position;
};
typedef struct
{
	GROUPING_MPEG4_STACK_2D
	u32 *positions;
} OrderedGroupStack;


static s32 compare_priority(const void* elem1, const void* elem2)
{
	struct og_pos *p1, *p2;
	p1 = (struct og_pos *)elem1;
	p2 = (struct og_pos *)elem2;
	if (p1->priority < p2->priority) return -1;
	if (p1->priority > p2->priority) return 1;
	return 0;
}


static void TraverseOrderedGroup(GF_Node *node, void *rs, Bool is_destroy)
{
	u32 i, count;
	struct og_pos *priorities;
	Bool invalidate_backup;
	OrderedGroupStack *stack = (OrderedGroupStack *) gf_node_get_private(node);
	M_OrderedGroup *og = (M_OrderedGroup *) node;
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;

	if (is_destroy) {
		gf_sc_check_focus_upon_destroy(node);
		group_2d_destroy(node, (GroupingNode2D*)stack);
		if (stack->positions) gf_free(stack->positions);
		gf_free(stack);
		return;
	}

	if (!og->order.count || (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) ) {
		gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
		group_2d_traverse(node, (GroupingNode2D*)stack, tr_state);
		return;
	}

	invalidate_backup = tr_state->invalidate_all;
	/*check whether the OrderedGroup node has changed*/
	if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {
		if (stack->positions) gf_free(stack->positions);
		count = gf_node_list_get_count(og->children);
		priorities = (struct og_pos*)gf_malloc(sizeof(struct og_pos)*count);
		for (i=0; i<count; i++) {
			priorities[i].position = i;
			priorities[i].priority = (i<og->order.count) ? og->order.vals[i] : 0;
		}
		qsort(priorities, count, sizeof(struct og_pos), compare_priority);

		stack->positions = (u32*)gf_malloc(sizeof(u32) * count);
		for (i=0; i<count; i++) stack->positions[i] = priorities[i].position;
		gf_free(priorities);

		tr_state->invalidate_all = 1;
		gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
	}
	group_2d_traverse_with_order(node, (GroupingNode2D*)stack, tr_state, stack->positions);
	tr_state->invalidate_all = invalidate_backup;
}

void compositor_init_orderedgroup(GF_Compositor *compositor, GF_Node *node)
{
	OrderedGroupStack *ptr;
	GF_SAFEALLOC(ptr, OrderedGroupStack);
	if (!ptr) return;
	
	gf_node_set_private(node, ptr);
	gf_node_set_callback_function(node, TraverseOrderedGroup);
}

#endif //!defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR)

