#include "swis.h"
#include "GlobHdr.h"
#include "Channels.h"
#include "Utils.h"
#include "IFileTypes.h"
#include "IReader.h"
#include "ICodec.h"
#include "IStream.h"

#include <string.h>
#include <stdio.h>

extern const ICodec MP3_Codec;

#define AudioMPEG_Init		0x52180
#define AudioMPEG_Quit		0x52181
#define AudioMPEG_Info		0x52182
#define AudioMPEG_Clear		0x52183
#define AudioMPEG_Attach	0x52184
#define AudioMPEG_Configure	0x52185
#define AudioMPEG_Process	0x52186

#define codec_state_fixduration	0x00000001

typedef struct
{
	int	size;
	int	start;
	int	free;
	int	finishflag;
	int	dummy[12];
} outb_t;

typedef struct
{
	outb_t*	outputptr;
	char	version;
	char	layer;
	int16_t	padding;
	int	channels;
	int	samplerate;
	int	kbitrate;
	int	initstate;
	int	frames;
	int last_free; // from outb_t
	// for total duration only
	int64_t	playedsamples;
	int64_t	totalsamples;
} work_t;

typedef struct
{
	int	version;
	int	layer;
	int	bitrate;
	int	samplerate;
	int	channels;
	int	vbr_frames;
} info_t;

#define INB_chunksize	(64*1024)
#define module_version 23

static const char Module[] = "AudioMPEG";
static const char ModuleFilename[] = "System:Modules.Audio.MP3.AudioMPEG";
static const _kernel_oserror Error_MP3_Failed =
{ErrNum_CodecError, "AudioMPEG module failed to play this file."};

static const int MP3_SamplesPerFrame[4] = {384, 384, 1152, 1152};
static const char MP3_Version[3][4] = {"2.0", "1.0", "2.5"};
static const char MP3_Type[] = "MPEG %s Layer %d";
static const char MP3_MType[] = "MPEG";

static const _kernel_oserror* MP3_Accept(IStream* s, const Desc* desc)
{
	const _kernel_oserror* e;

	if (stricmp(desc->filetype, MP3_MType))
		return NULL;

	// Ensure correct version of AudioMPEG is present
	e = RMEnsure(Module, module_version, ModuleFilename);
	if (e) return e;

	s->codec.prefn = &MP3_Codec;

	return NULL;
}

/*----------
 * MP3_Load
 */

static const _kernel_oserror* MP3_Load(IStream* s)
{
	const _kernel_oserror* e;
	work_t* w;

	if (s->codec.prefn != &MP3_Codec)
	{
		// File type MP3?
		// For compatibility, allow for not std file type in case of files
		if ((s->source.os_filetype != IFileType_MP3)
		&&  (s->reader.fn != &FromFile))
			return NULL;
		if (s->source.os_filetype == IFileType_Wave)
			return NULL;
	}

	// prepare buffer of MP3 stuff
	e = IStream_Alloc(s, (void**) &w, sizeof(*w));
	if (e) return e;

	s->codec.data = w;
	memset(w, 0, sizeof(*w));

	// Ensure correct version of AudioMPEG is present
	e = RMEnsure(Module, module_version, ModuleFilename);
	if (e) return e;

	// Configure output buffer size to use
	e = _swix(AudioMPEG_Configure, _INR(0, 1), 1, s->pGlobHdr->cfg.outputbufsize);
	if (e) return e;

	// Attach ourselves to AudioMPEG module
	e = _swix(AudioMPEG_Attach, _INR(0,1)|_OUT(2), s, s->inb, &w->outputptr);
	if (e) return e;

	// Try to force limited process configuration, ignore error
	_swix(AudioMPEG_Configure, _INR(0, 1), 256, s);

	s->info.chunksize = INB_chunksize;

	// set initial status
	s->codec.fn = &MP3_Codec;

	return NULL;
}

/*------------
 * MP3_Unload
 */

static const _kernel_oserror* MP3_Unload(IStream* s)
{
	work_t* w = s->codec.data;

	if (w)
	{
		IStream_Free(s, &s->codec.data);

		// Detach from AudioMPEG module
		return _swix(AudioMPEG_Quit, _IN(0), s);
	}

	return NULL;
}

/*-------------
 * MP3_IsReady
 */

static const _kernel_oserror* MP3_IsReady(IStream* s, bool* b)
{
	const _kernel_oserror* e;
	work_t* w = s->codec.data;
	info_t* info;
	char draft[32];

	*b = false;

	e = _swix(AudioMPEG_Info, _IN(0)|_OUT(0), s, &info);
	if (e) return e;

	if ((info == (void*) 1)
	||  (info == (void*) -1))
		return &Error_MP3_Failed;

	if (info == NULL)
		return NULL;

	w->version = info->version;
	w->layer = info->layer;
 	w->kbitrate = info->bitrate;
 	w->samplerate = info->samplerate;
 	w->channels = info->channels;
 	w->frames = info->vbr_frames;

	w->initstate = codec_state_fixduration;

	if (!w->channels
	||  (w->channels > 2))
		return &Error_MP3_Failed;

	int64_t t1, t2;

	t1 = (int64_t) (uint32_t) (s->source.end - s->source.start);
	t1 *= w->samplerate;

	// VBR info ?
	if (!w->frames)
	{
		if (w->kbitrate > 0)
		{
			// samples is freq*size*8/kbit/1000=freq*size/kbit/125
			t2 = (int64_t) (uint32_t) (w->kbitrate * 125);
			w->totalsamples = t1 / t2;
		}
		else
			w->totalsamples = 0;
	}
	else
	{
		// samples is samplesperframe*frames
		w->totalsamples = (int64_t) (uint32_t) (MP3_SamplesPerFrame[w->layer] * w->frames);
		if (w->totalsamples > 0)
		{
			// kbit=frq*size/samples/125
			t2 = w->totalsamples * 125;
			t1 += t2>>1; // for rounding
			w->kbitrate = (int)(int64_t) (t1 / t2);
		}
		else
			w->kbitrate = 0;
	}

	w->totalsamples *= w->channels;
	s->info.Channels = w->channels;
	s->info.ChannelsLayout = Channels_GetDefaultLayout(s->info.Channels);
 	s->info.SampleRate = w->samplerate;
 	s->info.MeanBitRate = w->kbitrate;

	// update type info
	snprintf(draft, sizeof(draft), MP3_Type, MP3_Version[w->version], w->layer);
	e = IStream_SetText(s, 1, EMeta_StreamType, draft, 0);
	if (e) return e;

	// get output buffer info
	Channels_Init(s, (int16_t*) (w->outputptr + 1), w->outputptr->size
		, mixstream_flag_forward|mixstream_flag_16bit|mixstream_flag_interlaced
		, w->samplerate);

	// Build stream list
	Channels_List(s, w->channels);

	*b = true;

	return NULL;
}

static const _kernel_oserror* MP3_BufferSizes(IStream* s, int* size, int* filled)
{
	work_t* w = s->codec.data;

	*size = w->outputptr->size / w->channels;
	*filled = w->outputptr->free - w->outputptr->start;
	if (*filled < 0) *filled += w->outputptr->size;
	*filled /= w->channels;
	if (w->outputptr->finishflag) *size = *filled;

	return NULL;
}

/*-------------
 * MP3_InError
 */

static const _kernel_oserror* MP3_InError(IStream* s)
{
	const _kernel_oserror* e;
	int info;

	e = _swix(AudioMPEG_Info, _IN(0)|_OUT(0), s, &info);
	if (e) return e;

	if ((info == 1)
	||  (info == -1))
		return &Error_MP3_Failed;

	return NULL;
}

/*-------------
 * MP3_ReadPos
 */

static const _kernel_oserror* MP3_ReadPos(IStream* s, int64_t* pos)
{
	work_t* w = s->codec.data;
	int t = s->codec.samples.played - s->info.refTime.position;
	if (t < 0) t = 0;
	int64_t t1 = t;

	t1 *= (uint32_t) 1000; // ms
	t1 /= (uint32_t) (w->samplerate * w->channels);

	*pos = s->info.refTime.time + t1;

	return NULL;
}

/*------------------
 * MP3_ReadDuration
 */

static const _kernel_oserror* MP3_ReadDuration(IStream* s, int64_t* duration)
{
	work_t* w = s->codec.data;
	int64_t t1 = w->totalsamples;

	t1 *= (uint32_t) 1000; // ms
	t1 /= (uint32_t) (w->samplerate * w->channels);

	*duration = t1;

	return NULL;
}

/*------------
 * MP3_SetPos
 */

static const _kernel_oserror* MP3_SetPos(IStream* s, int64_t position)
{
	const _kernel_oserror* e;
	work_t* w = s->codec.data;
	int ioff;

	// Clear all buffers
	e = _swix(AudioMPEG_Clear, _INR(0,1), s, 3);
	if (e) return e;

	ioff = _kernel_irqs_disabled();
	if (!ioff) _kernel_irqs_off();

	IStream_SaveInb(s);

	// reset position parameters
	w->playedsamples = 0;
	w->last_free = w->outputptr->free;

	// try to fix song duration only if played from start
	if (position)
		w->initstate &= ~codec_state_fixduration;
	else	w->initstate |= codec_state_fixduration;

	Channels_Reset(s);

	if (!ioff) _kernel_irqs_on();

	// Move in file to new pos
	position *= s->info.MeanBitRate;
	position >>= 3;
	e = IStream_SetPos(s, (int) position);

	return e;
}

/*-------------
 * MP3_ClearInput
 */

static const _kernel_oserror* MP3_ClearInput(IStream* s)
{
	const _kernel_oserror* e;

	// clear input buffer
	e = _swix(AudioMPEG_Clear, _INR(0,1), s, 1);
	if (e) return e;

	return e;
}

/*---------------
 * MP3_Process
 */

static const _kernel_oserror* MP3_Process(IStream* s, int remain)
{
	// call foreground process (may not exits in old modules)
	_swix(AudioMPEG_Process, _INR(0,1), s, remain);

	return NULL;
}

/*------------
 * MP3_Lister
 */

static int MP3_Lister(IStream* s)
{
	work_t* w = s->codec.data;
	MixStream* stream = &s->ChannelsArray[0];
	int remain;

	// Reminder: the output buffer is composed of interleaved 16-bit values

	// 0. Count samples added to output since last time
	{
		int free = w->outputptr->free;
		int added = free - w->last_free;
		if (added < 0) added += w->outputptr->size;
		w->last_free = free;
		s->codec.samples.decoded += added;
	}

	// 1. Update output buffer start from current mixer stream position
	{
		int pos = stream->pos * w->channels;
		int consumed = pos - w->outputptr->start;
		if (consumed < 0) consumed += w->outputptr->size;
		w->outputptr->start = pos;
		w->playedsamples += consumed;
		s->codec.samples.played += consumed;
	}

	// 2. Determine filled buffer size remaining in output buffer
	remain = w->outputptr->free - w->outputptr->start;
	if (remain < 0) remain += w->outputptr->size;

	// 3. Fix stream duration, only when played from start
	if (w->initstate & codec_state_fixduration)
	{
		if (w->outputptr->finishflag)
		{
			w->totalsamples = w->playedsamples + remain;

			if (w->totalsamples > 0)
			{
				int64_t t1, t2;

				t1 = (int64_t) (uint32_t) (s->source.end - s->source.start);
				t1 *= (uint32_t) (w->samplerate * w->channels);

				// kbit=frq*size/samples/125
				t2 = w->totalsamples * 125;
				t1 += t2>>1; // for rounding
				w->kbitrate = (int)(int64_t) (t1 / t2);
				s->info.MeanBitRate = w->kbitrate;
			}

			// Stop fixing song duration
			w->initstate &= ~codec_state_fixduration;
		}
		else
		{
			if (w->totalsamples < w->playedsamples + remain)
				w->totalsamples = w->playedsamples + remain;
		}
	}

	// 4. Treat completely decoded output case
	if (w->outputptr->finishflag)
		s->status |= swrk_status_decoded;

	return remain / w->channels;
}

const ICodec MP3_Codec =
{ .Name         = MP3_Type
, .Accept       = MP3_Accept
, .Load         = MP3_Load
, .Unload       = MP3_Unload
, .IsReady      = MP3_IsReady
, .BufferSizes  = MP3_BufferSizes
, .InError      = MP3_InError
, .ReadPos      = MP3_ReadPos
, .ReadDuration = MP3_ReadDuration
, .SetPos       = MP3_SetPos
, .ClearInput   = MP3_ClearInput
, .Process      = NULL
, .ExtProcess   = MP3_Process
, .Lister       = MP3_Lister
};
