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

#include <string.h>

extern ICodec AC3_Codec;

#define AC3_Configure   0x58640
#define AC3_Open        0x58641
#define AC3_Close       0x58642
#define AC3_Info        0x58643
#define AC3_Clear       0x58644
#define AC3_Process     0x58645

#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;
	int	version;
	int	channels;
	int	samplerate;
	int	kbitrate;
	int	initstate;
	int64_t	starttime;
	int64_t	playedsamples; // May be negative when playing last samples
	                       // while we have restarted decoding
	int64_t	totalsamples;
	int handle;
} work_t;

typedef struct
{
	int	channels;
	int	samplerate;
	int	kbitrate;
} info_t;

#define ac3_chunksize	(64*1024)
#define module_version 1

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

static const char AC3_Codec_Type[] = "AC3";

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

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

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

	s->codec.prefn = &AC3_Codec;

	return NULL;
}

/*-------------
 * AC3_Codec_Load
 */

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

	if (s->codec.prefn != &AC3_Codec)
	{
		// File type AC3?
		if (s->source.os_filetype != IFileType_AC3)
			return NULL;
	}

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

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

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

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

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

	// Attach ourselves to AC3 module
	e = _swix(AC3_Open, _IN(1)|_OUT(0)|_OUT(2), s->inb
		, &w->handle, &w->outputptr);
	if (e) return e;

	s->info.chunksize = ac3_chunksize;

	// prepare type info
	e = IStream_SetText(s, 1, EMeta_StreamType, AC3_Codec_Type, 0);
	if (e) return e;

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

	return NULL;
}

/*-------------
 * AC3_Codec_Unload
 */

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

	if (w)
	{
		// Detach from AC3 module
		if (w->handle != -1)
			e = _swix(AC3_Close, _IN(0), w->handle);

		IStream_Free(s, &s->codec.data);
	}

	return e;
}

static const _kernel_oserror* AC3_Codec_IsReady(IStream* s, bool* b)
{
	work_t* w = s->codec.data;
	const _kernel_oserror* e;
	info_t* info;

	*b = false;

	e = _swix(AC3_Info, _IN(0)|_OUT(1), w->handle, &info);
	if (e) return e;

	if (!info)
		return NULL;

	if (!info->channels
	||  (info->channels > 2))
		return &Error_AC3_Failed;

	// extract info
	w->version = 100;
	w->channels = info->channels;
	w->samplerate = info->samplerate;
	w->kbitrate = info->kbitrate;

	w->initstate = codec_state_fixduration;

	if (info->kbitrate > 0)
	{
		// samples is freq*size*8/kbit/1000=freq*size/kbit/125
		uint64_t t1, t2;

		t1 = (uint64_t) (uint32_t) (s->source.end - s->source.start);
		t1 *= w->samplerate;
		t2 = (uint64_t) (uint32_t) (w->kbitrate * 125);
		w->totalsamples = t1 / t2;
	}
	else
		w->totalsamples = 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;

	// 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* AC3_Codec_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;
}

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

	return _swix(AC3_Info, _IN(0) , w->handle);
}

static const _kernel_oserror* AC3_Codec_ReadPos(IStream* s, int64_t* pos)
{
	work_t* w = s->codec.data;
	int64_t t1 = w->playedsamples;

	// take loops into account
	if (t1 < 0) t1 += w->totalsamples;
	t1 *= (uint32_t) 1000; // ms
	t1 /= (uint32_t) (w->samplerate * w->channels);

	*pos = w->starttime + t1;

	return NULL;
}

static const _kernel_oserror* AC3_Codec_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;
}

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

	// Clear all buffers
	e = _swix(AC3_Clear, _INR(0,1), w->handle, 3);
	if (e) return e;

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

	IStream_SaveInb(s);

	// reset position parameters
	w->starttime = position;
	w->playedsamples = 0;

	// 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;
}

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

	// clear input buffer
	e = _swix(AC3_Clear, _INR(0,1), w->handle, 1);
	if (e) return e;

	return e;
}

/*---------------
 * AC3_Codec_Process
 */

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

	// call foreground process (may not exits in old modules
	_swix(AC3_Process, _IN(0), w->handle);

	return NULL;
}

/*---------------
 * AC3_Codec_Lister
 */

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

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

	// 1. Update output buffer start from current mixer stream position
	pos = stream->pos * w->channels;
	consumed = pos - w->outputptr->start;
	if (consumed < 0) consumed += w->outputptr->size;
	w->outputptr->start = pos;
	w->playedsamples += 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
	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)
	{
		// Mark as decoded, but may have to delay
		if (!(s->status & swrk_status_decoded)
		&&  (w->playedsamples >= 0))
		{
			s->status |= swrk_status_decoded;
			w->playedsamples = (int64_t)(int) -remain;
			w->starttime = 0;
		}
	}

	return remain / w->channels;
}

ICodec AC3_Codec =
{
	  AC3_Codec_Type
	, AC3_Codec_Accept
	, AC3_Codec_Load
	, AC3_Codec_Unload
	, AC3_Codec_IsReady
	, AC3_Codec_BufferSizes
	, AC3_Codec_InError
	, AC3_Codec_ReadPos
	, AC3_Codec_ReadDuration
	, AC3_Codec_SetPos
	, AC3_Codec_ClearInput
	, NULL
	, AC3_Codec_Process
	, AC3_Codec_Lister
};
