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

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

#define codec_state_fixduration	0x00000001

typedef struct
{
	struct
	{
		int16_t* data;
		int32_t  area;
		int32_t  size;
		int16_t* start;
		int16_t* free;
		int16_t* last;
		volatile int32_t  ostart;
		volatile int32_t  ofree;
		volatile int      finishflag;
	} output;
	int	version;
	int	bitrate;
	int	initstate;
	// for total duration only
	int64_t	decodedsamples;
	int64_t	totalsamples;
} work_t;

#define pcm_chunksize	(64*1024)

static const _kernel_oserror Error_PCM_Failed =
{ErrNum_CodecError, "PCM parameters incorrect."};

static const char PCM_Type[] = "PCM";

static const _kernel_oserror* PCM_Accept(IStream* s, const Desc* desc)
{
	const _kernel_oserror* e;
	pcm_params* params;

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

	if (!desc->pcm.channels
	||  (desc->pcm.channels > max_channels)
	||  (desc->pcm.frequency < 50)
	||  (desc->pcm.frequency > 200000))
		return &Error_PCM_Failed;

	if ((desc->pcm.bitspersample != 8)
	&&  (desc->pcm.bitspersample != 16)
	&&  (desc->pcm.bitspersample != 24)
	&&  (desc->pcm.bitspersample != 32))
		return &Error_PCM_Failed;

	if (desc->pcm.blocksize)
	{
		if (desc->pcm.blocksize*8 < desc->pcm.channels*desc->pcm.bitspersample)
			return &Error_PCM_Failed;
	}

	e = IStream_Alloc(s, (void**) &params, sizeof(*params));
	if (e) return e;

	params->flags = desc->flags;
	params->channels = desc->pcm.channels;
	params->channels_layout = Channels_GetDefaultLayout(params->channels);
	params->samplerate = desc->pcm.frequency;
	params->bitspersample = desc->pcm.bitspersample;
	params->blocksize = desc->pcm.blocksize;

	s->codec.prefn = &PCM_Codec;
	s->codec.params = params;

	return NULL;
}

/*-------------
 * PCM_Load
 */

static const _kernel_oserror* PCM_Load(IStream* s)
{
	int size = s->pGlobHdr->cfg.outputbufsize << 10;
	pcm_params* params = s->codec.params;
	const _kernel_oserror* e;
	work_t* w;

	// Format_WAVE alreay handles setup of WAV files
	if (s->codec.prefn != &PCM_Codec)
	{
		if (s->source.os_filetype == IFileType_CDRAW)
		{
			e = IStream_Alloc(s, (void**) &params, sizeof(*params));
			if (e) return e;

			params->flags = 0;
			params->channels = 2;
			params->channels_layout = Channels_GetDefaultLayout(params->channels);
			params->samplerate = 44100;
			params->bitspersample = 16;
			params->blocksize = 4;

			s->codec.prefn = &PCM_Codec;
			s->codec.params = params;
		}
		else
			return NULL;
	}

	if ((params->channels <= 0)
	||  (params->channels > 16)
	||  (params->samplerate < 50)
	||  (params->samplerate > 200000))
		return &Error_PCM_Failed;

	if ((params->bitspersample != 8)
	&&  (params->bitspersample != 16)
	&&  (params->bitspersample != 24)
	&&  (params->bitspersample != 32))
		return &Error_PCM_Failed;

	if (params->blocksize)
	{
		if ((params->blocksize*8 < params->channels*params->bitspersample)
		||  (params->blocksize*4 > params->channels*params->bitspersample)) // safety
			return &Error_PCM_Failed;
	}

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

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

	// Allocate output buffer to use
	e = IStream_NewOutputBuffer(s, &size, &w->output.area, (void**) &w->output.data);
	if (e) return e;
	w->output.size = size / (2*params->channels);
	w->output.last = w->output.data + w->output.size - 1;

	s->info.chunksize = pcm_chunksize;

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

	// set initial status
	s->codec.fn = &PCM_Codec;
	if (params->blocksize)
		w->bitrate = params->samplerate*params->blocksize*8;
	else
		w->bitrate = params->samplerate*params->bitspersample*params->channels;

	return NULL;
}

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

	if (w)
	{
		IStream_DeleteOutputBuffer(s, &w->output.area);

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

	return NULL;
}

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

	*b = false;

	if (w->bitrate <= 0)
		return NULL;

	// extract info
	w->version = 100;

	w->initstate = codec_state_fixduration;

	// 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 *= (uint32_t)(params->samplerate*8);
	t2 = (uint64_t) w->bitrate;
	w->totalsamples = t1 / t2;

	s->info.Channels = params->channels;
	s->info.ChannelsLayout = params->channels_layout;
	s->info.SampleRate = params->samplerate;
	s->info.MeanBitRate = (w->bitrate+500)/1000;

	// get output buffer info
	Channels_Init(s, (int16_t*) w->output.data, w->output.size
	            , mixstream_flag_forward|mixstream_flag_16bit
	            , params->samplerate);

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

	*b = true;

	return NULL;
}

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

	*size = w->output.size;
	*filled = w->output.ofree - w->output.ostart;
	if (*filled < 0) *filled += w->output.size;
	if (w->output.finishflag) *size = *filled;

	return NULL;
}

static const _kernel_oserror* PCM_InError(IStream* s)
{
	IGNORE(s);

	return NULL;
}

static const _kernel_oserror* PCM_ReadPos(IStream* s, int64_t* pos)
{
	const pcm_params* params = s->codec.params;
	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) params->samplerate;

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

	return NULL;
}

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

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

	*duration = t1;

	return NULL;
}

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

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

	// Clear all buffers
	w->output.start = w->output.data;
	w->output.free = w->output.data;
	w->output.ostart = 0;
	w->output.ofree = 0;
	w->output.finishflag = 0;
	s->inb->start = 0;
	s->inb->free = 0;
	s->inb->finishflag = 0;
	w->decodedsamples = 0;

	IStream_SaveInb(s);

	// 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;
	if (params->blocksize)
		blocksize = params->blocksize;
	else
		blocksize = params->channels * params->bitspersample >> 3;

	position = (position / blocksize) * blocksize;

	e = IStream_SetPos(s, (int) position);

	return e;
}

static const _kernel_oserror* PCM_ClearInput(IStream* s)
{
	// clear input buffer
	work_t* w = s->codec.data;
	int ioff;

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

	w->output.finishflag = 0;
	s->inb->start = 0;
	s->inb->free = 0;
	s->inb->finishflag = 0;
	w->decodedsamples = 0;

	if (!ioff) _kernel_irqs_on();

	return NULL;
}

static void PCM_SetFinished(IStream* s, work_t* w, const pcm_params* params)
{
	w->output.finishflag = 1;
	s->status |= swrk_status_decoded;

	// Fix stream duration, only when played from start
	if (w->initstate & codec_state_fixduration)
	{
		w->totalsamples = w->decodedsamples;

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

			// bit=frq*size*8/samples
			t1 = (int64_t) (uint32_t) (s->source.end - s->source.start);
			t1 *= (uint32_t) (params->samplerate*8);
			t2 = w->totalsamples;
			t1 += t2>>1; // for rounding
			w->bitrate = (int)(int64_t) (t1 / t2);
			s->info.MeanBitRate = (w->bitrate+500)/1000;
		}

		// Stop fixing song duration
		w->initstate &= ~codec_state_fixduration;
	}
}

/*---------------
 * PCM_Process
 */
static const _kernel_oserror* PCM_Process(IStream* s, int remain)
{
	const pcm_params* params = s->codec.params;
	work_t* w = s->codec.data;
	int ioff = _kernel_irqs_disabled();
	int16_t* po;
	int16_t* poc;
	int16_t oc;
	int i;

	if (w->output.finishflag)
		return NULL;

	// Copy buffers info so that we are not bothered by interrupts
	if (!ioff) _kernel_irqs_off();
	w->output.start = w->output.data + w->output.ostart;
	if (w->output.start < w->output.data) w->output.start = w->output.data;
	if (w->output.start > w->output.last) w->output.start = w->output.last;
	w->output.free  = w->output.data + w->output.ofree;
	if (w->output.free < w->output.data) w->output.free = w->output.data;
	if (w->output.free > w->output.last) w->output.free = w->output.last;
	if (!ioff) _kernel_irqs_on();

	// Process how many bytes?
	int count = s->bitb.free - s->bitb.start;
	if (count < 0)
		count += s->bitb.size;

	if (!s->inb->finishflag && (count <= remain))
		return NULL;

	// Decode 1 sample fox x channels
	if (params->blocksize)
		count /= params->blocksize;
	else
		count = (count*8)/(params->bitspersample*params->channels);

	if (!count && s->inb->finishflag)
	{
		s->bitb.start = s->bitb.free;
		PCM_SetFinished(s, w, params);
	}

	// Output cannot be entirely filled
	int max = w->output.start - w->output.free;
	if (max <= 0)
		max += w->output.size;
	max -= 1;

	if (count > max) count = max;

	w->decodedsamples += count;
	s->codec.samples.decoded += count;
	if (w->totalsamples < w->decodedsamples)
		w->totalsamples = w->decodedsamples;

	po = w->output.free;

	if (params->flags & DiskSample_Desc_BigEndian)
	{
		switch(params->bitspersample)
		{
			case 8:
			{
				for (; count > 0; count--)
				{
					poc = po;

					for (i = params->channels; i ; i--)
					{
						oc = (*s->bitb.start++) << 8;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;

						if (params->flags & DiskSample_Desc_Unsigned)
							oc ^= 0x8000;
						*poc = oc;
						poc += w->output.size;
					}

					if (params->blocksize)
					{
						s->bitb.start += (params->blocksize - params->channels);
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
					}

					po++;
					if (po > w->output.last)
							po = w->output.data;
				}
			}
			break;
			case 16:
			{
				for (; count > 0; count--)
				{
					poc = po;

					for (i = params->channels; i ; i--)
					{
						oc = (*s->bitb.start++) << 8;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						oc += *s->bitb.start++;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;

						if (params->flags & DiskSample_Desc_Unsigned)
							oc ^= 0x8000;
						*poc = oc;
						poc += w->output.size;
					}

					if (params->blocksize)
					{
						s->bitb.start += (params->blocksize - 2*params->channels);
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
					}

					po++;
					if (po > w->output.last)
						po = w->output.data;
				}
			}
			break;
			case 24:
			{
				for (; count > 0; count--)
				{
					poc = po;

					for (i = params->channels; i ; i--)
					{
						oc = (*s->bitb.start++) << 8;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						oc += *s->bitb.start++;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						s->bitb.start++;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;

						if (params->flags & DiskSample_Desc_Unsigned)
							oc ^= 0x8000;
						*poc = oc;
						poc += w->output.size;
					}

					if (params->blocksize)
					{
						s->bitb.start += (params->blocksize - 3*params->channels);
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
					}

					po++;
					if (po > w->output.last)
						po = w->output.data;
				}
			}
			break;
			case 32:
			{
				for (; count > 0; count--)
				{
					poc = po;

					for (i = params->channels; i ; i--)
					{
						oc = (*s->bitb.start++) << 8;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						oc += *s->bitb.start++;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						s->bitb.start+= 2;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;

						if (params->flags & DiskSample_Desc_Unsigned)
							oc ^= 0x8000;
						*poc = oc;
						poc += w->output.size;
					}

					if (params->blocksize)
					{
						s->bitb.start += (params->blocksize - 4*params->channels);
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
					}

					po++;
					if (po > w->output.last)
						po = w->output.data;
				}
			}
			break;
		}
	}
	else
	{
		switch(params->bitspersample)
		{
			case 8:
			{
				for (; count > 0; count--)
				{
					poc = po;

					for (i = params->channels; i ; i--)
					{
						oc = (*s->bitb.start++) << 8;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;

						if (params->flags & DiskSample_Desc_Unsigned)
							oc ^= 0x8000;
						*poc = oc;
						poc += w->output.size;
					}

					if (params->blocksize)
					{
						s->bitb.start += (params->blocksize - params->channels);
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
					}

					po++;
					if (po > w->output.last)
						po = w->output.data;
				}
			}
			break;
			case 16:
			{
				for (; count > 0; count--)
				{
					poc = po;

					for (i = params->channels; i ; i--)
					{
						oc = *s->bitb.start++;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						oc += (*s->bitb.start++) << 8;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;

						if (params->flags & DiskSample_Desc_Unsigned)
							oc ^= 0x8000;
						*poc = oc;
						poc += w->output.size;
					}

					if (params->blocksize)
					{
						s->bitb.start += (params->blocksize - 2*params->channels);
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
					}

					po++;
					if (po > w->output.last)
						po = w->output.data;
				}
			}
			break;
			case 24:
			{
				for (; count > 0; count--)
				{
					poc = po;

					for (i = params->channels; i ; i--)
					{
						s->bitb.start++;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						oc = *s->bitb.start++;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						oc += (*s->bitb.start++) << 8;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;

						if (params->flags & DiskSample_Desc_Unsigned)
							oc ^= 0x8000;
						*poc = oc;
						poc += w->output.size;
					}

					if (params->blocksize)
					{
						s->bitb.start += (params->blocksize - 3*params->channels);
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
					}

					po++;
					if (po > w->output.last)
						po = w->output.data;
				}
			}
			break;
			case 32:
			{
				for (; count > 0; count--)
				{
					poc = po;

					for (i = params->channels; i ; i--)
					{
						s->bitb.start+= 2;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						oc = *s->bitb.start++;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
						oc += (*s->bitb.start++) << 8;
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;

						if (params->flags & DiskSample_Desc_Unsigned)
							oc ^= 0x8000;
						*poc = oc;
						poc += w->output.size;
					}

					if (params->blocksize)
					{
						s->bitb.start += (params->blocksize - 4*params->channels);
						if (s->bitb.start > s->bitb.last)
							s->bitb.start -= s->bitb.size;
					}

					po++;
					if (po > w->output.last)
						po = w->output.data;
				}
			}
			break;
		}
	}
	w->output.free = po;

	// Update buffers
	if (!ioff) _kernel_irqs_off();
	w->output.ofree = w->output.free - w->output.data;
	if (!ioff) _kernel_irqs_on();

	return NULL;
}

/*---------------
 * PCM_Lister
 */

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

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

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

	return remain;
}

const ICodec PCM_Codec =
{ .Name         = PCM_Type
, .Accept       = PCM_Accept
, .Load         = PCM_Load
, .Unload       = PCM_Unload
, .IsReady      = PCM_IsReady
, .BufferSizes  = PCM_BufferSizes
, .InError      = PCM_InError
, .ReadPos      = PCM_ReadPos
, .ReadDuration = PCM_ReadDuration
, .SetPos       = PCM_SetPos
, .ClearInput   = PCM_ClearInput
, .Process      = PCM_Process
, .ExtProcess   = NULL
, .Lister       = PCM_Lister
};
