#include <v_lib.h>
#include <windows.h>

/* #define DEBUG */

#ifdef	DEBUG

void dput(char *str)
{
	DWORD dummy;
	HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
	WriteConsole(out,str,strlen(str),&dummy,NULL);
	FlushFileBuffers(out);
}

#else

#define dput(str)

#endif	/* !DEBUG */

typedef struct globs_s
{
	vboolean	bInfiniteLoop;
	HANDLE		hStopEvent;
	HANDLE		hCompletionEvent;
	vp_stack_t	flist;
	u32_t		cur_file;
	char_p		error;
	vboolean	done;
}	globs_t, *globs_p;

#define	DEC_SIZE		2048
#define	MAX_QUEUE_LEN	(44100 * 8)

void advance(globs_p globs,s32_t seekto)
{
	s32_t newpos = globs->cur_file + seekto;
	if(globs->bInfiniteLoop)
		globs->cur_file = newpos % vpsCount(&globs->flist);
	else
	{
		char buf[256];
		logFormatString(buf,"newpos = %i\n",newpos);
		dput(buf);
		if(newpos < 0)
			globs->cur_file = 0;
		else
		{
			if((u32_t)newpos >= vpsCount(&globs->flist))
				globs->done = true;
			else
				globs->cur_file = (u32_t)newpos;
		}
		logFormatString(buf,"cur_file = %i, done %i\n",globs->cur_file,globs->done);
		dput(buf);
	}
}

V_LOCAL	vboolean
Done(globs_p globs)
{
	if(WaitForSingleObject(globs->hStopEvent,0)==WAIT_OBJECT_0)
		globs->done = true;
	return globs->done;
}

#define	KEY_WAS_DOWN(code)	(GetAsyncKeyState(code) & 1)
#define	KEY_IS_DOWN(code)	(GetAsyncKeyState(code) & 0x8000)

void control_reset()
{
	GetAsyncKeyState(VK_PAUSE);
	GetAsyncKeyState(VK_LMENU);
	GetAsyncKeyState(VK_MENU);
	GetAsyncKeyState(VK_HOME);
	GetAsyncKeyState(VK_PRIOR);
	GetAsyncKeyState(VK_NEXT);
}

void check_pause_break(globs_p globs)
{
	if(KEY_WAS_DOWN(VK_PAUSE))
	{
		if(KEY_IS_DOWN(VK_LMENU) || KEY_IS_DOWN(VK_MENU))
		{
			globs->done = true;
			return;
		}
		while(KEY_IS_DOWN(VK_PAUSE))
			timeDelay(20);
		KEY_IS_DOWN(VK_PAUSE);
		while(!KEY_WAS_DOWN(VK_PAUSE))
		{
			timeDelay(20);
			if(Done(globs))
				break;
		}
		if(KEY_IS_DOWN(VK_LMENU) || KEY_IS_DOWN(VK_MENU))
			globs->done = true;
	}
}

vboolean check_next_prev_home(globs_p globs)
{
	vboolean result = false;
	if(KEY_IS_DOWN(VK_LMENU) || KEY_IS_DOWN(VK_MENU))
	{
		if(KEY_WAS_DOWN(VK_HOME))
		{
			globs->cur_file=0;
			result=true;
		}
		else if(KEY_WAS_DOWN(VK_PRIOR))
		{
			advance(globs,-1);
			result=true;
		}
		else if(KEY_WAS_DOWN(VK_NEXT))
		{
			dput("next\n");
			advance(globs,1);
			result=true;
		}
	}
	GetAsyncKeyState(VK_HOME);
	GetAsyncKeyState(VK_PRIOR);
	GetAsyncKeyState(VK_NEXT);
	return result;
}

vboolean control(globs_p globs)
{
	check_pause_break(globs);
	return check_next_prev_home(globs) || Done(globs);
}

void play_current_item_and_advance(globs_p globs)
{
	char_p fname;
	vhandle_t in, out;
	u32_t num_chan,bps,freq;
	u32_t len,queue_len;
	u8_t decompressed[DEC_SIZE];
	u32_t i,pages,pagebuf[4096];
	vboolean ctrl = false;
	if(Done(globs))
		return;
	/* Get next song name */
	fname = (char_p)vpsGet(&globs->flist,globs->cur_file);
	/* Precache file data */
	in = fileOpenRead(fname);
	if(!in)
	{
		advance(globs,1);
		return;
	}
	len=fileSize(in);
	pages = len >> 12;
	for(i=0;i<pages;i++)
		fileRead(in,pagebuf,4096);
	fileRead(in,pagebuf,len - (pages << 12));
	fileClose(in);
	/* Open MP3 decoder stream */
	in=mp3Open(fname);
	if(!in)
	{
		globs->error="Wrong playlist item";
		advance(globs,1);
		return;
	}
	/* Open wave output device */
	fileControl(in,pctl_wave_get_num_channels,&num_chan);
	fileControl(in,pctl_wave_get_sampling_frequency,&freq);
	fileControl(in,pctl_wave_get_bits_per_sample,&bps);
	out=wavePlaybackOpen(num_chan,freq,bps);
	if(!out)
	{
		globs->error="Sound hardware busy";
		globs->done=true;
		fileClose(in);
		return;
	}
	/* Decode wave data and write it to DSP */
	for(;;)
	{
		len = fileRead(in,decompressed,DEC_SIZE);
		if(!len)
			break;
		fileWrite(out,decompressed,len);
		ctrl = ctrl || control(globs);
		if(ctrl)
			break;
		queue_len=0;
		fileControl(out,pctl_waveout_get_pending_samples,&queue_len);
		while(queue_len > MAX_QUEUE_LEN)
		{
			timeDelay(20);
			ctrl = ctrl || control(globs);
			if(ctrl)
				break;
			fileControl(out,pctl_waveout_get_pending_samples,&queue_len);
		}
		ctrl = ctrl || control(globs);
		if(ctrl)
			break;
	}
	fileClose(in);
	ctrl = ctrl || control(globs);
	if(!ctrl)
		fileControl(out,pctl_waveout_wait,NULL);
	fileControl(out,pctl_waveout_reset,NULL);
	fileClose(out);
	ctrl = ctrl || control(globs);
	if(ctrl)
		return;
	advance(globs,1);
}

void play_infinite(globs_p globs)
{
	char_p fname;
	vhandle_t in, out;
	u32_t num_chan,bps,freq;
	u32_t len,queue_len;
	u8_t decompressed[DEC_SIZE];
	u32_t i,pages,pagebuf[4096];
	fname = (char_p)vpsGet(&globs->flist,0);
	/* Precache file data */
	in = fileOpenRead(fname);
	if(!in)
	{
		globs->done = true;
		return;
	}
	len = fileSize(in);
	pages = len >> 12;
	for(i=0;i<pages;i++)
		fileRead(in,pagebuf,4096);
	fileRead(in,pagebuf,len - (pages << 12));
	fileClose(in);
	/* Open MP3 decoder stream */
	in=mp3Open(fname);
	if(!in)
	{
		globs->error="Wrong playlist item";
		globs->done=true;
		return;
	}
	len=1;
	fileControl(in,pctl_wave_loop,&len);
	/* Open wave output device */
	fileControl(in,pctl_wave_get_num_channels,&num_chan);
	fileControl(in,pctl_wave_get_sampling_frequency,&freq);
	fileControl(in,pctl_wave_get_bits_per_sample,&bps);
	out=wavePlaybackOpen(num_chan,freq,bps);
	if(!out)
	{
		globs->error="Sound hardware busy";
		globs->done=true;
		fileClose(in);
		return;
	}
	/* Decode wave data and write it to DSP */
	for(;;)
	{
		len = fileRead(in,decompressed,DEC_SIZE);
		if(!len)
			break;
		fileWrite(out,decompressed,len);
		control(globs);
		if(Done(globs))
			break;
		queue_len=0;
		fileControl(out,pctl_waveout_get_pending_samples,&queue_len);
		while(queue_len > MAX_QUEUE_LEN)
		{
			timeDelay(20);
			control(globs);
			if(Done(globs))
				break;
			fileControl(out,pctl_waveout_get_pending_samples,&queue_len);
		}
		if(Done(globs))
			break;
	}
	fileClose(in);
	fileControl(out,pctl_waveout_reset,NULL);
	fileClose(out);
}

int WINAPI
WinMain(HINSTANCE hInst,HINSTANCE hStub,LPSTR CmdLine,int nCmdShow)
{
	char_p ptr;
	vhandle_t sc, fsc;
	vboolean bStopOnly=false;
	char_t ext[_MAX_EXT];
	struct globs_s globs;
#ifdef	DEBUG
	AllocConsole();
#endif
	control_reset();
	memZero(&globs,sizeof(globs));
	globs.hStopEvent=OpenEvent(EVENT_ALL_ACCESS,FALSE,"VPlayerStopEvent");
	if(globs.hStopEvent == NULL)
	{
		/* This is a first instance */
		globs.hStopEvent=CreateEvent(NULL,TRUE,FALSE,"VPlayerStopEvent");
		globs.hCompletionEvent=CreateEvent(NULL,TRUE,FALSE,"VPlayerCompletionEvent");
	}
	else
	{
		globs.hCompletionEvent=OpenEvent(EVENT_ALL_ACCESS,FALSE,"VPlayerCompletionEvent");
		/* We need to stop playing */
		SetEvent(globs.hStopEvent);
		WaitForSingleObject(globs.hCompletionEvent,INFINITE);
	}
	/* Extract all cmdline switches and file names */
	vpsClear(&globs.flist);
	sc=tokParseString(CmdLine,NULL);
	if(!sc)
		goto cleanup;
	while(tokNext(sc))
	{
		/* Is it a switch ? */
		if(tokLast(sc)[0]=='-')
		{
			ptr=&tokLast(sc)[1];
			if(*ptr)
			{
				/* get variable from switch */
				if(!strcmp(ptr,"-loop") || !strcmp(ptr,"l"))
					globs.bInfiniteLoop=true;
				else if(!strcmp(ptr,"stop"))
					bStopOnly=true;
			}
			continue;
		}
		/* Determine what type of file which name passed in cmdline */
		fsSplitPath(tokLast(sc),NULL,NULL,ext);
		if(!strcasecmp(ext,"m3u"))
		{
			/* list of files; parse it ! */
			fsc=tokParseFile(tokLast(sc),NULL);
			if(!fsc)
				continue;
			while(tokNext(fsc))
				vpsPush(&globs.flist,strDup(tokLast(fsc)));
			continue;
			tokDestroy(fsc);
		}
		/* this is file name */
		vpsPush(&globs.flist,strDup(tokLast(sc)));
	}
	tokDestroy(sc);
	if(!vpsCount(&globs.flist))
	{
		/* Nothing to play ! */
		if(!bStopOnly)
			globs.error="Nothing to play";
		goto cleanup;
	}
	if(bStopOnly)
		goto cleanup;
	if(vpsCount(&globs.flist) == 1 && globs.bInfiniteLoop)
		play_infinite(&globs);
	else
		while(!Done(&globs))
			play_current_item_and_advance(&globs);
cleanup:;
	SetEvent(globs.hCompletionEvent);
	while(vpsPop(&globs.flist,(void**)&ptr))
		if(ptr)
			memFree(ptr);
	timeDelay(10);
	CloseHandle(globs.hStopEvent);
	CloseHandle(globs.hCompletionEvent);
	vpsClear(&globs.flist);
	if(globs.error!=NULL)
	{
		/*
		MessageBoxA(NULL,globs.error,"MP3 player",MB_OK|MB_ICONEXCLAMATION);
		*/
		return -1;
	}
	return 0;
}
