/*
 * dmlib
 * -- Resource management
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2003-2013 Tecnic Software productions (TNSP)
 */
#include "dmres.h"
#include <time.h>

#ifdef DM_USE_PACKFS
#include <zlib.h>
#endif

#ifdef DM_USE_STDIO
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#endif


DMResource *dmResourceNew(DMResourceLib *lib, const char *filename, const size_t size)
{
    DMResource *node = dmMalloc0(sizeof(DMResource));
    if (node == NULL)
        return NULL;
    
    node->lib = lib;
    node->filename = dm_strdup(filename);
    node->rawSize = size;
    
    return node;
}


void dmResourceFreeResData(DMResource *node)
{
    if (node->resData != NULL &&
        node->rops != NULL &&
        node->rops->free != NULL)
    {
        node->rops->free(node);
    }

    node->resData = NULL;
    node->flags &= ~DMF_LOADED_RES;
}


void dmResourceFreeRawData(DMResource *node)
{
    if ((node->flags & DMF_UNALLOCATED) == 0)
    {
        dmFree(node->rawData);
        node->rawData = NULL;
        node->flags &= ~DMF_LOADED_RAW;
    }
}


void dmResourceFree(DMResource *node)
{
    if (node != NULL)
    {
        dmResourceFreeResData(node);
        dmResourceFreeRawData(node);
        dmFree(node->filename);
        dmFree(node);
    }
}


void dmResourceInsert(DMResourceLib *lib, DMResource * node)
{
    if (lib == NULL || node == NULL)
        return;
    
    node->lib = lib;

    if (lib->resources != NULL)
    {
        node->prev = lib->resources->prev;
        lib->resources->prev->next = node;
        lib->resources->prev = node;
    }
    else
    {
        lib->resources = node->prev = node;
    }
    
    node->next = NULL;
}


void dmResourceDelete(DMResourceLib *lib, DMResource * node)
{
    if (lib == NULL)
        return;

    if (node->prev)
        node->prev->next = node->next;

    if (node->next)
        node->next->prev = node->prev;
    else
        lib->resources->prev = node->prev;

    node->prev = node->next = NULL;
}


DMResource * dmResourceFind(DMResourceLib *lib, const char *filename)
{
    DMResource *node, *found = NULL;

    if (lib == NULL)
        return NULL;

    dmMutexLock(lib->mutex);

    for (node = lib->resources; node != NULL; node = node->next)
    {
        if (strcmp(node->filename, filename) == 0)
        {
            found = node;
            break;
        }
    }

    dmMutexUnlock(lib->mutex);

    return found;
}


#ifdef DM_USE_STDIO
/* Basic stdio file routines
 */
static int dm_stdio_fopen(DMResource *handle)
{
    char *rfilename = dm_strdup_printf("%s%s", DMRES_DATA_PATH, handle->filename);
    if (rfilename == NULL)
        return DMERR_MALLOC;

    handle->fh = fopen(rfilename, "rb");
    dmFree(rfilename);

    handle->error = dmGetErrno();
    return (handle->fh != NULL) ? DMERR_OK : DMERR_FOPEN;
}


static void dm_stdio_fclose(DMResource * f)
{
    if (f->fh != NULL)
    {
        fclose(f->fh);
        f->fh = NULL;
    }
}


static int dm_stdio_ferror(DMResource * f)
{
    return f->error;
}


static int dm_stdio_fseek(DMResource *f, const off_t pos, const int whence)
{
    int ret = fseek(f->fh, pos, whence);
    f->error = dmGetErrno();
    return ret;
}


static int dm_stdio_freset(DMResource * f)
{
    if (f->fh != NULL)
        return dm_stdio_fseek(f, 0L, SEEK_SET);
    else
        return DMERR_OK;
}


static off_t dm_stdio_fsize(DMResource *f)
{
    off_t savePos, fileSize;

    // Check if the size is cached
    if (f->rawSize != 0)
        return f->rawSize;

    // Get file size
    savePos = ftello(f->fh);
    if (fseeko(f->fh, 0L, SEEK_END) != 0)
    {
        f->error = dmGetErrno();
        return -1;
    }

    fileSize = ftello(f->fh);
    if (fseeko(f->fh, savePos, SEEK_SET) != 0)
    {
        f->error = dmGetErrno();
        return -1;
    }

    f->rawSize = fileSize;
    return fileSize;
}


static off_t dm_stdio_ftell(DMResource * f)
{
    return ftell(f->fh);
}


static BOOL dm_stdio_feof(DMResource * f)
{
    return feof(f->fh);
}


static int dm_stdio_fgetc(DMResource * f)
{
    int ret = fgetc(f->fh);
    f->error = dmGetErrno();
    return ret;
}


static int dm_stdio_fputc(int v, DMResource * f)
{
    int ret = fputc(v, f->fh);
    f->error = dmGetErrno();
    return ret;
}


static size_t dm_stdio_fread(void *ptr, size_t size, size_t nmemb, DMResource * f)
{
    size_t ret = fread(ptr, size, nmemb, f->fh);
    f->error = dmGetErrno();
    return ret;
}


static size_t dm_stdio_fwrite(void *ptr, size_t size, size_t nmemb, DMResource * f)
{
    size_t ret = fwrite(ptr, size, nmemb, f->fh);
    f->error = dmGetErrno();
    return ret;
}


static int dm_stdio_preload(DMResource *handle)
{
    int ret = dm_stdio_fopen(handle);
    if (ret != DMERR_OK)
        return ret;
    
    dm_stdio_fsize(handle);
    
    handle->rawData = dmMalloc(handle->rawSize);
    if (handle->rawData == NULL)
        return DMERR_MALLOC;
    
    if (dm_stdio_fread(handle->rawData, sizeof(Uint8), handle->rawSize, handle) != handle->rawSize)
        return DMERR_FREAD;
    
    return DMERR_OK;
}


DMResourceOps dfStdioFileOps =
{
    "Stdio",

    dm_stdio_freset,
    dm_stdio_ferror,
    dm_stdio_fseek,
    dm_stdio_fsize,
    dm_stdio_ftell,
    dm_stdio_feof,
    dm_stdio_fgetc,
    dm_stdio_fputc,
    dm_stdio_fread,
    dm_stdio_fwrite,

    dm_stdio_fopen,
    dm_stdio_fclose,
    dm_stdio_preload
};

DMResourceOps dfStdioFHOps =
{
    "StdioFH",

    dm_stdio_freset,
    dm_stdio_ferror,
    dm_stdio_fseek,
    dm_stdio_fsize,
    dm_stdio_ftell,
    dm_stdio_feof,
    dm_stdio_fgetc,
    dm_stdio_fputc,
    dm_stdio_fread,
    dm_stdio_fwrite,

    NULL,
    NULL,
    NULL
};
#endif


// Some mingw/windows headers define these as macros, which is bad for us
#ifdef __WIN32
#undef ferror
#undef feof
#endif


/*
 * PACK file routines
 */
#ifdef DM_USE_PACKFS
static int dm_pack_preload(DMResource *handle)
{
    DMPackEntry *node;
    int res = DMERR_OK, cres, cdataLeft;
    z_stream cstream;
    Uint8 *  cbuffer = NULL;

    if (handle->lib == NULL || handle->lib->packFile == NULL)
        return DMERR_NULLPTR;

    // Search PACK nodelist for file
    if ((node = dmPackFind(handle->lib->packFile->entries, handle->filename)) == NULL)
    {
        dmError("Entry '%s' not found in PACK file.\n", handle->filename);
        res = DMERR_NOT_FOUND;
        goto error;
    }

    // Seek to entry
    if (fseek(handle->lib->packFile->file, node->offset, SEEK_SET) == -1)
    {
        dmError("Could not seek node position in PACK file.\n");
        res = DMERR_FSEEK;
        goto error;
    }

    // Allocate a structures and buffers
    cbuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE);
    if (cbuffer == NULL)
    {
        res = DMERR_MALLOC;
        goto error;
    }

    // Initialize fields
    handle->rawOffset = 0;
    handle->rawSize = node->size;
    handle->rawData = (Uint8 *) dmMalloc(node->size);
    if (handle->rawData == NULL)
    {
        res = DMERR_MALLOC;
        goto error;
    }

    // Initialize decompression
    cstream.zalloc = (alloc_func) Z_NULL;
    cstream.zfree = (free_func) Z_NULL;
    cstream.opaque = (voidpf) Z_NULL;
    cstream.next_out = handle->rawData;
    cstream.avail_out = handle->rawSize;
    cdataLeft = node->length;
    cres = inflateInit(&(cstream));
    if (cres != Z_OK)
    {
        dmError("Could not initialize zlib stream inflation.\n");
        res = DMERR_INIT_FAIL;
        goto error;
    }

    // Uncompress the data
    while (cdataLeft > 0 &&
           cstream.avail_out > 0 && cres == Z_OK)
    {
        cstream.avail_in = fread(
            cbuffer, sizeof(Uint8),
            (cdataLeft >= DPACK_TMPSIZE) ? DPACK_TMPSIZE : cdataLeft,
            handle->lib->packFile->file);

        cdataLeft -= cstream.avail_in;
        cstream.next_in = cbuffer;
        cres = inflate(&cstream, Z_FULL_FLUSH);
    }

    // Cleanup
    inflateEnd(&(cstream));

error:
    dmFree(cbuffer);
    return res;
}


static int dm_pack_fopen(DMResource * f)
{
    if ((f->flags & DMF_LOADED_RAW) == 0)
    {
        int res = dm_pack_preload(f);
        if (res == DMERR_OK)
            f->flags |= DMF_LOADED_RAW;

        return res;
    }
    else
        return DMERR_OK;
}


static void dm_pack_fclose(DMResource * f)
{
    if ((f->flags & DMF_PERSIST) == 0)
        dmResourceFreeRawData(f);
}
#endif


static int dm_mem_freset(DMResource * f)
{
    f->rawOffset = 0;
    return DMERR_OK;
}


static int dm_mem_ferror(DMResource * f)
{
    return f->error;
}


static int dm_mem_fseek(DMResource * f, const off_t offset, const int whence)
{
    off_t newPos;

    // Calculate the new position
    switch (whence)
    {
        case SEEK_SET:
            newPos = offset;
            break;

        case SEEK_CUR:
            newPos = f->rawOffset + offset;
            break;

        case SEEK_END:
            newPos = f->rawSize + offset;
            break;

        default:
            return -1;
    }

    // Set the new position
    f->rawOffset = newPos;

    // Check the new position
    if (newPos < 0 && (size_t) newPos >= f->rawSize)
        return -1;

    return 0;
}


static off_t dm_mem_fsize(DMResource * f)
{
    return f->rawSize;
}


static off_t dm_mem_ftell(DMResource * f)
{
    return f->rawOffset;
}


static BOOL dm_mem_feof(DMResource * f)
{
    // Check for EOF
    if ((size_t) f->rawOffset <= f->rawSize)
        return FALSE;
    else
        return TRUE;
}


static int dm_mem_fgetc(DMResource * f)
{
    // Check for EOF
    if ((size_t) f->rawOffset < f->rawSize)
        return f->rawData[f->rawOffset++];
    else
        return EOF;
}


static size_t dm_mem_fread(void *buf, size_t size, size_t nmemb, DMResource * f)
{
    size_t length = (size * nmemb);

    // Check if we can read the whole chunk
    if (((size_t) f->rawOffset + length) >= f->rawSize)
    {
        nmemb = (f->rawSize - f->rawOffset) / size;
        length = size * nmemb;
    }

    memcpy(buf, f->rawData + f->rawOffset, length);
    f->rawOffset += length;
    return nmemb;
}


static int dm_mem_fputc(int ch, DMResource * f)
{
    // Check for EOF
    if ((size_t) f->rawOffset < f->rawSize)
    {
        f->rawData[f->rawOffset++] = ch;
        return ch;
    }
    else
        return EOF;
}


static size_t dm_mem_fwrite(void *buf, size_t size, size_t nmemb, DMResource * f)
{
    size_t length = (size * nmemb);

    // Check if we can write the whole chunk
    if (((size_t) f->rawOffset + length) >= f->rawSize)
    {
        nmemb = (f->rawSize - f->rawOffset) / size;
        length = size * nmemb;
    }

    if (length > 0)
    {
        memcpy(f->rawData + f->rawOffset, buf, length);
        f->rawOffset += length;
    }
    return nmemb;
}


#ifdef DM_USE_PACKFS
DMResourceOps dfPackFileOps =
{
    "PackFS",

    dm_mem_freset,
    dm_mem_ferror,
    dm_mem_fseek,
    dm_mem_fsize,
    dm_mem_ftell,
    dm_mem_feof,
    dm_mem_fgetc,
    NULL,
    dm_mem_fread,
    NULL,
    
    dm_pack_fopen,
    dm_pack_fclose,
    dm_pack_preload,
};
#endif


DMResourceOps dfMemIOFileOps =
{
    "MemIO",

    dm_mem_freset,
    dm_mem_ferror,
    dm_mem_fseek,
    dm_mem_fsize,
    dm_mem_ftell,
    dm_mem_feof,
    dm_mem_fgetc,
    dm_mem_fputc,
    dm_mem_fread,
    dm_mem_fwrite,
    
    NULL,
    dmResourceFree,
    NULL
};


/* FS file handling functions. These functions call the actual
 * functions depending on where the file is located.
 */
static int dmResourcePreload(DMResource *handle)
{
    int ret = DMERR_OK;

    // Check if we want to preload raw data?
    if ((handle->lib->flags & DRF_PRELOAD_RAW) ||
        handle->rops == NULL || handle->rops->load == NULL)
    {
        if (handle->flags & DMF_LOADED_RAW)
            ret = DMERR_OK;
        else
        if (handle->fops->preload != NULL)
        {
            ret = handle->fops->preload(handle);
            if (ret == DMERR_OK)
                handle->flags |= DMF_LOADED_RAW | DMF_PERSIST;
        }
        else
            ret = DMERR_INIT_FAIL;

        dmfreset(handle);
    }

    // Check if resource data is to be preloaded
    if (handle->lib->flags & DRF_PRELOAD_RES)
    {
        if (handle->flags & DMF_LOADED_RES)
            ret = DMERR_OK;
        else
        if (handle->rops != NULL &&
            handle->rops->load != NULL)
        {
            if ((ret = handle->fops->fopen(handle)) == DMERR_OK)
            {
                ret = handle->rops->load(handle);
                handle->fops->fclose(handle);
            }
            
            if (ret == DMERR_OK)
                handle->flags |= DMF_LOADED_RES;
        }

        dmfreset(handle);
    }

    return ret;
}


int dmf_open(DMResourceLib *lib, const char *filename, DMResource **phandle)
{
    DMResource *handle;
    int res;

    // Check master directory for resource
    if ((*phandle = handle = dmResourceFind(lib, filename)) == NULL)
    {
#ifdef DM_USE_STDIO
        if (lib->flags & DRF_USE_STDIO)
        {
            // Hmm.. does not exist? Fall back to a stdio file
            *phandle = handle = dmResourceNew(lib, filename, 0);
            if (handle == NULL)
                return DMERR_MALLOC;

            handle->fops   = &dfStdioFileOps;
        }
        else
            return DMERR_INIT_FAIL;
#else
        // Stdio not enabled, fail
        return DMERR_INIT_FAIL;
#endif
    }

    // Check if the data is preloaded
    if ((res = handle->fops->fopen(handle)) == DMERR_OK)
    {
        dmResourceRef(handle);
        if (handle->flags & DMF_TEMPORARY)
        {
            handle->flags &= ~DMF_TEMPORARY;
            dmResourceInsert(lib, handle);
        }
    }
    else
    if (handle->flags & DMF_TEMPORARY)
    {
        dmResourceFree(handle);
        *phandle = handle = NULL;
    }

    dmfreset(handle);
    return res;
}


int dmf_create_memio(DMResourceLib *lib, const char *filename,
    Uint8 *buf, const size_t size, DMResource **phandle)
{
    DMResource *handle;

    // Check master directory for resource
    if ((*phandle = handle = dmResourceFind(lib, filename)) == NULL)
    {
        if ((*phandle = handle = dmResourceNew(lib, filename, size)) == NULL)
            return DMERR_MALLOC;

        handle->flags   = DMF_LOADED_RAW | DMF_UNALLOCATED;
        handle->fops    = &dfMemIOFileOps;
        handle->rawData = buf;
        dmResourceInsert(lib, handle);
    }

    // Increase refcount
    dmResourceRef(handle);
    dmfreset(handle);
    return DMERR_OK;
}


#ifdef DM_USE_STDIO
int dmf_create_stdio(const char *filename, const char *mode, DMResource **phandle)
{
    DMResource *handle;
    if ((*phandle = handle = dmResourceNew(NULL, filename, 0)) == NULL)
        return DMERR_MALLOC;

    handle->fops  = &dfStdioFileOps;
    handle->fh    = fopen(filename, mode);
    handle->error = dmGetErrno();
    
    if (handle->fh == NULL)
    {
        dmResourceFree(handle);
        return handle->error;
    }

    dmResourceRef(handle);
    return DMERR_OK;
}


int dmf_create_stdio_stream(FILE *fh, DMResource **phandle)
{
    DMResource *handle;
    if ((*phandle = handle = dmResourceNew(NULL, "", 0)) == NULL)
        return DMERR_MALLOC;

    handle->fops = &dfStdioFHOps;
    handle->fh   = fh;
    dmResourceRef(handle);
    return DMERR_OK;
}
#endif


void dmf_close(DMResource * f)
{
    if (f == NULL)
        return;

    if (f->fops->fclose != NULL)
        f->fops->fclose(f);

    dmResourceUnref(f);
}


int dmfreset(DMResource *f)
{
    if (f == NULL)
        return DMERR_NULLPTR;
    
    if (f->fops == NULL || f->fops->freset == NULL)
        return DMERR_OK;
    
    return f->fops->freset(f);
}

int dmferror(DMResource * f)
{
    f->atime = time(NULL);
    return f->fops->ferror(f);
}

int dmfseek(DMResource * f, off_t offset, int whence)
{
    f->atime = time(NULL);
    return f->fops->fseek(f, offset, whence);
}

off_t dmfsize(DMResource * f)
{
    f->atime = time(NULL);
    return f->fops->fsize(f);
}

off_t dmftell(DMResource * f)
{
    f->atime = time(NULL);
    return f->fops->ftell(f);
}

BOOL dmfeof(DMResource * f)
{
    f->atime = time(NULL);
    return f->fops->feof(f);
}

int dmfgetc(DMResource * f)
{
    f->atime = time(NULL);
    return f->fops->fgetc(f);
}

int dmfputc(int v, DMResource * f)
{
    f->atime = time(NULL);
    return f->fops->fputc(v, f);
}

size_t dmfread(void *ptr, size_t size, size_t nmemb, DMResource * f)
{
    f->atime = time(NULL);
    return f->fops->fread(ptr, size, nmemb, f);
}

size_t dmfwrite(void *ptr, size_t size, size_t nmemb, DMResource * f)
{
    f->atime = time(NULL);
    return f->fops->fwrite(ptr, size, nmemb, f);
}

char *dmfgets(char *s, int size, DMResource * f)
{
    char *p = s, c;
    int n = 0;

    while ((c = f->fops->fgetc(f)) != EOF)
    {
        n++;
        if (c == '\n')
            break;
        else
        if (n < size - 1)
            *p++ = c;
    }
    *p = 0;
    
    return (n > 0) ? s : NULL;
}


int dmResourceRef(DMResource *node)
{
    if (node->lib != NULL) dmMutexLock(node->lib->mutex);
    node->atime = time(NULL);
    node->refcount++;
    if (node->lib != NULL) dmMutexUnlock(node->lib->mutex);

    return node->refcount;
}


int dmResourceUnref(DMResource *node)
{
    if (node->lib != NULL) dmMutexLock(node->lib->mutex);
    node->refcount--;
    if (node->lib != NULL) dmMutexUnlock(node->lib->mutex);

    return node->refcount;
}


#ifdef DM_USE_STDIO
static int dmResourcesLoadDirectory(DMResourceLib *lib, const char *path)
{
    int res = DMERR_OK;
    struct dirent *dh;
    DIR *hdir = opendir(path);
    if (hdir == NULL)
        return dmGetErrno();

    dmMutexLock(lib->mutex);

    do
    {
        DMResource *node = NULL;
        dh = readdir(hdir);
        if (dh != NULL)
        {
            struct stat sbuf;
            char *fname = dm_strdup_printf("%s/%s", path, dh->d_name);
            if (stat(fname, &sbuf) == -1)
            {
                res = dmGetErrno();
                dmError("Could not stat() %s, #%d: %s\n",
                    fname, res, dmErrorStr(res));
                dmFree(fname);
                goto out;
            }

            if (S_ISREG(sbuf.st_mode))
                node = dmResourceNew(lib, dh->d_name, sbuf.st_size);
        }

        if (node != NULL)
        {
            node->fops = &dfStdioFileOps;
            dmResourceInsert(lib, node);
        }
    } while (dh != NULL);

out:
    dmMutexUnlock(lib->mutex);

#ifdef __WIN32
#else
    closedir(hdir);
#endif

    return res;
}
#endif


/* Resources subsystem initialization and shutdown routines
 */
int dmResourcesInit(DMResourceLib **plib, const char *filename, const char *path, const int flags, int (*classifier)(DMResource *))
{
    DMResourceLib *lib;
    BOOL initialized = FALSE;

    // Allocate the resource library structure
    if ((*plib = lib = dmMalloc0(sizeof(DMResourceLib))) == NULL)
        return DMERR_MALLOC;

    // Basic data
    lib->mutex    = dmCreateMutex();
    lib->flags    = flags; 
    lib->resPath  = dm_strdup((path != NULL) ? path : DMRES_DATA_PATH);


#ifdef DM_USE_PACKFS
    if (flags & DRF_USE_PACK)
    {
        int ret;
        DMPackEntry *node;

        lib->packFilename = dm_strdup((filename != NULL) ? filename : DMRES_DATA_PACK);

        // Initialize PACK, open as read-only
        ret = dmPackOpen(lib->packFilename, &lib->packFile, TRUE);
        if (ret != DMERR_OK)
        {
            if ((flags & DRF_USE_STDIO) == 0)
            {
                dmError("Error opening PACK file '%s', #%d: %s\n",
                lib->packFilename, ret, dmErrorStr(ret));

                return DMERR_INIT_FAIL;
            }
            else
                dmError("Failed to open PACK, falling back to STDIO, '%s' %d: %s\n",
                lib->packFilename, ret, dmErrorStr(ret));
        }
        else
        {
            // Initialize resources from a PACK file
            for (node = lib->packFile->entries; node != NULL; node = node->next)
            {
                DMResource *res = dmResourceNew(lib, node->filename, node->size);
                if (res == NULL)
                {
                    dmError("Could not allocate memory for resource node '%s' [0x%08x], %d.\n",
                        node->filename, node->flags, node->size);
                    return DMERR_INIT_FAIL;
                }

                res->fops = &dfPackFileOps;
                dmResourceInsert(lib, res);
            }
            
            initialized = TRUE;
        }
    }
#endif

#ifdef DM_USE_STDIO
    if (!initialized && (flags & DRF_USE_STDIO))
    {
        // Initialize resources from a resource directory
        int ret = dmResourcesLoadDirectory(lib, lib->resPath);
        if (ret != DMERR_OK)
            return ret;
        initialized = TRUE;
    }
#endif

    if (!initialized)
        return DMERR_INIT_FAIL;

    // Okay, classify resources
    if (lib->resources != NULL && classifier != NULL)
    {
        DMResource *node;
        for (node = lib->resources; node != NULL; node = node->next)
        {
            int ret = classifier(node);
            if (ret != DMERR_OK)
                return ret;
        }
    }

    // Initialization complete
    return DMERR_OK;
}


int dmResourcesClose(DMResourceLib *lib)
{
    DMResource *node;
    
    if (lib == NULL)
        return DMERR_NULLPTR;
    
    dmMutexLock(lib->mutex);

    // Shutdown possible subsystems
#ifdef DM_USE_PACKFS
    if (lib->flags & DRF_USE_PACK)
    {
        int res = dmPackClose(lib->packFile);
        if (res != DMERR_OK)
        {
            dmError("Error closing PACK, #%i: %s\n",
                res, dmErrorStr(res));
        }

        dmFree(lib->packFilename);
    }
#endif

    // Free resource entries
    node = lib->resources;
    while (node != NULL)
    {
        DMResource *next = node->next;
        dmResourceFree(node);
        node = next;
    }

    // Etc.
    dmFree(lib->resPath);
    dmMutexUnlock(lib->mutex);
    dmDestroyMutex(lib->mutex);
    return DMERR_OK;
}


int dmResourcesPreload(DMResourceLib *lib, BOOL start, int *loaded, int *total)
{
    int ret = DMERR_OK;

    dmMutexLock(lib->mutex);
    
    // Initialize preloading 
    if (lib->preload == NULL || start)
    {
        DMResource *node;
        
        lib->preload = lib->resources;
        *loaded = 0;
        *total = 0;

        // Calculate total number of resources to be preloaded
        if (lib->flags & (DRF_PRELOAD_RAW | DRF_PRELOAD_RES))
        {
            for (node = lib->resources; node != NULL; node = node->next)
                (*total)++;
        }
    }
    else
    if (lib->preload != NULL)
    {
        // Attempt to preload the resource
        if ((ret = dmResourcePreload(lib->preload)) != DMERR_OK)
        {
            dmError("Error preloading '%s', %d: %s\n",
                lib->preload->filename, ret, dmErrorStr(ret));
            goto error;
        }

        (*loaded)++;
        lib->preload = lib->preload->next;
    }

    dmMutexUnlock(lib->mutex);
    return (lib->preload == NULL) ? DMERR_OK : DMERR_PROGRESS;

error:
    dmMutexUnlock(lib->mutex);
    return ret;
}


void dmResourcePrune(DMResourceLib *lib, const int agems, int const flags)
{
    DMResource *node;
    const int stamp = time(NULL);
    dmMutexLock(lib->mutex);

    for (node = lib->resources; node != NULL; node = node->next)
    {
        // Check if node has refcount of 0 and is
        // not marked as persistent resource
        if (node->refcount == 0 &&
            (node->flags & DMF_PERSIST) == 0 &&
            (node->flags & (DMF_LOADED_RES | DMF_LOADED_RAW)))
        {
            if (((flags & DMPRUNE_ATIME) && stamp - node->atime >= agems) ||
                ((flags & DMPRUNE_MTIME) && stamp - node->mtime >= agems))
            {
                dmResourceFreeResData(node);
                dmResourceFreeRawData(node);
            }
        }
    }

    dmMutexUnlock(lib->mutex);
}


/* Helper resource access routines
 */
int dmf_read_str(DMResource *f, void *s, size_t l)
{
    return dmfread(s, 1, l, f) == l;
}

BOOL dmf_read_byte(DMResource *f, Uint8 *val)
{
    int tmp = dmfgetc(f);
    *val = tmp;
    return tmp != EOF;
}

#define DM_DEFINE_FUNC(xname, xtype, xmacro)          \
BOOL dmf_read_ ## xname (DMResource *f, xtype *v) {      \
    xtype result;                                     \
    if (dmfread(&result, sizeof( xtype ), 1, f) != 1) \
        return FALSE;                                 \
    *v = DM_ ## xmacro ## _TO_NATIVE (result);        \
    return TRUE;                                      \
}

DM_DEFINE_FUNC(le16, Uint16, LE16)
DM_DEFINE_FUNC(le32, Uint32, LE32)

DM_DEFINE_FUNC(be16, Uint16, BE16)
DM_DEFINE_FUNC(be32, Uint32, BE32)

#ifdef DM_HAVE_64BIT
DM_DEFINE_FUNC(le64, Uint64, LE64)
DM_DEFINE_FUNC(be64, Uint64, BE64)
#endif
