// vim: set ts=4 sw=4 tw=99 noet:
//
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
// Copyright (C) The AMX Mod X Development Team.
//
// This software is licensed under the GNU General Public License, version 3 or higher.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
//     https://alliedmods.net/amxmodx-license

#include "amxmodx.h"
#include "CFileSystem.h"
#include "CLibrarySys.h"

using namespace ke;

// native read_dir(const dirname[], pos, output[], len, &outlen = 0);
static cell AMX_NATIVE_CALL read_dir(AMX *amx, cell *params)
{
	int length;
	const char* path = get_amxstring(amx, params[1], 0, length);
	const char* realpath = build_pathname("%s", path);

	AutoPtr<CDirectory> dir(g_LibSys.OpenDirectory(realpath));

	if (!dir)
	{
		return 0;
	}

	cell offset = Max(0, params[2]);

	if (offset >= 0)
	{
#if defined PLATFORM_WINDOWS
		// Should be declared after so entry starts to '.' and not '..'
		// But old code did that, so keep this for compatibility.
		++offset;

		for (cell i = 0; i < offset && dir->MoreFiles(); ++i)
		{
			dir->NextEntry();
		}

#elif defined PLATFORM_POSIX

		seekdir(dir->GetHandle(), offset);

		dir->NextEntry();

		if (dir->IsValid())
		{
			offset = telldir(dir->GetHandle());
		}
#endif
	}

	if (!dir->IsValid())
	{
		return 0;
	}

	const char* entry = dir->GetEntryName();
	cell* outputLen = get_amxaddr(amx, params[5]);

	*outputLen = set_amxstring_utf8(amx, params[3], entry, strlen(entry), params[4]);

	return offset;
}

// native read_file(const file[], line, text[], len, &txtlen = 0);
static cell AMX_NATIVE_CALL read_file(AMX *amx, cell *params)
{
	int length;
	const char* path = get_amxstring(amx, params[1], 0, length);
	const char* realpath = build_pathname("%s", path);

	AutoPtr<SystemFile> fp(SystemFile::Open(realpath, "r"));

	if (!fp)
	{
		LogError(amx, AMX_ERR_NATIVE, "Couldn't read file \"%s\"", path);
		return 0;
	}

	static char buffer[2048];

	size_t currentLine = 0;
	size_t targetLine = Max(0, params[2]);

	while (currentLine <= targetLine && fp->ReadLine(buffer, sizeof(buffer) - 1))
	{
		++currentLine;
	}

	if (currentLine > targetLine)
	{
		length = strlen(buffer);

		if (buffer[length - 1] == '\n')
			buffer[--length] = '\0';

		if (buffer[length - 1] == '\r')
			buffer[--length] = '\0';

		cell* textLen = get_amxaddr(amx, params[5]);
		*textLen = set_amxstring_utf8(amx, params[3], buffer, length, params[4]);

		return currentLine;
	}

	return 0;
}

// native write_file(const file[], const text[], line = -1);
static cell AMX_NATIVE_CALL write_file(AMX *amx, cell *params)
{
	int length;
	const char* path = get_amxstring(amx, params[1], 0, length);
	const char* text = get_amxstring(amx, params[2], 1, length);
	int targetLine = params[3];

	const char* realpath = build_pathname("%s", path);

	AutoPtr<SystemFile>fp;

	if (targetLine < 0)
	{
		if (!(fp = SystemFile::Open(realpath, "a")))
		{
			LogError(amx, AMX_ERR_NATIVE, "Couldn't write file \"%s\"", realpath);
			return 0;
		}

		fp->Write(text, length);
		fp->Write("\n", 1);

		return 1;
	}

	if (!(fp = SystemFile::Open(realpath, "r")))
	{
		if (!(fp = SystemFile::Open(realpath, "w")))
		{
			LogError(amx, AMX_ERR_NATIVE, "Couldn't write file \"%s\"", realpath);
			return 0;
		}

		for (int i = 0; i < targetLine; ++i)
		{
			fp->Write("\n", 1);
		}

		fp->Write(text, length);
		fp->Write("\n", 1);

		return 1;
	}

	SystemFile fptemp(tmpfile());

	if (!fptemp.handle())
	{
		LogError(amx, AMX_ERR_NATIVE, "Couldn't create temp file");
		return 0;
	}

	static char buffer[2048];

	for (int i = 0;; ++i)
	{
		if (i == targetLine)
		{
			fp->ReadLine(buffer, sizeof(buffer) - 1);

			fptemp.Write(text, length);
			fptemp.Write("\n", 1);
		}
		else if (fp->ReadLine(buffer, sizeof(buffer) - 1))
		{
			fptemp.Write(buffer, strlen(buffer));
		}
		else if (i < targetLine)
		{
			fptemp.Write("\n", 1);
		}
		else
		{
			break;
		}
	}

	rewind(fptemp.handle());

	if (!(fp = AutoPtr<SystemFile>(SystemFile::Open(realpath, "w"))))
	{
		LogError(amx, AMX_ERR_NATIVE, "Couldn't write file \"%s\"", realpath);
		return 0;
	}

	while (fptemp.ReadLine(buffer, sizeof(buffer) - 1))
	{
		fp->Write(buffer, strlen(buffer));
	}

	return 1;
}

// native delete_file(const file[], bool:use_valve_fs = false, const valve_path_id[] = "GAMECONFIG");
static cell AMX_NATIVE_CALL delete_file(AMX *amx, cell *params)
{
	int length;
	const char* file = get_amxstring(amx, params[1], 0, length);

	if (*params / sizeof(cell) >= 2 && params[2] > 0)
	{
		const char* pathID = get_amxstring_null(amx, params[3], 1, length);

		return ValveFile::Delete(file, pathID);
	}

	return SystemFile::Delete(build_pathname("%s", file));
}

// native file_exists(const file[], bool:use_valve_fs = false);
static cell AMX_NATIVE_CALL file_exists(AMX *amx, cell *params)
{
	int length;
	const char* file = get_amxstring(amx, params[1], 0, length);

	if (*params / sizeof(cell) >= 2 && params[2] > 0)
	{
		return g_FileSystem->FileExists(file);
	}

	return g_LibSys.IsPathFile(build_pathname("%s", file));
}

// native dir_exists(const dir[], bool:use_valve_fs = false);
static cell AMX_NATIVE_CALL dir_exists(AMX *amx, cell *params)
{
	int length;
	const char *path = get_amxstring(amx, params[1], 0, length);

	if (*params / sizeof(cell) >= 2 && params[2] > 0)
	{
		return g_FileSystem->IsDirectory(path);
	}

	return g_LibSys.IsPathDirectory(build_pathname("%s", path));
}

#define FSOPT_BYTES_COUNT  0
#define FSOPT_LINES_COUNT  1
#define FSOPT_END_WITH_LF  2

// native file_size(const file[], flag = 0, bool:use_valve_fs = false, const valve_path_id[] = "GAME");
static cell AMX_NATIVE_CALL file_size(AMX *amx, cell *params)
{
	int length;
	const char* path = get_amxstring(amx, params[1], 0, length);
	int flag = FSOPT_BYTES_COUNT;

	AutoPtr<FileObject> fp;

	size_t numParams = *params / sizeof(cell);

	if (numParams >= 3 && params[3] > 0)
	{
		const char* pathID = get_amxstring_null(amx, params[4], 1, length);

		fp = ValveFile::Open(path, "r", pathID);
	}
	else
	{
		fp = SystemFile::Open(build_pathname("%s", path), "r");
	}

	if (!fp)
	{
		return -1;
	}

	if (numParams >= 2)
	{
		flag = params[2];
	}

	switch (flag)
	{
		case FSOPT_BYTES_COUNT:
		{
			fp->Seek(0, SEEK_END);

			return fp->Tell();
		}
		case FSOPT_LINES_COUNT:
		{
			int8_t ch = 0;
			size_t lines = 0;

			while (!fp->EndOfFile() && !fp->HasError())
			{
				++lines;
				while (fp->Read(&ch, 1) == 1 && ch != '\n' && ch != EOF);
			}

			return lines;
		}
		case FSOPT_END_WITH_LF:
		{
			int8_t ch = 0;

			fp->Seek(-1, SEEK_END);
			fp->Read(&ch, 1);

			return ch == '\n';
		}
	}

	return -1;
}

// native fopen(const filename[], const mode[], bool:use_valve_fs = false, const valve_path_id[] = "GAME");
static cell AMX_NATIVE_CALL amx_fopen(AMX *amx, cell *params)
{
	int length;
	const char* file  = get_amxstring(amx, params[1], 0, length);
	const char* flags = get_amxstring(amx, params[2], 1, length);

	FileObject* fp = nullptr;

	if (*params / sizeof(cell) >= 3 && params[3] > 0)
	{
		const char* pathID = get_amxstring_null(amx, params[4], 2, length);

		fp = ValveFile::Open(file, flags, pathID);
	}
	else
	{
		fp = SystemFile::Open(build_pathname("%s", file), flags);
	}

	if (!fp)
	{
		return 0;
	}

	return reinterpret_cast<cell>(fp);
}

#define BLOCK_INT	4
#define BLOCK_SHORT	2
#define BLOCK_CHAR	1

// native fwrite_blocks(file, const data[], blocks, mode);
static cell AMX_NATIVE_CALL amx_fwrite_blocks(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	cell* data  = get_amxaddr(amx, params[2]);
	cell blocks = params[3];
	cell size   = params[4];

	size_t read = 0;

	switch (size)
	{
		case BLOCK_CHAR:
		{
			for (cell i = 0; i < blocks; ++i)
			{
				char value = data[i];

				if (fp->Write(&value, sizeof(value)) != sizeof(value))
				{
					break;
				}

				read += sizeof(value);
			}
			break;
		}
		case BLOCK_SHORT:
		{
			for (cell i = 0; i < blocks; ++i)
			{
				short value = data[i];

				if (fp->Write(&value, sizeof(value)) != sizeof(value))
				{
					break;
				}

				read += sizeof(value);
			}
			break;
		}
		case BLOCK_INT:
		{
			read = fp->Write(data, sizeof(cell) * blocks);
			break;
		}
		default:
		{
			return 0;
		}
	}

	return read / size;
}

// native fwrite(file, data, mode);
static cell AMX_NATIVE_CALL amx_fwrite(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	cell   data = params[2];
	size_t size = params[3];

	switch (size)
	{
		case BLOCK_CHAR:
		{
			char value = static_cast<char>(data);
			return fp->Write(&value, sizeof(value));
		}
		case BLOCK_SHORT:
		{
			short value = static_cast<short>(data);
			return fp->Write(&value, sizeof(value));
		}
		case BLOCK_INT:
		{
			int value = static_cast<int>(data);
			return fp->Write(&value, sizeof(value));
		}
	}

	return 0;
}

// native fwrite_raw(file, const stream[], blocksize, mode);
static cell AMX_NATIVE_CALL amx_fwrite_raw(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	cell* data = get_amxaddr(amx, params[2]);

	return fp->Write(&data, params[3] * params[4]);
}

// native fread_raw(file, stream[], blocksize, blocks);
static cell AMX_NATIVE_CALL amx_fread_raw(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	cell* data = get_amxaddr(amx, params[2]);

	return fp->Read(data, params[3] * params[4]);
}

// native fread(file, &data, mode);
static cell AMX_NATIVE_CALL amx_fread(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	cell *data = get_amxaddr(amx, params[2]);

	switch (params[3])
	{
		case BLOCK_CHAR:
		{
			char value;
			size_t res = fp->Read(&value, sizeof(value));
			*data = static_cast<cell>(value);
			return res;
		}
		case BLOCK_SHORT:
		{
			short value;
			size_t res = fp->Read(&value, sizeof(value));
			*data = static_cast<cell>(value);
			return res;
		}
		case BLOCK_INT:
		{
			int value;
			size_t res = fp->Read(&value, sizeof(value));
			*data = static_cast<cell>(value);
			return res;
		}
	}

	return 0;
}

// native fread_blocks(file, data[], blocks, mode);
static cell AMX_NATIVE_CALL amx_fread_blocks(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	cell *data  = get_amxaddr(amx, params[2]);
	cell blocks = params[3];
	cell size   = params[4];

	size_t read = 0;

	switch (size)
	{
		case BLOCK_CHAR:
		{
			for (cell i = 0; i < blocks; ++i)
			{
				char value;

				if (fp->Read(&value, sizeof(value)) != sizeof(value))
				{
					break;
				}

				read += sizeof(value);
				*data++ = value;
			}
			break;
		}
		case BLOCK_SHORT:
		{
			for (cell i = 0; i < blocks; ++i)
			{
				short value;

				if (fp->Read(&value, sizeof(value)) != sizeof(value))
				{
					break;
				}

				read += sizeof(value);
				*data++ = value;
			}
			break;
		}
		case BLOCK_INT:
		{
			read = fp->Read(data, sizeof(cell) * blocks);
			break;
		}
		default:
		{
			return 0;
		}
	}

	return read / size;
}

// native fputs(file, const text[], bool:null_term = false);
static cell AMX_NATIVE_CALL amx_fputs(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	int length;
	char *string = get_amxstring(amx, params[2], 0, length);

	if (*params / sizeof(cell) >= 3 && params[3] > 0)
	{
		++length;
	}

	if (fp->Write(string, length) != (size_t)length)
	{
		return -1;
	}

	return 0;
}

// native fgets(file, buffer[], maxlength);
static cell AMX_NATIVE_CALL amx_fgets(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	static char buffer[4096];
	buffer[0] = '\0';

	fp->ReadLine(buffer, sizeof(buffer) - 1);

	return set_amxstring_utf8(amx, params[2], buffer, strlen(buffer), params[3]);
}

// native fseek(file, position, start);
static cell AMX_NATIVE_CALL amx_fseek(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	return !fp->Seek(params[2], params[3]);
}

// native ftell(file);
static cell AMX_NATIVE_CALL amx_ftell(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	return fp->Tell();
}

// native fprintf(file, const fmt[], any:...);
static cell AMX_NATIVE_CALL amx_fprintf(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	int length;
	const char* string = format_amxstring(amx, params, 2, length);

	if (ValveFile *vfile = fp->AsValveFile())
	{
		return g_FileSystem->FPrintf(vfile->handle(), const_cast<char*>("%s"), string);
	}
	else if (SystemFile *sysfile = fp->AsSystemFile())
	{
		return fprintf(sysfile->handle(), "%s", string);
	}
	else
	{
		assert(false);
	}

	return 0;
}

// native feof(file);
static cell AMX_NATIVE_CALL amx_feof(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (fp)
	{
		return fp->EndOfFile();
	}

	return 1;
}

// native fclose(file);
static cell AMX_NATIVE_CALL amx_fclose(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (fp)
	{
		fp->Close();
	}

	return 1;
}

// native filesize(const filename[], any:...);
static cell AMX_NATIVE_CALL amx_filesize(AMX *amx, cell *params)
{
	int length;
	const char *realpath = build_pathname("%s", format_amxstring(amx, params, 1, length));

	AutoPtr<SystemFile> fp(SystemFile::Open(realpath, "rb"));

	if (fp)
	{
		fp->Seek(0, SEEK_END);

		return fp->Tell();
	}

	return -1;
}

// Undocumented.
static cell AMX_NATIVE_CALL amx_build_pathname(AMX *amx, cell *params)
{
	int length;
	const char* path = get_amxstring(amx, params[1], 0, length);

	return set_amxstring(amx, params[2], build_pathname("%s", path), params[3]);
}

enum FileType
{
	FileType_Unknown,       /* Unknown file type (device/socket) */
	FileType_Directory,     /* File is a directory */
	FileType_File,	        /* File is a file */
};

struct DirectoryHandle
{
	DirectoryHandle(CDirectory *handle_) : handle(handle_), valvefs(false) {}
	DirectoryHandle(FileFindHandle_t handle_) : handle_vfs(handle_), valvefs(true) {}

	union
	{
		CDirectory*      handle;
		FileFindHandle_t handle_vfs;
	};

	bool valvefs;
};

// native open_dir(dir[], firstfile[], length, &FileType:type = FileType_Unknown, bool:use_valve_fs=false, const valve_path_id[] = "GAME");
static cell AMX_NATIVE_CALL amx_open_dir(AMX *amx, cell *params)
{
	int length;
	const char* path = get_amxstring(amx, params[1], 0, length);

	if (!*path)
	{
		return 0;
	}

	size_t numParams = *params / sizeof(cell);

	if (numParams >= 4 && params[5] > 0)
	{
		const char* wildcardedPath = g_LibSys.PathFormat("%s%s*", path, (path[length - 1] != '/' && path[length - 1] != '\\') ? "/" : "");
		const char* pathID = get_amxstring_null(amx, params[6], 1, length);

		FileFindHandle_t handle;
		const char* pFirst = g_FileSystem->FindFirst(wildcardedPath, &handle, pathID);

		if (!pFirst)
		{
			return 0;
		}

		set_amxstring_utf8(amx, params[2], pFirst, strlen(pFirst), params[3]);
		cell* fileType = get_amxaddr(amx, params[4]);

		*fileType = g_FileSystem->FindIsDirectory(handle) ? FileType_Directory : FileType_File;

		return reinterpret_cast<cell>(new DirectoryHandle(handle));
	}

	CDirectory* dir = g_LibSys.OpenDirectory(build_pathname("%s", path));

	if (!dir)
	{
		return 0;
	}

	if (numParams >= 4)
	{
		cell* fileType = get_amxaddr(amx, params[4]);
		*fileType = dir->IsEntryDirectory() ? FileType_Directory : FileType_File;
	}

	const char* entry = dir->GetEntryName();
	set_amxstring_utf8(amx, params[2], entry, strlen(entry), params[3]);

	return reinterpret_cast<cell>(new DirectoryHandle(dir));
}

// native close_dir(dirh);
static cell AMX_NATIVE_CALL amx_close_dir(AMX *amx, cell *params)
{
	AutoPtr<DirectoryHandle> p(reinterpret_cast<DirectoryHandle*>(params[1]));

	if (!p)
	{
		return 0;
	}

	if (p->valvefs)
	{
		FileFindHandle_t handle = p->handle_vfs;
		g_FileSystem->FindClose(handle);
	}
	else
	{
		CDirectory* handle = reinterpret_cast<CDirectory*>(p->handle);
		g_LibSys.CloseDirectory(handle);
	}

	return 1;
}

// native next_file(dirh, buffer[], length, &FileType:type = FileType_Unknown);
static cell AMX_NATIVE_CALL amx_get_dir(AMX *amx, cell *params)
{
	DirectoryHandle* p = reinterpret_cast<DirectoryHandle*>(params[1]);

	if (!p)
	{
		return 0;
	}

	size_t numParams = *params / sizeof(cell);

	if (p->valvefs)
	{
		FileFindHandle_t handle = p->handle_vfs;

		const char* entry = g_FileSystem->FindNext(handle);

		if (!entry)
		{
			return 0;
		}

		if (numParams >= 4)
		{
			cell* fileType = get_amxaddr(amx, params[4]);
			*fileType = g_FileSystem->FindIsDirectory(handle) ? FileType_Directory : FileType_File;
		}

		set_amxstring_utf8(amx, params[2], entry, strlen(entry), params[3]);
	}
	else
	{
		CDirectory* handle = p->handle;

		if (!handle)
		{
			return 0;
		}

		handle->NextEntry();

		if (!handle->MoreFiles())
		{
			return 0;
		}

		if (numParams >= 4)
		{
			cell* fileType = get_amxaddr(amx, params[4]);
			*fileType = handle->IsEntryDirectory() ? FileType_Directory : FileType_File;
		}

		const char* entry = handle->GetEntryName();

		set_amxstring_utf8(amx, params[2], entry, strlen(entry), params[3]);
	}

	return 1;
}

//native fgetc(file);
static cell AMX_NATIVE_CALL amx_fgetc(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	uint8_t val;

	if (fp->Read(&val, sizeof(val)) != sizeof(val))
	{
		return -1;
	}

	return static_cast<cell>(val);
}

//native fputc(file, data);
static cell AMX_NATIVE_CALL amx_fputc(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	uint8_t val = static_cast<uint8_t>(params[2]);

	if (fp->Write(&val, sizeof(val)) != sizeof(val))
	{
		return -1;
	}

	return val;
}

//native ungetc(file, data);
static cell AMX_NATIVE_CALL amx_ungetc(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	SystemFile* sysfile = fp->AsSystemFile();

	if (!sysfile)
	{
		LogError(amx, AMX_ERR_NATIVE, "Can not ungetc to file in the Valve file system");
		return  0;
	}

	return ungetc(static_cast<int>(params[2]), sysfile->handle());
}

// native rmdir(const path[]);
static cell AMX_NATIVE_CALL amx_rmdir(AMX *amx, cell *params)
{
	int length;
	const char* realpath = build_pathname("%s", get_amxstring(amx, params[1], 0, length));

	return rmdir(realpath) == 0;
}

// native mkdir(const dirname[], mode, bool:use_valve_fs = false, const valve_path_id[] = "GAMECONFIG");
static cell AMX_NATIVE_CALL amx_mkdir(AMX *amx, cell *params)
{
	int length;
	char *path = get_amxstring(amx, params[1], 0, length);

	size_t numParams = *params / sizeof(cell);

	if (numParams >= 3 && params[3] > 0)
	{
		const char* pathID = get_amxstring_null(amx, params[4], 1, length);

		if (g_FileSystem->IsDirectory(path))
		{
			return -1;
		}

		g_FileSystem->CreateDirHierarchy(path, pathID);

		if (g_FileSystem->IsDirectory(path))
		{
			return 0;
		}
	}

	const char* realpath = build_pathname("%s", path);

#if defined PLATFORM_WINDOWS

	return mkdir(realpath);
#else
	if (numParams >= 2)
	{
		return mkdir(realpath, params[2]);
	}

	return mkdir(realpath, 0755);
#endif
}

// native rename_file(const oldname[], const newname[], relative = 0);
static cell AMX_NATIVE_CALL rename_file(AMX *amx, cell *params)
{
	int length;
	char file_old_r[PLATFORM_MAX_PATH];
	char file_new_r[PLATFORM_MAX_PATH];

	const char* file_old = get_amxstring(amx, params[1], 0, length);
	const char* file_new = get_amxstring(amx, params[2], 1, length);

	if (params[0] / sizeof(cell) >= 3 && params[3] > 0)
	{
		build_pathname_r(file_old_r, sizeof(file_old_r), "%s", file_old);
		build_pathname_r(file_new_r, sizeof(file_new_r), "%s", file_new);
	}
	else
	{
		g_LibSys.PathFormat(file_old_r, sizeof(file_old_r) - 1, "%s", file_old);
		g_LibSys.PathFormat(file_new_r, sizeof(file_new_r) - 1, "%s", file_new);
	}

#if defined PLATFORM_POSIX

	return (rename(file_old_r, file_new_r) == 0);

#elif defined PLATFORM_WINDOWS

	return MoveFileA(file_old_r, file_new_r);
#endif
}

// native LoadFileForMe(const file[], buffer[], maxlength, &length = 0);
static cell LoadFileForMe(AMX *amx, cell *params)
{
	int length;
	char *file = get_amxstring(amx, params[1], 0, length);
	char path[PLATFORM_MAX_PATH];

	build_pathname_r(path, sizeof(path), "%s", file);

	byte* addr = LOAD_FILE_FOR_ME(path, &length);

	if (!addr)
	{
		return -1;
	}

	cell *buffer = get_amxaddr(amx, params[2]);
	cell maxlength = params[3];
	cell *bytes_avail = get_amxaddr(amx, params[4]);

	*bytes_avail = length;

	cell count;

	for (count = 0; count < length && count < maxlength; count++)
	{
		buffer[count] = addr[count];
	}

	FREE_FILE(addr);

	return count;
}

// native fflush(file);
static cell AMX_NATIVE_CALL amx_fflush(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (fp)
	{
		return fp->Flush();
	}

	return -1;
}

// native GetFileTime(const file[], FileTimeType:tmode);
static cell AMX_NATIVE_CALL GetFileTime(AMX *amx, cell *params)
{
	int length;
	const char* file = get_amxstring(amx, params[1], 0, length);

	time_t time_val;

	if (!g_LibSys.FileTime(build_pathname("%s", file), static_cast<FileTimeType>(params[2]), &time_val))
	{
		return -1;
	}

	return static_cast<cell>(time_val);
}

#define FPERM_U_READ       0x0100   /* User can read.    */
#define FPERM_U_WRITE      0x0080   /* User can write.   */
#define FPERM_U_EXEC       0x0040   /* User can exec.    */
#define FPERM_G_READ       0x0020   /* Group can read.   */
#define FPERM_G_WRITE      0x0010   /* Group can write.  */
#define FPERM_G_EXEC       0x0008   /* Group can exec.   */
#define FPERM_O_READ       0x0004   /* Anyone can read.  */
#define FPERM_O_WRITE      0x0002   /* Anyone can write. */
#define FPERM_O_EXEC       0x0001   /* Anyone can exec.  */

// native bool:SetFilePermissions(const path[], int mode);
static cell SetFilePermissions(AMX *amx, cell *params)
{
	int length;
	const char* realpath = build_pathname(get_amxstring(amx, params[1], 0, length));

#if defined PLATFORM_WINDOWS

	int mask = 0;

	if (params[2] & (FPERM_U_WRITE | FPERM_G_WRITE | FPERM_O_WRITE))
	{
		mask |= _S_IWRITE;
	}

	if (params[2] & (FPERM_U_READ | FPERM_G_READ | FPERM_O_READ | FPERM_U_EXEC | FPERM_G_EXEC | FPERM_O_EXEC))
	{
		mask |= _S_IREAD;
	}

	return _chmod(realpath, mask) == 0;
#else
	return chmod(realpath, params[2]) == 0;
#endif
}

template <typename T>
static cell File_ReadTyped(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	cell* data = get_amxaddr(amx, params[2]);

	T value;

	if (fp->Read(&value, sizeof(value)) != sizeof(value))
	{
		return 0;
	}

	*data = value;
	return 1;
}

template <typename T>
static cell File_WriteTyped(AMX *amx, cell *params)
{
	FileObject* fp = reinterpret_cast<FileObject*>(params[1]);

	if (!fp)
	{
		return 0;
	}

	T value = static_cast<T>(params[2]);

	return !!(fp->Write(&value, sizeof(value)) == sizeof(value));
}

AMX_NATIVE_INFO file_Natives[] =
{
	{"read_dir",		read_dir},
	{"read_file",		read_file},
	{"write_file",		write_file},
	{"rename_file",		rename_file},
	{"delete_file",		delete_file},
	{"unlink",			delete_file},

	{"file_exists",		file_exists},
	{"file_size",		file_size},
	{"filesize",		amx_filesize},

	{"fopen",			amx_fopen},
	{"fclose",			amx_fclose},

	{"fread",			amx_fread},
	{"fread_blocks",	amx_fread_blocks},
	{"fread_raw",		amx_fread_raw},

	{"fwrite",			amx_fwrite},
	{"fwrite_blocks",	amx_fwrite_blocks},
	{"fwrite_raw",		amx_fwrite_raw},

	{"feof",			amx_feof},
	{"fprintf",			amx_fprintf},
	{"fgets",			amx_fgets},
	{"fseek",			amx_fseek},
	{"ftell",			amx_ftell},
	{"fgetc",			amx_fgetc},
	{"fputc",			amx_fputc},
	{"fungetc",			amx_ungetc},
	{"fputs",			amx_fputs},
	{"fflush",			amx_fflush},

	{"build_pathname",	amx_build_pathname},

	{"dir_exists",		dir_exists},
	{"open_dir",		amx_open_dir},
	{"close_dir",		amx_close_dir},
	{"next_file",		amx_get_dir},
	{"rmdir",			amx_rmdir},
	{"mkdir",			amx_mkdir},

	{"LoadFileForMe",		LoadFileForMe},
	{"GetFileTime",			GetFileTime},
	{"SetFilePermissions",	SetFilePermissions},
	{"FileReadInt8",		File_ReadTyped<int8_t>},
	{"FileReadUint8",		File_ReadTyped<uint8_t>},
	{"FileReadInt16",		File_ReadTyped<int16_t>},
	{"FileReadUint16",		File_ReadTyped<uint16_t>},
	{"FileReadInt32",		File_ReadTyped<int32_t>},
	{"FileWriteInt8",		File_WriteTyped<int8_t>},
	{"FileWriteInt16",		File_WriteTyped<int16_t>},
	{"FileWriteInt32",		File_WriteTyped<int32_t>},

	{NULL,				NULL}
};
