/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2012
 *					All rights reserved
 *
 *  This file is part of GPAC / Scene Management 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/scene_manager.h>
#include <gpac/constants.h>
#include <gpac/bifs.h>
#ifndef GPAC_DISABLE_LASER
#include <gpac/laser.h>
#endif



#if !defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_LOADER_ISOM)

static void UpdateODCommand(GF_ISOFile *mp4, GF_ODCom *com)
{
	u32 i, j;
	const char *szName;
	char szPath[2048];

	szName = gf_isom_get_filename(mp4);
	if (com->tag == GF_ODF_OD_UPDATE_TAG) {
		GF_ObjectDescriptor *od;
		GF_ODUpdate *odU = (GF_ODUpdate *)com;
		i=0;
		while ((od = (GF_ObjectDescriptor *)gf_list_enum(odU->objectDescriptors, &i))) {
			GF_ESD *esd;
			j=0;
			while ((esd = (GF_ESD *)gf_list_enum(od->ESDescriptors, &j))) {
				//can happen when dumping with keep-ods
				if (esd->tag!=GF_ODF_ESD_TAG) {
					continue;
				}
				if (esd->URLString) continue;

				if (esd->decoderConfig) {
					switch (esd->decoderConfig->streamType) {
					case GF_STREAM_OD:
						continue;

					case GF_STREAM_SCENE:
						if ((esd->decoderConfig->objectTypeIndication != GF_CODECID_AFX) &&
								(esd->decoderConfig->objectTypeIndication != GF_CODECID_SYNTHESIZED_TEXTURE)
						   ) {
							continue;
						}
						break;
					/*dump the OCR track duration in case the OCR is used by media controls & co*/
					case GF_STREAM_OCR:
					{
						u32 track;
						Double dur;
						GF_MuxInfo *mi = (GF_MuxInfo *) gf_odf_desc_new(GF_ODF_MUXINFO_TAG);
						gf_list_add(esd->extensionDescriptors, mi);
						track = gf_isom_get_track_by_id(mp4, esd->ESID);
						dur = (Double) (s64) gf_isom_get_track_duration(mp4, track);
						dur /= gf_isom_get_timescale(mp4);
						mi->duration = (u32) (dur * 1000);
						continue;
					}
						break;
					default:
						break;
					}
				}

				GF_MuxInfo *mi = (GF_MuxInfo *) gf_odf_desc_new(GF_ODF_MUXINFO_TAG);
				gf_list_add(esd->extensionDescriptors, mi);
				sprintf(szPath, "%s#%d", szName, esd->ESID);
				mi->file_name = gf_strdup(szPath);
				mi->streamFormat = gf_strdup("MP4");
			}
		}
		return;
	}
	if (com->tag == GF_ODF_ESD_UPDATE_TAG) {
		GF_ESD *esd;
		GF_ESDUpdate *esdU = (GF_ESDUpdate *)com;
		i=0;
		while ((esd = (GF_ESD *)gf_list_enum(esdU->ESDescriptors, &i))) {
			Bool import = 1;
			if (esd->URLString) continue;
			if (esd->decoderConfig) {
				switch (esd->decoderConfig->streamType) {
				case GF_STREAM_OD:
					import = 0;
					break;
				case GF_STREAM_SCENE:
					if ((esd->decoderConfig->objectTypeIndication != GF_CODECID_AFX) &&
							(esd->decoderConfig->objectTypeIndication != GF_CODECID_SYNTHESIZED_TEXTURE)
					   ) {
						import = 0;
					}
					break;
				/*dump the OCR track duration in case the OCR is used by media controls & co*/
				case GF_STREAM_OCR:
				{
					u32 track;
					Double dur;
					GF_MuxInfo *mi = (GF_MuxInfo *) gf_odf_desc_new(GF_ODF_MUXINFO_TAG);
					gf_list_add(esd->extensionDescriptors, mi);
					track = gf_isom_get_track_by_id(mp4, esd->ESID);
					dur = (Double) (s64) gf_isom_get_track_duration(mp4, track);
					dur /= gf_isom_get_timescale(mp4);
					mi->duration = (u32) (dur * 1000);
					import = 0;
				}
					break;
				default:
					break;
				}
			}
			if (import) {
				GF_MuxInfo *mi = (GF_MuxInfo *) gf_odf_desc_new(GF_ODF_MUXINFO_TAG);
				gf_list_add(esd->extensionDescriptors, mi);
				sprintf(szPath, "%s#%d", szName, esd->ESID);
				mi->file_name = gf_strdup(szPath);
				mi->streamFormat = gf_strdup("MP4");
			}
		}
		return;
	}
}

static void mp4_report(GF_SceneLoader *load, GF_Err e, char *format, ...)
{
#ifndef GPAC_DISABLE_LOG
	if (format && gf_log_tool_level_on(GF_LOG_PARSER, e ? GF_LOG_ERROR : GF_LOG_WARNING)) {
		char szMsg[1024];
		va_list args;
		va_start(args, format);
		vsnprintf(szMsg, 1024, format, args);
		va_end(args);
		GF_LOG((u32) (e ? GF_LOG_ERROR : GF_LOG_WARNING), GF_LOG_PARSER, ("[MP4 Loading] %s\n", szMsg) );
	}
#endif
}

static GF_Err gf_sm_load_run_isom(GF_SceneLoader *load)
{
	GF_Err e;
	FILE *logs;
	u32 i, j, di, nbBifs, nbLaser, nb_samp, samp_done, init_offset;
	GF_StreamContext *sc;
	GF_ESD *esd;
	GF_ODCodec *od_dec;
#ifndef GPAC_DISABLE_BIFS
	GF_BifsDecoder *bifs_dec;
#endif
#ifndef GPAC_DISABLE_LASER
	GF_LASeRCodec *lsr_dec;
#endif

	if (!load || !load->isom) return GF_BAD_PARAM;

	nbBifs = nbLaser = 0;
	e = GF_OK;
#ifndef GPAC_DISABLE_BIFS
	bifs_dec = gf_bifs_decoder_new(load->scene_graph, 1);
#endif
	od_dec = gf_odf_codec_new();
	logs = NULL;
#ifndef GPAC_DISABLE_LASER
	lsr_dec = gf_laser_decoder_new(load->scene_graph);
#endif
	esd = NULL;
	/*load each stream*/
	nb_samp = 0;
	for (i=0; i<gf_isom_get_track_count(load->isom); i++) {
		u32 type = gf_isom_get_media_type(load->isom, i+1);
		u32 subtype = gf_isom_get_mpeg4_subtype(load->isom, i+1, 1);
		switch (type) {
		case GF_ISOM_MEDIA_SCENE:
		case GF_ISOM_MEDIA_OD:
			nb_samp += gf_isom_get_sample_count(load->isom, i+1);
			break;
		default:
			if (subtype==GF_ISOM_SUBTYPE_MP4S) {
				nb_samp += gf_isom_get_sample_count(load->isom, i+1);
			}
			break;
		}
	}
	samp_done = 1;
	gf_isom_text_set_streaming_mode(load->isom, 1);

	for (i=0; i<gf_isom_get_track_count(load->isom); i++) {
		u32 type = gf_isom_get_media_type(load->isom, i+1);
		u32 subtype = gf_isom_get_mpeg4_subtype(load->isom, i+1, 1);
		switch (type) {
		case GF_ISOM_MEDIA_SCENE:
		case GF_ISOM_MEDIA_OD:
			break;
		default:
			if (subtype!=GF_ISOM_SUBTYPE_MP4S)
				continue;
			break;
		}
		esd = gf_isom_get_esd(load->isom, i+1, 1);
		if (!esd) continue;
		if (!esd->decoderConfig || (esd->decoderConfig->objectTypeIndication==GF_CODECID_TEXT_MPEG4)) {
			gf_odf_desc_del((GF_Descriptor *) esd);
			esd = NULL;
			continue;
		}

		if ((esd->decoderConfig->objectTypeIndication == GF_CODECID_AFX) ||
		        (esd->decoderConfig->objectTypeIndication == GF_CODECID_SYNTHESIZED_TEXTURE)
		   ) {
			nb_samp += gf_isom_get_sample_count(load->isom, i+1);
			gf_odf_desc_del((GF_Descriptor *) esd);
			esd = NULL;
			continue;
		}
		sc = gf_sm_stream_new(load->ctx, esd->ESID, esd->decoderConfig->streamType, esd->decoderConfig->objectTypeIndication);
		sc->streamType = esd->decoderConfig->streamType;
		sc->ESID = esd->ESID;
		sc->codec_id = esd->decoderConfig->objectTypeIndication;
		sc->timeScale = gf_isom_get_media_timescale(load->isom, i+1);

		/*we still need to reconfig the BIFS*/
		if (esd->decoderConfig->streamType==GF_STREAM_SCENE) {
#ifndef GPAC_DISABLE_BIFS
			/*BIFS*/
			if (esd->decoderConfig->objectTypeIndication<=2) {
				if (!esd->dependsOnESID && nbBifs && !i)
					mp4_report(load, GF_OK, "several scene namespaces used or improper scene dependencies in file - import may be incorrect");
				if (!esd->decoderConfig->decoderSpecificInfo) {
					/* Hack for T-DMB non compliant streams */
					e = gf_bifs_decoder_configure_stream(bifs_dec, esd->ESID, NULL, 0, esd->decoderConfig->objectTypeIndication);
				} else {
					e = gf_bifs_decoder_configure_stream(bifs_dec, esd->ESID, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, esd->decoderConfig->objectTypeIndication);
				}
				if (e) goto exit;
				nbBifs++;
			}
#endif

#ifndef GPAC_DISABLE_LASER
			/*LASER*/
			if (esd->decoderConfig->objectTypeIndication==0x09) {
				if (!esd->dependsOnESID && nbBifs && !i)
					mp4_report(load, GF_OK, "several scene namespaces used or improper scene dependencies in file - import may be incorrect");

				if (!esd->decoderConfig->decoderSpecificInfo)
					e = GF_NON_COMPLIANT_BITSTREAM;
				else
					e = gf_laser_decoder_configure_stream(lsr_dec, esd->ESID, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength);

				if (e) goto exit;
				nbLaser++;
			}
#endif
		}

		init_offset = 0;
		/*dump all AUs*/
		for (j=0; j<gf_isom_get_sample_count(load->isom, i+1); j++) {
			GF_AUContext *au;
			GF_ISOSample *samp = gf_isom_get_sample(load->isom, i+1, j+1, &di);
			if (!samp) {
				mp4_report(load, gf_isom_last_error(load->isom), "Unable to fetch sample %d from track ID %d - aborting track import", j+1, gf_isom_get_track_id(load->isom, i+1));
				break;
			}
			/*check if track has initial offset*/
			if (!j && gf_isom_get_edits_count(load->isom, i+1)) {
				u64 EditTime, dur, mtime;
				GF_ISOEditType mode;
				gf_isom_get_edit(load->isom, i+1, 1, &EditTime, &dur, &mtime, &mode);
				if (mode==GF_ISOM_EDIT_EMPTY) {
					init_offset = (u32) (dur * sc->timeScale / gf_isom_get_timescale(load->isom) );
				}
			}
			samp->DTS += init_offset;

			au = gf_sm_stream_au_new(sc, samp->DTS, ((Double)(s64) samp->DTS) / sc->timeScale, (samp->IsRAP==RAP) ? 1 : 0);

			if (esd->decoderConfig->streamType==GF_STREAM_SCENE) {
#ifndef GPAC_DISABLE_BIFS
				if (esd->decoderConfig->objectTypeIndication<=2)
					e = gf_bifs_decode_command_list(bifs_dec, esd->ESID, samp->data, samp->dataLength, au->commands);
#endif
#ifndef GPAC_DISABLE_LASER
				if (esd->decoderConfig->objectTypeIndication==0x09)
					e = gf_laser_decode_command_list(lsr_dec, esd->ESID, samp->data, samp->dataLength, au->commands);
#endif
			} else {
				e = gf_odf_codec_set_au(od_dec, samp->data, samp->dataLength);
				if (!e) e = gf_odf_codec_decode(od_dec);
				if (!e) {
					while (1) {
						GF_ODCom *odc = gf_odf_codec_get_com(od_dec);
						if (!odc) break;
						/*update ESDs if any*/
						UpdateODCommand(load->isom, odc);
						gf_list_add(au->commands, odc);
					}
				}
			}
			gf_isom_sample_del(&samp);
			if (e) {
				mp4_report(load, gf_isom_last_error(load->isom), "decoding sample %d from track ID %d failed", j+1, gf_isom_get_track_id(load->isom, i+1));
				goto exit;
			}

			samp_done++;
			gf_set_progress("MP4 Loading", samp_done, nb_samp);
		}
		gf_odf_desc_del((GF_Descriptor *) esd);
		esd = NULL;
	}
	gf_isom_text_set_streaming_mode(load->isom, 0);

exit:
#ifndef GPAC_DISABLE_BIFS
	gf_bifs_decoder_del(bifs_dec);
#endif
	gf_odf_codec_del(od_dec);
#ifndef GPAC_DISABLE_LASER
	gf_laser_decoder_del(lsr_dec);
#endif
	if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
	if (logs) gf_fclose(logs);
	return e;
}

static void gf_sm_load_done_isom(GF_SceneLoader *load)
{
	/*nothing to do the file is not ours*/
}

static GF_Err gf_sm_isom_suspend(GF_SceneLoader *loader, Bool suspend)
{
	return GF_OK;
}

GF_Err gf_sm_load_init_isom(GF_SceneLoader *load)
{
	u32 i;
	GF_BIFSConfig *bc;
	GF_ESD *esd;
	GF_Err e;
	char *scene_msg = "MPEG-4 BIFS Scene Parsing";
	if (!load->isom) return GF_BAD_PARAM;

	/*load IOD*/
	load->ctx->root_od = (GF_ObjectDescriptor *) gf_isom_get_root_od(load->isom);
	if (!load->ctx->root_od) {
		e = gf_isom_last_error(load->isom);
		if (e) return e;
	}
	else {
		switch (load->ctx->root_od->tag) {
		case GF_ODF_OD_TAG:
		case GF_ODF_IOD_TAG:
		case GF_ODF_ISOM_OD_TAG:
		case GF_ODF_ISOM_IOD_TAG:
			break;
		default:
			gf_odf_desc_del((GF_Descriptor *) load->ctx->root_od);
			load->ctx->root_od = NULL;
			break;
		}
	}

	esd = NULL;

	/*get root scene stream*/
	for (i=0; i<gf_isom_get_track_count(load->isom); i++) {
		u32 type = gf_isom_get_media_type(load->isom, i+1);
		if (type != GF_ISOM_MEDIA_SCENE) continue;
		if (! gf_isom_is_track_in_root_od(load->isom, i+1) ) continue;
		esd = gf_isom_get_esd(load->isom, i+1, 1);

		if (esd && esd->URLString) {
			gf_odf_desc_del((GF_Descriptor *)esd);
			esd = NULL;
			continue;
		}

		/*make sure we load the root BIFS stream first*/
		if (esd && esd->dependsOnESID && (esd->dependsOnESID!=esd->ESID) ) {
			u32 track = gf_isom_get_track_by_id(load->isom, esd->dependsOnESID);
			if (gf_isom_get_media_type(load->isom, track) != GF_ISOM_MEDIA_OD) {
				gf_odf_desc_del((GF_Descriptor *)esd);
				esd = NULL;
				continue;
			}
		}
		if (esd && esd->decoderConfig && esd->decoderConfig->objectTypeIndication==0x09) scene_msg = "MPEG-4 LASeR Scene Parsing";
		break;
	}
	if (!esd) return GF_OK;

	GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("%s\n", scene_msg));

	/*BIFS: update size & pixel metrics info*/
	if (esd->decoderConfig) {
		if (esd->decoderConfig->objectTypeIndication<=2) {
			bc = gf_odf_get_bifs_config(esd->decoderConfig->decoderSpecificInfo, esd->decoderConfig->objectTypeIndication);
			if (!bc->elementaryMasks && bc->pixelWidth && bc->pixelHeight) {
				load->ctx->scene_width = bc->pixelWidth;
				load->ctx->scene_height = bc->pixelHeight;
				load->ctx->is_pixel_metrics = bc->pixelMetrics;
			}
			gf_odf_desc_del((GF_Descriptor *) bc);
			/*note we don't load the first BIFS AU to avoid storing the BIFS decoder, needed to properly handle quantization*/
		}
		/*LASeR*/
		else if (esd->decoderConfig->objectTypeIndication==0x09) {
			load->ctx->is_pixel_metrics = 1;
		}
	}
	gf_odf_desc_del((GF_Descriptor *) esd);
	esd = NULL;

#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode()) {
		mp4_report(load, GF_OK, NULL);
		gf_sm_isom_suspend(load, GF_TRUE);
		gf_sm_isom_suspend(load, GF_FALSE);
	}
#endif

	load->process = gf_sm_load_run_isom;
	load->done = gf_sm_load_done_isom;
	load->suspend = gf_sm_isom_suspend;
	return GF_OK;
}

#endif /*GPAC_DISABLE_ISOM && GPAC_DISABLE_LOADER_ISOM*/
