/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      Helper routines to make file.c work on Windows platforms.
 *
 *      By Shawn Hargreaves.
 *
 *      See readme.txt for copyright information.
 */


#ifndef SCAN_DEPEND
   #include <time.h>
   #include <sys/stat.h>
#endif

#include "allegro.h"
#include "winalleg.h"
#include "allegro/internal/aintern.h"

#ifndef ALLEGRO_WINDOWS
#error something is wrong with the makefile
#endif


/* Set to true in if the compiler and version of Windows support wide-character
 * file name functions.
 * TODO: currently we only set this in the SYSTEM_DIRECTX case.
 */
int _al_win_unicode_filenames = FALSE;



/* _al_file_isok:
 *  Helper function to check if it is safe to access a file on a floppy
 *  drive. This really only applies to the DOS library, so we don't bother
 *  with it.
 */
int _al_file_isok(AL_CONST char *filename)
{
   return TRUE;
}



/* _al_file_size_ex:
 *  Measures the size of the specified file.
 */
uint64_t _al_file_size_ex(AL_CONST char *filename)
{
   struct _stat s;
   char tmp[1024];

   if (!_al_win_unicode_filenames) {
      if (_stat(uconvert(filename, U_CURRENT, tmp, U_ASCII, sizeof(tmp)), &s) != 0) {
         *allegro_errno = errno;
         return 0;
      }
   }
   else {
      if (_wstat((wchar_t*)uconvert(filename, U_CURRENT, tmp, U_UNICODE,
                 sizeof(tmp)), &s) != 0) {
         *allegro_errno = errno;
         return 0;
      }
   }

   return s.st_size;
}



/* _al_file_time:
 *  Returns the timestamp of the specified file.
 */
time_t _al_file_time(AL_CONST char *filename)
{
   struct _stat s;
   char tmp[1024];

   if (!_al_win_unicode_filenames) {
      if (_stat(uconvert(filename, U_CURRENT, tmp, U_ASCII, sizeof(tmp)), &s) != 0) {
         *allegro_errno = errno;
         return 0;
      }
   }
   else {
      if (_wstat((wchar_t*)uconvert(filename, U_CURRENT, tmp, U_UNICODE, sizeof(tmp)), &s) != 0) {
         *allegro_errno = errno;
         return 0;
      }
   }

   return s.st_mtime;
}



/* structure for use by the directory scanning routines */
struct FF_DATA
{
   union {
      struct _finddata_t a;
      struct _wfinddata_t w;
   } data;
   long handle;
   int attrib;
};



/* fill_ffblk:
 *  Helper function to fill in an al_ffblk structure.
 */
static void fill_ffblk(struct al_ffblk *info)
{
   struct FF_DATA *ff_data = (struct FF_DATA *) info->ff_data;

   if (!_al_win_unicode_filenames) {
      info->attrib = ff_data->data.a.attrib;
      info->time = ff_data->data.a.time_write;
      info->size = ff_data->data.a.size;

      do_uconvert(ff_data->data.a.name, U_ASCII, info->name, U_CURRENT,
                  sizeof(info->name));
   }
   else {
      info->attrib = ff_data->data.w.attrib;
      info->time = ff_data->data.w.time_write;
      info->size = ff_data->data.w.size;

      do_uconvert((const char*)ff_data->data.w.name, U_UNICODE, info->name,
                  U_CURRENT, sizeof(info->name));
   }
}



/* al_findfirst:
 *  Initiates a directory search.
 */
int al_findfirst(AL_CONST char *pattern, struct al_ffblk *info, int attrib)
{
   struct FF_DATA *ff_data;
   char tmp[1024];

   /* allocate ff_data structure */
   ff_data = _AL_MALLOC(sizeof(struct FF_DATA));

   if (!ff_data) {
      *allegro_errno = ENOMEM;
      return -1;
   }

   /* attach it to the info structure */
   info->ff_data = (void *) ff_data;

   /* Windows defines specific flags for NTFS permissions:
    *   FA_TEMPORARY            0x0100
    *   FA_SPARSE_FILE          0x0200 
    *   FA_REPARSE_POINT        0x0400
    *   FA_COMPRESSED           0x0800
    *   FA_OFFLINE              0x1000
    *   FA_NOT_CONTENT_INDEXED  0x2000
    *   FA_ENCRYPTED            0x4000
    * so we must set them in the mask by default; moreover,
    * in order to avoid problems with flags added in the
    * future, we simply set all bits past the first byte.
    */
   ff_data->attrib = attrib | 0xFFFFFF00;

   /* start the search */
   errno = *allegro_errno = 0;

   if (!_al_win_unicode_filenames) {
      ff_data->handle = _findfirst(uconvert(pattern, U_CURRENT, tmp,
                                            U_ASCII, sizeof(tmp)),
                                            &ff_data->data.a);

      if (ff_data->handle < 0) {
         *allegro_errno = errno;
         _AL_FREE(ff_data);
         info->ff_data = NULL;
         return -1;
      }

      if (ff_data->data.a.attrib & ~ff_data->attrib) {
         if (al_findnext(info) != 0) {
            al_findclose(info);
            return -1;
         }
         else
            return 0;
      }
   }
   else {
      ff_data->handle = _wfindfirst((wchar_t*)uconvert(pattern, U_CURRENT, tmp,
                                                       U_UNICODE, sizeof(tmp)),
                                                       &ff_data->data.w);

      if (ff_data->handle < 0) {
         *allegro_errno = errno;
         _AL_FREE(ff_data);
         info->ff_data = NULL;
         return -1;
      }

      if (ff_data->data.w.attrib & ~ff_data->attrib) {
         if (al_findnext(info) != 0) {
            al_findclose(info);
            return -1;
         }
         else
            return 0;
      }
   }

   fill_ffblk(info);
   return 0;
}



/* al_findnext:
 *  Retrieves the next file from a directory search.
 */
int al_findnext(struct al_ffblk *info)
{
   struct FF_DATA *ff_data = (struct FF_DATA *) info->ff_data;

   if (!_al_win_unicode_filenames) {
      do {
         if (_findnext(ff_data->handle, &ff_data->data.a) != 0) {
            *allegro_errno = errno;
            return -1;
         }
      } while (ff_data->data.a.attrib & ~ff_data->attrib);
   }
   else {
      do {
         if (_wfindnext(ff_data->handle, &ff_data->data.w) != 0) {
            *allegro_errno = errno;
            return -1;
         }
      } while (ff_data->data.w.attrib & ~ff_data->attrib);
   }

   fill_ffblk(info);
   return 0;
}



/* al_findclose:
 *  Cleans up after a directory search.
 */
void al_findclose(struct al_ffblk *info)
{
   struct FF_DATA *ff_data = (struct FF_DATA *) info->ff_data;

   if (ff_data) {
      _findclose(ff_data->handle);
      _AL_FREE(ff_data);
      info->ff_data = NULL;
   }
}



/* _al_drive_exists:
 *  Checks whether the specified drive is valid.
 */
int _al_drive_exists(int drive)
{
   return GetLogicalDrives() & (1 << drive);
}



/* _al_getdrive:
 *  Returns the current drive number (0=A, 1=B, etc).
 */
int _al_getdrive(void)
{
   return _getdrive() - 1;
}



/* _al_getdcwd:
 *  Returns the current directory on the specified drive.
 */
void _al_getdcwd(int drive, char *buf, int size)
{
   char tmp[1024];

   if (!_al_win_unicode_filenames) {
      if (_getdcwd(drive+1, tmp, sizeof(tmp)))
         do_uconvert(tmp, U_ASCII, buf, U_CURRENT, size);
      else
         usetc(buf, 0);
   }
   else {
      if (_wgetdcwd(drive+1, (wchar_t*)tmp, sizeof(tmp)/sizeof(wchar_t)))
         do_uconvert(tmp, U_UNICODE, buf, U_CURRENT, size);
      else
         usetc(buf, 0);
   }
}



/* _al_ffblk_get_size:
 *  Returns the size out of an _al_ffblk structure.
 */
uint64_t al_ffblk_get_size(struct al_ffblk *info)
{
   struct FF_DATA *ff_data;

   ASSERT(info);
   ff_data = (struct FF_DATA *) info->ff_data;

   if (!_al_win_unicode_filenames) {
      return ff_data->data.a.size;
   }
   else {
      return ff_data->data.w.size;
   }
}



/* _al_win_open:
 *  Open a file with open() or _wopen() depending on whether Unicode filenames
 *  are supported by this version of Windows and compiler.
 */
int _al_win_open(const char *filename, int mode, int perm)
{
   if (!_al_win_unicode_filenames) {
      return open(filename, mode, perm);
   }
   else {
      return _wopen((wchar_t*)filename, mode, perm);
   }
}



/* _al_win_unlink:
 *  Remove a file with unlink() or _wunlink() depending on whether Unicode
 *  filenames are supported by this version of Windows and compiler.
 */
int _al_win_unlink(const char *pathname)
{
   if (!_al_win_unicode_filenames) {
      return unlink(pathname);
   }
   else {
      return _wunlink((wchar_t*)pathname);
   }
}

