/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / MP4 cache plugin
 *
 *  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_decoder.h>
#include "mp4_io.h"


static M4Err MP4_Open(MediaCache *mc, LPNETSERVICE serv, const char *location_and_name, Bool keep_existing_files)
{
	char szRoot[M4_MAX_PATH], szPath[M4_MAX_PATH], *ext;
	M4Reader *cache = (M4Reader *)mc->priv;
	if (cache->mov || cache->service) return M4BadParam;

	strcpy(szRoot, location_and_name);
	ext = strrchr(szRoot, '.');
	if (ext) ext[0] = 0;
	strcpy(szPath, szRoot);
	strcat(szPath, ".mp4");
	if (keep_existing_files) {
		FILE *f = fopen(szPath, "rb");
		if (f) {
			u32 i=0;
			fclose(f);
			while (1) {
				sprintf(szPath, "%s_%04d.mp4", szRoot, i);
				f = fopen(szPath, "rb");
				if (!f) break;
				fclose(f);
				i++;
			}
		}
	}
	
	/*create a new movie in write mode (eg no editing)*/
	cache->mov = M4_MovieOpen(szPath, M4_OPEN_WRITE);
	if (!cache->mov) return M4_GetLastError(NULL);
	cache->service = serv;
	return M4OK;
}
static M4Err MP4_Close(MediaCache *mc, Bool delete_cache)
{
	M4Err e;
	M4Reader *cache = (M4Reader *)mc->priv;
	if (!cache->mov || !cache->service) return M4BadParam;

	while (ChainGetCount(cache->channels)) {
		M4Channel *ch = ChainGetEntry(cache->channels, 0);
		ChainDeleteEntry(cache->channels, 0);
		if (ch->cache_sample) {
			M4_AddSample(cache->mov, ch->track, 1, ch->cache_sample);
			M4_DeleteSample(&ch->cache_sample);
		}
		free(ch);
	}
	if (delete_cache) {
		M4_MovieDelete(cache->mov);
		e = M4OK;
	} else {
		e = M4_MovieClose(cache->mov);
	}
	cache->mov = NULL;
	cache->service = NULL;
	return e;
}
static M4Err MP4_Write(MediaCache *mc, LPNETCHANNEL ch, char *data, u32 data_size, SLHeader *sl_hdr)
{
	M4Channel *mch;
	ESDescriptor *esd;
	u32 mtype, DTS, CTS;
	u32 di;
	M4Reader *cache = (M4Reader *)mc->priv;
	if (!cache->mov || !cache->service) return M4BadParam;

	mch = m4_get_channel(cache, ch);
	if (!mch) {
		Bool mapped;
		NetworkCommand com;
		com.base.on_channel = ch;
		com.base.command_type = CHAN_GET_ESD;
		NM_OnCommand(cache->service, &com, M4OK);
		if (!com.cache_esd.esd) return M4ServiceError;

		esd = (ESDescriptor *)com.cache_esd.esd;
		switch (esd->decoderConfig->streamType) {
		case M4ST_OD: mtype = M4_ODMediaType; break;
		case M4ST_SCENE: mtype = M4_BIFSMediaType; break;
		case M4ST_VISUAL: mtype = M4_VisualMediaType; break;
		case M4ST_AUDIO: mtype = M4_AudioMediaType; break;
		case M4ST_MPEG7: mtype = M4_MPEG7MediaType; break;
		case M4ST_OCI: mtype = M4_OCIMediaType; break;
		case M4ST_IPMP: mtype = M4_IPMPMediaType; break;
		case M4ST_MPEGJ: mtype = M4_MPEGJMediaType; break;
		case M4ST_TEXT: mtype = M4_TimedTextMediaType; break;
		default: return M4NotSupported;
		}
		SAFEALLOC(mch, sizeof(M4Channel));
		mch->time_scale = esd->slConfig->timestampResolution;
		mch->is_video = (mtype==M4_VisualMediaType) ? 1 : 0;
		mch->track = M4_NewTrack(cache->mov, com.cache_esd.esd->ESID, mtype, mch->time_scale);
		mch->is_playing = 1;
		mch->channel = ch;
		mch->owner = cache;
		M4_SetTrackEnabled(cache->mov, mch->track, 1);
		/*translate 3GP streams to MP4*/
		mapped = 0;
		if (esd->decoderConfig->objectTypeIndication==GPAC_QT_CODECS_OTI) {
			char szCode[5];
			strncpy(szCode, esd->decoderConfig->decoderSpecificInfo->data, 4);
			if (!stricmp(szCode, "samr") || !stricmp(szCode, "amr ") || !stricmp(szCode, "sawb")) {
				GPPMediaConfig amrc;
				mapped = 1;
				memset(&amrc, 0, sizeof(GPPMediaConfig));
				amrc.frames_per_sample = 1;
				amrc.type = (!stricmp(szCode, "sawb")) ? M4_3GP_AMR_WB_SubType : M4_3GP_AMR_SubType;
				amrc.vendor = FOUR_CHAR_INT('G','P','A','C');
				M4_3GP_NewStreamConfig(cache->mov, mch->track, &amrc, NULL, NULL, &di);
			} else if (!stricmp(szCode, "h263")) {
				GPPMediaConfig h263c;
				memset(&h263c, 0, sizeof(GPPMediaConfig));
				h263c.type = M4_3GP_H263_SubType;
				h263c.vendor = FOUR_CHAR_INT('G','P','A','C');
				M4_3GP_NewStreamConfig(cache->mov, mch->track, &h263c, NULL, NULL, &di);
				mapped = 1;
			}
		} 
		if (!mapped) M4_NewStreamDescription(cache->mov, mch->track, esd, NULL, NULL, &di);
		if (com.cache_esd.is_iod_stream) M4_AddTrackToRootOD(cache->mov, mch->track);
		ChainAddEntry(cache->channels, mch);
	}

	/*first sample, cache it*/
	if (!mch->cache_sample) {
		mch->cache_seed_ts = sl_hdr->decodingTimeStamp;
		mch->cache_sample = M4_NewSample();
		mch->cache_sample->IsRAP = sl_hdr->randomAccessPointFlag;
		mch->cache_sample->dataLength = data_size;
		mch->cache_sample->data = malloc(sizeof(char)*data_size);
		memcpy(mch->cache_sample->data, data, sizeof(char)*data_size);
		return M4OK;
	}

	/*adjust DTS/CTS*/
	DTS = (u32) (sl_hdr->decodingTimeStamp - mch->cache_seed_ts);

	if (mch->is_video && (DTS<=mch->cache_sample->DTS)) {
		assert(DTS>mch->prev_dts);
		CTS = mch->cache_sample->DTS + mch->cache_sample->CTS_Offset;
		mch->cache_sample->CTS_Offset = 0;

		/*first time, shift all CTS*/
		if (!mch->frame_cts_offset) {
			u32 i, count = M4_GetSampleCount(cache->mov, mch->track);
			mch->frame_cts_offset = (DTS-mch->prev_dts);
			for (i=0; i<count; i++) {
				M4_SetSampleCTSOffset(cache->mov, mch->track, i+1, mch->frame_cts_offset);
			}
			mch->cache_sample->CTS_Offset += mch->frame_cts_offset;
		}
		mch->cache_sample->DTS = mch->prev_dts + mch->frame_cts_offset;
		mch->cache_sample->CTS_Offset += (u32) (CTS-mch->cache_sample->DTS);
	}
	/*deal with reference picture insertion: if no CTS offset and biggest CTS until now, this is 
	a reference insertion - we must check that in order to make sure we have strictly increasing DTSs*/
	if (mch->max_cts && !mch->cache_sample->CTS_Offset && (mch->cache_sample->DTS+mch->cache_sample->CTS_Offset > mch->max_cts)) {
		assert(mch->cache_sample->DTS > mch->prev_dts + mch->frame_cts_offset);
		CTS = mch->cache_sample->DTS + mch->cache_sample->CTS_Offset;
		mch->cache_sample->DTS = mch->prev_dts + mch->frame_cts_offset;
		mch->cache_sample->CTS_Offset = CTS-mch->cache_sample->DTS;
	}
	if (mch->cache_sample->CTS_Offset) 
		mch->max_cts = mch->cache_sample->DTS+mch->cache_sample->CTS_Offset;

	/*add cache*/
	M4_AddSample(cache->mov, mch->track, 1, mch->cache_sample);
	assert(!mch->prev_dts || (mch->prev_dts < mch->cache_sample->DTS));
	mch->prev_dts = mch->cache_sample->DTS;
	mch->duration = MAX(mch->max_cts, mch->prev_dts);
	M4_DeleteSample(&mch->cache_sample);

	/*store sample*/
	mch->cache_sample = M4_NewSample();
	mch->cache_sample->IsRAP = sl_hdr->randomAccessPointFlag;
	mch->cache_sample->DTS = DTS + mch->frame_cts_offset;
	mch->cache_sample->CTS_Offset = (u32) (sl_hdr->compositionTimeStamp - mch->cache_seed_ts) - DTS;
	mch->cache_sample->dataLength = data_size;
	mch->cache_sample->data = malloc(sizeof(char)*data_size);
	memcpy(mch->cache_sample->data, data, sizeof(char)*data_size);
	return M4OK;
}
static M4Err MP4_ServiceCommand(MediaCache *mc, NetworkCommand *com)
{
	M4Reader *cache = (M4Reader *)mc->priv;
	if (!cache->mov || !cache->service) return M4BadParam;

	return M4OK;
}
static M4Err MP4_ChannelGetSLP(MediaCache *mc, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, struct tagSLHeader *out_sl_hdr, Bool *sl_compressed, M4Err *out_reception_status, Bool *is_new_data)
{
	M4Reader *cache = (M4Reader *)mc->priv;
	if (!cache->mov || !cache->service) return M4BadParam;

	return M4OK;
}
static M4Err MP4_ChannelReleaseSLP(MediaCache *mc, LPNETCHANNEL channel)
{
	M4Reader *cache = (M4Reader *)mc->priv;
	if (!cache->mov || !cache->service) return M4BadParam;

	return M4OK;
}


BaseInterface *MP4_LoadCache()
{
	M4Reader *cache;
	MediaCache *plug;
	SAFEALLOC(plug, sizeof(MediaCache));
	M4_REG_PLUG(plug, M4MEDIACACHE, "GPAC IsoMedia Cache", "gpac distribution", 0)

	plug->Open = MP4_Open;
	plug->Close = MP4_Close;
	plug->Write = MP4_Write;
	plug->ChannelGetSLP = MP4_ChannelGetSLP;
	plug->ChannelReleaseSLP = MP4_ChannelReleaseSLP;
	plug->ServiceCommand = MP4_ServiceCommand;

	SAFEALLOC(cache, sizeof(M4Reader));
	cache->channels = NewChain();
	plug->priv = cache;
	return (BaseInterface *) plug;
}

void MP4_DeleteCache(BaseInterface *bi)
{
	MediaCache *mc = (MediaCache*) bi;
	M4Reader *cache = mc->priv;
	DeleteChain(cache->channels);
	free(cache);
	free(bi);
}
