#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "swis.h"
#include "kernel.h"

#include "WimpLib:mem.h"
#include "TimPlayer.h"
#include "WimpLib:DragDrop.h"
#include "WimpLib:Exception.h"
#include "WimpLib:File.h"
#include "WimpLib:Hourglass.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:Task.h"
#include "WimpLib:Utils.h"
#include "WimpLib:Window.h"
#include "WimpLib:CIcon.h"

extern const char ProgramName[];
const char ProgramName[] = "PlayTone";
static const char ProgramSprite[] = "!playtone";
static const char ProgramDir[] = "<PlayTone$Dir>";

#define Icon_FreqText   1
#define Icon_FreqDown   2
#define Icon_FreqUp     3
#define Icon_VolumeText 5
#define Icon_VolumeDown 6
#define Icon_VolumeUp   7
#define Icon_Stop       8
#define Icon_Play       9

#define M_PI 3.14159265358979323846f
#define MaxBufferSize 192000

static struct
{
	HWind	Wind;
	int     Playing;
	int     FxHandle;
	int     SongHandle;
	int     SampleSize;
	int     SampleRate;
	int     SampleFrequency;
	int     SampleVolume;
	int16_t* pBuffer;
} App;

static void Sample_Play(void)
{
	if (App.Playing)
	{
		// Play Sample
		_swix(TimPlayer_FXPlaySample, _INR(0,6)
			, App.FxHandle, 0, App.SongHandle, 1, 0x40, 256, 128);
	}
	else
	{
		// Stop Sample
		_swix(TimPlayer_FXNoteAction, _INR(0,3)
			, App.FxHandle, 0, 0, 0);
	}
}

static void Sample_Update(void)
{
	_swix(TimPlayer_Configure, _IN(0)|_OUT(1), 5, &App.SampleRate);

	// Define a 1 second buffer, as App.SampleFrequency is an integer
	// we are sure have complete periods when looping on the buffer
	App.SampleSize = App.SampleRate;
	if (App.SampleSize > MaxBufferSize)
		App.SampleSize = MaxBufferSize;
	// Define stepping
	float base = (float) App.SampleFrequency;
	base /= (float) App.SampleRate;
	base = 2.f*M_PI * base;
	int16_t* ps = App.pBuffer;
	// Note: float bug
	// float pos = base * (float) i; works
	// but float pos = 0; pos += base; gives problems FPEmulator? Compiler?
	double pos = 0;

	for (int i = 0; i < App.SampleSize; i++)
	{
		// float pos = base;
		//pos *= (float) i;
		float val = 32767.f * (float) sin(pos);
		*ps++ = (int16_t) val;
		pos += base;
	}

	// Update sample
	_swix(TimPlayer_SampleInfo, _INR(0,6)
		, App.SongHandle, 0x80000000u | 1, App.SampleSize, App.pBuffer, 0xC, NULL, NULL);
	_swix(TimPlayer_SampleMisc, _INR(0,7)
		, App.SongHandle, 0x80000000u | 1, 256, 256, 0, App.SampleRate, 0, 0);
	_swix(TimPlayer_SampleLoops, _INR(0,5)
		, App.SongHandle, 0x80000000u | 1, 0, App.SampleSize, 0, 0);
}

static void Sample_SetVolume(int volume)
{
	if (volume < 0) volume = 0;
	if (volume > 256) volume = 256;
	if (App.SampleVolume != volume)
	{
		App.SampleVolume = volume;
		Icon_Printf(App.Wind, Icon_VolumeText, "%d", volume);
		_swix(TimPlayer_FXGlobalSettings, _INR(0,2), App.FxHandle, 0, App.SampleVolume);
	}
}

static void Sample_SetFrequency(int freq)
{
	if (freq < 100) freq = 100;
	if (freq > 20000) freq = 20000;
	if (App.SampleFrequency != freq)
	{
		App.SampleFrequency = freq;
		Icon_Printf(App.Wind, Icon_FreqText, "%d", freq);
	}
}

static void App_Close(void)
{
	if (App.FxHandle != -1)
	{
		_swix(TimPlayer_FXUnregister, _IN(0), App.FxHandle);
		App.FxHandle = -1;
	}

	if (App.SongHandle != -1)
	{
		// Release sample
		 _swix(TimPlayer_SampleInfo, _INR(0,6)
			, App.SongHandle, 0x80000000u | 0, 0, NULL, 0, NULL, NULL);

		_swix(TimPlayer_SongUnload, _IN(0), App.SongHandle);
		App.SongHandle = -1;
	}

	// Release RMA buffer
	if (App.pBuffer != NULL)
	{
		_swix(OS_Module, _IN(0)|_IN(2), 7, App.pBuffer);
		App.pBuffer = NULL;
	}

	Window_Delete(App.Wind);

	Task_NotTask(EXIT_SUCCESS);
}

static EListenerAction App_DefaultHandler(void* handle, const Event* e)
{
	IGNORE(handle);

	switch(e->Type)
	{
		case EEvent_WindowClose:
		{
			App_Close();
		}
		break;
		case EEvent_Mouse:
		{
			const Mouse* m = e->pData;

			if (m->w == App.Wind)
			{
				switch(m->i)
				{
					case Icon_FreqDown:
					case Icon_FreqUp:
					{
						int step = (m->i == Icon_FreqUp) ? 1000 : -1000;
						if (m->but & EBut_Adjust) step = -step;
						if (Keyboard_PollShift()) step /= 10;
						if (Keyboard_PollCtrl()) step /= 100;
						Sample_SetFrequency(App.SampleFrequency + step);
					}
					break;
					case Icon_VolumeDown:
					case Icon_VolumeUp:
					{
						int step = (m->i == Icon_VolumeUp) ? 1 : -1;
						if (m->but & EBut_Adjust) step = -step;
						if (Keyboard_PollShift()) step <<= 4;
						if (Keyboard_PollCtrl()) step <<= 4;
						Sample_SetVolume(App.SampleVolume + step);
					}
					break;
					case Icon_Stop:
					{
						App.Playing = 0;
						Sample_Play();
					}
					break;
					case Icon_Play:
					{
						App.Playing = 1;
						Sample_SetFrequency(atoi(Icon_GetData(App.Wind, Icon_FreqText)));
						Sample_Update();
						Sample_Play();
					}
				}
				return EListenerAction_StopEvent;
			}
		}
		break;
		case EEvent_Message:
		case EEvent_MessageWantAck:
		{
			const Msg_FileData* msg = e->pData;

			switch(msg->hdr.action)
			{
				case EMsg_Quit:
				{
					App_Close();
				}
				break;
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static int messages[] =
{
	EMsg_DataSave,
	EMsg_DataSaveAck,
	EMsg_DataLoad,
	EMsg_DataLoadAck,
	EMsg_Dragging,
	EMsg_DragClaim,
	EMsg_HelpRequest,
	EMsg_MenusDeleted,
	0
};

int main(int argc, char* argv[])
{
	IGNORE(argc);
	IGNORE(argv);

	App.SampleFrequency = 0;
	App.SampleVolume = 0;
	App.Playing = 0;
	App.FxHandle = -1;
	App.SongHandle = -1;

	/* Wimp initialisations */
	throw_Task_Task(false, 310, ProgramName, ProgramSprite, ProgramDir, messages);

	try
	{
		const _kernel_oserror* e;

		throw_Task_RegisterEventHandler(App_DefaultHandler, NULL, false);

		App.Wind = throw_Window_CreateFrom("Main", NULL);

		throw_Window_RegisterEventHandler(App.Wind, App_DefaultHandler, NULL, false);

		// Ensure TimPlayer module is loaded
		e = RMEnsure("TimPlayer", 123, "System:Modules.Audio.Trackers.TimPlayer");

		// Allocate RMA buffer for our sample
		if (!e) e = _swix(OS_Module, _IN(0)|_IN(3)|_OUT(2), 6, MaxBufferSize*2, &App.pBuffer);

		// Request an FX handler with 1 Note polyphony from TimPlayer.
		// It will be used to play samples.
		if (!e) e = _swix(TimPlayer_FXRegister, _IN(1)|_OUT(0), 1, &App.FxHandle);
		// Disable automatic volume scaling with polyphony.
		if (!e) e = _swix(TimPlayer_FXGlobalSettings, _INR(0,2), App.FxHandle, 4, 0);

		// Request an empty song handler from TimPlayer.
		// We will attach our sample to it so that we can play it.
		if (!e) e = _swix(TimPlayer_SongNew, _OUT(0), &App.SongHandle);
		if (e) throw_os(e);
		Sample_SetVolume(256);
		Sample_SetFrequency(1000);

		Window_Open(App.Wind);

		Task_MainLoop(EPollMask_WindowPtrLeave | EPollMask_WindowPtrEnter, 0);
	}
	catch
	{
		Task_ReportException();
		Task_NotTask(EXIT_FAILURE);
	}
	catch_end

	return 0;
}
