/*
 *			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 "mpeg4_grouping.h"
#include "visual_manager.h"
#include "texturing.h"
#include "nodes_stacks.h"

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


#define B2D_PLANE_HSIZE		FLT2FIX(0.5025f)

typedef struct
{
	DrawableContext ctx;
	BoundInfo bi;
} BackgroundStatus;

static void DestroyBackground2D(GF_Node *node)
{
	Background2DStack *stack = (Background2DStack *) gf_node_get_private(node);

	PreDestroyBindable(node, stack->reg_stacks);
	gf_list_del(stack->reg_stacks);

	while (gf_list_count(stack->status_stack)) {
		BackgroundStatus *status = (BackgroundStatus *)gf_list_get(stack->status_stack, 0);
		gf_list_rem(stack->status_stack, 0);
		gf_free(status);
	}
	gf_list_del(stack->status_stack);

	drawable_del(stack->drawable);
	gf_sc_texture_destroy(&stack->txh);
#ifndef GPAC_DISABLE_3D
	if (stack->mesh) mesh_free(stack->mesh);
#endif
	gf_free(stack);
}

static void b2D_new_status(Background2DStack *bck, M_Background2D*back)
{
	BackgroundStatus *status;

	GF_SAFEALLOC(status, BackgroundStatus);
	if (!status) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate background2D status\n"));
		return;
	}
	gf_mx2d_init(status->ctx.transform);
	status->ctx.drawable = bck->drawable;
	status->ctx.flags = CTX_IS_BACKGROUND;
	status->ctx.bi = &status->bi;
	status->ctx.aspect.fill_color = GF_COL_ARGB_FIXED(FIX_ONE, back->backColor.red, back->backColor.green, back->backColor.blue);
	status->ctx.aspect.fill_texture = &bck->txh;
	gf_list_add(bck->status_stack, status);
}

static BackgroundStatus *b2d_get_status(Background2DStack *stack, GF_List *background_stack)
{
	u32 i, count;
	if (!background_stack) return NULL;

	count = gf_list_count(stack->reg_stacks);
	for (i=0; i<count; i++) {
		GF_List *bind_stack = (GF_List *)gf_list_get(stack->reg_stacks, i);
		if (bind_stack == background_stack) {
			return gf_list_get(stack->status_stack, i);
		}
	}
	return NULL;
}


static Bool back_use_texture(M_Background2D *bck)
{
	if (!bck->url.count) return 0;
	if (bck->url.vals[0].OD_ID > 0) return 1;
	if (bck->url.vals[0].url && strlen(bck->url.vals[0].url)) return 1;
	return 0;
}

static void DrawBackground2D_2D(DrawableContext *ctx, GF_TraverseState *tr_state)
{
	Bool clear_all = GF_TRUE;
	u32 color;
	Bool use_texture;
	Bool is_offscreen = GF_FALSE;
	Background2DStack *stack;
	if (!ctx || !ctx->drawable || !ctx->drawable->node) return;
	stack = (Background2DStack *) gf_node_get_private(ctx->drawable->node);

	if (!ctx->bi->clip.width || !ctx->bi->clip.height) return;

	stack->flags &= ~CTX_PATH_FILLED;
	color = ctx->aspect.fill_color;

	use_texture = back_use_texture((M_Background2D *)ctx->drawable->node);
	if (!use_texture && !tr_state->visual->is_attached) {
		use_texture = 1;
		stack->txh.data = stack->col_tx;
		stack->txh.width = 2;
		stack->txh.height = 2;
		stack->txh.stride = 6;
		stack->txh.pixelformat = GF_PIXEL_RGB;
	}

	if (use_texture) {

		if (!tr_state->visual->DrawBitmap(tr_state->visual, tr_state, ctx)) {
			/*set target rect*/
			gf_path_reset(stack->drawable->path);
			gf_path_add_rect_center(stack->drawable->path,
			                        ctx->bi->unclip.x + ctx->bi->unclip.width/2,
			                        ctx->bi->unclip.y - ctx->bi->unclip.height/2,
			                        ctx->bi->unclip.width, ctx->bi->unclip.height);

			/*draw texture*/
			visual_2d_texture_path(tr_state->visual, stack->drawable->path, ctx, tr_state);
		}
		//a quick hack, if texture not ready return (we don't have direct notification of this through the above functions
#ifndef GPAC_DISABLE_3D
		if (tr_state->visual->compositor->hybrid_opengl && !stack->txh.tx_io)
			return;
#endif

		stack->flags &= ~(CTX_APP_DIRTY | CTX_TEXTURE_DIRTY);
		tr_state->visual->has_modif = 1;
#ifndef GPAC_DISABLE_3D
		//in opengl auto mode we still have to clear the canvas
		if (!tr_state->immediate_draw && !tr_state->visual->offscreen && tr_state->visual->compositor->hybrid_opengl) {
			clear_all = GF_FALSE;
			is_offscreen = GF_TRUE;
			color &= 0x00FFFFFF;
		} else
#endif
			return;
	}


#ifndef GPAC_DISABLE_3D
	if (ctx->flags & CTX_BACKROUND_NOT_LAYER) {
		if (clear_all && !tr_state->visual->offscreen && tr_state->visual->compositor->hybrid_opengl) {
			compositor_2d_hybgl_clear_surface(tr_state->visual, NULL, color, GF_FALSE);
			is_offscreen = GF_TRUE;
			color &= 0x00FFFFFF;
			//we may need to clear the canvas for immediate mode
		}
	} else {
		is_offscreen = GF_TRUE;
	}
	if (ctx->flags & CTX_BACKROUND_NO_CLEAR) {
		stack->flags &= ~(CTX_APP_DIRTY | CTX_TEXTURE_DIRTY);
		tr_state->visual->has_modif = 1;
		return;
	}
#endif

	/*direct drawing, draw without clippers */
	if (tr_state->immediate_draw
	   ) {
		/*directly clear with specified color*/
		if (clear_all)
			tr_state->visual->ClearSurface(tr_state->visual, &ctx->bi->clip, color, is_offscreen);
	} else {
		u32 i;
		GF_IRect clip;
		for (i=0; i<tr_state->visual->to_redraw.count; i++) {
			/*there's an opaque region above, don't draw*/
#ifdef TRACK_OPAQUE_REGIONS
			if (tr_state->visual->draw_node_index < tr_state->visual->to_redraw.list[i].opaque_node_index) continue;
#endif
			clip = ctx->bi->clip;
			gf_irect_intersect(&clip, &tr_state->visual->to_redraw.list[i].rect);
			if (clip.width && clip.height) {
				tr_state->visual->ClearSurface(tr_state->visual, &clip, color, is_offscreen ? 2 : 0);
			}
		}
	}
	stack->flags &= ~(CTX_APP_DIRTY | CTX_TEXTURE_DIRTY);
	tr_state->visual->has_modif = 1;
}

#ifndef GPAC_DISABLE_3D
static Bool back_texture_enabled(M_Background2D *bck, GF_TextureHandler *txh)
{
	Bool use_texture = back_use_texture(bck);
	if (use_texture) {
		/*texture not ready*/
		if (!txh->tx_io) {
			use_texture = 0;
			gf_sc_invalidate(txh->compositor, NULL);
		}
		gf_sc_texture_set_blend_mode(txh, gf_sc_texture_is_transparent(txh) ? TX_REPLACE : TX_DECAL);
	}
	return use_texture;
}

static void DrawBackground2D_3D(M_Background2D *bck, Background2DStack *st, GF_TraverseState *tr_state)
{
	GF_Matrix mx, bck_mx, bck_mx_cam;
	Bool use_texture;

	use_texture = back_texture_enabled(bck, &st->txh);

	visual_3d_set_background_state(tr_state->visual, 1);

	gf_mx_copy(bck_mx_cam, tr_state->camera->modelview);
	gf_mx_copy(bck_mx, tr_state->model_matrix);

	/*little opt: if we clear the main visual clear it entirely */
	if (! tr_state->is_layer) {
		visual_3d_clear(tr_state->visual, bck->backColor, FIX_ONE);
		if (!use_texture) {
			visual_3d_set_background_state(tr_state->visual, 0);
			return;
		}
		/*we need a hack here because main vp is always traversed before main background, and in the case of a
		2D viewport it modifies the modelview matrix, which we don't want ...*/
		gf_mx_init(tr_state->model_matrix);
		gf_mx_init(tr_state->camera->modelview);
	}
	if (!use_texture || (!tr_state->is_layer && st->txh.transparent) ) visual_3d_set_material_2d(tr_state->visual, bck->backColor, FIX_ONE);
	if (use_texture) {
		visual_3d_set_state(tr_state->visual, V3D_STATE_COLOR, ! tr_state->is_layer);
		tr_state->mesh_num_textures = gf_sc_texture_enable(&st->txh, NULL);
		if (!tr_state->mesh_num_textures) visual_3d_set_material_2d(tr_state->visual, bck->backColor, FIX_ONE);
	}

	/*create mesh object if needed*/
	if (!st->mesh) {
		st->mesh = new_mesh();
		mesh_set_vertex(st->mesh, -B2D_PLANE_HSIZE, -B2D_PLANE_HSIZE, 0,  0,  0,  FIX_ONE, 0, 0);
		mesh_set_vertex(st->mesh,  B2D_PLANE_HSIZE, -B2D_PLANE_HSIZE, 0,  0,  0,  FIX_ONE, FIX_ONE, 0);
		mesh_set_vertex(st->mesh,  B2D_PLANE_HSIZE,  B2D_PLANE_HSIZE, 0,  0,  0,  FIX_ONE, FIX_ONE, FIX_ONE);
		mesh_set_vertex(st->mesh, -B2D_PLANE_HSIZE,  B2D_PLANE_HSIZE, 0,  0,  0,  FIX_ONE, 0, FIX_ONE);
		mesh_set_triangle(st->mesh, 0, 1, 2);
		mesh_set_triangle(st->mesh, 0, 2, 3);
		st->mesh->flags |= MESH_IS_2D;
	}

	gf_mx_init(mx);
	if (tr_state->camera->is_3D) {
		Fixed sx, sy;
		/*reset matrix*/
		gf_mx_init(tr_state->model_matrix);
		sx = sy = 2 * gf_mulfix(gf_tan(tr_state->camera->fieldOfView/2), tr_state->camera->z_far);
		if (tr_state->camera->width > tr_state->camera->height) {
			sx = gf_muldiv(sx, tr_state->camera->width, tr_state->camera->height);
		} else {
			sy = gf_muldiv(sy, tr_state->camera->height, tr_state->camera->width);
		}
		gf_mx_add_scale(&mx, sx, sy, FIX_ONE);
#ifdef GPAC_FIXED_POINT
		gf_mx_add_translation(&mx, 0, 0, - (tr_state->camera->z_far/100)*99);
#else
		gf_mx_add_translation(&mx, 0, 0, -0.995f*tr_state->camera->z_far);
#endif
	} else {
		gf_mx_add_scale(&mx, tr_state->bbox.max_edge.x - tr_state->bbox.min_edge.x,
		                tr_state->bbox.max_edge.y - tr_state->bbox.min_edge.y,
		                FIX_ONE);
		/*when in layer2D, DON'T MOVE BACKGROUND TO ZFAR*/
		if (!tr_state->is_layer) {
			Fixed tr;
#ifdef GPAC_FIXED_POINT
			tr = -(tr_state->camera->z_far/100)*99;
#else
			tr = -0.999f*tr_state->camera->z_far;
#endif
			if (!tr_state->camera->is_3D) tr = -tr;
			gf_mx_add_translation(&mx, 0, 0, tr);
		}
	}
	gf_mx_add_matrix(&tr_state->model_matrix, &mx);
	visual_3d_mesh_paint(tr_state, st->mesh);
	if (tr_state->mesh_num_textures) {
		gf_sc_texture_disable(&st->txh);
		tr_state->mesh_num_textures = 0;
	}

	gf_mx_copy(tr_state->model_matrix, bck_mx);
	gf_mx_copy(tr_state->camera->modelview, bck_mx_cam);

	visual_3d_set_background_state(tr_state->visual, 0);
}
#endif

static void TraverseBackground2D(GF_Node *node, void *rs, Bool is_destroy)
{
	u32 col;
	BackgroundStatus *status;
	M_Background2D *bck;
	Background2DStack *stack = (Background2DStack *) gf_node_get_private(node);
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;

	if (is_destroy) {
		DestroyBackground2D(node);
		return;
	}
	if (tr_state->visual->compositor->noback)
		return;

	bck = (M_Background2D *)node;

	/*special case for background in Layer2D: the background is seen as a regular drawable, so
	RENDER_BINDABLE is not used*/
	switch (tr_state->traversing_mode) {
	case TRAVERSE_DRAW_2D:
		DrawBackground2D_2D(tr_state->ctx, tr_state);
		return;
	case  TRAVERSE_PICK:
	case TRAVERSE_GET_BOUNDS:
		return;
	}

	/*first traverse, bound if needed*/
	if (gf_list_find(tr_state->backgrounds, node) < 0) {
		M_Background2D *top_bck;

		gf_list_add(tr_state->backgrounds, node);
		if (gf_list_find(stack->reg_stacks, tr_state->backgrounds)<0)
			gf_list_add(stack->reg_stacks, tr_state->backgrounds);
		b2D_new_status(stack, bck);

		/*only bound if we're on top*/
		top_bck = gf_list_get(tr_state->backgrounds, 0);
		if (!bck->isBound) {
			if (top_bck== bck) {
				Bindable_SetIsBound(node, 1);
			} else if (!top_bck->isBound) {
				bck->set_bind = 1;
				bck->on_set_bind(node, NULL);
			}
		}
		/*open the stream if any*/
		if (back_use_texture(bck) && !stack->txh.is_open) gf_sc_texture_play(&stack->txh, &bck->url);
		/*in any case don't draw the first time (since the background could have been declared last)*/
		gf_sc_invalidate(stack->txh.compositor, NULL);
		return;
	}
	if (!bck->isBound) return;

	status = b2d_get_status(stack, tr_state->backgrounds);
	if (!status) return;

	if (gf_node_dirty_get(node)) {
		u32 i;

		stack->flags |= CTX_APP_DIRTY;
		gf_node_dirty_clear(node, 0);


		col = GF_COL_ARGB_FIXED(FIX_ONE, bck->backColor.red, bck->backColor.green, bck->backColor.blue);
		if (col != status->ctx.aspect.fill_color) {
			status->ctx.aspect.fill_color = col;
			stack->flags |= CTX_APP_DIRTY;
		}
		for (i=0; i<4; i++) {
			stack->col_tx[3*i] = FIX2INT(255 * bck->backColor.red);
			stack->col_tx[3*i+1] = FIX2INT(255 * bck->backColor.green);
			stack->col_tx[3*i+2] = FIX2INT(255 * bck->backColor.blue);
		}
	}

	if (back_use_texture(bck) ) {
#ifndef GPAC_DISABLE_3D
		if (stack->txh.compositor->hybrid_opengl && !tr_state->visual->offscreen && stack->hybgl_init) {
			stack->flags |= CTX_HYBOGL_NO_CLEAR;
		}
		stack->hybgl_init = 1;
#endif
		if (stack->txh.tx_io && !(status->ctx.flags & CTX_APP_DIRTY) && stack->txh.needs_refresh) {
			stack->flags |= CTX_TEXTURE_DIRTY;
		}
	}
	if (status->ctx.flags & CTX_BACKROUND_NOT_LAYER) {
		status->ctx.flags = stack->flags | CTX_BACKROUND_NOT_LAYER;
	} else {
		status->ctx.flags = stack->flags;
		if (tr_state->is_layer)
			status->ctx.flags &= ~CTX_BACKROUND_NOT_LAYER;
	}


	if (tr_state->traversing_mode != TRAVERSE_BINDABLE) return;

	/*3D mode*/
#ifndef GPAC_DISABLE_3D
	if (tr_state->visual->type_3d) {
		DrawBackground2D_3D(bck, stack, tr_state);
	} else
#endif
		DrawBackground2D_2D(&status->ctx, tr_state);
}


static void b2D_set_bind(GF_Node *node, GF_Route *route)
{
	Background2DStack *stack = (Background2DStack *)gf_node_get_private(node);
	Bindable_OnSetBind(node, stack->reg_stacks, NULL);

	if (stack->drawable->flags & DRAWABLE_IS_OVERLAY) {
		stack->txh.compositor->video_out->Blit(stack->txh.compositor->video_out, NULL, NULL, NULL, 1);
	}
	stack->flags |= CTX_APP_DIRTY;

}

DrawableContext *b2d_get_context(M_Background2D *node, GF_List *from_stack)
{
	Background2DStack *stack = (Background2DStack *)gf_node_get_private((GF_Node *)node);
	BackgroundStatus *status = b2d_get_status(stack, from_stack);
	if (status) {
		status->ctx.bi = &status->bi;
		return &status->ctx;
	}
	return NULL;
}



static void UpdateBackgroundTexture(GF_TextureHandler *txh)
{
	gf_sc_texture_update_frame(txh, 0);

	if (!txh->compositor->player && !txh->compositor->passthrough_txh && txh->stream && txh->stream->odm && (txh->stream->odm->flags & GF_ODM_PASSTHROUGH)) {
		if (!txh->width || ((txh->width==txh->compositor->display_width) && (txh->height==txh->compositor->display_height)))
			txh->compositor->passthrough_txh = txh;
		else
			txh->compositor->passthrough_txh = NULL;
	}

	/*restart texture if needed (movie background controled by MediaControl)*/
	if (txh->stream_finished && gf_mo_get_loop(txh->stream, 0))
		gf_sc_texture_restart(txh);
}

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

	ptr->status_stack = gf_list_new();
	ptr->reg_stacks = gf_list_new();
	/*setup drawable object for background*/
	ptr->drawable = drawable_stack_new(compositor, node);
	ptr->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
	((M_Background2D *)node)->on_set_bind = b2D_set_bind;


	gf_sc_texture_setup(&ptr->txh, compositor, node);
	ptr->txh.update_texture_fcnt = UpdateBackgroundTexture;
	ptr->txh.flags = GF_SR_TEXTURE_REPEAT_S | GF_SR_TEXTURE_REPEAT_T;
	ptr->flags = CTX_IS_BACKGROUND;

	gf_node_set_private(node, ptr);
	gf_node_set_callback_function(node, TraverseBackground2D);
}

void compositor_background2d_modified(GF_Node *node)
{
	M_Background2D *bck = (M_Background2D *)node;
	Background2DStack *st = (Background2DStack *) gf_node_get_private(node);
	if (!st) return;

	/*dirty node and parents in order to trigger parent visual redraw*/
	gf_node_dirty_set(node, 0, 1);

	/*if open and changed, stop and play*/
	if (st->txh.is_open) {
		if (! gf_sc_texture_check_url_change(&st->txh, &bck->url)) return;
		gf_sc_texture_stop(&st->txh);
		gf_sc_texture_play(&st->txh, &bck->url);
		return;
	}
	/*if not open and changed play*/
	if (bck->url.count)
		gf_sc_texture_play(&st->txh, &bck->url);
	gf_sc_invalidate(st->txh.compositor, NULL);
}

#if 0 //unused
Bool compositor_background_transparent(GF_Node *node)
{
	if (node && (gf_node_get_tag(node) == TAG_MPEG4_Background2D)) {
		Background2DStack *st;
		if (!((M_Background2D *)node)->isBound) return 1;

		st = (Background2DStack *) gf_node_get_private(node);
		if (st->txh.transparent) return 1;
		return 0;
	}
	/*consider all other background nodes transparent*/
	return 1;
}
#endif


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

