/*-------------------------------------------------------------------------
 * drawElements Utility Library
 * ----------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief File abstraction.
 *//*--------------------------------------------------------------------*/

#include "deFile.h"
#include "deMemory.h"

#if (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_OSX) || (DE_OS == DE_OS_IOS) || (DE_OS == DE_OS_ANDROID) || (DE_OS == DE_OS_SYMBIAN)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

struct deFile_s
{
	int fd;
};

deBool deFileExists (const char* filename)
{
	struct stat st;
	int result = stat(filename, &st);
	return result == 0;
}

deBool deDeleteFile (const char* filename)
{
	return unlink(filename) == 0;
}

deFile* deFile_createFromHandle (deUintptr handle)
{
	int		fd		= (int)handle;
	deFile* file	= (deFile*)deCalloc(sizeof(deFile));
	if (!file)
	{
		close(fd);
		return file;
	}

	file->fd = fd;
	return file;
}

static int mapOpenMode (deFileMode mode)
{
	int flag = 0;

	/* Read, write or read and write access is required. */
	DE_ASSERT((mode & DE_FILEMODE_READ) != 0 || ((mode & DE_FILEMODE_WRITE) != 0));

	/* Create, open or create and open mode is required. */
	DE_ASSERT((mode & DE_FILEMODE_OPEN) != 0 || ((mode & DE_FILEMODE_CREATE) != 0));

	/* Require write when using create. */
	DE_ASSERT(!(mode & DE_FILEMODE_CREATE) || (mode & DE_FILEMODE_WRITE));

	/* Require write and open when using truncate */
	DE_ASSERT(!(mode & DE_FILEMODE_TRUNCATE) || ((mode & DE_FILEMODE_WRITE) && (mode & DE_FILEMODE_OPEN)));

	if (mode & DE_FILEMODE_READ)
		flag |= O_RDONLY;

	if (mode & DE_FILEMODE_WRITE)
		flag |= O_WRONLY;

	if (mode & DE_FILEMODE_TRUNCATE)
		flag |= O_TRUNC;

	if (mode & DE_FILEMODE_CREATE)
		flag |= O_CREAT;

	if (!(mode & DE_FILEMODE_OPEN))
		flag |= O_EXCL;

	return flag;
}

deFile* deFile_create (const char* filename, deUint32 mode)
{
	int fd = open(filename, mapOpenMode(mode), 0777);
	if (fd >= 0)
		return deFile_createFromHandle((deUintptr)fd);
	else
		return DE_NULL;
}

void deFile_destroy (deFile* file)
{
	close(file->fd);
	deFree(file);
}

deBool deFile_setFlags (deFile* file, deUint32 flags)
{
	/* Non-blocking. */
	{
		int oldFlags = fcntl(file->fd, F_GETFL, 0);
		int newFlags = (flags & DE_FILE_NONBLOCKING) ? (oldFlags | O_NONBLOCK) : (oldFlags & ~O_NONBLOCK);
		if (fcntl(file->fd, F_SETFL, newFlags) != 0)
			return DE_FALSE;
	}

	/* Close on exec. */
	{
		int oldFlags = fcntl(file->fd, F_GETFD, 0);
		int newFlags = (flags & DE_FILE_CLOSE_ON_EXEC) ? (oldFlags | FD_CLOEXEC) : (oldFlags & ~FD_CLOEXEC);
		if (fcntl(file->fd, F_SETFD, newFlags) != 0)
			return DE_FALSE;
	}

	return DE_TRUE;
}

static int mapSeekPosition (deFilePosition position)
{
	switch (position)
	{
		case DE_FILEPOSITION_BEGIN:		return SEEK_SET;
		case DE_FILEPOSITION_END:		return SEEK_END;
		case DE_FILEPOSITION_CURRENT:	return SEEK_CUR;
		default:
			DE_ASSERT(DE_FALSE);
			return 0;
	}
}

deBool deFile_seek (deFile* file, deFilePosition base, deInt64 offset)
{
	return lseek(file->fd, offset, mapSeekPosition(base)) >= 0;
}

deInt64 deFile_getPosition (const deFile* file)
{
	return lseek(file->fd, 0, SEEK_CUR);
}

deInt64 deFile_getSize (const deFile* file)
{
	deInt64 size	= 0;
	deInt64 curPos	= lseek(file->fd, 0, SEEK_CUR);

	if (curPos < 0)
		return -1;

	if (lseek(file->fd, -1, SEEK_END) < 0)
		return -1;

	size = lseek(file->fd, 0, SEEK_CUR);
	lseek(file->fd, curPos, SEEK_SET);

	return size;
}

static deFileResult mapReadWriteResult (deInt64 numBytes)
{
	if (numBytes > 0)
		return DE_FILERESULT_SUCCESS;
	else if (numBytes == 0)
		return DE_FILERESULT_END_OF_FILE;
	else
		return errno == EAGAIN ? DE_FILERESULT_WOULD_BLOCK : DE_FILERESULT_ERROR;
}

deFileResult deFile_read (deFile* file, void* buf, deInt64 bufSize, deInt64* numReadPtr)
{
	deInt64 numRead = read(file->fd, buf, bufSize);

	if (numReadPtr)
		*numReadPtr = numRead;

	return mapReadWriteResult(numRead);
}

deFileResult deFile_write (deFile* file, const void* buf, deInt64 bufSize, deInt64* numWrittenPtr)
{
	deInt64 numWritten = write(file->fd, buf, bufSize);

	if (numWrittenPtr)
		*numWrittenPtr = numWritten;

	return mapReadWriteResult(numWritten);
}

#elif (DE_OS == DE_OS_WIN32)

#define VC_EXTRALEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

struct deFile_s
{
	HANDLE handle;
};

deBool deFileExists (const char* filename)
{
	return GetFileAttributes(filename) != INVALID_FILE_ATTRIBUTES;
}

deBool deDeleteFile (const char* filename)
{
	return DeleteFile(filename) == TRUE;
}

deFile* deFile_createFromHandle (deUintptr handle)
{
	deFile* file = (deFile*)deCalloc(sizeof(deFile));
	if (!file)
	{
		CloseHandle((HANDLE)handle);
		return file;
	}

	file->handle = (HANDLE)handle;
	return file;
}

deFile* deFile_create (const char* filename, deUint32 mode)
{
	DWORD	access		= 0;
	DWORD	create		= OPEN_EXISTING;
	HANDLE	handle		= DE_NULL;

	/* Read, write or read and write access is required. */
	DE_ASSERT((mode & DE_FILEMODE_READ) != 0 || ((mode & DE_FILEMODE_WRITE) != 0));

	/* Create, open or create and open mode is required. */
	DE_ASSERT((mode & DE_FILEMODE_OPEN) != 0 || ((mode & DE_FILEMODE_CREATE) != 0));

	/* Require write when using create. */
	DE_ASSERT(!(mode & DE_FILEMODE_CREATE) || (mode & DE_FILEMODE_WRITE));

	/* Require write and open when using truncate */
	DE_ASSERT(!(mode & DE_FILEMODE_TRUNCATE) || ((mode & DE_FILEMODE_WRITE) && (mode & DE_FILEMODE_OPEN)));


	if (mode & DE_FILEMODE_READ)
		access |= GENERIC_READ;

	if (mode & DE_FILEMODE_WRITE)
		access |= GENERIC_WRITE;

	if ((mode & DE_FILEMODE_TRUNCATE))
	{
		if ((mode & DE_FILEMODE_CREATE) && (mode & DE_FILEMODE_OPEN))
			create = CREATE_ALWAYS;
		else if (mode & DE_FILEMODE_OPEN)
			create = TRUNCATE_EXISTING;
		else
			DE_ASSERT(DE_FALSE);
	}
	else
	{
		if ((mode & DE_FILEMODE_CREATE) && (mode & DE_FILEMODE_OPEN))
			create = OPEN_ALWAYS;
		else if (mode & DE_FILEMODE_CREATE)
			create = CREATE_NEW;
		else if (mode & DE_FILEMODE_OPEN)
			create = OPEN_EXISTING;
		else
			DE_ASSERT(DE_FALSE);
	}

	handle = CreateFile(filename, access, FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE, DE_NULL, create, FILE_ATTRIBUTE_NORMAL, DE_NULL);
	if (handle == INVALID_HANDLE_VALUE)
		return DE_NULL;

	return deFile_createFromHandle((deUintptr)handle);
}

void deFile_destroy (deFile* file)
{
	CloseHandle(file->handle);
	deFree(file);
}

deBool deFile_setFlags (deFile* file, deUint32 flags)
{
	/* Non-blocking. */
	if (flags & DE_FILE_NONBLOCKING)
		return DE_FALSE; /* Not supported. */

	/* Close on exec. */
	if (!SetHandleInformation(file->handle, HANDLE_FLAG_INHERIT, (flags & DE_FILE_CLOSE_ON_EXEC) ? HANDLE_FLAG_INHERIT : 0))
		return DE_FALSE;

	return DE_TRUE;
}

deBool deFile_seek (deFile* file, deFilePosition base, deInt64 offset)
{
	DWORD	method		= 0;
	LONG	lowBits		= (LONG)(offset & 0xFFFFFFFFll);
	LONG	highBits	= (LONG)((offset >> 32) & 0xFFFFFFFFll);

	switch (base)
	{
		case DE_FILEPOSITION_BEGIN:		method = FILE_BEGIN;	break;
		case DE_FILEPOSITION_END:		method = FILE_END;		break;
		case DE_FILEPOSITION_CURRENT:	method = FILE_CURRENT;	break;
		default:
			DE_ASSERT(DE_FALSE);
			return DE_FALSE;
	}

	return SetFilePointer(file->handle, lowBits, &highBits, method) != INVALID_SET_FILE_POINTER;
}

deInt64 deFile_getPosition (const deFile* file)
{
	LONG	highBits	= 0;
	LONG	lowBits		= SetFilePointer(file->handle, 0, &highBits, FILE_CURRENT);

	return (deInt64)(((deUint64)highBits << 32) | (deUint64)lowBits);
}

deInt64 deFile_getSize (const deFile* file)
{
	DWORD	highBits	= 0;
	DWORD	lowBits		= GetFileSize(file->handle, &highBits);

	return (deInt64)(((deUint64)highBits << 32) | (deUint64)lowBits);
}

static deFileResult mapReadWriteResult (BOOL retVal, DWORD numBytes)
{
	if (retVal && numBytes > 0)
		return DE_FILERESULT_SUCCESS;
	else if (retVal && numBytes == 0)
		return DE_FILERESULT_END_OF_FILE;
	else
	{
		DWORD error = GetLastError();

		if (error == ERROR_HANDLE_EOF)
			return DE_FILERESULT_END_OF_FILE;
		else
			return DE_FILERESULT_ERROR;
	}
}

deFileResult deFile_read (deFile* file, void* buf, deInt64 bufSize, deInt64* numReadPtr)
{
	DWORD	bufSize32	= (DWORD)bufSize;
	DWORD	numRead32	= 0;
	BOOL	result;

	/* \todo [2011-10-03 pyry] 64-bit IO. */
	DE_ASSERT((deInt64)bufSize32 == bufSize);

	result = ReadFile(file->handle, buf, bufSize32, &numRead32, DE_NULL);

	if (numReadPtr)
		*numReadPtr = (deInt64)numRead32;

	return mapReadWriteResult(result, numRead32);
}

deFileResult deFile_write (deFile* file, const void* buf, deInt64 bufSize, deInt64* numWrittenPtr)
{
	DWORD	bufSize32		= (DWORD)bufSize;
	DWORD	numWritten32	= 0;
	BOOL	result;

	/* \todo [2011-10-03 pyry] 64-bit IO. */
	DE_ASSERT((deInt64)bufSize32 == bufSize);

	result = WriteFile(file->handle, buf, bufSize32, &numWritten32, DE_NULL);

	if (numWrittenPtr)
		*numWrittenPtr = (deInt64)numWritten32;

	return mapReadWriteResult(result, numWritten32);
}

#else
#	error Implement deFile for your OS.
#endif