/*
   Copyright (C) 2003 - 2015 by David White <dave@whitevine.net>
   Part of the Battle for Wesnoth Project http://www.wesnoth.org/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2
   or at your option any later version.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.

   See the COPYING file for more details.
*/

/**
 * @file filesystem_win32.ii
 * Win32 platform-specific filesystem code.
 */

/* /////////////////////////////////////////////////////////////////////////
 * This code swiped from dirent.c in the unixem library, version 1.7.3.
 * See http://synesis.com.au/software/unixem.html for full sources.
 * It's under BSD license.
 */

#include <direct.h>
#include <io.h>
#include <errno.h>
#include <stdlib.h>
#include <windows.h>


/* /////////////////////////////////////////////////////////////////////////
 * Compiler differences
 */

#if defined(__BORLANDC__)
# define DIRENT_PROVIDED_BY_COMPILER
#elif defined(__DMC__)
# define DIRENT_PROVIDED_BY_COMPILER
#elif defined(__GNUC__)
# define DIRENT_PROVIDED_BY_COMPILER
#elif defined(__INTEL_COMPILER)
#elif defined(_MSC_VER)
#elif defined(__MWERKS__)
#elif defined(__WATCOMC__)
#else
# error Compiler not discriminated
#endif /* compiler */

#if defined(DIRENT_PROVIDED_BY_COMPILER)
#include <dirent.h>
#else

/* ////////////////////////////////////////////////////////////////////// */

#include <stddef.h>

#ifndef NAME_MAX
# define NAME_MAX   (260)
#endif /* !NAME_MAX */

struct dirent
{
	char	d_name[NAME_MAX + 1];	   /*!< file name (null-terminated) */
	int 	d_mode;
};

struct DIR
{
	char			  directory[_MAX_DIR+1];   /* . */
	WIN32_FIND_DATAA  find_data;			   /* The Win32 FindFile data. */
	HANDLE			  hFind;				   /* The Win32 FindFile handle. */
	struct dirent	  dirent;				   /* The handle's entry. */
};

#ifndef FILE_ATTRIBUTE_ERROR
# define FILE_ATTRIBUTE_ERROR			(0xFFFFFFFF)
#endif /* FILE_ATTRIBUTE_ERROR */

/* /////////////////////////////////////////////////////////////////////////
 * Helper functions
 */

static HANDLE dirent__findfile_directory(char const *name, LPWIN32_FIND_DATAA data)
{
	char	search_spec[_MAX_PATH +1];

	// Simply add the *.*, ensuring the path separator is included.
	(void)lstrcpyA(search_spec, name);
	if( '\\' != search_spec[lstrlenA(search_spec) - 1] &&
		'/' != search_spec[lstrlenA(search_spec) - 1])
	{
		(void)lstrcatA(search_spec, "\\*.*");
	}
	else
	{
		(void)lstrcatA(search_spec, "*.*");
	}

	return FindFirstFileA(search_spec, data);
}

/* /////////////////////////////////////////////////////////////////////////
 * API functions
 */

DIR *opendir(char const *name)
{
	DIR 	*result =	NULL;
	DWORD	dwAttr;

	// Must be a valid name
	if( !name ||
		!*name ||
		(dwAttr = GetFileAttributes(name)) == 0xFFFFFFFF)
	{
		errno = ENOENT;
	}
	// Must be a directory
	else if(!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
	{
		errno = ENOTDIR;
	}
	else
	{
		result = (DIR*)malloc(sizeof(DIR));

		if(result == NULL)
		{
			errno = ENOMEM;
		}
		else
		{
			result->hFind=dirent__findfile_directory(name, &result->find_data);

			if(result->hFind == INVALID_HANDLE_VALUE)
			{
				free(result);

				result = NULL;
			}
			else
			{
				// Save the directory, in case of rewind.
				(void)lstrcpyA(result->directory, name);
				(void)lstrcpyA(result->dirent.d_name, result->find_data.cFileName);
				result->dirent.d_mode	=	(int)result->find_data.dwFileAttributes;
			}
		}
	}

	return result;
}

int closedir(DIR *dir)
{
	int ret;

	if(dir == NULL)
	{
		errno = EBADF;

		ret = -1;
	}
	else
	{
		// Close the search handle, if not already done.
		if(dir->hFind != INVALID_HANDLE_VALUE)
		{
			(void)FindClose(dir->hFind);
		}

		free(dir);

		ret = 0;
	}

	return ret;
}

struct dirent *readdir(DIR *dir)
{
	// The last find exhausted the matches, so return NULL.
	if(dir->hFind == INVALID_HANDLE_VALUE)
	{
		if(FILE_ATTRIBUTE_ERROR == dir->find_data.dwFileAttributes)
		{
			errno = EBADF;
		}
		else
		{
			dir->find_data.dwFileAttributes = FILE_ATTRIBUTE_ERROR;
		}

		return NULL;
	}
	else
	{
		// Copy the result of the last successful match to dirent.
		(void)lstrcpyA(dir->dirent.d_name, dir->find_data.cFileName);

		// Attempt the next match.
		if(!FindNextFileA(dir->hFind, &dir->find_data))
		{
			// Exhausted all matches, so close and null the handle.
			(void)FindClose(dir->hFind);
			dir->hFind = INVALID_HANDLE_VALUE;
		}

		return &dir->dirent;
	}
}

/*
 * Microsoft C uses _stat instead of stat,
 * for both the function name and the structure name.
 * See <http://svn.ghostscript.com:8080/ghostscript/trunk/gs/src/stat_.h>
 */
#ifdef _MSC_VER
#  define stat _stat
namespace {
	typedef int mode_t;
}
#endif

#ifndef S_IFMT
#define S_IFMT	(S_IFDIR|S_IFREG)
#endif
#ifndef S_ISREG
#define S_ISREG(x) (((x) & S_IFMT) == S_IFREG)
#endif
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
#endif

#endif /* !DIRENT_PROVIDED_BY_COMPILER */

#define mkdir(a,b) (_mkdir(a))
