#include "GlobHdr.h"
#include "ICodec.h"
#include "IWriter.h"
#include "TimLib:Seq.h"
#include "Channels.h"
#include <string.h>

typedef struct
{
	SeqHandler		Seq;
} ToData;

extern void IStream_ASM_Changer(void);
extern void IStream_ASM_Lister(void);

/*-----------------
 * ToSpeakers_Init
 */

static const _kernel_oserror* ToSpeakers_Initialize(IStream* s, const _kernel_swi_regs* r)
{
	ToData* od;
	const _kernel_oserror* e;

	IGNORE(r);

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

	s->writer.data = od;

	od->Seq.changer = IStream_ASM_Changer;
	od->Seq.lister = IStream_ASM_Lister;
	od->Seq.R12 = s;
	od->Seq.maxstreams = 0;
	od->Seq.volume = s->writer.volume;

	return NULL;
}

/*-----------------
 * ToSpeakers_Open
 */

static const _kernel_oserror* ToSpeakers_Open(IStream* s)
{
	ToData* od = s->writer.data;

	// No, must find out codec to use
	CSeq_ResetMaxWavePercentage(s->pGlobHdr);

	return CSeq_RegisterHandler(s->pGlobHdr, &od->Seq);
}

/*----------------------
 * ToSpeakers_SetParams
 */

static const _kernel_oserror* ToSpeakers_SetParams(IStream* s)
{
	ToData* od = s->writer.data;

	od->Seq.maxstreams = s->info.Channels;
	CSeq_SetMixScale(s->pGlobHdr);

	return NULL;
}

/*-----------------
 * ToSpeakers_Fill
 */

static const _kernel_oserror* ToSpeakers_Fill(IStream* s)
{
	ToData* od = s->writer.data;

	IGNORE(od);

	return NULL;
}

/*-------------------
 * ToSpeakers_Volume
 */

static const _kernel_oserror* ToSpeakers_Volume(IStream* s, int* volume)
{
	ToData* od = s->writer.data;

	if (*volume >= 0)
	{
		od->Seq.volume = *volume;
		CSeq_SetMixScale(s->pGlobHdr);
	}

	*volume = od->Seq.volume;

	return NULL;
}

/*------------------
 * ToSpeakers_Close
 */

static const _kernel_oserror* ToSpeakers_Close(IStream* s)
{
	ToData* od = s->writer.data;

	CSeq_UnregisterHandler(s->pGlobHdr, &od->Seq);

	return NULL;
}

/*---------------------
 * ToSpeakers_Finalize
 */

static const _kernel_oserror* ToSpeakers_Finalize(IStream* s)
{
	ToData* od = s->writer.data;

	if (!od) return NULL;

	CSeq_ResetMaxWavePercentage(s->pGlobHdr);

	IStream_Free(s, &s->writer.data);

	return NULL;
}

IWriter ToSpeakers =
{
	  ToSpeakers_Initialize
	, ToSpeakers_Volume
	, ToSpeakers_Open
	, ToSpeakers_SetParams
	, ToSpeakers_Fill
	, ToSpeakers_Close
	, ToSpeakers_Finalize
};

/*---------------
 * IStream_Lister
 *
 * In  - R0  fill duration in 1/256 us
 *
 * Out - R0  streams ptr
 *       R1  nr of streams
 */

extern const _kernel_oserror* IStream_Lister(_kernel_swi_regs* r, IStream* s);

const _kernel_oserror* IStream_Lister(_kernel_swi_regs* r, IStream* s)
{
	ToData* od = s->writer.data;
	MixStream* stream = &s->ChannelsArray[0];
	int fill, remain;

	// check if song is playing
	if ((s->status & (swrk_status_playing|swrk_status_ready
	                 |swrk_status_played))
	    != (swrk_status_playing|swrk_status_ready))
	{
		// return empty list
		r->r[1] = 0;

		return NULL;
	}

	fill = Channel_DurationToSamples(stream, r->r[0]);

	remain = s->codec.fn->Lister(s);

	if (fill < remain)
	{
		int i;

		// check buffering only here so that other status flags can be updated
		if (s->status & swrk_status_buffering)
		{
			// return empty list
			r->r[1] = 0;

			return NULL;
		}

		s->status &= ~swrk_status_outputdone;
		s->info.PlayingTime += r->r[0];

		// update mixer streams volume
		int volume = (od->Seq.volume << 9) / od->Seq.maxstreams;
		for (i = 0; i < od->Seq.maxstreams; i++)
		{
			if (s->info.ChannelsMuting & (1<<i))
				s->ChannelsArray[i].volume = 0;
			else
				s->ChannelsArray[i].volume = volume;
		}

		// return stream list
		r->r[0] = (int) &s->StreamList[0];
		r->r[1] = s->info.Channels;
	}
	else
	{
		s->status |= swrk_status_outputdone;

		// If completely decoded and nothing more to play, set as played
		if (s->status & swrk_status_decoded)
			s->status |= swrk_status_played;
		else
		{
			// If sub-stream decoded, prepare for next sub-stream
			if (s->status & swrk_status_sub_decoded)
				s->status |= swrk_status_sub_init;

			s->status |= swrk_status_buffering;
		}

		// return empty list
		r->r[1] = 0;
	}

	return NULL;
}
