/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005 
 *					All rights reserved
 *
 *  This file is part of GPAC / AMR decoder 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. 
 *
 */



/*decoder Interface*/
#include <gpac/m4_decoder.h>
#include <gpac/m4_network.h>
#include <gpac/m4_author.h>

#ifdef HAS_AMR_FT
/*AMR NB*/
#include "AMR_NB_FT/interf_dec.h"
#endif

#ifdef HAS_AMR_FT_WB
/*AMR WB*/
#include "AMR_WB_FT/dec_if.h"
#endif

/*default size in CU of composition memory for audio*/
#define DEFAULT_AUDIO_CM_SIZE			12
/*default critical size in CU of composition memory for audio*/
#define DEFAULT_AUDIO_CM_TRIGGER		4

typedef struct
{
	Bool is_amr_wb;
	u32 sample_rate, out_size, num_samples;
	u8 num_channels;
	/*no support for scalability in AMR*/
	u16 ES_ID;
	u32 cb_size, cb_trig;

	/*AMR NB state vars*/
	int *nb_destate;
    void *wb_destate;
	u32 frame_count;
} AMRFTDec;

#define AMRFTCTX() AMRFTDec *ctx = (AMRFTDec *) ifcg->privateStack


static M4Err AMR_AttachStream(BaseDecoder *ifcg, u16 ES_ID, unsigned char *decSpecInfo, u32 decSpecInfoSize, u16 DependsOnES_ID, u32 objectTypeIndication, Bool UpStream)
{
	BitStream *bs;
	char name[5];
	u32 codec_4cc;
	AMRFTCTX();
	if (ctx->ES_ID && ctx->ES_ID!=ES_ID) return M4NotSupported;

	bs = NewBitStream(decSpecInfo, decSpecInfoSize, BS_READ);
	codec_4cc = BS_ReadU32(bs);
	DeleteBitStream(bs);
	FCC_TO_STR(codec_4cc, name);
	

	/*AMRWB dec is another plugin*/
	if (!stricmp(name, "sawb")) ctx->is_amr_wb = 1;
	else if (!stricmp(name, "samr") || !stricmp(name, "amr ")) ctx->is_amr_wb = 0;
	else return M4NotSupported;

	ctx->frame_count=0;

	ctx->num_channels = 1;
	if (ctx->is_amr_wb) {
#ifdef HAS_AMR_FT_WB
		ctx->wb_destate = D_IF_init();
		if (!ctx->wb_destate) return M4IOErr;
#else
		return M4NotSupported;
#endif
		ctx->num_samples = 320;
		ctx->sample_rate = 16000;
	} else {
#ifdef HAS_AMR_FT
		ctx->nb_destate = Decoder_Interface_init();
		if (!ctx->nb_destate) return M4IOErr;
#else
		return M4NotSupported;
#endif
		ctx->num_samples = 160;
		ctx->sample_rate = 8000;
	}

	/*max possible frames in a sample are seen in MP4, that's 15*/
	ctx->out_size = 15 * 2 * ctx->num_samples * ctx->num_channels;
	ctx->ES_ID = ES_ID;

	return M4OK;
}

static M4Err AMR_DetachStream(BaseDecoder *ifcg, u16 ES_ID)
{
	AMRFTCTX();
	if (ES_ID != ctx->ES_ID) return M4BadParam;
#ifdef HAS_AMR_FT
	if (ctx->nb_destate) Decoder_Interface_exit(ctx->nb_destate);
#endif
	ctx->nb_destate = NULL;
#ifdef HAS_AMR_FT_WB
	if (ctx->wb_destate) D_IF_exit(ctx->wb_destate);
#endif
	ctx->wb_destate = NULL;
	ctx->ES_ID = 0;
	ctx->sample_rate = ctx->out_size = ctx->num_samples = 0;
	ctx->num_channels = 0;
	return M4OK;
}
static M4Err AMR_GetCapabilities(BaseDecoder *ifcg, CapObject *capability)
{
	AMRFTCTX();
	switch (capability->CapCode) {
	/*not tested yet*/
	case CAP_HASRESILIENCE:
		capability->cap.valueInt = 1;
		break;
	case CAP_OUTPUTSIZE:
		capability->cap.valueInt = ctx->out_size;
		break;
	case CAP_SAMPLERATE:
		capability->cap.valueInt = ctx->sample_rate;
		break;
	case CAP_NBCHANNELS:
		capability->cap.valueInt = ctx->num_channels;
		break;
	case CAP_BITSPERSAMPLE:
		capability->cap.valueInt = 16;
		break;
	case CAP_BUFFER_MIN:
		capability->cap.valueInt = ctx->cb_trig;
		break;
	case CAP_BUFFER_MAX:
		capability->cap.valueInt = ctx->cb_size;
		break;
	/*FIXME: get exact sampling window*/
	case CAP_CU_DURATION:
		capability->cap.valueInt = ctx->num_samples;
		break;
	case CAP_PADDING_BYTES:
		capability->cap.valueInt = 0;
		break;
	case CAP_CHANNEL_CONFIG:
		if (ctx->num_channels==1) {
			capability->cap.valueInt = CHANNEL_FRONT_CENTER;
		} else {
			capability->cap.valueInt = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT;
		}
		break;
	default:
		capability->cap.valueInt = 0;
		break;
	}
	return M4OK;
}

static M4Err AMR_SetCapabilities(BaseDecoder *ifcg, CapObject capability)
{
	/*return unsupported to avoid confusion by the player (like SR changing ...) */
	return M4NotSupported;
}


static M4Err AMR_ProcessData(MediaDecoder *ifcg, 
		unsigned char *inBuffer, u32 inBufferLength,
		u16 ES_ID,
		unsigned char *outBuffer, u32 *outBufferLength,
		u8 PaddingBits, u32 mmlevel)
{
    u32 offset;
    u8 toc, ft;
	AMRFTCTX();

	/*not using scalabilty*/
	assert(ctx->ES_ID == ES_ID);

	/*if late or seeking don't decode (each frame is a RAP)*/
	/*	switch (mmlevel) {
	case MM_LEVEL_SEEK:
	case MM_LEVEL_DROP:
		*outBufferLength = 0;
		return M4OK;
	default:
		break;
	}
	*/
	if (ctx->out_size > *outBufferLength) {
		*outBufferLength = ctx->out_size;
		return M4BufferTooSmall;
	}
	
	*outBufferLength = 0;

    while (inBufferLength) {
		toc = inBuffer[0];
		ft = (toc >> 3) & 0x0F;

		offset = 0;
		if (ctx->is_amr_wb) {
#ifdef HAS_AMR_FT_WB
			D_IF_decode(ctx->wb_destate, inBuffer, (Word16 *) outBuffer, 0);
			*outBufferLength += 320*2;
			outBuffer += 320*2;
			offset = amr_wb_frame_size[ft] + 1;
#endif
		} else {
#ifdef HAS_AMR_FT
			Decoder_Interface_Decode(ctx->nb_destate, inBuffer, (Word16 *) outBuffer, 0);
			*outBufferLength += 160*2;
			outBuffer += 160*2;
			offset = amr_nb_frame_size[ft] + 1;
#endif
		}
		/*don't complain but...*/
		if (inBufferLength<offset) return M4OK;
		inBuffer += offset;
		inBufferLength -= offset;

		ctx->frame_count++;
	}
	return M4OK;
}


static u32 AMR_CanHandleStream(BaseDecoder *dec, u32 StreamType, u32 ObjectType, unsigned char *decSpecInfo, u32 decSpecInfoSize, u32 PL)
{
	BitStream *bs;
	char name[5];
	u32 codec_4cc;

	/*we handle audio only*/
	if (!ObjectType) return (StreamType==M4ST_AUDIO) ? 1 : 0;

	/*audio dec*/
	if (!decSpecInfo || (StreamType != M4ST_AUDIO) || (ObjectType != GPAC_QT_CODECS_OTI)) return 0;
	bs = NewBitStream(decSpecInfo, decSpecInfoSize, BS_READ);
	codec_4cc = BS_ReadU32(bs);
	DeleteBitStream(bs);

	FCC_TO_STR(codec_4cc, name);
#ifdef HAS_AMR_FT
	if (!stricmp(name, "samr") || !stricmp(name, "amr ")) return 1;
#endif
#ifdef HAS_AMR_FT_WB
	if (!stricmp(name, "sawb")) return 1;
#endif
	return 0;
}


static const char *AMR_GetCodecName(BaseDecoder *ifcg)
{
	AMRFTCTX();
	if (ctx->is_amr_wb) return "3GPP Floating-point AMR Wideband";
	return "3GPP Floating-point AMR";
}

MediaDecoder *NewAMRFTDecoder()
{
	AMRFTDec *dec;
	MediaDecoder *ifce;
	SAFEALLOC(ifce , sizeof(MediaDecoder));
	dec = malloc(sizeof(AMRFTDec));
	memset(dec, 0, sizeof(AMRFTDec));
	ifce->privateStack = dec;
	ifce->CanHandleStream = AMR_CanHandleStream;

	dec->cb_size = DEFAULT_AUDIO_CM_SIZE;
	dec->cb_trig = DEFAULT_AUDIO_CM_TRIGGER;

	/*setup our own interface*/	
	ifce->AttachStream = AMR_AttachStream;
	ifce->DetachStream = AMR_DetachStream;
	ifce->GetCapabilities = AMR_GetCapabilities;
	ifce->SetCapabilities = AMR_SetCapabilities;
	ifce->ProcessData = AMR_ProcessData;
	ifce->GetName = AMR_GetCodecName;

	M4_REG_PLUG(ifce, M4MEDIADECODERINTERFACE, "AMR-FT 3GPP decoder", "gpac distribution", 0);

	return ifce;
}

void DeleteAMRFTDecoder(BaseDecoder *ifcg)
{
	AMRFTCTX();
	free(ctx);
	free(ifcg);
}

/*re-include AMR reader (we coul make it an independant plugin...)*/
NetClientPlugin *NewAESReader();
void DeleteAESReader(void *ifce);

Bool QueryInterface(u32 InterfaceType)
{
	switch (InterfaceType) {
	case M4MEDIADECODERINTERFACE: return 1;
	case M4STREAMINGCLIENT: return 1;
	default:
		return 0;
	}
}

void *LoadInterface(u32 InterfaceType)
{
	switch (InterfaceType) {
	case M4MEDIADECODERINTERFACE: return NewAMRFTDecoder();
	case M4STREAMINGCLIENT: return NewAESReader();
	default: return NULL;
	}
}

void ShutdownInterface(void *ifce)
{
	BaseDecoder *ifcd = (BaseDecoder *)ifce;
	switch (ifcd->InterfaceType) {
	case M4MEDIADECODERINTERFACE: DeleteAMRFTDecoder(ifcd); break;
	case M4STREAMINGCLIENT:  DeleteAESReader(ifce); break;
	}
}

