/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / command-line client
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */


#include <gpac/m4_terminal.h>
#include <gpac/m4_render.h>
#include <gpac/m4_network.h>

#ifndef WIN32
#include <pwd.h>
#include <unistd.h>
#endif

/*local prototypes*/
void PrintWorldInfo(MPEG4CLIENT term);
void ViewOD(MPEG4CLIENT term, u32 OD_ID);
void PrintODList(MPEG4CLIENT term);
void ViewODs(MPEG4CLIENT term, Bool show_timing);
void PrintGPACConfig(MPEG4CLIENT term, LPINIFILE iniFile);
/*console handling*/
Bool has_input();
u8 get_a_char();

static Bool is_connected = 0;
static Bool Run;
static u32 Duration;
static Bool CanSeek = 0;
static u32 Volume=100;
static char the_url[M4_MAX_PATH];
static Bool NavigateTo = 0;
static char the_next_url[M4_MAX_PATH];
static MPEG4CLIENT term;
static LPINIFILE iniFile;


void PrintHelp()
{
	fprintf(stdout, "MP4Client command keys:\n"
		"\to: connect to the specified URL\n"
		"\tr: restart current presentation\n"
		"\tp: play/pause the presentation\n"
		"\ts: step one frame ahead\n"
		"\tz: seek into presentation\n"
		"\tt: print current timing\n"
		"\n"
		"\tw: view world info\n"
		"\tv: view ObjectDescriptor list\n"
		"\ti: view ObjectDescriptor info\n"
		"\tb: view media objects timing and buffering info\n"
		"\tm: view media objects buffering and memory info\n"
		"\td: dumps scene graph\n"
		"\n"
		"\tC: Enable Streaming Cache\n"
		"\tS: Stops Streaming Cache and save to file\n"
		"\tA: Aborts Streaming Cache\n"
		"\n"
		"\tk: turns stress mode on/off\n"
		"\tn: changes navigation mode\n"
		"\tx: reset to last active viewpoint\n"
		"\n"
		"\t2: restart using 2D renderer\n"
		"\t3: restart using 3D renderer\n"
		"\n"
		"\t4: forces 4/3 Aspect Ratio\n"
		"\t5: forces 16/9 Aspect Ratio\n"
		"\t6: forces no Aspect Ratio (always fill screen)\n"
		"\t7: forces original Aspect Ratio (default)\n"
		"\n"
		"\tl: list available plugins\n"
		"\tc: prints some GPAC configuration info\n"
		"\tq: exit the application\n"
		"\th: print this message\n"
		"\nStartup Options:\n"
		"\t-c config_path: specifies config file path\n"
		"\n"
		"MP4Client - GPAC command line player - version %s\n"
		"GPAC Written by Jean Le Feuvre (c) 2001-2005 - ENST (c) 2005-\n",

		M4_VERSION
		);
}



LPINIFILE create_default_config(char *file_path, char *file_name)
{
	LPINIFILE cfg;
	char szPath[M4_MAX_PATH];
	FILE *f;
	sprintf(szPath, "%s%c%s", file_path, M4_PATH_SEPARATOR, file_name);
	f = fopen(szPath, "wt");
	fprintf(stdout, "create %s: %s\n", szPath, (f==NULL) ? "Error" : "OK");
	if (!f) return NULL;
	fclose(f);

	cfg = NewIniFile(file_path, file_name);
	if (!cfg) return NULL;

#ifdef M4_PLUGIN_PATH
	fprintf(stdout, "Using plugin directory %s \n", M4_PLUGIN_PATH);
	strcpy(szPath, M4_PLUGIN_PATH);
#else
	fprintf(stdout, "Please enter full path to GPAC plugins directory:\n");
	scanf("%s", szPath);
#endif
	IF_SetKey(cfg, "General", "PluginsDirectory", szPath);
	IF_SetKey(cfg, "Audio", "ForceConfig", "yes");
	IF_SetKey(cfg, "Audio", "NumBuffers", "8");
	IF_SetKey(cfg, "Audio", "BuffersPerSecond", "16");
	IF_SetKey(cfg, "Audio", "DisableNotification", "no");
	IF_SetKey(cfg, "FontEngine", "DriverName", "ft_font");
	fprintf(stdout, "Please enter full path to a TrueType font directory (.ttf, .ttc):\n");
	scanf("%s", szPath);
	IF_SetKey(cfg, "FontEngine", "FontDirectory", szPath);
	fprintf(stdout, "Please enter full path to a cache directory for HTTP downloads:\n");
	scanf("%s", szPath);
	IF_SetKey(cfg, "General", "CacheDirectory", szPath);
	IF_SetKey(cfg, "Downloader", "CleanCache", "yes");
	IF_SetKey(cfg, "Rendering", "AntiAlias", "All");
	IF_SetKey(cfg, "Rendering", "Framerate", "30");
	/*use power-of-2 emulation*/
	IF_SetKey(cfg, "Render3D", "EmulatePOW2", "yes");
#ifdef WIN32
	IF_SetKey(cfg, "Render2D", "ScalableZoom", "yes");
	IF_SetKey(cfg, "Video", "DriverName", "DirectX Video Output");
#else
	/*in case we use SDL, better use non-scalable zoom (much faster since no HW stretch blit)*/
	IF_SetKey(cfg, "Render2D", "ScalableZoom", "no");
	IF_SetKey(cfg, "Video", "DriverName", "SDL Video Output");
#endif
	/*don't use res switching: it gives better results with openGL renderer and it's broken under linux...*/
	IF_SetKey(cfg, "Video", "SwitchResolution", "no");

	IF_SetKey(cfg, "Network", "AutoReconfigUDP", "yes");
	IF_SetKey(cfg, "Network", "UDPNotAvailable", "no");
	IF_SetKey(cfg, "Network", "UDPTimeout", "10000");
	IF_SetKey(cfg, "Network", "BufferLength", "3000");

	/*store and reload*/
	IF_Delete(cfg);
	return NewIniFile(file_path, file_name);
}

static void PrintTime(u32 time)
{
	u32 ms, h, m, s;
	h = time / 1000 / 3600;
	m = time / 1000 / 60 - h*60;
	s = time / 1000 - h*3600 - m*60;
	ms = time - (h*3600 + m*60 + s) * 1000;
	fprintf(stdout, "%02d:%02d:%02d.%02d", h, m, s, ms);
}

static Bool is_supported_file(const char *fileName)
{
	char szExt[20], *ext;
	u32 keyCount, i;

	ext = strrchr(fileName, '/');
	if (ext) {
		ext = strchr(fileName, '.');
		/*fixme - a proper browser should check mime & co here*/
		if (!ext) return strstr(fileName, "http://") ? 0 : 1;
	}

	ext = strrchr(fileName, '.');
	/*this may be anything so let's try*/
	if (!ext) return 1;

	strcpy(szExt, ext+1);
	ext =strrchr(szExt, '#');
	if (ext) ext[0] = 0;
	strlwr(szExt);

	keyCount = IF_GetKeyCount(iniFile, "MimeTypes");
	for (i=0; i<keyCount; i++) {
		const char *sKey;
		sKey = IF_GetKeyName(iniFile, "MimeTypes", i);
		if (!sKey) continue;
		sKey = IF_GetKey(iniFile, "MimeTypes", sKey);
		if (strstr(sKey, szExt)) return 1;
	}
	return 0;
}

Bool GPAC_EventProc(void *ptr, M4Event *evt)
{
	switch (evt->type) {
	case M4E_DURATION:
		Duration = (u32) (evt->duration.duration*1000);
		CanSeek = evt->duration.can_seek;
		break;
	case M4E_MESSAGE:
	{
		const char *servName;
		if (!evt->message.service || !strcmp(evt->message.service, the_url)) {
			servName = "main service";
		} else {
			servName = evt->message.service;
		}
		if (!evt->message.message) return 0;
		if (evt->message.error) 
			fprintf(stdout, "%s (%s): %s\n", evt->message.message, servName, M4ErrToString(evt->message.error));
		/*file download*/
		else if (strstr(evt->message.message, "Download") || strstr(evt->message.message, "Buffering") || strstr(evt->message.message, "Importing")) 
			fprintf(stdout, "%s (%s)\r", evt->message.message, servName);
		else
			fprintf(stdout, "%s (%s)\n", evt->message.message, servName);
	}
	fflush(stdout);
		break;
	
	case M4E_VKEYDOWN:
		if ((evt->key.key_states & M4KM_ALT)) {
			switch (evt->key.m4_vk_code) {
			case M4VK_LEFT:
				if (Duration>=2000) {
					s32 res = M4T_GetCurrentTimeInMS(term) - 5*Duration/100;
					if (res<0) res=0;
					fprintf(stdout, "seeking to %.2f %% (", 100*(Float)res / Duration);
					PrintTime(res);
					fprintf(stdout, ")\n");
					M4T_PlayFromTime(term, res);
				} 
				break;
			case M4VK_RIGHT:
				if (Duration>=2000) {
					u32 res = M4T_GetCurrentTimeInMS(term) + 5*Duration/100;
					if (res>=Duration) res = 0;
					fprintf(stdout, "seeking to %.2f %% (", 100*(Float)res / Duration);
					PrintTime(res);
					fprintf(stdout, ")\n");
					M4T_PlayFromTime(term, res);
				}
				break;
			/*these 2 are likely not supported by most audio ouput plugins*/
			case M4VK_UP:
				if (Volume!=100) { Volume = MIN(Volume + 5, 100); M4T_SetOption(term, M4O_AudioVolume, Volume); } 
				break;
			case M4VK_DOWN: 
				if (Volume) { Volume = (Volume > 5) ? (Volume-5) : 0; M4T_SetOption(term, M4O_AudioVolume, Volume); }
				break;
			}
		} else {
			switch (evt->key.m4_vk_code) {
			case M4VK_HOME:
				M4T_SetOption(term, M4O_NavigationType, 1);
				break;
			case M4VK_ESCAPE:
				M4T_SetOption(term, M4O_Fullscreen, !M4T_GetOption(term, M4O_Fullscreen));
				break;
			}
		}
		return 0;

	case M4E_LDOUBLECLICK:
		M4T_SetOption(term, M4O_Fullscreen, !M4T_GetOption(term, M4O_Fullscreen));
		return 0;

	/*we use CTRL and not ALT for keys, since windows shortcuts keypressed with ALT*/
	case M4E_KEYDOWN:
		if (!(evt->key.key_states & M4KM_CTRL)) return 0;
		switch (evt->key.virtual_code) {
		case 'F':
		case 'f':
			fprintf(stdout, "Rendering rate: %f FPS\n", M4T_GetCurrentFPS(term, 0));
			break;
		case 'R':
		case 'r':
			M4T_SetOption(term, M4O_ForceRedraw, 1);
			break;
		case '4': M4T_SetOption(term, M4O_AspectRatio, M4_AR_4_3); break;
		case '5': M4T_SetOption(term, M4O_AspectRatio, M4_AR_16_9); break;
		case '6': M4T_SetOption(term, M4O_AspectRatio, M4_AR_None); break;
		case '7': M4T_SetOption(term, M4O_AspectRatio, M4_AR_Keep); break;
		case 'p':
		case 'P':
			M4T_SetOption(term, M4O_PlayState, (M4T_GetOption(term, M4O_PlayState)==M4_Paused) ? M4_Playing : M4_Paused);
			break;
		case 's':
		case 'S':
			M4T_SetOption(term, M4O_PlayState, M4_StepPause);
			break;
		}
		break;

	case M4E_CONNECT:
		if (evt->connect.is_connected) {
			is_connected = 1;
			fprintf(stdout, "Service Connected\n");
		} else {
			fprintf(stdout, "Service %s\n", is_connected ? "Disconnected" : "Connection Failed");
			is_connected = 0;
			Duration = 0;
		}
		break;
	case M4E_QUIT:
		Run = 0;
		break;

	case M4E_NAVIGATE:
		if (is_supported_file(evt->navigate.to_url)) {
			/*eek that's ugly but I don't want to write an event queue for that*/
			strcpy(the_next_url, evt->navigate.to_url);
			NavigateTo = 1;
			return 1;
		} else {
			fprintf(stdout, "Navigation other than MPEG-4 not supported\nGo to URL: %s\n", evt->navigate.to_url);
		}
		break;
	}
	return 0;
}

LPINIFILE loadconfigfile()
{
	LPINIFILE cfg;
	char szPath[M4_MAX_PATH];

#ifdef WIN32
#ifdef _DEBUG
	strcpy(szPath, "d:\\CVS\\gpac\\bin\\debug\\");
#else
	strcpy(szPath, "d:\\CVS\\gpac\\bin\\release\\");
#endif
	cfg = NewIniFile(szPath, "GPAC.cfg");
	if (cfg) goto success;
	strcpy(szPath, ".");
	cfg = NewIniFile(szPath, "GPAC.cfg");
	if (cfg) goto success;
	strcpy(szPath, "C:\\Program Files\\GPAC");
	cfg = NewIniFile(szPath, "GPAC.cfg");
	if (cfg) goto success;
	strcpy(szPath, ".");
	cfg = NewIniFile(szPath, "GPAC.cfg");
	if (cfg) goto success;
	cfg = create_default_config(szPath, "GPAC.cfg");
#else
	/*linux*/
	char *cfg_dir = getenv("HOME");
	if (cfg_dir) {
		strcpy(szPath, cfg_dir);
	} else {
		fprintf(stdout, "WARNING: HOME env var not set - using current directory for config file\n");
		strcpy(szPath, ".");
	}
	cfg = NewIniFile(szPath, ".gpacrc");
	if (cfg) goto success;
	fprintf(stdout, "GPAC config file not found in %s - creating new file\n", szPath);
	cfg = create_default_config(szPath, ".gpacrc");
#endif
	if (!cfg) {
	  fprintf(stdout, "cannot create config file in %s directory\n", szPath);
	  return NULL;
	}
 success:
	fprintf(stdout, "Using config file in %s directory\n", szPath);
	return cfg;
}

void list_plugins(LPPLUGMAN plugins)
{
	u32 i;
	fprintf(stdout, "\rAvailable plugins:\n");
	for (i=0; i<PM_GetPluginsCount(plugins); i++) {
		char *str = (char *) PM_GetFileName(plugins, i);
		if (str) fprintf(stdout, "\t%s\n", str);
	}
	fprintf(stdout, "\n");
}

void set_navigation()
{
	M4Err e;
	char navstr[20], nav;
	u32 type = M4T_GetOption(term, M4O_NavigationType);
	e = M4OK;
	if (!type) {
		fprintf(stdout, "Content/renderer doesn't allow user-selectable navigation\n");
	} else if (type==1) {
		fprintf(stdout, "Select Navigation (\'N\'one, \'E\'xamine, \'S\'lide): ");
		scanf("%s", navstr);
		nav = navstr[0];
		if (nav=='N') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavNone);
		else if (nav=='E') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavExamine);
		else if (nav=='S') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavSlide);
		else fprintf(stdout, "Unknown selector \'%c\' - only \'N\',\'E\',\'S\' allowed\n", nav);
	} else if (type==2) {
		fprintf(stdout, "Select Navigation (\'N\'one, \'W\'alk, \'F\'ly, \'E\'xamine, \'P\'an, \'S\'lide, \'G\'ame, \'V\'R, \'O\'rbit): ");
		scanf("%s", navstr);
		nav = navstr[0];
		if (nav=='N') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavNone);
		else if (nav=='W') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavWalk);
		else if (nav=='F') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavFly);
		else if (nav=='E') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavExamine);
		else if (nav=='P') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavPan);
		else if (nav=='S') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavSlide);
		else if (nav=='G') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavGame);
		else if (nav=='O') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavOrbit);
		else if (nav=='V') e = M4T_SetOption(term, M4O_NavigationMode, M4_NavVR);
		else fprintf(stdout, "Unknown selector %c - only \'N\',\'W\',\'F\',\'E\',\'P\',\'S\',\'G\', \'V\', \'O\' allowed\n", nav);
	}
	if (e) fprintf(stdout, "Error setting mode: %s\n", M4ErrToString(e));
}



int main (int argc, char **argv)
{
	char *str;
	u32 i, url_arg;
	M4User user;
	LPPLUGMAN plugins;

	/*by default use current dir*/
	strcpy(the_url, ".");

	url_arg = (argc == 2) ? 1 : 0;

	iniFile = loadconfigfile();
	if (argc >= 2) {
		if (!strcmp(argv[1], "-c")) {
			if (argc > 4) {
				fprintf(stdout, "Usage: MP4Client [-c config_path] [url]\n");
				if (iniFile) IF_Delete(iniFile);
				return 1;
			}
			strcpy(the_url, argv[2]);
			url_arg = (argc == 3) ? 0 : 3;
		} else if (argc >= 3) {
			PrintHelp();
			if (iniFile) IF_Delete(iniFile);
			return 1;
		}
	}

	if (!iniFile) {
		fprintf(stdout, "Error: Configuration File \"GPAC.cfg\" not found\n");
		return 1;
	}

	Run = 1;
	
	fprintf(stdout, "Loading plugins ... ");
	str = IF_GetKey(iniFile, "General", "PluginsDirectory");

	plugins = NewPluginManager((const unsigned char *) str, iniFile);
	i = PM_GetPluginsCount(plugins);
	if (!i) {
		fprintf(stdout, "Error: no plugins found in %s - exiting\n", str);
		PM_Delete(plugins);
		IF_Delete(iniFile);
		return 0;
	}
	fprintf(stdout, "OK (%d found in %s)\n", i, str);

	memset(&user, 0, sizeof(M4User));
	user.config = iniFile;
	user.plugins = plugins;
	user.EventProc = GPAC_EventProc;
	/*dummy in this case (global vars) but MUST be non-NULL*/
	user.opaque = plugins;

	fprintf(stdout, "Loading MPEG-4 terminal ... ");
	term = NewMPEG4Term(&user);
	if (!term) {
		fprintf(stdout, "Init error - check you have at least one video out...\nFound Plugins:\n");
		list_plugins(plugins);
		PM_Delete(plugins);
		IF_Delete(iniFile);
		return 1;
	}
	fprintf(stdout, "OK\n");

	/*check video output*/
	str = IF_GetKey(iniFile, "Video", "DriverName");
	if (!strcmp(str, "Raw Video Output")) fprintf(stdout, "WARNING: using raw output video (memory only) - no display used\n");
	/*check audio output*/
	str = IF_GetKey(iniFile, "Audio", "DriverName");
	if (!strcmp(str, "No Audio Output Available")) fprintf(stdout, "WARNING: no audio output availble - make sure no other program is locking the sound card\n");

	fprintf(stdout, "Using %s\n", IF_GetKey(iniFile, "Rendering", "RendererName"));

	/*connect if requested*/
	if (url_arg) {
		strcpy(the_url, argv[url_arg]);
		fprintf(stdout, "Opening URL %s\n", the_url);
		M4T_ConnectURL(term, the_url);
	} else {
		fprintf(stdout, "Hit 'h' for help\n\n");
	}

	while (Run) {
		char c;
		
		if (NavigateTo) {
			fprintf(stdout, "Navigating to URL %s\n", the_next_url);
			NavigateTo = 0;
			M4T_CloseURL(term);
			strcpy(the_url, the_next_url);
			M4T_ConnectURL(term, the_url);
		}

		/*we don't want getchar to block*/
		if (!has_input()) {
			M4_Sleep(10);
			continue;
		}
		c = get_a_char();

		switch (c) {
		case 'q':
			Run = 0;
			break;
		case 'o':
			M4T_CloseURL(term);
			fprintf(stdout, "Enter the absolute URL\n");
			scanf("%s", the_url);
			M4T_ConnectURL(term, the_url);
			break;
		case 'r':
			if (is_connected) {
				M4T_CloseURL(term);
				M4T_ConnectURL(term, the_url);
			}
			break;

		case 'p':
			if (is_connected) {
				Bool is_pause = M4T_GetOption(term, M4O_PlayState);
				fprintf(stdout, "[Status: %s]\n", is_pause ? "Playing" : "Paused");
				M4T_SetOption(term, M4O_PlayState, is_pause ? M4_Playing : M4_Paused);
			}
			break;
		case 's':
			if (is_connected) {
				M4T_SetOption(term, M4O_PlayState, M4_StepPause);
				fprintf(stdout, "Step time: ");
				PrintTime(M4T_GetCurrentTimeInMS(term));
				fprintf(stdout, "\n");
			}
			break;

		case 'z':
			if (!CanSeek || (Duration<=2000)) {
				fprintf(stdout, "scene not seekable\n");
			} else {
				Double res;
				s32 seekTo;
				fprintf(stdout, "Duration: ");
				PrintTime(Duration);
				res = M4T_GetCurrentTimeInMS(term) * 100;
				res /= Duration;
				fprintf(stdout, " (current %.2f %%)\nEnter Seek percentage:\n", res);
				if (scanf("%d", &seekTo) == 1) { 
					if (seekTo > 100) seekTo = 100;
					M4T_PlayFromTime(term, seekTo*Duration/100);
				}
			}
			break;

		case 't':
		{
			if (is_connected) {
				fprintf(stdout, "Current Time: ");
				PrintTime(M4T_GetCurrentTimeInMS(term));
				fprintf(stdout, " - Duration: ");
				PrintTime(Duration);
				fprintf(stdout, "\n");
			}
		}
			break;
		case 'w':
			if (is_connected) PrintWorldInfo(term);
			break;
		case 'v':
			if (is_connected) PrintODList(term);
			break;
		case 'i':
			if (is_connected) {
				u32 ID;
				fprintf(stdout, "Enter OD ID (0 for main OD): ");
				scanf("%d", &ID);
				ViewOD(term, ID);
			}
			break;
		case 'b':
			if (is_connected) ViewODs(term, 1);
			break;

		case 'm':
			if (is_connected) ViewODs(term, 0);
			break;

		case 'l':
			list_plugins(plugins);
			break;

		case 'n':
			if (is_connected) set_navigation();
			break;
		case 'x':
			if (is_connected) M4T_SetOption(term, M4O_NavigationType, 0);
			break;

		case 'd':
			if (is_connected) {
				char file[M4_MAX_PATH], *sExt;
				M4Err e;
				Bool xml_dump, std_out;
				fprintf(stdout, "Enter file radical name (+\'.x\' for XML dumping) - \"std\" for stdout: ");
				scanf("%s", file);
				sExt = strrchr(file, '.');
				xml_dump = 0;
				if (sExt) {
					if (!stricmp(sExt, ".x")) xml_dump = 1;
					sExt[0] = 0;
				}
				std_out = strnicmp(file, "std", 3) ? 0 : 1;
				e = M4T_DumpSceneGraph(term, std_out ? NULL : file, xml_dump, 0, NULL);
				fprintf(stdout, "Dump done (%s)\n", M4ErrToString(e));
			}
			break;

		case 'c':
			PrintGPACConfig(term, iniFile);
			break;
		case '2':
		case '3':
		{
			Bool reconnect = is_connected;
			str = IF_GetKey(iniFile, "Rendering", "RendererName");
			if (strstr(str, "2D") && (c=='2')) { fprintf(stdout, "Already using 2D Renderer\n"); break; }
			if (strstr(str, "3D") && (c=='3')) { fprintf(stdout, "Already using 3D Renderer\n"); break; }
			M4T_Delete(term);
			IF_SetKey(iniFile, "Rendering", "RendererName", (c=='2') ? "GPAC 2D Renderer" : "GPAC 3D Renderer");
			term = NewMPEG4Term(&user);
			if (!term) {
				fprintf(stdout, "Error reloading renderer - aborting\n");
				goto exit;
			}
			fprintf(stdout, "Using %s\n", IF_GetKey(iniFile, "Rendering", "RendererName"));
			if (reconnect) M4T_ConnectURL(term, the_url);
		}
			break;
		case 'k':
		{
			Bool opt = M4T_GetOption(term, M4O_StressMode);
			opt = !opt;
			fprintf(stdout, "Turning stress mode %s\n", opt ? "on" : "off");
			M4T_SetOption(term, M4O_StressMode, opt);
		}
			break;
		case '4': M4T_SetOption(term, M4O_AspectRatio, M4_AR_4_3); break;
		case '5': M4T_SetOption(term, M4O_AspectRatio, M4_AR_16_9); break;
		case '6': M4T_SetOption(term, M4O_AspectRatio, M4_AR_None); break;
		case '7': M4T_SetOption(term, M4O_AspectRatio, M4_AR_Keep); break;

		case 'C':
			switch (M4T_GetOption(term, M4O_MediaCache)) {
			case M4_Cache_Disabled: M4T_SetOption(term, M4O_MediaCache, M4_Cache_Enabled); break;
			case M4_Cache_Enabled: M4T_SetOption(term, M4O_MediaCache, M4_Cache_Disabled); break;
			case M4_Cache_Running: fprintf(stdout, "Streaming Cache is running - please stop it first\n"); continue;
			}
			switch (M4T_GetOption(term, M4O_MediaCache)) {
			case M4_Cache_Enabled: fprintf(stdout, "Streaming Cache Enabled\n"); break;
			case M4_Cache_Disabled: fprintf(stdout, "Streaming Cache Disabled\n"); break;
			case M4_Cache_Running: fprintf(stdout, "Streaming Cache Running\n"); break;
			}
			break;
		case 'S':
		case 'A':
			if (M4T_GetOption(term, M4O_MediaCache)==M4_Cache_Running) {
				M4T_SetOption(term, M4O_MediaCache, (c=='S') ? M4_Cache_Disabled : M4_Cache_Discard);
				fprintf(stdout, "Streaming Cache stoped\n");
			} else {
				fprintf(stdout, "Streaming Cache not running\n");
			}
			break;
		case 'h':
			PrintHelp();
			break;
		default:
			break;
		}
	}

	fprintf(stdout, "Deleting MPEG4 terminal...\n");
	M4T_Delete(term);
exit:
	fprintf(stdout, "Unloading plugins...\n");
	PM_Delete(plugins);
	IF_Delete(iniFile);
	fprintf(stdout, "goodbye\n");
	return 0;
}




void PrintWorldInfo(MPEG4CLIENT term)
{
	u32 i;
	char *title;
	Chain *descs;
	descs = NewChain();
	title = M4T_GetWorldInfo(term, NULL, descs);
	if (!title) {
		fprintf(stdout, "No World Info available\n");
	} else {
		fprintf(stdout, "\t%s\n", title);
		for (i=0; i<ChainGetCount(descs); i++) {
			char *str = ChainGetEntry(descs, i);
			fprintf(stdout, "%s\n", str);
			free(str);
		}
	}
	DeleteChain(descs);
}

void PrintODList(MPEG4CLIENT term)
{
	ODInfo odi;
	u32 i, count;
	LPODMANAGER odm, root_odm = M4T_GetRootOD(term);
	if (!root_odm) return;
	if (M4T_GetODInfo(term, root_odm, &odi) != M4OK) return;
	if (!odi.od) {
		fprintf(stdout, "Service not attached\n");
		return;
	}
	fprintf(stdout, "Currently loaded objects:\n");
	fprintf(stdout, "\tRootOD ID %d\n", odi.od->objectDescriptorID);

	count = M4T_GetODCount(term, root_odm);
	for (i=0; i<count; i++) {
		odm = M4T_GetODManager(term, root_odm, i);
		if (!odm) break;
		if (M4T_GetODInfo(term, odm, &odi) == M4OK) 
			fprintf(stdout, "\t\tOD %d - ID %d (%s)\n", i+1, odi.od->objectDescriptorID, 
			(odi.od_type==M4ST_VISUAL) ? "Video" : (odi.od_type==M4ST_AUDIO) ? "Audio" : "Systems");
	}
}

void ViewOD(MPEG4CLIENT term, u32 OD_ID)
{
	ODInfo odi;
	u32 i, j, count, d_enum,id;
	M4Err e;
	char code[5];
	NetStatCommand com;
	LPODMANAGER odm, root_odm = M4T_GetRootOD(term);
	if (!root_odm) return;

	odm = NULL;
	if (!OD_ID) {
		odm = root_odm;
		if ((M4T_GetODInfo(term, odm, &odi) != M4OK)) odm=NULL;
	} else {
		count = M4T_GetODCount(term, root_odm);
		for (i=0; i<count; i++) {
			odm = M4T_GetODManager(term, root_odm, i);
			if (!odm) break;
			if ((M4T_GetODInfo(term, odm, &odi) == M4OK) && (odi.od->objectDescriptorID == OD_ID)) break;
			odm = NULL;
		}
	}
	if (!odm) {
		fprintf(stdout, "cannot find OD with ID %d\n", OD_ID);
		return;
	}
	if (!odi.od) {
		fprintf(stdout, "Object %d not attached yet\n", OD_ID);
		return;
	}

	while (odi.od && odi.od->URLString) {
		fprintf(stdout, "OD %d points to %s\n", odi.od->objectDescriptorID, odi.od->URLString);
		odm = M4T_GetRemoteOD(term, odm);
		if (!odm) return;
		if (M4T_GetODInfo(term, odm, &odi) != M4OK) return;
	}
	if (!odi.od) {
		fprintf(stdout, "Service not attached\n");
		return;
	}

	if (odi.od->tag==InitialObjectDescriptor_Tag) {
		fprintf(stdout, "InitialObjectDescriptor %d\n", odi.od->objectDescriptorID);
		fprintf(stdout, "Profiles and Levels: Scene %x - Graphics %x - Visual %x - Audio %x - OD %x\n", 
			odi.scene_pl, odi.graphics_pl, odi.visual_pl, odi.audio_pl, odi.OD_pl);
		fprintf(stdout, "Inline Profile Flag %d\n", odi.inline_pl);
	} else {
		fprintf(stdout, "ObjectDescriptor %d\n", odi.od->objectDescriptorID);
	}

	fprintf(stdout, "Object Duration: ");
	if (odi.duration) {
	  PrintTime((u32) (odi.duration*1000));
	} else {
	  fprintf(stdout, "unknown");
	}
	fprintf(stdout, "\n");

	if (odi.owns_service) {
		fprintf(stdout, "Service Handler: %s\n", odi.service_handler);
		fprintf(stdout, "Service URL: %s\n", odi.service_url);
	}		
	if (odi.codec_name) {
		switch (odi.od_type) {
		case M4ST_VISUAL:
			fprintf(stdout, "Video Object: Width %d - Height %d\r\n", odi.width, odi.height);
			fprintf(stdout, "Media Codec: %s\n", odi.codec_name);
			break;
		case M4ST_AUDIO:
			fprintf(stdout, "Audio Object: Sample Rate %d - %d channels\r\n", odi.sample_rate, odi.num_channels);
			fprintf(stdout, "Media Codec: %s\n", odi.codec_name);
			break;
		case M4ST_SCENE:
			if (odi.width && odi.height) {
				fprintf(stdout, "Scene Description - Width %d - Height %d\n", odi.width, odi.height);
			} else {
				fprintf(stdout, "Scene Description - no size specified\n");
			}
			fprintf(stdout, "Scene Codec: %s\n", odi.codec_name);
			break;
		case M4ST_TEXT:
			if (odi.width && odi.height) {
				fprintf(stdout, "Text Object: Width %d - Height %d\n", odi.width, odi.height);
			} else {
				fprintf(stdout, "Text Object: No size specified\n");
			}
			fprintf(stdout, "Text Codec %s\n", odi.codec_name);
			break;
		}
	}
	if (odi.protection) fprintf(stdout, "Encrypted Media%s\n", (odi.protection==2) ? " NOT UNLOCKED" : "");

	count = ChainGetCount(odi.od->ESDescriptors);
	fprintf(stdout, "%d streams in OD\n", count);
	for (i=0; i<count; i++) {
		ESDescriptor *esd = (ESDescriptor *) ChainGetEntry(odi.od->ESDescriptors, i);

		fprintf(stdout, "\nStream ID %d - Clock ID %d\n", esd->ESID, esd->OCRESID);
		if (esd->dependsOnESID) fprintf(stdout, "\tDepends on Stream ID %d for decoding\n", esd->dependsOnESID);

		switch (esd->decoderConfig->streamType) {
		case M4ST_OD: fprintf(stdout, "\tOD Stream - version %d\n", esd->decoderConfig->objectTypeIndication); break;
		case M4ST_OCR: fprintf(stdout, "\tOCR Stream\n"); break;
		case M4ST_SCENE: fprintf(stdout, "\tScene Description Stream - version %d\n", esd->decoderConfig->objectTypeIndication); break;
		case M4ST_VISUAL:
			fprintf(stdout, "\tVisual Stream - media type: ");
			switch (esd->decoderConfig->objectTypeIndication) {
			case 0x20: fprintf(stdout, "MPEG-4\n"); break;
			case 0x60: fprintf(stdout, "MPEG-2 Simple Profile\n"); break;
			case 0x61: fprintf(stdout, "MPEG-2 Main Profile\n"); break;
			case 0x62: fprintf(stdout, "MPEG-2 SNR Profile\n"); break;
			case 0x63: fprintf(stdout, "MPEG-2 Spatial Profile\n"); break;
			case 0x64: fprintf(stdout, "MPEG-2 High Profile\n"); break;
			case 0x65: fprintf(stdout, "MPEG-2 422 Profile\n"); break;
			case 0x6A: fprintf(stdout, "MPEG-1\n"); break;
			case 0x6C: fprintf(stdout, "JPEG\n"); break;
			case 0x6D: fprintf(stdout, "PNG\n"); break;
			case 0x80:
				memcpy(code, esd->decoderConfig->decoderSpecificInfo->data, 4);
				code[4] = 0;
				fprintf(stdout, "GPAC Intern (%s)\n", code);
				break;
			default:
				fprintf(stdout, "Private Type (0x%x)\n", esd->decoderConfig->objectTypeIndication);
				break;
			}
			break;

		case M4ST_AUDIO:
			fprintf(stdout, "\tAudio Stream - media type: ");
			switch (esd->decoderConfig->objectTypeIndication) {
			case 0x40: fprintf(stdout, "MPEG-4\n"); break;
			case 0x66: fprintf(stdout, "MPEG-2 AAC Main Profile\n"); break;
			case 0x67: fprintf(stdout, "MPEG-2 AAC LowComplexity Profile\n"); break;
			case 0x68: fprintf(stdout, "MPEG-2 AAC Scalable Sampling Rate Profile\n"); break;
			case 0x69: fprintf(stdout, "MPEG-2 Audio\n"); break;
			case 0x6B: fprintf(stdout, "MPEG-1 Audio\n"); break;
			case 0xA0: fprintf(stdout, "EVRC Audio\n"); break;
			case 0xA1: fprintf(stdout, "SMV Audio\n"); break;
			case 0xE1: fprintf(stdout, "QCELP Audio\n"); break;
			case 0x80:
				memcpy(code, esd->decoderConfig->decoderSpecificInfo->data, 4);
				code[4] = 0;
				fprintf(stdout, "GPAC Intern (%s)\n", code);
				break;
			default:
				fprintf(stdout, "Private Type (0x%x)\n", esd->decoderConfig->objectTypeIndication);
				break;
			}
			break;
		case M4ST_MPEG7: fprintf(stdout, "\tMPEG-7 Stream - version %d\n", esd->decoderConfig->objectTypeIndication); break;
		case M4ST_IPMP: fprintf(stdout, "\tIPMP Stream - version %d\n", esd->decoderConfig->objectTypeIndication); break;
		case M4ST_OCI: fprintf(stdout, "\tOCI Stream - version %d\n", esd->decoderConfig->objectTypeIndication); break;
		case M4ST_MPEGJ: fprintf(stdout, "\tMPEGJ Stream - version %d\n", esd->decoderConfig->objectTypeIndication); break;
		case M4ST_INTERACT: fprintf(stdout, "\tUser Interaction Stream - version %d\n", esd->decoderConfig->objectTypeIndication); break;
		case M4ST_TEXT: fprintf(stdout, "\tStreaming Text Stream - version %d\n", esd->decoderConfig->objectTypeIndication); break;
		default: fprintf(stdout, "Unknown Stream\r\n"); break;
		}

		fprintf(stdout, "\tBuffer Size %d\n\tAverage Bitrate %d bps\n\tMaximum Bitrate %d bps\n", esd->decoderConfig->bufferSizeDB, esd->decoderConfig->avgBitrate, esd->decoderConfig->maxBitrate);
		if (esd->slConfig->predefined==SLPredef_SkipSL) {
			fprintf(stdout, "\tNot using MPEG-4 Synchronization Layer\n");
		} else {
			fprintf(stdout, "\tStream Clock Resolution %d\n", esd->slConfig->timestampResolution);
		}
		if (esd->URLString) fprintf(stdout, "\tStream Location: %s\n", esd->URLString);

		/*check language*/
		if (esd->langDesc) fprintf(stdout, "\tStream Language: %c%c%c\n", (char) ((esd->langDesc->langCode>>16)&0xFF), (char) ((esd->langDesc->langCode>>8)&0xFF), (char) (esd->langDesc->langCode & 0xFF) );
	}
	fprintf(stdout, "\n");
	/*check OCI (not everything interests us) - FIXME: support for unicode*/
	count = ChainGetCount(odi.od->OCIDescriptors);
	if (count) {
		fprintf(stdout, "%d Object Content Information descriptors in OD\n", count);
		for (i=0; i<count; i++) {
			Descriptor *desc = (Descriptor *) ChainGetEntry(odi.od->OCIDescriptors, i);
			switch (desc->tag) {
			case SegmentDescriptor_Tag:
			{
				SegmentDescriptor *sd = (SegmentDescriptor *) desc;
				fprintf(stdout, "Segment Descriptor: Name: %s - start time %g sec - duration %g sec\n", sd->SegmentName, sd->startTime, sd->Duration);
			}
				break;
			case ContentCreatorName_Tag:
			{
				ContentCreatorNameDescriptor *ccn = (ContentCreatorNameDescriptor *)desc;
				fprintf(stdout, "Content Creators:\n");
				for (j=0; j<ChainGetCount(ccn->ContentCreators); j++) {
					contentCreatorInfo *ci = (contentCreatorInfo *) ChainGetEntry(ccn->ContentCreators, j);
					if (!ci->isUTF8) continue;
					fprintf(stdout, "\t%s\n", ci->contentCreatorName);
				}
			}
				break;

			case ShortTextualDescriptor_Tag:
				{
					ShortTextualDescriptor *std = (ShortTextualDescriptor *)desc;
					fprintf(stdout, "Description:\n\tEvent: %s\n\t%s\n", std->eventName, std->eventText);
				}
				break;
			default:
				break;
			}
		}
		fprintf(stdout, "\n");
	}

	switch (odi.status) {
	case 0: fprintf(stdout, "Stoped - "); break;
	case 1: fprintf(stdout, "Playing - "); break;
	case 2: fprintf(stdout, "Paused - "); break;
	case 3: fprintf(stdout, "Not setup yet\n"); return;
	default: fprintf(stdout, "Setup Failed\n"); return;
	}
	if (odi.buffer>=0) fprintf(stdout, "Buffer: %d ms - ", odi.buffer);
	else fprintf(stdout, "Not buffering - ");
	fprintf(stdout, "Clock drift: %d ms\n", odi.clock_drift);
	if (odi.db_unit_count) fprintf(stdout, "%d AU in DB\n", odi.db_unit_count);
	if (odi.cb_max_count) fprintf(stdout, "Composition Buffer: %d CU (%d max)\n", odi.cb_unit_count, odi.cb_max_count);
	fprintf(stdout, "\n");

	if (odi.owns_service) {
		const char *url;
		u32 done, total, bps;
		d_enum = 0;
		while (M4T_GetDownloadInfo(term, odm, &d_enum, &url, &done, &total, &bps)) {
			if (d_enum==1) fprintf(stdout, "Current Downloads in service:\n");
			if (total) {
				fprintf(stdout, "%s: %d / %d bytes (%.2f %%) - %.2f kBps\n", url, done, total, (100.0f*done)/total, ((Float)bps)/1024.0f);
			} else {
				fprintf(stdout, "%s: %.2f kBps\n", url, ((Float)bps)/1024.0f);
			}
		}
		if (!d_enum) fprintf(stdout, "No Downloads in service\n");
		fprintf(stdout, "\n");
	}
	d_enum = 0;
	while (M4T_GetChannelNetInfo(term, odm, &d_enum, &id, &com, &e)) {
		if (e) continue;
		if (!com.bw_down && !com.bw_up) continue;

		fprintf(stdout, "Stream ID %d statistics:\n", id);
		if (com.multiplex_port) {
			fprintf(stdout, "\tMultiplex Port %d - multiplex ID %d\n", com.multiplex_port, com.port);
		} else {
			fprintf(stdout, "\tPort %d\n", com.port);
		}
		fprintf(stdout, "\tPacket Loss Percentage: %.4f\n", com.pck_loss_percentage);
		fprintf(stdout, "\tDown Bandwidth: %d bps\n", com.bw_down);
		if (com.bw_up) fprintf(stdout, "\tUp Bandwidth: %d bps\n", com.bw_up);
		if (com.ctrl_port) {
			if (com.multiplex_port) {
				fprintf(stdout, "\tControl Multiplex Port: %d - Control Multiplex ID %d\n", com.multiplex_port, com.ctrl_port);
			} else {
				fprintf(stdout, "\tControl Port: %d\n", com.ctrl_port);
			}
			fprintf(stdout, "\tDown Bandwidth: %d bps\n", com.ctrl_bw_down);
			fprintf(stdout, "\tUp Bandwidth: %d bps\n", com.ctrl_bw_up);
		}
		fprintf(stdout, "\n");
	}
}

void PrintODTiming(MPEG4CLIENT term, LPODMANAGER odm)
{
	ODInfo odi;
	if (!odm) return;

	if (M4T_GetODInfo(term, odm, &odi) != M4OK) return;
	while (odi.od && odi.od->URLString) {
		odm = M4T_GetRemoteOD(term, odm);
		if (!odm) return;
		if (M4T_GetODInfo(term, odm, &odi) != M4OK) return;
	}
	if (!odi.od) {
		fprintf(stdout, "Service not attached\n");
		return;
	}

	fprintf(stdout, "OD %d: ", odi.od->objectDescriptorID);
	switch (odi.status) {
	case 1: fprintf(stdout, "Playing - "); break;
	case 2: fprintf(stdout, "Paused - "); break;
	default: fprintf(stdout, "Stoped - "); break;
	}
	if (odi.buffer>=0) fprintf(stdout, "Buffer: %d ms - ", odi.buffer);
	else fprintf(stdout, "Not buffering - ");
	fprintf(stdout, "Clock drift: %d ms", odi.clock_drift);
	fprintf(stdout, " - time: ");
	PrintTime((u32) (odi.current_time*1000));
	fprintf(stdout, "\n");
}

void PrintODBuffer(MPEG4CLIENT term, LPODMANAGER odm)
{
	Float avg_dec_time;
	ODInfo odi;
	if (!odm) return;

	if (M4T_GetODInfo(term, odm, &odi) != M4OK) return;
	while (odi.od && odi.od->URLString) {
		odm = M4T_GetRemoteOD(term, odm);
		if (!odm) return;
		if (M4T_GetODInfo(term, odm, &odi) != M4OK) return;
	}
	if (!odi.od) {
		fprintf(stdout, "Service not attached\n");
		return;
	}

	fprintf(stdout, "OD %d: ", odi.od->objectDescriptorID);
	switch (odi.status) {
	case 1: fprintf(stdout, "Playing"); break;
	case 2: fprintf(stdout, "Paused"); break;
	default: fprintf(stdout, "Stoped"); break;
	}
	if (odi.buffer>=0) fprintf(stdout, " - Buffer: %d ms", odi.buffer);
	if (odi.db_unit_count) fprintf(stdout, " - DB: %d AU", odi.db_unit_count);
	if (odi.cb_max_count) fprintf(stdout, " - CB: %d/%d CUs", odi.cb_unit_count, odi.cb_max_count);
	
	avg_dec_time = 0;
	if (odi.nb_dec_frames) { avg_dec_time = (Float) odi.total_dec_time; avg_dec_time /= odi.nb_dec_frames; }
	fprintf(stdout, "\n * Avg Bitrate %d kbps (%d max) - Avg Decoding Time %.2f ms (%d max)\n",
								(u32) odi.avg_bitrate/1024, odi.max_bitrate/1024, avg_dec_time, odi.max_dec_time);
}

void ViewODs(MPEG4CLIENT term, Bool show_timing)
{
	u32 i, count;
	LPODMANAGER odm, root_odm = M4T_GetRootOD(term);
	if (!root_odm) return;

	if (show_timing) {
		PrintODTiming(term, root_odm);
	} else {
		PrintODBuffer(term, root_odm);
	}
	count = M4T_GetODCount(term, root_odm);
	for (i=0; i<count; i++) {
		odm = M4T_GetODManager(term, root_odm, i);
		if (show_timing) {
			PrintODTiming(term, odm);
		} else {
			PrintODBuffer(term, odm);
		}
	}
	fprintf(stdout, "\n");
}


void PrintGPACConfig(MPEG4CLIENT term, LPINIFILE iniFile)
{
	char *str;
	fprintf(stdout, "\n\n*** GPAC Configuration ***\n\n");
	/*I've only put there important options when debugging...*/
	fprintf(stdout, "Video Output: %s\n", IF_GetKey(iniFile, "Video", "DriverName"));
	str = IF_GetKey(iniFile, "Audio", "DriverName"); fprintf(stdout, "Audio Output: %s\n", str ? str : "None");
	str = IF_GetKey(iniFile, "Audio", "NoResync"); fprintf(stdout, "Audio Resync disabled: %s\n", str ? str : "no");
	str = IF_GetKey(iniFile, "FontEngine", "DriverName"); fprintf(stdout, "Font engine: %s\n", str ? str : "None");
	fprintf(stdout, "JavaScript %s\n", M4T_GetOption(term, M4O_HasScript) ? "Enabled" : "Disabled");
	str = IF_GetKey(iniFile, "Systems", "ForceSingleClock"); fprintf(stdout, "Single timeline forced: %s\n", str ? str : "no");
	str = IF_GetKey(iniFile, "Systems", "NoVisualThread"); 
	if (str && !stricmp(str, "yes")) fprintf(stdout, "Visual Rendering done through media scheduler thread\n");
	else fprintf(stdout, "Visual Renderer self-threaded\n");

	str = IF_GetKey(iniFile, "Rendering", "RendererName"); fprintf(stdout, "Using Renderer: %s\n", str);
	str = IF_GetKey(iniFile, "Rendering", "Raster2D"); fprintf(stdout, "2D Rasterizer: %s\n", str ? str : "None");
	if (strstr(str, "3D")) {
		str = IF_GetKey(iniFile, "Render3D", "RasterOutlines"); fprintf(stdout, "Using Raster Outlines: %s\n", str ? str : "no");
		str = IF_GetKey(iniFile, "Render3D", "EmulatePOW2"); fprintf(stdout, "Power-of-2 texture emulation: %s\n", str ? str : "no");
	} else {
		str = IF_GetKey(iniFile, "Render2D", "DirectRendering"); fprintf(stdout, "Direct Rendering Mode: %s\n", str ? str : "no");
	}
	str = IF_GetKey(iniFile, "Rendering", "FrameRate"); fprintf(stdout, "Target Frame Rate: %s\n", str ? str : "30.0");
	str = IF_GetKey(iniFile, "Rendering", "AntiAlias"); fprintf(stdout, "Anti-Aliasing level: %s\n", str ? str : "None");
	str = IF_GetKey(iniFile, "FontEngine", "UseTextureText"); fprintf(stdout, "Text uses texturing: %s\n", str ? str : "no");
	str = IF_GetKey(iniFile, "Network", "BufferLength"); fprintf(stdout, "Network Buffering: %s ms - ", str ? str : "no");
	str = IF_GetKey(iniFile, "Network", "RebufferLength"); 
	if (!str || !atoi(str)) {
		fprintf(stdout, "Rebuffering Disabled\n");
	} else {
		fprintf(stdout, "Rebuffering below %s ms\n", str);
	}
	str = IF_GetKey(iniFile, "Network", "UDPNotAvailable"); if (str && !stricmp(str, "yes")) fprintf(stdout, "UDP Traffic not available\n");
}

/*seems OK under mingw also*/
#ifdef WIN32
#include <conio.h>
Bool has_input()
{
	return kbhit();
}
u8 get_a_char()
{
	return getchar();
}
#else
/*linux kbhit/getchar- borrowed on debian mailing lists, (author Mike Brownlow)*/
#include <termios.h>

static struct termios t_orig, t_new;
static s32 ch_peek = -1;

void init_keyboard()
{
	tcgetattr(0, &t_orig);
	t_new = t_orig;
	t_new.c_lflag &= ~ICANON;
	t_new.c_lflag &= ~ECHO;
	t_new.c_lflag &= ~ISIG;
	t_new.c_cc[VMIN] = 1;
	t_new.c_cc[VTIME] = 0;
	tcsetattr(0, TCSANOW, &t_new);
}
void close_keyboard(Bool new_line)
{
	tcsetattr(0,TCSANOW, &t_orig);
	if (new_line) fprintf(stdout, "\n");
}
Bool has_input()
{
	u8 ch;
	s32 nread;

	init_keyboard();
	if (ch_peek != -1) return 1;
	t_new.c_cc[VMIN]=0;
	tcsetattr(0, TCSANOW, &t_new);
	nread = read(0, &ch, 1);
	t_new.c_cc[VMIN]=1;
	tcsetattr(0, TCSANOW, &t_new);
	if(nread == 1) {
		ch_peek = ch;
		return 1;
	}
	close_keyboard(0);
	return 0;
}

u8 get_a_char()
{
	u8 ch;
	if (ch_peek != -1) {
		ch = ch_peek;
		ch_peek = -1;
		close_keyboard(1);
		return ch;
	}
	read(0,&ch,1);
	close_keyboard(1);
	return ch;
}

#endif

