/*
 * 
 * File system handling
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/* DEVELOPER NOTE: 
 * This Overview should be updated if either this file or the one containing
 * the memory interface is changed.
 */

/**
 * \ingroup rwos
 * \page rwosoverview RwOs Overview
 *
 * This object exposes the file and memory interfaces to the application.
 *
 * RenderWare Graphics maintains tables of file I/O and memory handling functions. The prototypes are 
 * based on standard ANSI functionality. Developers are allowed to change individual entries 
 * in these tables so that they can provide their own functions. 
 *
 * Extreme care should be taken with these functions as memory leaks or data corruption may 
 * occur if this feature is misused. RwEngineInit() can be used instead of these functions 
 * if you only need to change the functions once, when your application starts.
 *
 */

/****************************************************************************
 Includes
 */

#include <stdlib.h>
#include <string.h>

#ifdef FSYS_WIN32
#include <windows.h>
#else /* FSYS_WIN32 */
#include <stdio.h>
#endif /* FSYS_WIN32 */

#include "batypes.h"
#include "balibtyp.h"
#include "badebug.h"

/* Abstraction of string API for unicode support */
#include "rwstring.h"

#include "bafsys.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = "@@(#)$Id: bafsys.c,v 1.46 2001/07/16 15:39:07 johns Exp $";
#endif /* (!defined(DOXYGEN)) */


/****************************************************************************
 Local Types
 */

/****************************************************************************
 Local (static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

/****************************************************************************
 Globals (across program)
 */

 /****************************************************************************
 Local (static) Globals
 */

/**
 * \ingroup rwos
 * \ref RwOsGetFileInterface is used to retrieve the file system 
 * functions used by RenderWare. The default file system interface is 
 * installed when the rendering engine is initialized and uses the standard 
 * ANSI functions. The application may install an alternative file system 
 * interface providing it complies with the ANSI interface. This new interface
 * will be used by all subsequent RenderWare file operations.
 *
 * See \ref RwFileFunctions for details of the file interface.
 *
 * \return Returns pointer to a \ref RwFileFunctions value containing pointers to 
 * file system access functions. The elements of this structure may be 
 * modified directly to install an alternative file system.
 *
 * \see RwOsGetMemoryInterface
 * \see RwEngineInit
 *
 */
RwFileFunctions *
RwOsGetFileInterface(void)
{
    RWAPIFUNCTION(RWSTRING("RwOsGetFileInterface"));

    RWRETURN(&RWSRCGLOBAL(fileFuncs));
}

static RwBool
rwfexist(const RwChar * name)
{
    void *handle;
    RwBool fileExists;

    RWFUNCTION(RWSTRING("rwfexist"));

    /* Test for read-ability */
    handle = RwFopen(name, RWSTRING("rb"));
    fileExists = (handle != NULL);
    if (handle)
    {
        RwFclose(handle);
    }

    RWRETURN(fileExists);
}

#ifdef FSYS_WIN32

static void *
rwWin32fopen(const RwChar * name, const RwChar * access)
{
    HANDLE handle;

    RWFUNCTION(RWSTRING("rwWin32fopen"));
    RWASSERT(name);
    RWASSERT(access);

    if (rwstrstr(access, RWSTRING("w")))
    {
        /* Write */
        handle = CreateFile(name, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, NULL);
    }
    else
    {
        /* Read */
        handle = CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    }

    /* We offset the handle by an invalid handle value to make zero the invalid handle value */
    RWRETURN((void *) ((RwUInt32) handle - (RwUInt32) INVALID_HANDLE_VALUE));
}

int
_rwWin32fclose(void *fptr)
{
    BOOL result;

    RWFUNCTION(RWSTRING("_rwWin32fclose"));
    RWASSERT(fptr);

    result = CloseHandle((HANDLE) ((RwUInt32) fptr + (RwUInt32) INVALID_HANDLE_VALUE));

    if (result)
    {
        /* Return 0 on success, like fclose() */
        RWRETURN(0);
    }

    RWRETURN(1);
}

size_t
_rwWin32fread(void *addr, size_t size, size_t count, void *fptr)
{
    DWORD numBytesRead;

    RWFUNCTION(RWSTRING("_rwWin32fread"));
    RWASSERT(addr);
    RWASSERT(fptr);

    ReadFile((HANDLE) ((RwUInt32) fptr + (RwUInt32) INVALID_HANDLE_VALUE),
             addr, (size * count), &numBytesRead, NULL);

    /* Return number of items read */
    RWRETURN(numBytesRead / size);
}

size_t
_rwWin32fwrite(const void *addr, size_t size, size_t count, void *fptr)
{
    DWORD numBytesWritten;

    RWFUNCTION(RWSTRING("_rwWin32fwrite"));
    RWASSERT(addr);
    RWASSERT(fptr);

    WriteFile((HANDLE) ((RwUInt32) fptr + (RwUInt32) INVALID_HANDLE_VALUE),
              addr, (size * count), &numBytesWritten, NULL);

    /* Return number of items written */
    RWRETURN(numBytesWritten / size);
}

RwChar *
_rwWin32fgets(RwChar * buffer, int maxLen, void *fptr)
{
    DWORD numBytesRead;

    RWFUNCTION(RWSTRING("_rwWin32fgets"));
    RWASSERT(buffer);
    RWASSERT(fptr);

    /* This isn't an accurate implementation, and could use some work */
    ReadFile((HANDLE) ((RwUInt32) fptr + (RwUInt32) INVALID_HANDLE_VALUE),
             buffer, maxLen, &numBytesRead, NULL);

    if (numBytesRead)
    {
        RWRETURN(buffer);
    }
    RWRETURN(NULL);
}

int
_rwWin32fputs(const RwChar * buffer, void *fptr)
{
    DWORD numBytesWritten;

    RWFUNCTION(RWSTRING("_rwWin32fputs"));
    RWASSERT(buffer);
    RWASSERT(fptr);

    WriteFile((HANDLE) ((RwUInt32) fptr + (RwUInt32) INVALID_HANDLE_VALUE),
      buffer, (rwstrlen(buffer) * sizeof (RwChar)), &numBytesWritten, NULL);

    if (numBytesWritten)
    {
        RWRETURN(0);
    }

    RWRETURN(-1);
}

int
_rwWin32feof(void *fptr)
{
    int retVal;

    RWFUNCTION(RWSTRING("_rwWin32feof"));
    RWASSERT(fptr);

    /* Innocent until proven guilty */
    retVal = 0;
    if (GetLastError() == ERROR_HANDLE_EOF)
    {
        retVal = 1;
    }

    RWRETURN(retVal);
}

int
_rwWin32fseek(void *fptr, long offset, int origin)
{
    DWORD moveOrigin;

    RWFUNCTION(RWSTRING("_rwWin32fseek"));
    RWASSERT(fptr);

    /* Success until proven failed */
    moveOrigin = 0;
    switch (origin)
    {
        case SEEK_CUR:
            {
                moveOrigin = FILE_CURRENT;
                break;
            }
        case SEEK_END:
            {
                moveOrigin = FILE_END;
                break;
            }
        case SEEK_SET:
            {
                moveOrigin = FILE_BEGIN;
                break;
            }
        default:
            {
                moveOrigin = 0;
                break;
            }
    }

    if (moveOrigin)
    {
        if (SetFilePointer((HANDLE) ((RwUInt32) fptr + (RwUInt32) INVALID_HANDLE_VALUE),
                           offset, NULL, moveOrigin) != 0xFFFFFFFF)
        {
            /* Success */
            RWRETURN(0);
        }
    }

    /* Invalid origin specified, or SetFilePointer call failed */
    RWRETURN(-1);
}

int
_rwWin32ftell(void *fptr)
{
    DWORD filePos;

    RWFUNCTION(RWSTRING("_rwWin32ftell"));
    RWASSERT(fptr);

    filePos = SetFilePointer((HANDLE) ((RwUInt32) fptr + (RwUInt32) INVALID_HANDLE_VALUE),
                           NULL, NULL, FILE_CURRENT);

    RWRETURN(filePos);
}

int
_rwWin32fflush(void *fptr)
{
    RWFUNCTION(RWSTRING("_rwWin32fflush"));
    RWASSERT(fptr);

    /* Does nothing yet */
    RWRETURN(0);
}

#endif /* FSYS_WIN32 */

/****************************************************************************
 _rwFileSystemOpen

 On entry   : Pointer to optional default filing system overload
 On exit    : TRUE on success
 */

RwBool
_rwFileSystemOpen(void)
{
    RWFUNCTION(RWSTRING("_rwFileSystemOpen"));

    /* A really noddy default implementation of fexist */
    RWSRCGLOBAL(fileFuncs).rwfexist = (rwFnFexist) rwfexist;

#ifdef FSYS_WIN32
    /* Install Win32 file system */
    RWSRCGLOBAL(fileFuncs).rwfopen = rwWin32fopen;
    RWSRCGLOBAL(fileFuncs).rwfclose = _rwWin32fclose;
    RWSRCGLOBAL(fileFuncs).rwfread = _rwWin32fread;
    RWSRCGLOBAL(fileFuncs).rwfwrite = _rwWin32fwrite;
    RWSRCGLOBAL(fileFuncs).rwfgets = _rwWin32fgets;
    RWSRCGLOBAL(fileFuncs).rwfputs = _rwWin32fputs;
    RWSRCGLOBAL(fileFuncs).rwfeof = _rwWin32feof;
    RWSRCGLOBAL(fileFuncs).rwfseek = _rwWin32fseek;
    RWSRCGLOBAL(fileFuncs).rwfflush = _rwWin32fflush;
    RWSRCGLOBAL(fileFuncs).rwftell = _rwWin32ftell;
#else /* FSYS_WIN32 */
    /* Install ANSI file system */

    /* Do some casts here to changed FILE * params into void * params - a tiny bit naughty */
#ifdef RWUNICODE
    RWSRCGLOBAL(fileFuncs).rwfopen = (rwFnFopen) _wfopen;
#else
    RWSRCGLOBAL(fileFuncs).rwfopen = (rwFnFopen) fopen;
#endif
    RWSRCGLOBAL(fileFuncs).rwfclose = (rwFnFclose) fclose;
    RWSRCGLOBAL(fileFuncs).rwfread = (rwFnFread) fread;
    RWSRCGLOBAL(fileFuncs).rwfwrite = (rwFnFwrite) fwrite;
#ifdef RWUNICODE
    RWSRCGLOBAL(fileFuncs).rwfgets = (rwFnFgets) fgetws;
    RWSRCGLOBAL(fileFuncs).rwfputs = (rwFnFputs) fputws;
#else
    RWSRCGLOBAL(fileFuncs).rwfgets = (rwFnFgets) fgets;
    RWSRCGLOBAL(fileFuncs).rwfputs = (rwFnFputs) fputs;
#endif
    RWSRCGLOBAL(fileFuncs).rwfeof = (rwFnFeof) feof;
    RWSRCGLOBAL(fileFuncs).rwfseek = (rwFnFseek) fseek;
    RWSRCGLOBAL(fileFuncs).rwfflush = (rwFnFflush) fflush;
    RWSRCGLOBAL(fileFuncs).rwftell = (rwFnFtell) ftell;
#endif /* FSYS_WIN32 */

    RWRETURN(TRUE);
}

/****************************************************************************
 _rwFileSystemClose

 On entry   : void
 On exit    : void
 */

void
_rwFileSystemClose(void)
{
    RWFUNCTION(RWSTRING("_rwFileSystemClose"));

    RWRETURNVOID();
}

