/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program 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 of the License, or (at
 * your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * =======================================================================
 *
 * This file implements all system dependend generic funktions
 *
 * =======================================================================
 */

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <errno.h>
#include <dlfcn.h>
#include <dirent.h>

#include "../../common/header/common.h"
#include "../../common/header/glob.h"
#include "../generic/header/input.h"
#include "header/unix.h"

unsigned sys_frame_time;
int curtime;
static void *game_library;

static char findbase[MAX_OSPATH];
static char findpath[MAX_OSPATH];
static char findpattern[MAX_OSPATH];
static DIR *fdir;

qboolean stdin_active = true;
extern FILE	*logfile;

static qboolean
CompareAttributes(char *path, char *name, unsigned musthave, unsigned canthave)
{
	struct stat st;
	char fn[MAX_OSPATH];

	/* . and .. never match */
	if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0))
	{
		return false;
	}

	return true;

	if (stat(fn, &st) == -1)
	{
		return false; /* shouldn't happen */
	}

	if ((st.st_mode & S_IFDIR) && (canthave & SFF_SUBDIR))
	{
		return false;
	}

	if ((musthave & SFF_SUBDIR) && !(st.st_mode & S_IFDIR))
	{
		return false;
	}

	return true;
}

void
Sys_Init(void)
{
}

int
Sys_Milliseconds(void)
{
	struct timeval tp;
	struct timezone tzp;
	static int secbase;

	gettimeofday(&tp, &tzp);

	if (!secbase)
	{
		secbase = tp.tv_sec;
		return tp.tv_usec / 1000;
	}

	curtime = (tp.tv_sec - secbase) * 1000 + tp.tv_usec / 1000;

	return curtime;
}

void
Sys_Mkdir(char *path)
{
	mkdir(path, 0755);
}

char *
Sys_GetCurrentDirectory(void)
{
	static char dir[MAX_OSPATH];

	if (!getcwd(dir, sizeof(dir)))
	{
		Sys_Error("Couldn't get current working directory");
	}

	return dir;
}

char *
Sys_FindFirst(char *path, unsigned musthave, unsigned canhave)
{
	struct dirent *d;
	char *p;

	if (fdir)
	{
		Sys_Error("Sys_BeginFind without close");
	}

	strcpy(findbase, path);

	if ((p = strrchr(findbase, '/')) != NULL)
	{
		*p = 0;
		strcpy(findpattern, p + 1);
	}
	else
	{
		strcpy(findpattern, "*");
	}

	if (strcmp(findpattern, "*.*") == 0)
	{
		strcpy(findpattern, "*");
	}

	if ((fdir = opendir(findbase)) == NULL)
	{
		return NULL;
	}

	while ((d = readdir(fdir)) != NULL)
	{
		if (!*findpattern || glob_match(findpattern, d->d_name))
		{
			if (CompareAttributes(findbase, d->d_name, musthave, canhave))
			{
				sprintf(findpath, "%s/%s", findbase, d->d_name);
				return findpath;
			}
		}
	}

	return NULL;
}

char *
Sys_FindNext(unsigned musthave, unsigned canhave)
{
	struct dirent *d;

	if (fdir == NULL)
	{
		return NULL;
	}

	while ((d = readdir(fdir)) != NULL)
	{
		if (!*findpattern || glob_match(findpattern, d->d_name))
		{
			if (CompareAttributes(findbase, d->d_name, musthave, canhave))
			{
				sprintf(findpath, "%s/%s", findbase, d->d_name);
				return findpath;
			}
		}
	}

	return NULL;
}

void
Sys_FindClose(void)
{
	if (fdir != NULL)
	{
		closedir(fdir);
	}

	fdir = NULL;
}

void
Sys_ConsoleOutput(char *string)
{
	fputs(string, stdout);
}

void
Sys_Printf(char *fmt, ...)
{
	va_list argptr;
	char text[1024];
	unsigned char *p;

	va_start(argptr, fmt);
	vsnprintf(text, 1024, fmt, argptr);
	va_end(argptr);

	for (p = (unsigned char *)text; *p; p++)
	{
		*p &= 0x7f;

		if (((*p > 128) || (*p < 32)) && (*p != 10) && (*p != 13) && (*p != 9))
		{
			printf("[%02x]", *p);
		}
		else
		{
			putc(*p, stdout);
		}
	}
}

void
Sys_Quit(void)
{
#ifndef DEDICATED_ONLY
	CL_Shutdown();
#endif

	if (logfile)
	{
		fclose(logfile);
		logfile = NULL;
	}

	Qcommon_Shutdown();
	fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY);

	printf("------------------------------------\n");

	exit(0);
}

void
Sys_Error(char *error, ...)
{
	va_list argptr;
	char string[1024];

	/* change stdin to non blocking */
	fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY);

#ifndef DEDICATED_ONLY
	CL_Shutdown();
#endif
	Qcommon_Shutdown();

	va_start(argptr, error);
	vsnprintf(string, 1024, error, argptr);
	va_end(argptr);
	fprintf(stderr, "Error: %s\n", string);

	exit(1);
}

/*
 * returns -1 if not present
 */
int
Sys_FileTime(char *path)
{
	struct  stat buf;

	if (stat(path, &buf) == -1)
	{
		return -1;
	}

	return buf.st_mtime;
}

void
floating_point_exception_handler(int whatever)
{
	signal(SIGFPE, floating_point_exception_handler);
}

char *
Sys_ConsoleInput(void)
{
	static char text[256];
	int len;
	fd_set fdset;
	struct timeval timeout;

	if (!dedicated || !dedicated->value)
	{
		return NULL;
	}

	if (!stdin_active)
	{
		return NULL;
	}

	FD_ZERO(&fdset);
	FD_SET(0, &fdset); /* stdin */
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;

	if ((select(1, &fdset, NULL, NULL, &timeout) == -1) || !FD_ISSET(0, &fdset))
	{
		return NULL;
	}

	len = read(0, text, sizeof(text));

	if (len == 0)   /* eof! */
	{
		stdin_active = false;
		return NULL;
	}

	if (len < 1)
	{
		return NULL;
	}

	text[len - 1] = 0; /* rip off the /n and terminate */

	return text;
}

void
Sys_UnloadGame(void)
{
	if (game_library)
	{
		dlclose(game_library);
	}

	game_library = NULL;
}

/*
 * Loads the game dll
 */
void *
Sys_GetGameAPI(void *parms)
{
	void *(*GetGameAPI)(void *);

	FILE *fp;
	char name[MAX_OSPATH];
	char *path;
	char *str_p;
	const char *gamename = "game.so";

	setreuid(getuid(), getuid());
	setegid(getgid());

	if (game_library)
	{
		Com_Error(ERR_FATAL, "Sys_GetGameAPI without Sys_UnloadingGame");
	}

	Com_Printf("LoadLibrary(\"%s\")\n", gamename);

	/* now run through the search paths */
	path = NULL;

	while (1)
	{
		path = FS_NextPath(path);

		if (!path)
		{
			return NULL;     /* couldn't find one anywhere */
		}

		snprintf(name, MAX_OSPATH, "%s/%s", path, gamename);

		/* skip it if it just doesn't exist */
		fp = fopen(name, "rb");

		if (fp == NULL)
		{
			continue;
		}

		fclose(fp);

		game_library = dlopen(name, RTLD_NOW);

		if (game_library)
		{
			Com_MDPrintf("LoadLibrary (%s)\n", name);
			break;
		}
		else
		{
			Com_Printf("LoadLibrary (%s):", name);

			path = (char *)dlerror();
			str_p = strchr(path, ':');   /* skip the path (already shown) */

			if (str_p == NULL)
			{
				str_p = path;
			}
			else
			{
				str_p++;
			}

			Com_Printf("%s\n", str_p);

			return NULL;
		}
	}

	GetGameAPI = (void *)dlsym(game_library, "GetGameAPI");

	if (!GetGameAPI)
	{
		Sys_UnloadGame();
		return NULL;
	}

	return GetGameAPI(parms);
}

void
Sys_SendKeyEvents(void)
{
#ifndef DEDICATED_ONLY
	IN_Update();
#endif

	/* grab frame time */
	sys_frame_time = Sys_Milliseconds();
}

char *
Sys_GetHomeDir(void)
{
	static char gdir[MAX_OSPATH];
	char *home;

	home = getenv("HOME");

	if (!home)
	{
		return NULL;
	}

	snprintf(gdir, sizeof(gdir), "%s/%s/", home, CFGDIR);

	return gdir;
}

void *
Sys_GetProcAddress(void *handle, const char *sym)
{
	return dlsym(handle, sym);
}

void *
Sys_LoadLibrary(const char *path, const char *sym, void **handle)
{
	void *module, *entry;

	*handle = NULL;

	module = dlopen(path, RTLD_LAZY);

	if (!module)
	{
		Com_Printf("%s failed: %s\n", __func__, dlerror());
		return NULL;
	}

	if (sym)
	{
		entry = dlsym(module, sym);

		if (!entry)
		{
			Com_Printf("%s failed: %s\n", __func__, dlerror());
			dlclose(module);
			return NULL;
		}
	}
	else
	{
		entry = NULL;
	}

	Com_DPrintf("%s succeeded: %s\n", __func__, path);

	*handle = module;

	return entry;
}

void
Sys_FreeLibrary(void *handle)
{
	if (handle && dlclose(handle))
	{
		Com_Error(ERR_FATAL, "dlclose failed on %p: %s", handle, dlerror());
	}
}

