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

/*
#define DEBUG
#define CONSOLE
*/

#ifdef	DEBUG

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

void mem_stats(void)
{
	char buf[256];
	u32_t blk, hnd, total;
	
	memStats(&total,&blk,&hnd);
	sprintf(buf,"%u total, %u blk, %u hnd\n",total,blk,hnd);
	dput(buf);
}

#else

#define dput(str)

#endif	/* !DEBUG */

typedef struct globs_s
{
	vboolean	bInfiniteLoop; // fixme
	HANDLE		hStopEvent; // fixme
	HANDLE		hCompletionEvent; // fixme
	vp_stack_t	flist;
	u32_t		cur_file;
	char_p		error;
	vboolean	done;
	vhandle_t	wavedev;
	u32_t		nc, freq, bps;
}	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
	{
#ifdef	DEBUG
		char buf[256];
		logFormatString(buf,"newpos = %i\n",newpos);
		dput(buf);
#endif
		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;
		}
#ifdef	DEBUG
		logFormatString(buf,"cur_file = %i, done %i\n",globs->cur_file,globs->done);
		dput(buf);
#endif
	}
}

V_LOCAL	vboolean
Done(globs_p globs)
{
	if(!globs->done && 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)
#define	RESET_STATE(code)	(GetAsyncKeyState(code))

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

void check_pause_break(globs_p globs)
{
	if(KEY_WAS_DOWN(VK_PAUSE))
	{
		wavePlaybackPause(globs->wavedev);
		if(KEY_IS_DOWN(VK_LMENU) || KEY_IS_DOWN(VK_MENU))
		{
			globs->done = true;
			wavePlaybackResume(globs->wavedev);
			wavePlaybackReset(globs->wavedev);
			return;
		}
		while(KEY_IS_DOWN(VK_PAUSE))
			timeDelay(50); /* are 5 quantums too much time ? */
		RESET_STATE(VK_PAUSE);
		while(!KEY_WAS_DOWN(VK_PAUSE))
		{
			timeDelay(20);
			if(Done(globs))
				break;
		}
		wavePlaybackResume(globs->wavedev);
		if(KEY_IS_DOWN(VK_LMENU) || KEY_IS_DOWN(VK_MENU))
		{
			wavePlaybackReset(globs->wavedev);
			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))
		{
			advance(globs,1);
			result = true;
		}
	}
	RESET_STATE(VK_HOME);
	RESET_STATE(VK_PRIOR);
	RESET_STATE(VK_NEXT);
	return result;
}

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

STATIC_STRING(wpitem,"Wrong playlist item(s)");

void play_current_item_and_advance(globs_p globs)
{
	char_p fname;
	vhandle_t in;
	u32_t nc,bps,freq;
	u32_t len,queue_len;
	u8_t decompressed[DEC_SIZE];
	u32_t i,pages;
	u8_t 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)
	{
		globs->error = wpitem;
		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 = wpitem;
		advance(globs,1);
		return;
	}
	/* Re-open wave output device, if necessary */
	fileControl(in,pctl_wave_get_sampling_frequency,&freq);
	fileControl(in,pctl_wave_get_bits_per_sample,&bps);
	fileControl(in,pctl_wave_get_num_channels,&nc);
	if(globs->wavedev == LIBV_INVALID_HANDLE ||
		nc != globs->nc || freq != globs->freq || bps != globs->bps)
	{
		globs->nc = nc;
		globs->freq = freq;
		globs->bps = bps;
		if(globs->wavedev != LIBV_INVALID_HANDLE)
			fileClose(globs->wavedev);
		globs->wavedev = wavePlaybackOpen(globs->nc,globs->freq,globs->bps);
	}
	else
		wavePlaybackReset(globs->wavedev);
	if(globs->wavedev == LIBV_INVALID_HANDLE)
	{
		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;
		len = fileWrite(globs->wavedev,decompressed,len);
		ctrl = ctrl || control(globs);
		if(ctrl)
			break;
		queue_len = 0;
		fileControl(globs->wavedev,pctl_waveout_get_pending_samples,&queue_len);
		while(queue_len > MAX_QUEUE_LEN)
		{
			timeDelay(20);
			ctrl = ctrl || control(globs);
			if(ctrl)
				break;
			queue_len = 0;
			fileControl(globs->wavedev,pctl_waveout_get_pending_samples,&queue_len);
		}
		ctrl = ctrl || control(globs);
		if(ctrl)
			break;
	}
	fileClose(in);
	ctrl = ctrl || control(globs);
	if(!ctrl)
		fileControl(globs->wavedev,pctl_waveout_wait,NULL);
	ctrl = ctrl || control(globs);
	if(ctrl)
		return;
	advance(globs,1);
}

void play_infinite(globs_p globs)
{
	char_p fname;
	vhandle_t in;
	u32_t nc,bps,freq;
	u32_t len,queue_len,loop=1;
	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)
	{
		/* The only filename is invalid */
		globs->error = wpitem;
		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 = wpitem;
		globs->done = true;
		return;
	}
	fileControl(in,pctl_wave_loop,&loop);
	fileControl(in,pctl_wave_get_num_channels,&nc);
	fileControl(in,pctl_wave_get_sampling_frequency,&freq);
	fileControl(in,pctl_wave_get_bits_per_sample,&bps);
	/* Open wave output device */
	if(globs->wavedev == LIBV_INVALID_HANDLE ||
		nc != globs->nc || freq != globs->freq || bps != globs->bps)
	{
		globs->nc = nc;
		globs->freq = freq;
		globs->bps = bps;
		if(globs->wavedev != LIBV_INVALID_HANDLE)
			fileClose(globs->wavedev);
		globs->wavedev = wavePlaybackOpen(globs->nc,globs->freq,globs->bps);
	}
	if(globs->wavedev == LIBV_INVALID_HANDLE)
	{
		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(globs->wavedev,decompressed,len);
		control(globs);
		if(Done(globs))
			break;
		queue_len = 0;
		fileControl(globs->wavedev,pctl_waveout_get_pending_samples,&queue_len);
		while(queue_len > MAX_QUEUE_LEN)
		{
			timeDelay(20);
			control(globs);
			if(Done(globs))
				break;
			fileControl(globs->wavedev,pctl_waveout_get_pending_samples,&queue_len);
		}
		if(Done(globs))
			break;
	}
	fileClose(in);
	fileControl(globs->wavedev,pctl_waveout_wait,NULL);
}

#ifdef	DEBUG

vhandle_t t2;

int done=0;
	
void V_CALL th2(void *arg)
{
	for(;;)
	{
		timeDelay(2500);
		mem_stats();
		if(done)
			return;
	}
}

#endif

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];
	u32_t errcount = 0; /* make compilers happy */
	struct globs_s globs;
	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");
		/* Tell other instance to stop playing */
		SetEvent(globs.hStopEvent);
		WaitForSingleObject(globs.hCompletionEvent,INFINITE);
		ResetEvent(globs.hStopEvent);
		ResetEvent(globs.hCompletionEvent);
	}
	/* Extract all cmdline switches and file names */
	vpsClear(&globs.flist);
	sc = lexParseString(CmdLine,NULL);
	if(sc == LIBV_INVALID_HANDLE)
		goto cleanup;
	while(lexNext(sc))
	{
		/* Is it a switch ? */
		if(lexLast(sc)[0]=='-')
		{
			ptr = &lexLast(sc)[1];
			if(*ptr)
			{
				/* get variable from switch */
				if(!strcmp(ptr,"-loop") || !strcmp(ptr,"l"))
				{
					globs.bInfiniteLoop=true;
				}
				/*
				VFS broken under WinApi
				else if(!strcmp(ptr,"-initfs"))
				{
					fsInit();
				}
				*/
				else if(!strcmp(ptr,"stop"))
					bStopOnly = true;
			}
			continue;
		}
		/* Determine what type of file which name passed in cmdline */
		fsSplitPath(lexLast(sc),NULL,NULL,ext);
		if(!strcasecmp(ext,"m3u"))
		{
			/* list of files; parse it ! */
			fsc = lexParseFile(lexLast(sc),NULL);
			if(fsc == LIBV_INVALID_HANDLE)
				continue;
			while(lexNext(fsc))
				vpsPush(&globs.flist,strDup(lexLast(fsc)));
			lexDestroy(fsc);
			continue;
		}
		/* this is file name */
		vpsPush(&globs.flist,strDup(lexLast(sc)));
	}
	lexDestroy(sc);
	if(!vpsCount(&globs.flist) || bStopOnly)
		goto cleanup;
	globs.wavedev = LIBV_INVALID_HANDLE;
#ifdef	DEBUG
	AllocConsole();
	t2 = threadCreate(th2,NULL);
#endif
	/* 22.jul.05: evil stuff */
#ifndef	DEBUG
	SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
#endif	/* DEBUG */
	if(vpsCount(&globs.flist) == 1 && globs.bInfiniteLoop)
	{
		play_infinite(&globs);
	}
	else
	{
		while(!Done(&globs))
		{
			if(!globs.cur_file)
				errcount = 0; /* reset error count on 1st playlist item */
			play_current_item_and_advance(&globs);
			if(globs.error == wpitem && ++errcount == vpsCount(&globs.flist))
			{
				/* No valid items at all; done */
#ifdef	DEBUG
				dput(globs.error);
#endif	/* DEBUG */
				globs.done = true;
				continue; /* can't hurt */
			}
		}
	}
cleanup:;
	while(vpsPop(&globs.flist,(void**)&ptr))
		if(ptr)
			memFree(ptr);
	timeDelay(10);
	if(globs.wavedev != LIBV_INVALID_HANDLE)
		fileClose(globs.wavedev);
	SetEvent(globs.hCompletionEvent);
	CloseHandle(globs.hStopEvent);
	CloseHandle(globs.hCompletionEvent);
	vpsClear(&globs.flist);
	if(globs.error!=NULL)
	{
#ifdef	DEBUG
		MessageBoxA(NULL,globs.error,"MP3 player",MB_OK|MB_ICONEXCLAMATION);
#endif
		return -1;
	}
#ifdef	DEBUG
	done = 1;
	threadJoin(t2,NULL);
	mem_stats();
	timeDelay(5000);
#endif
	return 0;
}

#ifdef	CONSOLE

int main()
{
	char *args = GetCommandLine();
	while(*args && *args > 32)
		args++;
	while(*args && *args <= 32)
		args++;
	return WinMain(NULL,NULL,args,0);
}

#endif
