/*
		MPEG-4 chunk encoding
*/
#include "bifscodec.h"
#include "ibifscodec.h"

#include <gpac/intern/m4_author_dev.h>
#include <gpac/intern/m4_bifs_dev.h>
#include <gpac/intern/m4_scenegraph_dev.h>

static u32 GetNbBits(u32 MaxVal)
{
	u32 k=0;
	while ((s32) MaxVal > ((1<<k)-1) ) k+=1;
	return k;
}

/* returns M4OK if the codec is of the right type, !=0 otherwise */
static M4Err CheckCodecInterface(void *_codec)
{
	BifsEngine *codec = (BifsEngine *)_codec;
	M4Err e = M4OK;
	if (codec==NULL) { e = -1; return e; }
	if (codec->id!=M4BIFSENCODERINTERFACE) { e = -2; return e;}
	return e;
}

static M4Err EncodeBIFSAU(M4SceneManager *ctx, 
						  M4Err (*AUCallback)(void *, M4Sample *), void *to_be_called_back,
						  u32 currentAUCount)
{
	M4Err e;
	char *data;
	u32 data_len;
	LPBIFSENC bifsenc;
	InitialObjectDescriptor *iod;
	u32	i, j, count;
	M4StreamContext *sc;
	ESDescriptor *esd;
	Bool is_in_iod, delete_desc, encode_names;
	BIFSConfigDescriptor bcfg;
	M4AUContext	*au;
	M4Sample *samp;

	if (!AUCallback) return M4BadParam;

	/* from here, the coding is mainly the same as in M4SM_EncodeBIFS (see SceneManager.c) */
	e = M4OK;
	bifsenc = BIFS_NewEncoder(ctx->scene_graph);

	iod = (InitialObjectDescriptor *) ctx->root_od;
	/*if no iod check we only have one bifs*/
	if (!iod) {
		count = 0;
		for (i=0; i<ChainGetCount(ctx->streams); i++) {
			sc = (M4StreamContext *)ChainGetEntry(ctx->streams, i);
			if (sc->streamType == M4ST_OD) count++;
		}
		if (!iod && count>1) return M4NotSupported;
	}

	count = ChainGetCount(ctx->streams);
	for (i=0; i<ChainGetCount(ctx->streams); i++) {
		u32 nbb;
		M4StreamContext *sc = (M4StreamContext *)ChainGetEntry(ctx->streams, i);
		esd = NULL;
		if (sc->streamType != M4ST_SCENE) continue;

		delete_desc = 0;
		esd = NULL;
		is_in_iod = 1;
		if (iod) {
			is_in_iod = 0;
			for (j=0; j<ChainGetCount(iod->ESDescriptors); j++) {
				esd = (ESDescriptor *)ChainGetEntry(iod->ESDescriptors, j);
				if (esd->decoderConfig && esd->decoderConfig->streamType == M4ST_SCENE) {
					if (!sc->ESID) sc->ESID = esd->ESID;
					if (sc->ESID == esd->ESID) {
						is_in_iod = 1;
						break;
					}
				}
				/*special BIFS direct import from NHNT*/
				else if (ChainGetCount(iod->ESDescriptors)==1) {
					sc->ESID = esd->ESID;
					is_in_iod = 1;
					break;
				}
				esd = NULL;
			}
		}
		// if (!esd && sc->ESID) esd = M4SM_LocateESD(ctx, sc->ESID);

		if (!esd) {
			delete_desc = 1;
			esd = OD_NewESDescriptor(2);
			OD_DeleteDescriptor((Descriptor **) &esd->decoderConfig->decoderSpecificInfo);
			esd->ESID = sc->ESID;
			esd->decoderConfig->streamType = M4ST_SCENE;
		}

		/*should NOT happen (means inputctx is not properly setup)*/
		if (!esd->decoderConfig->decoderSpecificInfo) {
			memset(&bcfg, 0, sizeof(BIFSConfigDescriptor));
			bcfg.tag = BIFSConfig_Tag;
		}
		/*regular retrieve from ctx*/
		else if (esd->decoderConfig->decoderSpecificInfo->tag == BIFSConfig_Tag) {
			memcpy(&bcfg, (BIFSConfigDescriptor *)esd->decoderConfig->decoderSpecificInfo, sizeof(BIFSConfigDescriptor));
		}
		/*should not happen either (unless loading from MP4 in which case BIFSc is not decoded)*/
		else {
			OD_GetBIFSConfig(esd->decoderConfig->decoderSpecificInfo, esd->decoderConfig->objectTypeIndication, &bcfg);
		}
		/*NO CHANGE TO BIFSC otherwose the generated update will not match the input context*/
		nbb = GetNbBits(ctx->max_node_id);
		if (bcfg.nodeIDbits<nbb) 
		{
			fprintf(stdout, "Warning: BIFSConfig.NodeIDBits TOO SMALL\n");
		}
		nbb = GetNbBits(ctx->max_route_id);
		if (bcfg.routeIDbits<nbb) 
		{
			fprintf(stdout, "Warning: BIFSConfig.RouteIDBits TOO SMALL\n");
		}
		nbb = GetNbBits(ctx->max_proto_id);
		if (bcfg.protoIDbits<nbb) 
		{
			fprintf(stdout, "Warning: BIFSConfig.ProtoIDBits TOO SMALL\n");
		}

		/*this is the real pb, not stored in cfg or file level, set at EACH replaceScene*/
		encode_names = 0;

		/* The BIFS Config that is passed here should be the BIFSConfig from the IOD */
		BIFS_NewStream(bifsenc, sc->ESID, &bcfg, encode_names, 0);

		if (!esd->slConfig) esd->slConfig = (SLConfigDescriptor *) OD_NewDescriptor(SLConfigDescriptor_Tag);
		if (sc->timeScale) esd->slConfig->timestampResolution = sc->timeScale;
		if (!esd->slConfig->timestampResolution) esd->slConfig->timestampResolution = 1000;

		esd->ESID = sc->ESID;
		BIFS_GetStreamConfig(bifsenc, sc->ESID, &data, &data_len);

		if (esd->decoderConfig->decoderSpecificInfo) OD_DeleteDescriptor((Descriptor **) &esd->decoderConfig->decoderSpecificInfo);
		esd->decoderConfig->decoderSpecificInfo = (DefaultDescriptor *) OD_NewDescriptor(DecoderSpecificInfo_Tag);
		esd->decoderConfig->decoderSpecificInfo->data = data;
		esd->decoderConfig->decoderSpecificInfo->dataLength = data_len;
		esd->decoderConfig->objectTypeIndication = BIFS_GetVersion(bifsenc, sc->ESID);		

		for (j=currentAUCount; j<ChainGetCount(sc->AUs); j++) {
			au = (M4AUContext *)ChainGetEntry(sc->AUs, j);
			samp = M4_NewSample();
			/*in case using XMT*/
			if (au->timing_sec) au->timing = (u32) (au->timing_sec * esd->slConfig->timestampResolution);
			samp->DTS = au->timing;
			e = BIFS_EncodeAU(bifsenc, sc->ESID, au->commands, &samp->data, &samp->dataLength);
			AUCallback(to_be_called_back, samp);
			M4_DeleteSample(&samp);
		}
	}

	BIFS_DeleteEncoder(bifsenc);
	return e;
}

M4Err BENC_AggregateCurrentContext(void *_codec, void *extension)
{
	BifsEngine *codec = (BifsEngine *)_codec;
	M4Err	e;

	if (e = CheckCodecInterface(codec)) return e;
	
	/*make random access for storage*/
	e = M4SM_MakeRandomAccess(codec->ctx);
	if (e) return M4BadParam;
	return M4OK;
}

M4Err BENC_SaveCurrentContext(void *_codec, char *  ctxFileName, void * extension)
{
	BifsEngine *codec = (BifsEngine *)_codec;
	u32	d_mode, do_enc;
	char szF[M4_MAX_PATH], *ext;
	M4Err	e;

	if (e = CheckCodecInterface(codec)) return e;

	/*check if we dump to BT, XMT or encode to MP4*/
	strcpy(szF, ctxFileName);
	ext = strrchr(szF, '.');
	d_mode = M4SM_DUMP_BT;
	do_enc = 0;
	if (ext) {
		if (!stricmp(ext, ".xmt") || !stricmp(ext, ".xmta")) d_mode = M4SM_DUMP_XMTA;
		else if (!stricmp(ext, ".mp4")) do_enc = 1;
		ext[0] = 0;
	}

	if (do_enc) {
		M4File *mp4;
		strcat(szF, ".mp4");
		mp4 = M4_MovieOpen(szF, M4_WRITE_EDIT);
		e = M4SM_EncodeFile(codec->ctx, mp4, NULL, NULL, 0, 0);
		if (e) M4_MovieDelete(mp4);
		else M4_MovieClose(mp4);
	} else {
		e = M4SM_DumpToText(codec->ctx, szF, d_mode);
	}
	return e;
}

M4Err BENC_EncodeAUFromString(void *_codec, char *auString, M4Err (*AUCallback)(void *, M4Sample *), void *extension)
{
	BifsEngine *codec = (BifsEngine *)_codec;
	M4Err	e;
	M4StreamContext *sc;
	u32 i, count;

	if (e = CheckCodecInterface(codec)) return e;

	memset(&(codec->load), 0, sizeof(M4ContextLoader));
	codec->load.ctx = codec->ctx;
	
	/* Assumes there is only one BIFS stream in the context
	   TODO: check how to do it when several BIFS streams are encoded at the same time */
	count = ChainGetCount(codec->ctx->streams);
	for (i=0; i<ChainGetCount(codec->ctx->streams); i++) {
		sc = (M4StreamContext *)ChainGetEntry(codec->ctx->streams, i);
		if (sc->streamType == M4ST_SCENE) break;
	}
	codec->currentAUCount = ChainGetCount(sc->AUs);

	codec->load.flags = M4CL_MPEG4_STRICT | M4CL_CTX_IS_LOADED;
	codec->load.type = M4CL_BT;
	e = M4SM_LoaderInit_BTString(&codec->load, auString);
	if (!e) e = M4SM_LoaderRun(&codec->load);
	M4SM_LoaderDone_String(&codec->load); 
	if (e) {
		goto exit;
	}

	e = EncodeBIFSAU(codec->ctx, AUCallback, codec->calling_object, codec->currentAUCount); 
	if (e) goto exit;
exit:
	return e;
}

M4Err BENC_EncodeAUFromFile(void *_codec, char *auFile, M4Err (*AUCallback)(void *, M4Sample *), void *extension)
{
	BifsEngine *codec = (BifsEngine *)_codec;
	M4Err e;
	M4StreamContext *sc;
	u32 i, count;

	if (e = CheckCodecInterface(codec)) return e;

	memset(&(codec->load), 0, sizeof(M4ContextLoader));
	codec->load.fileName = auFile;
	codec->load.ctx = codec->ctx;

	/* Assumes there is only one BIFS stream in the context
	   TODO: check how to do it when several BIFS streams are encoded at the same time */
	count = ChainGetCount(codec->ctx->streams);
	for (i=0; i<ChainGetCount(codec->ctx->streams); i++) {
		sc = (M4StreamContext *)ChainGetEntry(codec->ctx->streams, i);
		if (sc->streamType == M4ST_SCENE) break;
	}
	codec->currentAUCount = ChainGetCount(sc->AUs);

	codec->load.flags = M4CL_MPEG4_STRICT | M4CL_CTX_IS_LOADED;
	e = M4SM_LoaderInit(&codec->load);
	if (!e) e = M4SM_LoaderRun(&codec->load);
	M4SM_LoaderDone(&codec->load);
	if (e) {
		fprintf(stderr, "Cannot load AU File %s: error %s\n", auFile, M4ErrToString(e));
		goto exit;
	}

	e = EncodeBIFSAU(codec->ctx, AUCallback, codec->calling_object, codec->currentAUCount); 
	if (e) goto exit;
exit:
	return e;
}

M4Err BENC_Terminate(void *_codec)
{
	LPSCENEGRAPH sg;
	M4Err e;
	BifsEngine *codec = (BifsEngine *)_codec;
	if (e = CheckCodecInterface(codec)) return e;
	if (codec->ctx) {
		sg = codec->ctx->scene_graph;
		M4SM_Delete(codec->ctx);
		SG_Delete(sg);
		codec->sg = NULL;
		codec->ctx= NULL;
		free(codec);
	}
	return M4OK;
}

void *BENC_Init(void *calling_object, char * inputContext, void * extension)
{
	M4Err e = M4OK;
	BifsEngine *codec;

	SAFEALLOC(codec, sizeof(BifsEngine))
	if (!codec) return NULL;

	codec->id = M4BIFSENCODERINTERFACE;
	codec->calling_object = calling_object;

	/*Step 1: create context and load input*/
	codec->sg = NewSceneGraph();
	codec->ctx = NewSceneManager(codec->sg);
	memset(&(codec->load), 0, sizeof(M4ContextLoader));
	codec->load.fileName = inputContext;
	codec->load.ctx = codec->ctx;
	/*since we're encoding in BIFS we must get MPEG-4 nodes only*/
	codec->load.flags = M4CL_MPEG4_STRICT;
	
	e = M4SM_LoaderInit(&(codec->load));
	if (!e) e = M4SM_LoaderRun(&(codec->load));
	M4SM_LoaderDone(&(codec->load));
	if (e) {
		fprintf(stderr, "Cannot load context from %s: error %s\n", inputContext, M4ErrToString(e));
		goto exit;
	}
	return (void *)codec;

exit:
	BENC_Terminate(codec);
	return NULL;
}



