/*
 *			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 <gpac/constants.h>
#include <gpac/internal/compositor_dev.h>

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

/*render : setup media sensor and update timing in case of inline scenes*/
void RenderMediaSensor(GF_Node *node, void *rs, Bool is_destroy)
{
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;
	GF_Clock *ck;
	Bool do_update_clock = 1;
	MediaSensorStack *st = (MediaSensorStack *)gf_node_get_private(node);

	if (is_destroy) {
		/*unlink from OD*/
		if (st->stream && st->stream->odm)
			gf_list_del_item(st->stream->odm->ms_stack, st);

		gf_list_del(st->seg);
		gf_free(st);
		return;
	}
	//we need to disable culling otherwise we may never be called back again ...
	tr_state->disable_cull = 1;

	if (!st->is_init) {
		if (!st->stream) {
			st->stream = gf_mo_register(node, &st->sensor->url, 0, 0);
			if (!st->stream) return;
		}
		if (!st->stream->odm) return;
		
		gf_list_add(st->stream->odm->ms_stack, st);
		gf_odm_init_segments(st->stream->odm, st->seg, &st->sensor->url);
		st->is_init = 1;
		st->active_seg = 0;
	}
	//st->stream may be NULL when destroying the ODM
	if (!st->stream || !st->stream->odm) return;

	/*media sensor bound to natural media (audio, video) is updated when fetching the stream
	data for rendering.*/

	ck = NULL;
	/*check inline scenes - if the scene is set to restart DON'T MODIFY SENSOR: since we need a 2 render
	passes to restart inline, scene is considered as not running*/
	if (st->stream->odm->subscene && !st->stream->odm->subscene->needs_restart) {
		if (! st->stream->odm->subscene->is_dynamic_scene) ck = st->stream->odm->subscene->root_od->ck;
		/*dynamic scene*/
		else ck = st->stream->odm->ck;

		if (st->stream->odm->subscene->is_dynamic_scene) do_update_clock = 0;
	}
	/*check anim or OCR streams*/
	else if (st->stream->odm->type==GF_STREAM_SCENE) ck = st->stream->odm->ck;
	/*check OCR streams*/
	else if (st->stream->odm->ck) ck = st->stream->odm->ck;

	if (ck && ck->clock_init ) {
		if (do_update_clock)
			st->stream->odm->media_current_time = gf_clock_media_time(ck);
		mediasensor_update_timing(st->stream->odm, GF_FALSE);
	}
	//if main addon is VoD , fire a timeshift update
	else if (st->stream->odm->subscene && st->stream->odm->subscene->sys_clock_at_main_activation) {
		GF_Event evt;
		memset(&evt, 0, sizeof(evt));
		evt.type = GF_EVENT_TIMESHIFT_UPDATE;
		gf_sc_send_event(st->stream->odm->parentscene->compositor, &evt);
	}
}

void InitMediaSensor(GF_Scene *scene, GF_Node *node)
{
	MediaSensorStack *st;
	GF_SAFEALLOC(st, MediaSensorStack);
	if (!st) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPTIME, ("[Compositor] Failed to allocate media sensor stack\n"));
		return;
	}

	st->parent = scene;
	st->sensor = (M_MediaSensor *)node;
	st->seg = gf_list_new();
	gf_node_set_callback_function(node, RenderMediaSensor);
	gf_node_set_private(node, st);

}

/*only URL can be changed, so reset and get new URL*/
void MS_Modified(GF_Node *node)
{
	MediaSensorStack *st = (MediaSensorStack *)gf_node_get_private(node);
	if (!st) return;

	while (gf_list_count(st->seg)) gf_list_rem(st->seg, 0);

	if (st->stream) {
		/*unlink from OD*/
		if (st->stream->odm && st->stream->odm->ms_stack)
			gf_list_del_item(st->stream->odm->ms_stack, st);

		gf_mo_unregister(node, st->stream);
		if (st->sensor->isActive) {
			st->sensor->isActive = 0;
			gf_node_event_out((GF_Node *) st->sensor, 4/*"isActive"*/);
		}
	}
	st->stream = NULL;
	st->is_init = 0;
	gf_sc_invalidate(st->parent->compositor, NULL);
}

void mediasensor_update_timing(GF_ObjectManager *odm, Bool is_eos)
{
	GF_Segment *desc;
	u32 i, count, j, ms_count;
	Double time;
	ms_count = gf_list_count(odm->ms_stack);
	if (!ms_count) return;

	time = odm->media_current_time / 1000.0;
	//dirty hack to get timing of frame when very late (openhevc debug)
	if (odm->subscene && odm->ck && odm->ck->last_ts_rendered)
		time = odm->ck->last_ts_rendered / 1000.0;

	for (j=0; j<ms_count; j++) {
		MediaSensorStack *media_sens = (MediaSensorStack *)gf_list_get(odm->ms_stack, j);
		if (!media_sens->is_init) continue;
		count = gf_list_count(media_sens->seg);

		/*full object controled*/
		if (!media_sens->active_seg && !count) {
			/*check for end of scene (MediaSensor on inline)*/
			if (odm->subscene && odm->subscene->duration) {
				GF_Clock *ck = gf_odm_get_media_clock(odm);
				if (ck->has_seen_eos && (1000*time>=(Double) (s64)odm->subscene->duration)) {
					if (media_sens->sensor->isActive) {
						/*force notification of time (notify the scene duration rather than the current clock*/
						media_sens->sensor->mediaCurrentTime = (Double) odm->subscene->duration;
						media_sens->sensor->mediaCurrentTime /= 1000;
						gf_node_event_out((GF_Node *) media_sens->sensor, 1/*"mediaCurrentTime"*/);
						media_sens->sensor->isActive = 0;
						gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/);

						GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor\n", odm->ID));
					}
					continue;
				}
			}

			if (!is_eos && !media_sens->sensor->isActive) {
				media_sens->sensor->isActive = 1;
				gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/);

				gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/);
				if (odm->subscene) {
					media_sens->sensor->mediaDuration = (Double) (s64)odm->subscene->duration;
				} else {
					media_sens->sensor->mediaDuration = (Double) (s64)odm->duration;
				}
				if (media_sens->sensor->mediaDuration)
					media_sens->sensor->mediaDuration /= 1000;
				else
					media_sens->sensor->mediaDuration = -FIX_ONE;

				gf_node_event_out((GF_Node *) media_sens->sensor, 3/*"mediaDuration"*/);
			}

			if (is_eos && media_sens->sensor->isActive) {
				if (media_sens->sensor->mediaDuration>=0) {
					media_sens->sensor->mediaCurrentTime = media_sens->sensor->mediaDuration;
				} else {
					media_sens->sensor->mediaCurrentTime = time;
				}
				gf_node_event_out((GF_Node *) media_sens->sensor, 1/*"mediaCurrentTime"*/);
				media_sens->sensor->isActive = 0;
				gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/);
			} else {
				if (media_sens->sensor->isActive && (media_sens->sensor->mediaCurrentTime != time)) {
					media_sens->sensor->mediaCurrentTime = time;
					gf_node_event_out((GF_Node *) media_sens->sensor, 1/*"mediaCurrentTime"*/);
				}
			}
			continue;
		}

		/*locate segment*/
		for (i=media_sens->active_seg; i<count; i++) {
			desc = (GF_Segment*)gf_list_get(media_sens->seg, i);
			/*not controled*/
			if (desc->startTime > time) {
				if (media_sens->sensor->isActive) {
					media_sens->sensor->isActive = 0;
					gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/);

					GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor at time %g - segment %s\n", odm->ID, time, desc->SegmentName));
				}
				continue;
			}
			if (desc->startTime + desc->Duration < time) continue;
			if (desc->startTime + desc->Duration == time) {
				continue;
			}
			/*segment switch, force activation (isActive TRUE send at each seg)*/
			if (media_sens->active_seg != i) {
				media_sens->active_seg = i;
				media_sens->sensor->isActive = 0;
			}

			if (!media_sens->sensor->isActive) {

				GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Activating media sensor time %g - segment %s\n", odm->ID, time, desc->SegmentName));

				media_sens->sensor->isActive = 1;
				gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/);
				/*set info*/
				gf_sg_vrml_mf_reset(& media_sens->sensor->info, GF_SG_VRML_MFSTRING);
				gf_sg_vrml_mf_alloc(& media_sens->sensor->info, GF_SG_VRML_MFSTRING, 1);
				media_sens->sensor->info.vals[0] = desc->SegmentName ? gf_strdup(desc->SegmentName) : NULL;
				gf_node_event_out((GF_Node *) media_sens->sensor, 5/*"info"*/);
				/*set duration*/
				media_sens->sensor->mediaDuration = desc->Duration;
				gf_node_event_out((GF_Node *) media_sens->sensor, 3/*"mediaDuration"*/);
				/*set seg start time*/
				media_sens->sensor->streamObjectStartTime = desc->startTime;
				gf_node_event_out((GF_Node *) media_sens->sensor, 2/*"streamObjectStartTime"*/);

			}

			/*set media time - relative to segment start time*/
			time -= desc->startTime;
			if (media_sens->sensor->mediaCurrentTime != time) {
				media_sens->sensor->mediaCurrentTime = time;
				gf_node_event_out((GF_Node *) media_sens->sensor, 1/*"mediaCurrentTime"*/);
			}
			break;
		}
		if (i==count) {
			/*we're after last segment, deactivate*/
			if (media_sens->sensor->isActive) {
				media_sens->sensor->isActive = 0;
				gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/);
				media_sens->active_seg = count;
				GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor at time %g: no more segments\n", odm->ID, time));
			}
		}
	}
}

void MS_Stop(MediaSensorStack *st)
{
	if (st->sensor->isActive) {
		st->sensor->isActive = 0;
		gf_node_event_out((GF_Node *) st->sensor, 4/*"isActive"*/);

		GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor\n", st->stream->odm->ID));
	}
	st->active_seg = 0;
}

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