/* VisPatch :  Quake level patcher for water visibility.
 *
 * Copyright (C) 1996-1997  Id Software, Inc.
 * Copyright (C) 1997-2006  Andy Bay <IMarvinTPA@bigfoot.com>
 * Copyright (C) 2006-2011  O. Sezer <sezero@users.sourceforge.net>
 *
 * 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:
 *
 * Free Software Foundation, Inc.
 * 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
*/

#include "arch_def.h"
#include "compiler.h"
#include "q_stdinc.h"
#include "q_endian.h"
#include <sys/stat.h>
#if defined(PLATFORM_WINDOWS)
#include <windows.h>
#include <io.h>
#include <direct.h>
#endif	/* PLATFORM_WINDOWS */
#if defined(PLATFORM_UNIX)
#include <unistd.h>
#include <dirent.h>
#include <fnmatch.h>
#endif	/* PLATFORM_UNIX */
#include <ctype.h>
#include "utilslib.h"

/*============================================================================*/

/* platform dependant (v)snprintf function names: */
#if defined(PLATFORM_WINDOWS)
#define	snprintf_func		_snprintf
#define	vsnprintf_func		_vsnprintf
#else
#define	snprintf_func		snprintf
#define	vsnprintf_func		vsnprintf
#endif

int q_vsnprintf(char *str, size_t size, const char *format, va_list args)
{
	int		ret;

	ret = vsnprintf_func (str, size, format, args);

	if (ret < 0)
		ret = (int)size;
	if (size == 0)	/* no buffer */
		return ret;
	if ((size_t)ret >= size)
		str[size - 1] = '\0';

	return ret;
}

int q_snprintf (char *str, size_t size, const char *format, ...)
{
	int		ret;
	va_list		argptr;

	va_start (argptr, format);
	ret = q_vsnprintf (str, size, format, argptr);
	va_end (argptr);

	return ret;
}

static inline int q_isupper(int c)
{
	return (c >= 'A' && c <= 'Z');
}

static inline int q_tolower(int c)
{
	return ((q_isupper(c)) ? (c | ('a' - 'A')) : c);
}

int q_strcasecmp(const char * s1, const char * s2)
{
	const char * p1 = s1;
	const char * p2 = s2;
	char c1, c2;

	if (p1 == p2)
		return 0;

	do
	{
		c1 = q_tolower (*p1++);
		c2 = q_tolower (*p2++);
		if (c1 == '\0')
			break;
	} while (c1 == c2);

	return (int)(c1 - c2);
}

int q_strncasecmp(const char *s1, const char *s2, size_t n)
{
	const char * p1 = s1;
	const char * p2 = s2;
	char c1, c2;

	if (p1 == p2 || n == 0)
		return 0;

	do
	{
		c1 = q_tolower (*p1++);
		c2 = q_tolower (*p2++);
		if (c1 == '\0' || c1 != c2)
			break;
	} while (--n > 0);

	return (int)(c1 - c2);
}

char *q_strlwr (char *str)
{
	char	*c;
	c = str;
	while (*c)
	{
		*c = q_tolower(*c);
		c++;
	}
	return str;
}

char *q_strrev (char *str)
{
	char a, *b, *e;

	b = e = str;

	while (*e++)
		;
	e -= 2;

	while ( b < e )
	{
		a = *b;
		*b = *e;
		*e = a;
		b++;
		e--;
	}
	return str;
}


/*============================================================================*/

void Error (const char *error, ...)
{
	va_list argptr;

	fprintf (stderr, "*** ERROR: ***\n");
	va_start (argptr, error);
	vfprintf (stderr, error, argptr);
	va_end (argptr);
	fprintf (stderr, "\n");
	exit (1);
}

/*============================================================================*/

#if defined(PLATFORM_WINDOWS)

static HANDLE findhandle = INVALID_HANDLE_VALUE;
static WIN32_FIND_DATA finddata;
static char		findstr[MAX_OSPATH];

const char *Sys_FindFirstFile (const char *path, const char *pattern)
{
	if (findhandle != INVALID_HANDLE_VALUE)
		Error ("FindFirst without FindClose");
	q_snprintf (findstr, sizeof(findstr), "%s/%s", path, pattern);
	findhandle = FindFirstFile(findstr, &finddata);
	if (findhandle == INVALID_HANDLE_VALUE)
		return NULL;
	if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		return Sys_FindNextFile();
	return finddata.cFileName;
}

const char *Sys_FindNextFile (void)
{
	if (findhandle == INVALID_HANDLE_VALUE)
		return NULL;
	while (FindNextFile(findhandle, &finddata) != 0)
	{
		if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			continue;
		return finddata.cFileName;
	}
	return NULL;
}

void Sys_FindClose (void)
{
	if (findhandle != INVALID_HANDLE_VALUE)
	{
		FindClose(findhandle);
		findhandle = INVALID_HANDLE_VALUE;
	}
}

int Sys_filesize (const char *filename)
{
	HANDLE fh;
	WIN32_FIND_DATA data;
	int size;

	fh = FindFirstFile(filename, &data);
	if (fh == INVALID_HANDLE_VALUE)
		return -1;
	FindClose(fh);
	if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		return -1;
//	we're not dealing with gigabytes of files.
//	size should normally smaller than INT_MAX.
//	size = (data.nFileSizeHigh * (MAXDWORD + 1)) + data.nFileSizeLow;
	size = (int) data.nFileSizeLow;
	return size;
}

int Sys_getcwd (char *buf, size_t size)
{
	const int sz = (int) size;

	if (sz <= 0) return 0;
	if (_getcwd(buf, sz) == NULL)
		return 1;

	return 0;
}

#endif	/* PLATFORM_WINDOWS */


//============================================================================

#if defined(PLATFORM_UNIX)

static DIR		*finddir;
static struct dirent	*finddata;
static char		*findpath, *findpattern;
static char		matchpath[MAX_OSPATH];

const char *Sys_FindFirstFile (const char *path, const char *pattern)
{
	if (finddir)
		Error ("FindFirst without FindClose");

	finddir = opendir (path);
	if (!finddir)
		return NULL;

	findpattern = strdup (pattern);
	if (!findpattern)
	{
		Sys_FindClose();
		return NULL;
	}

	findpath = strdup (path);
	if (!findpath)
	{
		Sys_FindClose();
		return NULL;
	}

	if (*findpath != '\0')
	{
	/* searching under "/" won't be a good idea, for example.. */
		size_t siz = strlen(findpath) - 1;
		if (findpath[siz] == '/' || findpath[siz] == '\\')
			findpath[siz] = '\0';
	}

	return Sys_FindNextFile();
}

const char *Sys_FindNextFile (void)
{
	struct stat	test;

	if (!finddir)
		return NULL;

	while ((finddata = readdir(finddir)) != NULL)
	{
		if (!fnmatch (findpattern, finddata->d_name, FNM_PATHNAME))
		{
			q_snprintf(matchpath, sizeof(matchpath), "%s/%s", findpath, finddata->d_name);
			if ( (stat(matchpath, &test) == 0)
						&& S_ISREG(test.st_mode))
				return finddata->d_name;
		}
	}

	return NULL;
}

void Sys_FindClose (void)
{
	if (finddir != NULL)
	{
		closedir(finddir);
		finddir = NULL;
	}
	if (findpath != NULL)
	{
		free (findpath);
		findpath = NULL;
	}
	if (findpattern != NULL)
	{
		free (findpattern);
		findpattern = NULL;
	}
}

int Sys_filesize (const char *filename)
{
	struct stat status;

	if (stat(filename, &status) == -1)
		return -1;
	if (! S_ISREG(status.st_mode))
		return -1;

	return (int) status.st_size;
}

int Sys_getcwd (char *buf, size_t size)
{
	if (getcwd(buf, size) == NULL)
		return 1;

	return 0;
}

#endif	/* PLATFORM_UNIX */


/*============================================================================*/

/*========== BYTE ORDER STUFF ================================================*/

#if ENDIAN_RUNTIME_DETECT
#define __byteswap_func static
#else
#define __byteswap_func
#endif

int host_byteorder;
int host_bigendian; /* qboolean */

FUNC_NOINLINE FUNC_NOCLONE
unsigned int get_0x12345678 (void) {
	return 0x12345678;
	/*       U N I X  */
}

int DetectByteorder (void)
{
	volatile union {
		unsigned int i;
		unsigned char c[4];
	} bint;

	bint.i = get_0x12345678 ();

	/*
	BE_ORDER:  12 34 56 78
	           U  N  I  X

	LE_ORDER:  78 56 34 12
	           X  I  N  U

	PDP_ORDER: 34 12 78 56
	           N  U  X  I
	*/

	if (bint.c[0] == 0x12)
		return BIG_ENDIAN;
	if (bint.c[0] == 0x78)
		return LITTLE_ENDIAN;
	if (bint.c[0] == 0x34)
		return PDP_ENDIAN;

	return -1;
}

__byteswap_func
int LongSwap (int l)
{
	unsigned char	b1, b2, b3, b4;

	b1 = l & 255;
	b2 = (l>>8 ) & 255;
	b3 = (l>>16) & 255;
	b4 = (l>>24) & 255;

	return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
}

#if ENDIAN_RUNTIME_DETECT

__byteswap_func
int LongNoSwap (int l)
{
	return l;
}

int   (*BigLong) (int);
int   (*LittleLong) (int);

#endif /* ENDIAN_RUNTIME_DETECT */

void ByteOrder_Init (void)
{
	host_byteorder = DetectByteorder ();
	host_bigendian = (host_byteorder == BIG_ENDIAN);

#if ENDIAN_RUNTIME_DETECT
	switch (host_byteorder)
	{
	case BIG_ENDIAN:
		BigLong = LongNoSwap;
		LittleLong = LongSwap;
		break;

	case LITTLE_ENDIAN:
		BigLong = LongSwap;
		LittleLong = LongNoSwap;
		break;

	default:
		break;
	}
#endif /* ENDIAN_RUNTIME_DETECT */
}

/* call this from your main() */
void ValidateByteorder (void)
{
	const char	*endianism[] = { "BE", "LE", "PDP", "Unknown" };
	const char	*tmp;

	ByteOrder_Init ();
	switch (host_byteorder)
	{
	case BIG_ENDIAN:
		tmp = endianism[0];
		break;
	case LITTLE_ENDIAN:
		tmp = endianism[1];
		break;
	case PDP_ENDIAN:
		tmp = endianism[2];
		host_byteorder = -1;	/* not supported */
		break;
	default:
		tmp = endianism[3];
		break;
	}
	if (host_byteorder < 0)
		Error ("Unsupported byte order.");
//	printf("Detected byte order: %s\n", tmp);
#if !ENDIAN_RUNTIME_DETECT
	if (host_byteorder != BYTE_ORDER)
	{
		const char	*tmp2;

		switch (BYTE_ORDER)
		{
		case BIG_ENDIAN:
			tmp2 = endianism[0];
			break;
		case LITTLE_ENDIAN:
			tmp2 = endianism[1];
			break;
		case PDP_ENDIAN:
			tmp2 = endianism[2];
			break;
		default:
			tmp2 = endianism[3];
			break;
		}
		Error ("Detected byte order %s doesn't match compiled %s order!", tmp, tmp2);
	}
#endif	/* ENDIAN_RUNTIME_DETECT */
}

