/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005 
 *					All rights reserved
 *
 *  This file is part of GPAC / command-line mp4 toolbox
 *
 *  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 <gpac/m4_author.h>
#include <gpac/m4_x3d_nodes.h>
#include <gpac/intern/m4_bifs_dev.h>
#include <gpac/m4_decoder.h>

extern u32 swf_flags;
extern Float swf_flatten_angle;
extern u32 get_file_type_by_ext(char *inName);

void dump_file_text(char *file, char *inName, u32 dump_mode)
{
	M4Err e;
	M4SceneManager *ctx;
	LPSCENEGRAPH sg;
	M4ContextLoader load;
	e = M4OK;

	sg = NewSceneGraph();
	ctx = NewSceneManager(sg);
	memset(&load, 0, sizeof(M4ContextLoader));
	load.fileName = file;
	load.ctx = ctx;
	load.OnProgress = MP4T_OnProgress;
	load.cbk = "Parsing";
	load.swf_import_flags = swf_flags;
	load.swf_flatten_limit = swf_flatten_angle;

	if (get_file_type_by_ext(file) == 1) {
		load.isom = M4_MovieOpen(file, M4_OPEN_READ);
		if (!load.isom) {
			fprintf(stdout, "Cannot open file: %s\n", M4ErrToString(M4_GetLastError(NULL)));
			M4SM_Delete(ctx);
			SG_Delete(sg);
			return;
		}
	}
	e = M4SM_LoaderInit(&load);
	if (!e) e = M4SM_LoaderRun(&load);
	M4SM_LoaderDone(&load);
	if (!e) {
		fprintf(stdout, "Scene loaded - dumping %d systems streams\n", ChainGetCount(ctx->streams));
		e = M4SM_DumpToText(ctx, inName, dump_mode);
	}

	M4SM_Delete(ctx);
	SG_Delete(sg);
	if (e) fprintf(stdout, "Error loading scene: %s\n", M4ErrToString(e));
	if (load.isom) M4_MovieDelete(load.isom);
}


static void dump_stats(FILE *dump, M4SceneStatistics *stats)
{
	u32 i;
	s32 created, count, draw_created, draw_count, deleted, draw_deleted;
	created = count = draw_created = draw_count = deleted = draw_deleted = 0;

	fprintf(dump, "<NodeStatistics>\n");
	fprintf(dump, "<General NumberOfNodeTypes=\"%d\"/>\n", ChainGetCount(stats->node_stats));
	for (i=0; i<ChainGetCount(stats->node_stats); i++) {
		NodeStats *ptr = ChainGetEntry(stats->node_stats, i);
		fprintf(dump, "<NodeStat NodeName=\"%s\">\n", ptr->name);
		
		switch (ptr->tag) {
		case TAG_MPEG4_Bitmap:
		case TAG_MPEG4_Background2D:
		case TAG_MPEG4_Background:
		case TAG_MPEG4_Box:
		case TAG_MPEG4_Circle:
		case TAG_MPEG4_CompositeTexture2D:
		case TAG_MPEG4_CompositeTexture3D:
		case TAG_MPEG4_Cylinder:
		case TAG_MPEG4_Cone:
		case TAG_MPEG4_Curve2D:
		case TAG_MPEG4_Extrusion:
		case TAG_MPEG4_ElevationGrid:
		case TAG_MPEG4_IndexedFaceSet2D:
		case TAG_MPEG4_IndexedFaceSet:
		case TAG_MPEG4_IndexedLineSet2D:
		case TAG_MPEG4_IndexedLineSet:
		case TAG_MPEG4_PointSet2D:
		case TAG_MPEG4_PointSet:
		case TAG_MPEG4_Rectangle:
		case TAG_MPEG4_Sphere:
		case TAG_MPEG4_Text:
		case TAG_MPEG4_Ellipse:
		case TAG_MPEG4_XCurve2D:
			draw_count += ptr->nb_created + ptr->nb_used - ptr->nb_del;
			draw_deleted += ptr->nb_del;
			draw_created += ptr->nb_created;
			break;
		}
		fprintf(dump, "<Instanciation NbObjects=\"%d\" NbUse=\"%d\" NbDestroy=\"%d\"/>\n", ptr->nb_created, ptr->nb_used, ptr->nb_del);
		count += ptr->nb_created + ptr->nb_used;
		deleted += ptr->nb_del;
		created += ptr->nb_created;
		fprintf(dump, "</NodeStat>\n");
	}
	if (i) {
		fprintf(dump, "<CumulatedStat TotalNumberOfNodes=\"%d\" ReallyAllocatedNodes=\"%d\" DeletedNodes=\"%d\"/>\n", count, created, deleted);
		fprintf(dump, "<DrawableNodesCumulatedStat TotalNumberOfNodes=\"%d\" ReallyAllocatedNodes=\"%d\" DeletedNodes=\"%d\"/>\n", draw_count, draw_created, draw_deleted);
	}
	fprintf(dump, "</NodeStatistics>\n");

	created = count = deleted = 0;
	if (ChainGetCount(stats->proto_stats)) {
		fprintf(dump, "<ProtoStatistics NumberOfProtoUsed=\"%d\">\n", ChainGetCount(stats->proto_stats));
		for (i=0; i<ChainGetCount(stats->proto_stats); i++) {
			NodeStats *ptr = ChainGetEntry(stats->proto_stats, i);
			fprintf(dump, "<ProtoStat ProtoName=\"%s\">\n", ptr->name);
			fprintf(dump, "<Instanciation NbObjects=\"%d\" NbUse=\"%d\" NbDestroy=\"%d\"/>\n", ptr->nb_created, ptr->nb_used, ptr->nb_del);
			count += ptr->nb_created + ptr->nb_used;
			deleted += ptr->nb_del;
			created += ptr->nb_created;
			fprintf(dump, "</ProtoStat>\n");
		}
		if (i) fprintf(dump, "<CumulatedStat TotalNumberOfProtos=\"%d\" ReallyAllocatedProtos=\"%d\" DeletedProtos=\"%d\"/>\n", count, created, deleted);
		fprintf(dump, "</ProtoStatistics>\n");
	}
	fprintf(dump, "<FieldStatistic FieldType=\"MFVec2f\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\" NumRemoved=\"%d\"/>", stats->count_2d, stats->rem_2d);
	if (stats->count_2d) {
		fprintf(dump, "<ExtendInfo MinVec2f=\"%f %f\" MaxVec2f=\"%f %f\"/>\n", FIX2FLT( stats->min_2d.x) , FIX2FLT( stats->min_2d.y ), FIX2FLT( stats->max_2d.x ), FIX2FLT( stats->max_2d.y ) );
	}
	fprintf(dump, "</FieldStatistic>\n");

	fprintf(dump, "<FieldStatistic FieldType=\"MFVec3f\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\" NumRemoved=\"%d\"/>", stats->count_3d, stats->rem_3d);
	if (stats->count_3d) {
		fprintf(dump, "<ExtendInfo MinVec3f=\"%f %f %f\" MaxVec3f=\"%f %f %f\"/>\n", FIX2FLT( stats->min_3d.x ), FIX2FLT( stats->min_3d.y ), FIX2FLT( stats->min_3d.z ), FIX2FLT( stats->max_3d.x ), FIX2FLT( stats->max_3d.y ), FIX2FLT( stats->max_3d.z ) );
	}
	fprintf(dump, "</FieldStatistic>\n");

	fprintf(dump, "<FieldStatistic FieldType=\"MF/SFColor\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\" NumRemoved=\"%d\"/>", stats->count_color, stats->rem_color);
	fprintf(dump, "</FieldStatistic>\n");

	fprintf(dump, "<FieldStatistic FieldType=\"MF/SFFloat\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\" NumRemoved=\"%d\"/>", stats->count_float, stats->rem_float);
	fprintf(dump, "</FieldStatistic>\n");

	fprintf(dump, "<FieldStatistic FieldType=\"SFVec2f\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\"/>", stats->count_2f);
	fprintf(dump, "</FieldStatistic>\n");
	fprintf(dump, "<FieldStatistic FieldType=\"SFVec3f\">\n");
	fprintf(dump, "<ParsingInfo NumParsed=\"%d\"/>", stats->count_3f);
	fprintf(dump, "</FieldStatistic>\n");
}


static void ReorderAU(Chain *sample_list, M4AUContext *au)
{
	u32 i;
	for (i=0; i<ChainGetCount(sample_list); i++) {
		M4AUContext *ptr = ChainGetEntry(sample_list, i);
		if (
			/*time ordered*/
			(ptr->timing_sec > au->timing_sec) 
			/*set bifs first*/
			|| ((ptr->timing_sec == au->timing_sec) && (ptr->owner->streamType < au->owner->streamType))
		) {
			ChainInsertEntry(sample_list, au, i);
			return;
		}
	}
	ChainAddEntry(sample_list, au);
}

void dump_scene_stats(char *file, char *inName, u32 stat_level)
{
	M4Err e;
	FILE *dump;
	Bool close;
	u32 i, j, count;
	char szBuf[1024];
	M4SceneManager *ctx;
	M4ContextLoader load;
	LPSTATMAN sm;	
	Chain *sample_list;
	LPSCENEGRAPH scene_graph;

	dump = NULL;
	sm = NULL;
	sample_list = NULL;
	
	close = 0;

	scene_graph = NewSceneGraph();
	ctx = NewSceneManager(scene_graph);
	memset(&load, 0, sizeof(M4ContextLoader));
	load.fileName = file;
	load.ctx = ctx;

	if (get_file_type_by_ext(file) == 1) {
		load.isom = M4_MovieOpen(file, M4_OPEN_READ);
		if (!load.isom) {
			fprintf(stdout, "Cannot open file: %s\n", M4ErrToString(M4_GetLastError(NULL)));
			M4SM_Delete(ctx);
			SG_Delete(scene_graph);
			return;
		}
	}

	load.cbk = "Parsing";
	load.OnProgress = MP4T_OnProgress;
	e = M4SM_LoaderInit(&load);
	if (!e) e = M4SM_LoaderRun(&load);
	M4SM_LoaderDone(&load);
	if (e) goto exit;

	if (inName) {
		strcpy(szBuf, inName);
		strcat(szBuf, "_stat.xml");
		dump = fopen(szBuf, "wt");
		close = 1;
	} else {
		dump = stdout;
		close = 0;
	}

	fprintf(stdout, "Analysing Scene\n");

	fprintf(dump, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
	fprintf(dump, "<!-- Scene Graph Statistics Generated by MP4Box - GPAC " M4_VERSION" -->\n");
	fprintf(dump, "<SceneStatistics file=\"%s\" DumpType=\"%s\">\n", file, (stat_level==1) ? "full scene" : ((stat_level==2) ? "AccessUnit based" : "SceneGraph after each AU"));

	sm = M4SM_NewStatisitics();

	/*stat level 1: complete scene stat*/
	if (stat_level == 1) {
		e = M4SM_GetStatistics(sm, ctx);
		if (!e) dump_stats(dump, M4SM_GetStatisitics(sm) );
		goto exit;
	}
	/*re_order all BIFS-AUs*/
	sample_list = NewChain();
	/*configure all systems streams we're dumping*/
	for (i=0; i<ChainGetCount(ctx->streams); i++) {
		M4StreamContext *sc = ChainGetEntry(ctx->streams, i);
		if (sc->streamType != M4ST_SCENE) continue;
		for (j=0; j<ChainGetCount(sc->AUs); j++) {
			M4AUContext *au = ChainGetEntry(sc->AUs, j);
			ReorderAU(sample_list, au);
		}
	}

	count = ChainGetCount(sample_list);
	for (i=0; i<count; i++) {
		M4AUContext *au = ChainGetEntry(sample_list, i);
		
		for (j=0; j<ChainGetCount(au->commands); j++) {
			SGCommand *com = ChainGetEntry(au->commands, j);
			/*stat level 2 - get command stats*/
			if (stat_level==2) {
				e = M4SM_GetCommandStatistics(sm, com);
				if (e) goto exit;
			}
			/*stat level 3 - apply command*/
			if (stat_level==3) SG_ApplyCommand(scene_graph, com, 0);
		}
		/*stat level 3: get graph stat*/
		if (stat_level==3) {
			e = M4SM_GetGraphStatistics(sm, scene_graph);
			if (e) goto exit;
		}
		if (stat_level==2) {
			fprintf(dump, "<AUStatistics StreamID=\"%d\" AUTime=\"%d\">\n", au->owner->ESID, au->timing);
		} else {
			fprintf(dump, "<GraphStatistics StreamID=\"%d\" AUTime=\"%d\">\n", au->owner->ESID, au->timing);
		}
		/*dump stats*/
		dump_stats(dump, M4SM_GetStatisitics(sm) );
		/*reset stats*/
		M4SM_ResetStatisitics(sm);
		if (stat_level==2) {
			fprintf(dump, "</AUStatistics>\n");
		} else {
			fprintf(dump, "</GraphStatistics>\n");
		}

		MP4T_OnProgress("Analysing AU", i+1, count);
	}


exit:	
	if (sample_list) DeleteChain(sample_list);
	if (sm) M4SM_DeleteStatisitics(sm);
	M4SM_Delete(ctx);
	SG_Delete(scene_graph);
	if (e) {
		fprintf(stdout, "%s\n", M4ErrToString(e));
	} else {
		fprintf(dump, "</SceneStatistics>\n");
	}
	if (dump && close) fclose(dump);
	fprintf(stdout, "done\n");
}

void PrintFixed(Fixed val, Bool add_space)
{
	if (add_space) fprintf(stdout, " ");
	if (val==FIX_MIN) fprintf(stdout, "-I");
	else if (val==FIX_MAX) fprintf(stdout, "+I");
	else fprintf(stdout, "%g", FIX2FLT(val));
}

void PrintNodeSFField(u32 type, void *far_ptr)
{
	if (!far_ptr) return;
	switch (type) {
	case FT_SFBool:
		fprintf(stdout, "%s", (*(SFBool *)far_ptr) ? "TRUE" : "FALSE");
		break;
	case FT_SFInt32:
		fprintf(stdout, "%d", (*(SFInt32 *)far_ptr));
		break;
	case FT_SFFloat:
		PrintFixed((*(SFFloat *)far_ptr), 0);
		break;
	case FT_SFTime:
		fprintf(stdout, "%g", (*(SFTime *)far_ptr));
		break;
	case FT_SFVec2f:
		PrintFixed(((SFVec2f *)far_ptr)->x, 0);
		PrintFixed(((SFVec2f *)far_ptr)->y, 1);
		break;
	case FT_SFVec3f:
		PrintFixed(((SFVec3f *)far_ptr)->x, 0);
		PrintFixed(((SFVec3f *)far_ptr)->y, 1);
		PrintFixed(((SFVec3f *)far_ptr)->z, 1);
		break;
	case FT_SFRotation:
		PrintFixed(((SFRotation *)far_ptr)->x, 0);
		PrintFixed(((SFRotation *)far_ptr)->y, 1);
		PrintFixed(((SFRotation *)far_ptr)->z, 1);
		PrintFixed(((SFRotation *)far_ptr)->q, 1);
		break;
	case FT_SFColor:
		PrintFixed(((SFColor *)far_ptr)->red, 0);
		PrintFixed(((SFColor *)far_ptr)->green, 1);
		PrintFixed(((SFColor *)far_ptr)->blue, 1);
		break;
	case FT_SFString:
		if (((SFString*)far_ptr)->buffer)
			fprintf(stdout, "\"%s\"", ((SFString*)far_ptr)->buffer);
		else
			fprintf(stdout, "NULL");
		break;
	}
}
void PrintNode(const char *name, Bool x3d_node)
{
	const char *nname, *std_name;
	SFNode *node;
	LPSCENEGRAPH sg;
	u32 tag, nbF, i;
	FieldInfo f;
	u8 qt, at;
	Fixed bmin, bmax;
	u32 nbBits;

	if (x3d_node) {
		tag = X3D_GetTagByName(name);
		std_name = "X3D";
	} else {
		tag = MPEG4_GetTagByName(name);
		std_name = "MPEG4";
	}
	if (!tag) {
		fprintf(stdout, "Unknown %s node %s\n", std_name, name);
		return;
	}

	sg = NewSceneGraph();
	node = SG_NewNode(sg, tag);
	Node_Register(node, NULL);
	nname = Node_GetName(node);
	if (!node) {
		fprintf(stdout, "Node %s not supported in current built\n", nname);
		return;
	}
	nbF = Node_GetFieldCount(node);

	fprintf(stdout, "Node Syntax:\n%s {\n", nname);

	for (i=0; i<nbF; i++) {
		Node_GetField(node, i, &f);
		fprintf(stdout, "\t%s %s %s", VRML_GetEventTypeName(f.eventType, 0), VRML_GetFieldTypeName(f.fieldType), f.name);
		if (f.fieldType==FT_SFNode) fprintf(stdout, " NULL");
		else if (f.fieldType==FT_MFNode) fprintf(stdout, " []");
		else if (VRML_IsSFField(f.fieldType)) {
			fprintf(stdout, " ");
			PrintNodeSFField(f.fieldType, f.far_ptr);
		} else {
			void *ptr;
			u32 i, sftype;
			GenMFField *mffield = (GenMFField *) f.far_ptr;
			fprintf(stdout, " [");
			sftype = VRML_GetSFType(f.fieldType);
			for (i=0; i<mffield->count; i++) {
				if (i) fprintf(stdout, " ");
				VRML_MF_GetItem(f.far_ptr, f.fieldType, &ptr, i);
				PrintNodeSFField(sftype, ptr);
			}
			fprintf(stdout, "]");
		}
		if (Node_GetAQInfo(node, i, &qt, &at, &bmin, &bmax, &nbBits)) {
			if (qt) {
				fprintf(stdout, " #QP=%d", qt);
				if (qt==13) fprintf(stdout, " NbBits=%d", nbBits);
				if (bmin && bmax) {
					fprintf(stdout, " Bounds=[");
					PrintFixed(bmin, 0);
					fprintf(stdout, ",");
					PrintFixed(bmax, 0);
					fprintf(stdout, "]");
				}
			}
		}
		fprintf(stdout, "\n");
	}
	fprintf(stdout, "}\n\n");

	Node_Unregister(node, NULL);
	SG_Delete(sg);
}

void PrintBuiltInNodes(Bool x3d_node)
{
	SFNode *node;
	LPSCENEGRAPH sg;
	u32 i, nb_in, nb_not_in, start_tag, end_tag;

	if (x3d_node) {
		start_tag = TAG_RANGE_FIRST_X3D;
		end_tag = TAG_LastImplementedX3D;
	} else {
		start_tag = TAG_RANGE_FIRST_MPEG4;
		end_tag = TAG_LastImplementedMPEG4;
	}
	nb_in = nb_not_in = 0;
	sg = NewSceneGraph();
	if (x3d_node) {
		fprintf(stdout, "Available X3D nodes in this build (dumping):\n");
	} else {
		fprintf(stdout, "Available MPEG-4 nodes in this build (encoding/decoding/dumping):\n");
	}
	for (i=start_tag; i<end_tag; i++) {
		node = SG_NewNode(sg, i);
		if (node) {
			Node_Register(node, NULL);
			fprintf(stdout, " %s\n", Node_GetName(node));
			Node_Unregister(node, NULL);
			nb_in++;
		} else {
			nb_not_in++;
		}
	}
	SG_Delete(sg);
	fprintf(stdout, "\n%d nodes supported - %d nodes not supported\n", nb_in, nb_not_in);
}

void dump_file_mp4(M4File *file, char *inName)
{
	FILE *dump;
	char szBuf[1024];

	if (inName) {
		strcpy(szBuf, inName);
		strcat(szBuf, "_info.xml");
		dump = fopen(szBuf, "wt");
		M4_FileDump(file, dump);
		fclose(dump);
	} else {
		M4_FileDump(file, stdout);
	}
}

void dump_file_rtp(M4File *file, char *inName)
{
	u32 i, j;
	FILE *dump;
	char szBuf[1024];

	if (inName) {
		strcpy(szBuf, inName);
		strcat(szBuf, "_rtp.xml");
		dump = fopen(szBuf, "wt");
	} else {
		dump = stdout;
	}

	fprintf(dump, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
	fprintf(dump, "<!-- MP4Box RTP trace -->\n");
	fprintf(dump, "<RTPFile>\n");

	for (i=0; i<M4_GetTrackCount(file); i++) {
		if (M4_GetMediaType(file, i+1) != M4_HintMediaType) continue;

		fprintf(dump, "<RTPHintTrack trackID=\"%d\">\n", M4_GetTrackID(file, i+1));
		for (j=0; j<M4_GetSampleCount(file, i+1); j++) {
			M4H_DumpHintSample(file, i+1, j+1, dump);
		}
		fprintf(dump, "</RTPHintTrack>\n");
	}
	fprintf(dump, "</RTPFile>\n");
	if (inName) fclose(dump);
}

#define TS_DIV 1000
#define TS_MUL 24

void dump_file_ts(M4File *file, char *inName)
{
	u32 i, j, k, count;
	FILE *dump;
	char szBuf[1024];

	if (inName) {
		strcpy(szBuf, inName);
		strcat(szBuf, "_ts.txt");
		dump = fopen(szBuf, "wt");
	} else {
		dump = stdout;
	}

	for (i=0; i<M4_GetTrackCount(file); i++) {	
		Bool has_cts_offset = M4_HasTimeOffsets(file, i+1);

		fprintf(dump, "#dumping track ID %d timing\n", M4_GetTrackID(file, i+1));
		count = M4_GetSampleCount(file, i+1);
		for (j=0; j<count; j++) {
			s32 dts, cts;
			M4Sample *samp = M4_GetSampleInfo(file, i+1, j+1, NULL, NULL);
			dts = samp->DTS;
			cts = dts + (s32) samp->CTS_Offset;
			M4_DeleteSample(&samp);
//			dts /= TS_DIV; cts /= TS_DIV;
//			dts *= TS_MUL; cts *= TS_MUL;

			fprintf(dump, "Sample %d - DTS %d - CTS %d", j+1, dts, cts);
			if (cts<dts) fprintf(dump, " #NEGATIVE CTS OFFSET!!!");
		
			if (has_cts_offset) {
				for (k=0; k<count; k++) {
					s32 adts, acts;
					if (k==j) continue;
					samp = M4_GetSampleInfo(file, i+1, k+1, NULL, NULL);
					adts = samp->DTS;
					acts = adts + (s32) samp->CTS_Offset;
//					adts /= TS_DIV; acts /= TS_DIV;
//					adts *= TS_MUL; acts *= TS_MUL;

					if (adts==dts) fprintf(dump, " #SAME DTS USED!!!");
					if (acts==cts) fprintf(dump, " #SAME CTS USED!!! ");

					M4_DeleteSample(&samp);
				}
			}

			fprintf(dump, "\n");
			MP4T_OnProgress("Analysing Track", j+1, count);
		}
		fprintf(dump, "\n\n");
		MP4T_OnProgress("Analysing Track", count, count);
	}
	if (inName) fclose(dump);
}

void dump_file_ismacryp(M4File *file, char *inName)
{
	u32 i, j;
	FILE *dump;
	char szBuf[1024];

	if (inName) {
		strcpy(szBuf, inName);
		strcat(szBuf, "_ismacryp.xml");
		dump = fopen(szBuf, "wt");
	} else {
		dump = stdout;
	}

	fprintf(dump, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
	fprintf(dump, "<!-- MP4Box ISMACryp trace -->\n");
	fprintf(dump, "<ISMACrypFile>\n");


	for (i=0; i<M4_GetTrackCount(file); i++) {
		if (M4_GetMediaSubType(file, i+1, 1) != M4_ENC_MPEG4_SubType) continue;

		M4_DumpISMACrypSampleEntries(file, i+1, dump);

		fprintf(dump, "<ISMACrypTrack trackID=\"%d\">\n", M4_GetTrackID(file, i+1));
		for (j=0; j<M4_GetSampleCount(file, i+1); j++) {
			M4_DumpISMACrypSample(file, i+1, j+1, dump);
		}
		fprintf(dump, "</ISMACrypTrack >\n");
	}
	fprintf(dump, "</ISMACrypFile>\n");
	if (inName) fclose(dump);
}


void dump_timed_text_track(M4File *file, u32 trackID, char *inName, Bool is_convert, Bool to_srt)
{
	FILE *dump;
	M4Err e;
	u32 track;
	char szBuf[1024];

	track = M4_GetTrackByID(file, trackID);
	if (!track) {
		fprintf(stdout, "Cannot find track ID %d\n", trackID);
		return;
	}

	if (M4_GetMediaType(file, track) != M4_TimedTextMediaType) {
		fprintf(stdout, "Track ID %d is not a 3GPP text track\n", trackID);
		return;
	}

	if (inName) {
		if (is_convert)	
			sprintf(szBuf, "%s.%s", inName, to_srt ? "srt" : "ttxt");
		else
			sprintf(szBuf, "%s_%d_text.%s", inName, trackID, to_srt ? "srt" : "ttxt");
		dump = fopen(szBuf, "wt");
	} else {
		dump = stdout;
	}
	e = M4_DumpTextTrack(file, track, dump, to_srt, MP4T_OnProgress, "Converting");
	if (inName) fclose(dump);

	if (e) fprintf(stdout, "Conversion failed (%s)\n", M4ErrToString(e));
	else fprintf(stdout, "Conversion done\n");
}


void DumpSDP(M4File *file, char *inName)
{
	const char *sdp;
	u32 size, i;
	FILE *dump;
	char szBuf[1024];

	if (inName) {
		char *ext;
		strcpy(szBuf, inName);
		ext = strchr(szBuf, '.');
		if (ext) ext[0] = 0;
		strcat(szBuf, "_sdp.txt");
		dump = fopen(szBuf, "wt");
	} else {
		dump = stdout;
		fprintf(dump, "* File SDP content *\n\n");
	}
	//get the movie SDP
	M4H_SDP_GetSDP(file, &sdp, &size);
	fprintf(dump, "%s", sdp);
	fprintf(dump, "\r\n");

	//then tracks
	for (i=0; i<M4_GetTrackCount(file); i++) {
		if (M4_GetMediaType(file, i+1) != M4_HintMediaType) continue;
		M4H_SDP_GetTrackSDP(file, i+1, &sdp, &size);
		fprintf(dump, "%s", sdp);
	}
	fprintf(dump, "\n\n");
	if (inName) fclose(dump);
}

static char *format_duration(u64 dur, u32 timescale, char *szDur)
{
	u32 h, m, s, ms;
	dur = (u32) (( ((Double) (s64) dur)/timescale)*1000);
	h = (u32) (dur / 3600000);
	m = (u32) (dur/ 60000) - h*60;
	s = (u32) (dur/1000) - h*3600 - m*60;
	ms = (u32) (dur) - h*3600000 - m*60000 - s*1000;
	sprintf(szDur, "%02d:%02d:%02d.%03d", h, m, s, ms);
	return szDur;
}


static void DumpMetaItem(M4File *file, Bool root_meta, u32 tk_num, char *name)
{
	u32 i, count, brand;
	char sType[5];

	brand = M4_GetMetaType(file, root_meta, tk_num);
	if (!brand) return;

	count = M4_GetMetaItemCount(file, root_meta, tk_num);
	fprintf(stdout, "%s type: \"%s\" - %d resource item(s)\n", name, FCC_TO_STR(brand, sType), count);
	switch (M4_HasMetaXML(file, root_meta, tk_num)) {
	case 1: fprintf(stdout, "Meta has XML resource\n"); break;
	case 2: fprintf(stdout, "Meta has BinaryXML resource\n"); break;
	}

	for (i=0; i<count; i++) {
		const char *it_name, *mime, *enc, *url, *urn;
		Bool self_ref;
		u32 ID;
		M4_GetMetaItemInfo(file, root_meta, tk_num, i+1, &ID, NULL, &self_ref, &it_name, &mime, &enc, &url, &urn);
		fprintf(stdout, "Item #%d - ID %d", i+1, ID);
		if (self_ref) fprintf(stdout, " - Self-Reference");
		else if (it_name) fprintf(stdout, " - Name: %s", it_name);
		if (mime) fprintf(stdout, " - MimeType: %s", mime);
		if (enc) fprintf(stdout, " - ContentEncoding: %s", enc);
		fprintf(stdout, "\n");
		if (url) fprintf(stdout, "URL: %s\n", url);
		if (urn) fprintf(stdout, "URN: %s\n", urn);
	}
}

void DumpTrackInfo(M4File *file, u32 trackID, Bool full_dump)
{
	Float scale;
	u32 trackNum, j, size, dur, max_rate, rate, time_slice, ts, mtype, msub_type, timescale, sr, nb_ch, count;
	u8 bps;
	ESDescriptor *esd;
	char sType[5], szDur[50];

	trackNum = M4_GetTrackByID(file, trackID);
	if (!trackNum) {
		fprintf(stdout, "No track with ID %d found\n", trackID);
		return;
	}

	timescale = M4_GetMediaTimeScale(file, trackNum);
	fprintf(stdout, "Track # %d Info - TrackID %d - TimeScale %d - Duration %s\n", trackNum, trackID, timescale, format_duration(M4_GetMediaDuration(file, trackNum), timescale, szDur));
	if (M4_IsTrackInRootOD(file, trackNum) ) fprintf(stdout, "Track is present in Root OD\n");
	M4_GetMediaLanguage(file, trackNum, sType);
	fprintf(stdout, "Media Info: Language \"%s\" - ", sType);
	mtype = M4_GetMediaType(file, trackNum);
	fprintf(stdout, "Type \"%s\" - ", FCC_TO_STR(mtype, sType));
	msub_type = M4_GetMPEG4SubType(file, trackNum, 1);
	if (!msub_type) msub_type = M4_GetMediaSubType(file, trackNum, 1);
	fprintf(stdout, "Sub Type \"%s\" - %d samples\n", FCC_TO_STR(msub_type, sType), M4_GetSampleCount(file, trackNum));

	M4_GetAudioEntryInfo(file, trackNum, 1, &sr, &nb_ch, &bps);
	
	msub_type = M4_GetMediaSubType(file, trackNum, 1);
	if ((msub_type==M4_MPEG4_SubType) || (msub_type==M4_ENC_MPEG4_SubType) || (msub_type==M4_AVC_H264_SubType))  {
		esd = M4_GetStreamDescriptor(file, trackNum, 1);
		if (!esd) {
			fprintf(stdout, "WARNING: Broken MPEG-4 Track\n");
		} else {
			const char *st = OD_GetStreamTypeName(esd->decoderConfig->streamType);
			if (st) {
				fprintf(stdout, "MPEG-4 Config%s%s Stream - ObjectTypeIndication 0x%02x\n",
							full_dump ? "\n\t" : ": ", st, esd->decoderConfig->objectTypeIndication);
			} else {
				fprintf(stdout, "MPEG-4 Config%sStream Type 0x%02x - ObjectTypeIndication 0x%02x\n",
							full_dump ? "\n\t" : ": ", esd->decoderConfig->streamType, esd->decoderConfig->objectTypeIndication);
			}
			if (esd->decoderConfig->streamType==M4ST_VISUAL) {
				u32 w, h;
				w = h = 0;
				if (esd->decoderConfig->objectTypeIndication==0x20) {
					M4VDecoderSpecificInfo dsi;
					M4V_GetConfig(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
					if (full_dump) fprintf(stdout, "\t");
					w = dsi.width;
					h = dsi.height;
					if (w && h) 
						fprintf(stdout, "MPEG-4 Visual Size %d x %d - %s\n", dsi.width, dsi.height, M4V_VideoProfileName(dsi.VideoPL));
				} else if (esd->decoderConfig->objectTypeIndication==0x21) {
					M4_GetVisualEntrySize(file, trackNum, 1, &w, &h);
					if (full_dump) fprintf(stdout, "\t");
					fprintf(stdout, "AVC/H264 Video - Visual Size %d x %d\n", w, h);
					if (full_dump) {
						AVCConfig *avccfg = M4_AVC_GetStreamConfig(file, trackNum, 1);
						fprintf(stdout, "\tConfig Version %d Profile 0x%02x Level 0x%02x\n", avccfg->configurationVersion, avccfg->AVCProfileIndication, avccfg->AVCLevelIndication);
						AVC_DeleteConfig(avccfg);
					}
				} 
				/*OGG media*/
				else if (esd->decoderConfig->objectTypeIndication==GPAC_OGG_MEDIA_OTI) {
					char *szName;
					M4_GetVisualEntrySize(file, trackNum, 1, &w, &h);
					if (full_dump) fprintf(stdout, "\t");
					if (!strnicmp(&esd->decoderConfig->decoderSpecificInfo->data[1], "theora", 6)) szName = "Theora";
					else szName = "Unknown";
					fprintf(stdout, "Ogg/%s video / GPAC Mux  - Visual Size %d x %d\n", szName, w, h);
				}
				if (!w || !h) {
					M4_GetVisualEntrySize(file, trackNum, 1, &w, &h);
					if (full_dump) fprintf(stdout, "\t");
					fprintf(stdout, "Visual Size %d x %d\n", w, h);
				}
			} else if (esd->decoderConfig->streamType==M4ST_AUDIO) {
				M4ADecoderSpecificInfo a_cfg;
				M4Err e;
				u32 oti, is_mp2 = 0;
				switch (esd->decoderConfig->objectTypeIndication) {
				case 0x66:
				case 0x67:
				case 0x68:
					is_mp2 = 1;
				case 0x40:
					e = M4A_GetConfig(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &a_cfg);
					if (full_dump) fprintf(stdout, "\t");
					if (e) fprintf(stdout, "Corrupted AAC Config\n");
					else {
						fprintf(stdout, "MPEG-%d Audio %s - %d Channel(s) - SampleRate %d", is_mp2 ? 2 : 4, M4A_GetObjectTypeName(a_cfg.base_object_type), a_cfg.nb_chan, a_cfg.base_sr);
						if (a_cfg.has_sbr) fprintf(stdout, " - SBR SampleRate %d", a_cfg.sbr_sr);
						fprintf(stdout, "\n");
					}
					break;
				case 0x69:
				case 0x6B:
					if (msub_type == M4_ENC_MPEG4_SubType) {
						fprintf(stdout, "MPEG-1/2 Audio - %d Channels - SampleRate %d\n", nb_ch, sr);
					} else {
						M4Sample *samp = M4_GetSample(file, trackNum, 1, &oti);
						oti = FOUR_CHAR_INT((u8)samp->data[0], (u8)samp->data[1], (u8)samp->data[2], (u8)samp->data[3]);
						if (full_dump) fprintf(stdout, "\t");
						fprintf(stdout, "%s Audio - %d Channel(s) - SampleRate %d - Layer %d\n",
							MP3_GetVersionName(oti),
							MP3_GetNumChannels(oti), 
							MP3_GetSamplingRate(oti), 
							MP3_GetLayer(oti)
						);
						M4_DeleteSample(&samp);
					}
					break;
				/*OGG media*/
				case GPAC_OGG_MEDIA_OTI:
				{
					char *szName;
					if (full_dump) fprintf(stdout, "\t");
					if (!strnicmp(&esd->decoderConfig->decoderSpecificInfo->data[1], "vorbis", 6)) szName = "Vorbis";
					else if (!strnicmp(&esd->decoderConfig->decoderSpecificInfo->data[0], "Speex", 5)) szName = "Speex";
					else if (!strnicmp(&esd->decoderConfig->decoderSpecificInfo->data[0], "Flac", 4)) szName = "Flac";
					else szName = "Unknown";
					fprintf(stdout, "Ogg/%s audio / GPAC Mux - Sample Rate %d - %d channel(s)\n", szName, sr, nb_ch);
				}
					break;
				case 0xA0: fprintf(stdout, "EVRC Audio - Sample Rate 8000 - 1 channel\n"); break;
				case 0xA1: fprintf(stdout, "SMV Audio - Sample Rate 8000 - 1 channel\n"); break;
				case 0xE1: fprintf(stdout, "QCELP Audio - Sample Rate 8000 - 1 channel\n"); break;
				/*packetVideo hack for EVRC...*/
				case 0xD1: 
					if (esd->decoderConfig->decoderSpecificInfo && (esd->decoderConfig->decoderSpecificInfo->dataLength==8)
					&& !strnicmp(esd->decoderConfig->decoderSpecificInfo->data, "pvmm", 4)) {
						if (full_dump) fprintf(stdout, "\t");
						fprintf(stdout, "EVRC Audio (PacketVideo Mux) - Sample Rate 8000 - 1 channel\n"); 
					}
					break;
				}
			}

			/*sync is only valid if we open all tracks to take care of default MP4 sync..*/
			if (!full_dump) {
				if (!esd->OCRESID || (esd->OCRESID == esd->ESID))
					fprintf(stdout, "Self-synchronized\n");
				else
					fprintf(stdout, "Synchronized on stream %d\n", esd->OCRESID);
			} else {
				fprintf(stdout, "\tDecoding Buffer size %d - Average bitrate %d kbps - Max Bitrate %d kbps\n", esd->decoderConfig->bufferSizeDB, esd->decoderConfig->avgBitrate/1024, esd->decoderConfig->maxBitrate/1024);
				if (esd->dependsOnESID)
					fprintf(stdout, "\tDepends on stream %d for decoding\n", esd->dependsOnESID);
				else
					fprintf(stdout, "\tNo stream dependencies for decoding\n");

				fprintf(stdout, "\tStreamPriority %d\n", esd->streamPriority);
				if (esd->URLString) fprintf(stdout, "\tRemote Data Source %s\n", esd->URLString);
			}
			OD_DeleteDescriptor((Descriptor **)&esd);

			/*ISMACryp*/
			if (msub_type == M4_ENC_MPEG4_SubType) {
				char name[5];
				const char *scheme_URI, *KMS_URI;
				u32 scheme_type, version;
				u8 IV_size;
				Bool use_sel_enc;
				M4_GetISMACrypInfo(file, trackNum, 1, NULL, &scheme_type, &version, &scheme_URI, &KMS_URI, &use_sel_enc, &IV_size, NULL);
				FCC_TO_STR(scheme_type, name);

				if (M4_IsISMACrypMedia(file, trackNum, 1)) {
					fprintf(stdout, "\n*Encrypted stream - ISMA scheme %s (version %d)\n", name, version);
					if (scheme_URI) fprintf(stdout, "scheme location: %s\n", scheme_URI);
					if (KMS_URI) {
						if (!strnicmp(KMS_URI, "(key)", 5)) fprintf(stdout, "KMS location: key in file\n");
						else fprintf(stdout, "KMS location: %s\n", KMS_URI);
					}
					fprintf(stdout, "ISMA Config: IV length: %d - Selective Encryption: %s\n", IV_size, use_sel_enc ? "Yes" : "No");
				} else {
					fprintf(stdout, "\n*Encrypted stream - unknown scheme %s\n", name);
					if (scheme_URI) fprintf(stdout, "scheme location: %s\n", scheme_URI);
				}
			}

		}
	} else if (msub_type == M4_3GP_H263_SubType) {
		u32 w, h;
		M4_GetVisualEntrySize(file, trackNum, 1, &w, &h);
		fprintf(stdout, "\t3GPP H263 stream - Resolution %d x %d\n", w, h);
	} else if ((msub_type == M4_3GP_AMR_SubType) || (msub_type == M4_3GP_AMR_WB_SubType)) {
		fprintf(stdout, "\t3GPP AMR%s stream - Sample Rate %d - %d channel(s) %d bits per samples\n", (msub_type == M4_3GP_AMR_WB_SubType) ? " Wide Band" : "", sr, nb_ch, (u32) bps);
	} else if (msub_type == M4_3GP_EVRC_SubType) {
		fprintf(stdout, "\t3GPP EVRC stream - Sample Rate %d - %d channel(s) %d bits per samples\n", sr, nb_ch, (u32) bps);
	} else if (msub_type == M4_3GP_QCELP_SubType) {
		fprintf(stdout, "\t3GPP QCELP stream - Sample Rate %d - %d channel(s) %d bits per samples\n", sr, nb_ch, (u32) bps);
	} else if (msub_type == M4_3GP_SMV_SubType) {
		fprintf(stdout, "\t3GPP SMV stream - Sample Rate %d - %d channel(s) %d bits per samples\n", sr, nb_ch, (u32) bps);
	} else if (mtype==M4_HintMediaType) {
		u32 refTrack;
		s32 i, refCount = M4_GetTrackReferenceCount(file, trackNum, M4_HintTrack_Ref);
		if (refCount) {
			fprintf(stdout, "\tStreaming Hint Track for track%s ", (refCount>1) ? "s" :"");
			for (i=0; i<refCount; i++) {
				M4_GetTrackReference(file, trackNum, M4_HintTrack_Ref, i+1, &refTrack);
				if (i) fprintf(stdout, " - ");
				fprintf(stdout, "ID %d", M4_GetTrackID(file, refTrack));
			}
			fprintf(stdout, "\n");
		} else {
			fprintf(stdout, "\tStreaming Hint Track (no refs)\n");
		}
	} else if (mtype==M4_FlashMediaType) {
		fprintf(stdout, "Macromedia Flash Movie\n");
	} else if (mtype==M4_TimedTextMediaType) {
		u32 w, h;
		s16 l;
		s32 tx, ty;
		M4_GetTrackVideoInfo(file, trackNum, &w, &h, &tx, &ty, &l);
		fprintf(stdout, "3GPP/MPEG-4 Timed Text - Size %d x %d - Translation X=%d Y=%d - Layer %d\n", w>>16, h>>16, tx>>16, ty>>16, l);
	} else {
		UnknownStreamDescription *udesc = M4_GetUnknownStreamDescription(file, trackNum, 1);
		if (udesc) {
			if (mtype==M4_VisualMediaType) {
				fprintf(stdout, "Visual Track - Compressor \"%s\" - Resolution %d x %d\n", udesc->szCompressorName, udesc->width, udesc->height);
			} else if (mtype==M4_AudioMediaType) {
				fprintf(stdout, "Audio Track - Compressor \"%s\" - Sample Rate %d - %d channel(s)\n", udesc->szCompressorName, udesc->SampleRate, udesc->NumChannels);
			} else {
				fprintf(stdout, "Unknown media type - Compressor \"%s\"\n", udesc->szCompressorName);
			}
			fprintf(stdout, "\tVendor code %x - Version %d - revision %d\n", udesc->vendor_code, udesc->version, udesc->revision);
			if (udesc->extension_buf) {
				fprintf(stdout, "\tCodec configuration data size: %d bytes\n", udesc->extension_buf_size);
				free(udesc->extension_buf);
			}
			free(udesc);
		} else {
			fprintf(stdout, "Unknown track type\n");
		}
	}

	DumpMetaItem(file, 0, trackNum, "Track Meta");
	if (!full_dump) {
		fprintf(stdout, "\n");
		return;
	}

	dur = size = 0;
	max_rate = rate = time_slice = 0;
	ts = M4_GetMediaTimeScale(file, trackNum);
	for (j=0; j<M4_GetSampleCount(file, trackNum); j++) {
		M4Sample *samp = M4_GetSampleInfo(file, trackNum, j+1, NULL, NULL);
		dur = samp->DTS+samp->CTS_Offset;
		size += samp->dataLength;
		rate += samp->dataLength;
		if (samp->DTS - time_slice>ts) {
			if (max_rate < rate) max_rate = rate;
			rate = 0;
			time_slice = samp->DTS;
		}
		M4_DeleteSample(&samp);
	}
	fprintf(stdout, "\nComputed info from media:\n");
	scale = 1000;
	scale /= ts;
	dur = (u32) (dur * scale);
	fprintf(stdout, "\tTotal size %d bytes - Total samples duration %d ms\n", size, dur);
	if (!dur) {
		fprintf(stdout, "\n");
		return;
	}
	dur /= 1000;
	rate = size * 8 / dur;
	max_rate *= 8;
	if (rate >= 1500) {
		rate /= 1024;
		max_rate /= 1024;
		fprintf(stdout, "\tAverage rate %d kbps - Max Rate %d kbps\n", rate, max_rate);
	} else {
		fprintf(stdout, "\tAverage rate %d bps - Max Rate %d bps\n", rate, max_rate);
	}
	fprintf(stdout, "\n");

	count = M4_GetChapterCount(file, trackNum);
	if (count) {
		char szDur[20];
		const char *name;
		u64 time;
		fprintf(stdout, "\nChapters:\n");
		for (j=0; j<count; j++) {
			M4_GetChapter(file, trackNum, j+1, &time, &name);
			fprintf(stdout, "\tChapter #%d - %s - \"%s\"\n", j+1, format_duration(time, 1000, szDur), name);
		}
	}
}

void DumpMovieInfo(M4File *file)
{
	InitialObjectDescriptor *iod;
	u32 i, brand, min, timescale, count;
	char sType[5], szDur[50];
	
	DumpMetaItem(file, 1, 0, "Root Meta");
	if (!M4_HasMovie(file)) {
		fprintf(stdout, "File has no movie (moov) - static data container\n");
		return;
	}

	timescale = M4_GetTimeScale(file);
	fprintf(stdout, "* Movie Info *\n\tTimescale %d - Duration %s\n\tFragmented File %s - %d track(s)\n",
		timescale, format_duration(M4_GetDuration(file), timescale, szDur), M4_IsMovieFragmented(file) ? "yes" : "no", M4_GetTrackCount(file));

	if (M4_GetMovieVersionInfo(file, &brand, &min, NULL) == M4OK) {
		fprintf(stdout, "\tFile Brand %s - version %d\n", FCC_TO_STR(brand, sType), min);
	}
	fprintf(stdout, "\n");

	DumpMetaItem(file, 0, 0, "Moov Meta");

	iod = (InitialObjectDescriptor *) M4_GetRootOD(file);
	if (iod) {
		if (iod->tag == InitialObjectDescriptor_Tag) {
			fprintf(stdout, "File has root IOD\n");
			fprintf(stdout, "Scene PL 0x%02x - Graphics PL 0x%02x - OD PL 0x%02x\n", iod->scene_profileAndLevel, iod->graphics_profileAndLevel, iod->OD_profileAndLevel);
			fprintf(stdout, "Visual PL: %s (0x%02x)\n", M4V_VideoProfileName(iod->visual_profileAndLevel), iod->visual_profileAndLevel);
			fprintf(stdout, "Audio PL: %s (0x%02x)\n", M4A_AudioProfileName(iod->audio_profileAndLevel), iod->audio_profileAndLevel);
			//fprintf(stdout, "inline profiles included %s\n", iod->inlineProfileFlag ? "yes" : "no");
		} else {
			fprintf(stdout, "File has root OD\n");
		}
		if (!ChainGetCount(iod->ESDescriptors)) fprintf(stdout, "No streams included in root OD\n");
		OD_DeleteDescriptor((Descriptor **) &iod);
	} else {
		fprintf(stdout, "File has no MPEG4 IOD/OD\n");
	}

	count = M4_GetCopyrightCount(file);
	if (count) {
		const char *lang, *note;
		fprintf(stdout, "\nCopyrights:\n");
		for (i=0; i<count; i++) {
			M4_GetCopyright(file, i+1, &lang, &note);
			fprintf(stdout, "\t(%s) %s\n", lang, note);
		}
	}
	
	count = M4_GetChapterCount(file, 0);
	if (count) {
		char szDur[20];
		const char *name;
		u64 time;
		fprintf(stdout, "\nChapters:\n");
		for (i=0; i<count; i++) {
			M4_GetChapter(file, 0, i+1, &time, &name);
			fprintf(stdout, "\tChapter #%d - %s - \"%s\"\n", i+1, format_duration(time, 1000, szDur), name);
		}
	}

	fprintf(stdout, "\n");
	for (i=0; i<M4_GetTrackCount(file); i++) {
		DumpTrackInfo(file, M4_GetTrackID(file, i+1), 0);
	}
}

