#include <stdlib.h>
#include <string.h>
#include "swis.h"

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

extern const ICodec Vorbis_Codec;

#define Vorbis_Configure  0x056180
#define Vorbis_Close      0x056182
#define Vorbis_Info       0x056183
#define Vorbis_Clear      0x056184
#define Vorbis_Attach     0x056186
#define Vorbis_Position64 0x056187
#define Vorbis_Process    0x056188

#define codec_waitclearoutput   0x00000001
#define codec_waitstreamready   0x00000002
#define codec_waittotaltime     0x00000004
#define codec_waitall           0x00000007
#define codec_state_fixduration 0x00000008

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;
	int     last_free; // from outb_t
	// for total duration only
	int64_t totalsamples;

	int     serialno;
	int     handle;
	int     dskversion;
} work_t;

typedef struct
{
	int version;
	int channels;
	int samplerate;
	int kbitrate_upper;
	int kbitrate_nominal;
	int kbitrate_lower;
} info_t;

typedef struct
{
	char** ptrs;
	int*   lens;
	int    count;
	char*  vendor;
} comments_t;

typedef struct
{
	int64_t pos;
	int64_t seek;
	int64_t total;
} pos64_t;

#define vorbis_chunksize (64*1024)

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

static const struct
{
	const char* pString;
	unsigned int MetaId;
	unsigned int type; // 0 string, 1 integer
} Metas[] =
{ {"ARTIST=", EMeta_StreamAuthor, 0}
, {"TITLE=", EMeta_StreamTitle, 0}
, {"ALBUM=", EMeta_StreamAlbum, 0}
, {"DESCRIPTION=", EMeta_StreamDescription, 0}
, {"GENRE=", EMeta_StreamGenre, 0}
, {"COMMENT=", EMeta_StreamComment, 0}
, {"DATE=", EMeta_StreamDate, 0}
, {"TRACKNUMBER=", EMeta_StreamTrackNumber, 1}
, {NULL, 0}
};
static const char Vorbis_Codec_Type[] = "Ogg Vorbis";

// Used for FLAC too
const _kernel_oserror* Vorbis_Comment(IStream* s, const char* pstr)
{
	for (int i = 0; Metas[i].pString; i++)
	{
		if (!strnicmp(pstr, Metas[i].pString, strlen(Metas[i].pString)))
		{
			if (Metas[i].type == 0)
				return IStream_SetText(s, 1, Metas[i].MetaId, pstr + strlen(Metas[i].pString), 0);
			else
			{
				int val = atoi(pstr + strlen(Metas[i].pString));
				return IStream_SetMetadata(s, 1, Metas[i].MetaId, &val, sizeof(int));
			}
		}
	}

	return NULL;
}

static const _kernel_oserror* Vorbis_Codec_Accept(IStream* s, const Desc* desc)
{
	IGNORE(s);
	IGNORE(desc);

	return NULL;
}

/*-------------
 * Vorbis_Codec_Load
 */

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

	if (s->codec.prefn != &Vorbis_Codec)
	{
		// File type vorbis?
		if (s->source.os_filetype != IFileType_Ogg)
			return NULL;
	}

	// prepare buffer of Vorbis 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 Vorbis is present
	e = RMEnsure(Module, 013, ModuleFilename);
	if (e) return e;

	// Read DiskSample Version
	e = _swix(Vorbis_Configure, _IN(0)|_OUT(1), 0, &w->dskversion);
	if (e) return e;

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

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

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

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

	// Try to force limited process configuration, ignore error
	_swix(Vorbis_Configure, _INR(0, 1), 256, w->handle);

	s->info.chunksize = vorbis_chunksize;

	// set initial status
	s->codec.fn = &Vorbis_Codec;
	w->initstate = codec_waitall | codec_state_fixduration;

	return NULL;
}

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

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

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

	return e;
}

static const _kernel_oserror* Vorbis_Codec_NewSerialNo(IStream* s
					, comments_t* com
					, unsigned int serialno)
{
	work_t* w = s->codec.data;
	const _kernel_oserror* e = NULL;
	int i;

	w->serialno = serialno;

	// reset previous metadata
	IStream_ClearMetadata(s, 1);

	// force update of type info
	e = IStream_SetText(s, 1, EMeta_StreamType, Vorbis_Codec_Type, 0);
	if (e) return e;

	// extract comments
	e = IStream_SetText(s, 1, EMeta_StreamEncoder, com->vendor, 0);
	if (e) return e;

	for (i = 0; i < com->count; i++)
	{
		e = Vorbis_Comment(s, com->ptrs[i]);
		if (e) return e;
	}

	return e;
}

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

	*b = false;

	if (w->initstate & codec_waitclearoutput)
	{
		// clear output buffer
		_swix(Vorbis_Clear, _INR(0,1), w->handle, 2);
		w->channels = 0;
		w->initstate &= ~codec_waitclearoutput;
	}

	if (w->initstate & codec_waitstreamready)
	{
		info_t* info;
		comments_t* com;
		unsigned int serialno;

		e = _swix(Vorbis_Info, _IN(0)|_OUTR(1,3), w->handle
			, &info, &com, &serialno);
		if (e) return e;

		if (!info || !com)
			return NULL;

		if (!info->channels
		||  (info->channels > max_channels))
			return &Error_Vorbis_Failed;

		if ((w->dskversion < 17)
		&&  (info->channels > 2))
			return &Error_Vorbis_Failed;

		e = Vorbis_Codec_NewSerialNo(s, com, serialno);
		if (e) return e;

		// extract info
		w->version = info->version;
		w->channels = info->channels;
		s->info.Channels = w->channels;
		s->info.ChannelsLayout = Channels_GetDefaultLayout(s->info.Channels);
		s->info.SampleRate = w->samplerate = info->samplerate;
		if (info->kbitrate_nominal <= 1024)
			w->kbitrate = 128;
		else	w->kbitrate = info->kbitrate_nominal/1000;
		s->info.MeanBitRate = w->kbitrate;

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

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

			// They are not in the correct order!
			if (s->info.Channels <= 8)
			{
				static const int permute[8][8] = { {0}, {0,1}, {0,2,1}, {0,1,2,3}
				                                 , {0,1,2,3,4}, {0,2,1,5,3,4}
				                                 , {0,2,1,6,5,3,4}, {0,2,1,7,5,6,3,4}};
				int i;
				MixStream* stream = &s->ChannelsArray[0];
				int16_t* smp_ptr = (int16_t*) stream->smp_ptr;

				for (i = 0; i < s->info.Channels; i++)
				{
					stream = &s->ChannelsArray[i];
					stream->smp_ptr = (void*)(smp_ptr + stream->smp_lend*permute[s->info.Channels-1][i]);
				}
			}
		}
		else
			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);

		// finished that part
		w->initstate &= ~codec_waitstreamready;
		// next is only for files
		if (IStream_IsSeekable(s))
		{
			pos64_t pos64;
			// Move near end of file and seek to unreachable value
			IStream_Seek(s, s->source.end - s->source.start - 16*1024);
			pos64.pos = INT64_MAX;
			_swix(Vorbis_Position64, _INR(0,1), w->handle, &pos64);

			return NULL;
		}
		else
		{
			w->initstate &= ~codec_waittotaltime;
		}
	}

	if (w->initstate & codec_waittotaltime)
	{
		pos64_t pos64;

		// just consume output just in case
		w->outputptr->start = w->outputptr->free;

		// get pos
		pos64.pos = -1;
		e = _swix(Vorbis_Position64, _INR(0,1), w->handle, &pos64);
		if (e) return e;

		// still seeking or info not available?
		if (pos64.seek != -1)
			return NULL;

		if (pos64.total > 0)
		{
			w->totalsamples = pos64.total;

			// kbit=frq*size/samples/125
			uint64_t t1, t2;

			t1 = (uint64_t) (uint32_t) (s->source.end - s->source.start);
			t1 *= w->samplerate;
			t2 = w->totalsamples * UINT64_C(125);
			t1 /= t2;
			s->info.MeanBitRate = w->kbitrate = (int) t1;

			w->initstate &= ~codec_state_fixduration;
		}

		// finished that part
		w->initstate &= ~codec_waittotaltime;

		// Clear all buffers
		e = _swix(Vorbis_Clear, _INR(0,1), w->handle, 7);
		if (e) return e;
		IStream_SaveInb(s);

		// Move to start of file
		e = IStream_Seek(s, 0);
		if (e) return e;
	}

	*b = true;

	return NULL;
}

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

	// Non interlaced for more than 2 channels
	if (s->info.Channels > 2)
	{
		*size = w->outputptr->size;
		*filled = w->outputptr->free - w->outputptr->start;
		if (*filled < 0) *filled += w->outputptr->size;
	}
    else
    {
		*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* Vorbis_Codec_InError(IStream* s)
{
	work_t* w = s->codec.data;

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

static const _kernel_oserror* Vorbis_Codec_ReadPos(IStream* s, int64_t* pos)
{
	work_t* w = s->codec.data;
	const _kernel_oserror* e;
	pos64_t pos64;
    int rate;

	pos64.pos = -1;
	e = _swix(Vorbis_Position64, _INR(0,1)|_OUT(2), w->handle, &pos64, &rate);
	if (e) return e;

	// seeking?
	if (pos64.seek != -1) pos64.pos = pos64.seek;

	if (rate > 0)
	{
		pos64.pos *= 1000;
		pos64.pos /= rate;
		*pos = pos64.pos;
	}
	else
		*pos = 0;

	info_t* info;
	comments_t* com;
	unsigned int serialno;

	e = _swix(Vorbis_Info, _IN(0)|_OUTR(1,3), w->handle
		, &info, &com, &serialno);
	if (e) return e;

	if (serialno == w->serialno)
		return NULL;

	if (!info || !com)
		return NULL;

	if (!info->channels
	||  (info->channels > max_channels))
		return &Error_Vorbis_Failed;

	if ((w->dskversion < 17)
	&&  (info->channels > 2))
		return &Error_Vorbis_Failed;

	e = Vorbis_Codec_NewSerialNo(s, com, serialno);
	if (e) return e;

	return NULL;
}

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

	if (w->initstate & codec_state_fixduration)
	{
		pos64_t pos64;

		pos64.pos = -1;
		_swix(Vorbis_Position64, _INR(0,1), w->handle, &pos64);
		if (pos64.total >= 0)
			w->totalsamples = pos64.total;
	}

	t1 = w->totalsamples * UINT64_C(1000);
	t2 = (int64_t) w->samplerate;
	*duration = t1/ t2;

	return NULL;
}

static const _kernel_oserror* Vorbis_Codec_SetPos(IStream* s, int64_t time)
{
	work_t* w = s->codec.data;
	const _kernel_oserror* e;
	int ioff;
	pos64_t pos64;

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

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

	IStream_SaveInb(s);

	w->last_free = w->outputptr->free;

	Channels_Reset(s);

	if (!ioff) _kernel_irqs_on();

	// Move in file to new pos
	int64_t offset = time - INT64_C(2000); // minus 2s
	if (offset < 0) offset = 0;
	offset *= (int64_t) s->info.MeanBitRate;
	offset >>= 3;
	e = IStream_SetPos(s, (int) offset);
	if (e) return e;

	// Ask Vorbis to seek till given sample position
	pos64.pos = time;
	pos64.pos *= s->ChannelsArray[0].frequency;
	pos64.pos /= 1000;
	_swix(Vorbis_Position64, _INR(0,1), w->handle, &pos64);

	return NULL;
}

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

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

	return e;
}

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

	// process data in the foreground (may not exist in old modules)
	_swix(Vorbis_Process, _INR(0,1), w->handle, remain);

	return NULL;
}

/*---------------
 * Vorbis_Codec_Lister
 */

static int Vorbis_Codec_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 unless more than 2 channels

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

	if (w->channels > 2)
	{
		// 1. Update output buffer start from current mixer stream position
		int pos = stream->pos;
		int consumed = pos - w->outputptr->start;
		if (consumed < 0) consumed += w->outputptr->size;
		w->outputptr->start = pos;
		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;
	}
	else
	{
		// 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;
		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;
		remain /= w->channels;
	}

	// 3. Treat completely decoded output case
	if (w->outputptr->finishflag)
	{
		// Yes, Mark as decoded and fix stream duration
		if (!(s->status & swrk_status_sub_decoded))
		{
			pos64_t pos64;

			s->status |= swrk_status_sub_decoded;
			if (s->inb->finishflag)
				s->status |= swrk_status_decoded;
			else
			{
				// Prepare for next sub-stream
				w->initstate = codec_waitclearoutput | codec_waitstreamready | codec_state_fixduration;
			}

			pos64.pos = -1;
			_swix(Vorbis_Position64, _INR(0,1), w->handle, &pos64);
			w->totalsamples = pos64.total;
		}
	}

	return remain;
}

const ICodec Vorbis_Codec =
{ .Name         = Vorbis_Codec_Type
, .Accept       = Vorbis_Codec_Accept
, .Load         = Vorbis_Codec_Load
, .Unload       = Vorbis_Codec_Unload
, .IsReady      = Vorbis_Codec_IsReady
, .BufferSizes  = Vorbis_Codec_BufferSizes
, .InError      = Vorbis_Codec_InError
, .ReadPos      = Vorbis_Codec_ReadPos
, .ReadDuration = Vorbis_Codec_ReadDuration
, .SetPos       = Vorbis_Codec_SetPos
, .ClearInput   = Vorbis_Codec_ClearInput
, .Process      = NULL
, .ExtProcess   = Vorbis_Codec_Process
, .Lister       = Vorbis_Codec_Lister
};
