/*
 * ALURE  OpenAL utility library
 * Copyright (c) 2009 by Chris Robinson.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

/* Title: Main and Miscellanious */

#include "config.h"

#include "main.h"

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif

#include <vector>
#include <string>
#include <map>

std::map<ALint,UserCallbacks> InstalledCallbacks;
CRITICAL_SECTION cs_StreamPlay;
alureStream::ListType alureStream::StreamList;

PFNALCSETTHREADCONTEXTPROC palcSetThreadContext;
PFNALCGETTHREADCONTEXTPROC palcGetThreadContext;


template<typename T>
static inline void LoadALCProc(ALCdevice *dev, const char *name, T **ptr)
{ *ptr = reinterpret_cast<T*>(alcGetProcAddress(dev, name)); }


#ifdef HAVE_GCC_CONSTRUCTOR
static void init_alure(void) __attribute__((constructor));
static void deinit_alure(void) __attribute__((destructor));
static struct MyConstructorClass {
    ~MyConstructorClass()
    { alureStream::Clear(); };
} MyConstructor;
#elif defined(_WIN32) && !defined(ALURE_STATIC_LIBRARY)
static void init_alure(void);
static void deinit_alure(void);
static struct MyConstructorClass {
    ~MyConstructorClass()
    { alureStream::Clear(); };
} MyConstructor;

extern "C" BOOL APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/)
{
    // Perform actions based on the reason for calling.
    switch(reason)
    {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(module);
            init_alure();
            break;

        case DLL_PROCESS_DETACH:
            deinit_alure();
            break;
    }
    return TRUE;
}
#else
static void init_alure(void);
static void deinit_alure(void);

static struct MyConstructorClass {
    MyConstructorClass()
    { init_alure(); };
    ~MyConstructorClass()
    { alureStream::Clear();
      deinit_alure(); };
} MyConstructor;
#endif

static void init_alure(void)
{
    InitializeCriticalSection(&cs_StreamPlay);

    // These calls actually just return references to the codecs' Decoder
    // objects. They aren't really used for anything other than to prevent the
    // compiler from removing the codec initializers.
    alure_init_wav();
    alure_init_aiff();
#ifdef HAS_VORBISFILE
    alure_init_vorbisfile();
#endif
#ifdef HAS_FLAC
    alure_init_flac();
#endif
#ifdef HAS_SNDFILE
    alure_init_sndfile();
#endif
#ifdef HAS_FLUIDSYNTH
    alure_init_fluidsynth();
#endif
#ifdef HAS_DUMB
    alure_init_dumb();
#endif
#ifdef HAS_MODPLUG
    alure_init_modplug();
#endif
#ifdef HAS_MPG123
    alure_init_mpg123();
#endif

    if(alcIsExtensionPresent(NULL, "ALC_EXT_thread_local_context"))
    {
        LoadALCProc(NULL, "alcSetThreadContext", &palcSetThreadContext);
        LoadALCProc(NULL, "alcGetThreadContext", &palcGetThreadContext);
        if(!palcSetThreadContext || !palcGetThreadContext)
        {
            fprintf(stderr, "Alure lib: ALC_EXT_thread_local_context advertised, but missing function:\n"
                            "    alcSetThreadContext=%p\n"
                            "    alcGetThreadContext=%p\n",
                            palcSetThreadContext, palcGetThreadContext);
            palcSetThreadContext = NULL;
            palcGetThreadContext = NULL;
        }
    }
}

static void deinit_alure(void)
{
    alureUpdateInterval(0.0f);
    DeleteCriticalSection(&cs_StreamPlay);
}


#ifndef HAVE_WINDOWS_H

void EnterCriticalSection(CRITICAL_SECTION *cs)
{
    int ret;
    ret = pthread_mutex_lock(cs);
    assert(ret == 0);
}
void LeaveCriticalSection(CRITICAL_SECTION *cs)
{
    int ret;
    ret = pthread_mutex_unlock(cs);
    assert(ret == 0);
}
void InitializeCriticalSection(CRITICAL_SECTION *cs)
{
    pthread_mutexattr_t attrib;
    int ret;

    ret = pthread_mutexattr_init(&attrib);
    assert(ret == 0);

    ret = pthread_mutexattr_settype(&attrib, PTHREAD_MUTEX_RECURSIVE);
#ifdef HAVE_PTHREAD_NP_H
    if(ret != 0)
        ret = pthread_mutexattr_setkind_np(&attrib, PTHREAD_MUTEX_RECURSIVE);
#endif
    assert(ret == 0);
    ret = pthread_mutex_init(cs, &attrib);
    assert(ret == 0);

    pthread_mutexattr_destroy(&attrib);
}
void DeleteCriticalSection(CRITICAL_SECTION *cs)
{
    int ret;
    ret = pthread_mutex_destroy(cs);
    assert(ret == 0);
}

#endif

#ifdef DYNLOAD
#ifdef _WIN32
void *OpenLib(const char *libname)
{ return LoadLibraryA(libname); }
void CloseLib(void *hdl)
{ FreeLibrary((HINSTANCE)hdl); }
void *GetLibProc(void *hdl, const char *funcname)
{ return (void*)GetProcAddress((HINSTANCE)hdl, funcname); }
#else
void *OpenLib(const char *libname)
{
    const char *err = dlerror();
    void *hdl = dlopen(libname, RTLD_NOW);
    if((err=dlerror()) != NULL)
    {
        fprintf(stderr, "Error loading %s: %s\n", libname, err);
        return NULL;
    }
    return hdl;
}
void *GetLibProc(void *hdl, const char *funcname)
{
    const char *err = dlerror();
    void *fn = dlsym(hdl, funcname);
    if((err=dlerror()) != NULL)
    {
        fprintf(stderr, "Error loading %s: %s\n", funcname, err);
        return NULL;
    }
    return fn;
}
void CloseLib(void *hdl)
{ dlclose(hdl); }
#endif
#endif


static const ALchar *last_error = "No error";
void SetError(const char *err)
{
    last_error = err;
}


ALuint DetectBlockAlignment(ALenum format)
{
    switch(format)
    {
#define CHECK_RET(f,s) case (f): return (s)
        CHECK_RET(AL_FORMAT_MONO8, sizeof(ALubyte));
        CHECK_RET(AL_FORMAT_MONO16, sizeof(ALshort));
        CHECK_RET(AL_FORMAT_MONO_FLOAT32, sizeof(ALfloat));
        CHECK_RET(AL_FORMAT_MONO_DOUBLE_EXT, sizeof(ALdouble));
        CHECK_RET(AL_FORMAT_MONO_MULAW, sizeof(ALubyte)*1);

        CHECK_RET(AL_FORMAT_STEREO8, sizeof(ALubyte)*2);
        CHECK_RET(AL_FORMAT_STEREO16, sizeof(ALshort)*2);
        CHECK_RET(AL_FORMAT_STEREO_FLOAT32, sizeof(ALfloat)*2);
        CHECK_RET(AL_FORMAT_STEREO_DOUBLE_EXT, sizeof(ALdouble)*2);
        CHECK_RET(AL_FORMAT_STEREO_MULAW, sizeof(ALubyte)*2);

        CHECK_RET(AL_FORMAT_QUAD8, sizeof(ALubyte)*4);
        CHECK_RET(AL_FORMAT_QUAD16, sizeof(ALshort)*4);
        CHECK_RET(AL_FORMAT_QUAD32, sizeof(ALfloat)*4);
        CHECK_RET(AL_FORMAT_QUAD_MULAW, sizeof(ALubyte)*4);

        CHECK_RET(AL_FORMAT_REAR8, sizeof(ALubyte)*2);
        CHECK_RET(AL_FORMAT_REAR16, sizeof(ALshort)*2);
        CHECK_RET(AL_FORMAT_REAR32, sizeof(ALfloat)*2);
        CHECK_RET(AL_FORMAT_REAR_MULAW, sizeof(ALubyte)*2);

        CHECK_RET(AL_FORMAT_51CHN8, sizeof(ALubyte)*6);
        CHECK_RET(AL_FORMAT_51CHN16, sizeof(ALshort)*6);
        CHECK_RET(AL_FORMAT_51CHN32, sizeof(ALfloat)*6);
        CHECK_RET(AL_FORMAT_51CHN_MULAW, sizeof(ALubyte)*6);

        CHECK_RET(AL_FORMAT_61CHN8, sizeof(ALubyte)*7);
        CHECK_RET(AL_FORMAT_61CHN16, sizeof(ALshort)*7);
        CHECK_RET(AL_FORMAT_61CHN32, sizeof(ALfloat)*7);
        CHECK_RET(AL_FORMAT_61CHN_MULAW, sizeof(ALubyte)*7);

        CHECK_RET(AL_FORMAT_71CHN8, sizeof(ALubyte)*8);
        CHECK_RET(AL_FORMAT_71CHN16, sizeof(ALshort)*8);
        CHECK_RET(AL_FORMAT_71CHN32, sizeof(ALfloat)*8);
        CHECK_RET(AL_FORMAT_71CHN_MULAW, sizeof(ALubyte)*8);

        CHECK_RET(AL_FORMAT_MONO_IMA4, 36);
        CHECK_RET(AL_FORMAT_STEREO_IMA4, 36*2);
#undef CHECK_RET
    }
    return 0;
}

ALuint DetectCompressionRate(ALenum format)
{
    switch(format)
    {
    case AL_FORMAT_MONO8:
    case AL_FORMAT_MONO16:
    case AL_FORMAT_MONO_FLOAT32:
    case AL_FORMAT_MONO_DOUBLE_EXT:
    case AL_FORMAT_STEREO8:
    case AL_FORMAT_STEREO16:
    case AL_FORMAT_STEREO_FLOAT32:
    case AL_FORMAT_STEREO_DOUBLE_EXT:
    case AL_FORMAT_QUAD8:
    case AL_FORMAT_QUAD16:
    case AL_FORMAT_QUAD32:
    case AL_FORMAT_REAR8:
    case AL_FORMAT_REAR16:
    case AL_FORMAT_REAR32:
    case AL_FORMAT_51CHN8:
    case AL_FORMAT_51CHN16:
    case AL_FORMAT_51CHN32:
    case AL_FORMAT_61CHN8:
    case AL_FORMAT_61CHN16:
    case AL_FORMAT_61CHN32:
    case AL_FORMAT_71CHN8:
    case AL_FORMAT_71CHN16:
    case AL_FORMAT_71CHN32:
        return 1;

    case AL_FORMAT_MONO_MULAW:
    case AL_FORMAT_STEREO_MULAW:
    case AL_FORMAT_QUAD_MULAW:
    case AL_FORMAT_REAR_MULAW:
    case AL_FORMAT_51CHN_MULAW:
    case AL_FORMAT_61CHN_MULAW:
    case AL_FORMAT_71CHN_MULAW:
        return 1;

    case AL_FORMAT_MONO_IMA4:
    case AL_FORMAT_STEREO_IMA4:
        return 65;
    }
    fprintf(stderr, "Alure lib: Unhandled format: %#x\n", format);
    return 0;
}

ALenum GetSampleFormat(ALuint channels, ALuint bits, bool isFloat)
{
#define CHECK_FMT_RET(f) do {                                                 \
    ALenum fmt = alGetEnumValue(#f);                                          \
    if(alGetError() == AL_NO_ERROR && fmt != 0 && fmt != -1)                  \
        return fmt;                                                           \
} while(0)
    if(!isFloat)
    {
        if(bits == 8)
        {
            if(channels == 1) CHECK_FMT_RET(AL_FORMAT_MONO8);
            if(channels == 2) CHECK_FMT_RET(AL_FORMAT_STEREO8);
            if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
            {
                if(channels == 4) CHECK_FMT_RET(AL_FORMAT_QUAD8);
                if(channels == 6) CHECK_FMT_RET(AL_FORMAT_51CHN8);
                if(channels == 7) CHECK_FMT_RET(AL_FORMAT_61CHN8);
                if(channels == 8) CHECK_FMT_RET(AL_FORMAT_71CHN8);
            }
            if(alIsExtensionPresent("AL_LOKI_quadriphonic"))
            {
                if(channels == 4) CHECK_FMT_RET(AL_FORMAT_QUAD8_LOKI);
            }
            SetError("Unsupported 8-bit channel count\n");
            return AL_NONE;
        }
        if(bits == 16)
        {
            if(channels == 1) CHECK_FMT_RET(AL_FORMAT_MONO16);
            if(channels == 2) CHECK_FMT_RET(AL_FORMAT_STEREO16);
            if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
            {
                if(channels == 4) CHECK_FMT_RET(AL_FORMAT_QUAD16);
                if(channels == 6) CHECK_FMT_RET(AL_FORMAT_51CHN16);
                if(channels == 7) CHECK_FMT_RET(AL_FORMAT_61CHN16);
                if(channels == 8) CHECK_FMT_RET(AL_FORMAT_71CHN16);
            }
            if(alIsExtensionPresent("AL_LOKI_quadriphonic"))
            {
                if(channels == 4) CHECK_FMT_RET(AL_FORMAT_QUAD16_LOKI);
            }
            SetError("Unsupported 16-bit channel count\n");
            return AL_NONE;
        }
        SetError("Unsupported PCM bit depth\n");
        return AL_NONE;
    }

    if(bits == 32 && alIsExtensionPresent("AL_EXT_FLOAT32"))
    {
        if(channels == 1) CHECK_FMT_RET(AL_FORMAT_MONO_FLOAT32);
        if(channels == 2) CHECK_FMT_RET(AL_FORMAT_STEREO_FLOAT32);
        if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
        {
            if(channels == 4) CHECK_FMT_RET(AL_FORMAT_QUAD32);
            if(channels == 6) CHECK_FMT_RET(AL_FORMAT_51CHN32);
            if(channels == 7) CHECK_FMT_RET(AL_FORMAT_61CHN32);
            if(channels == 8) CHECK_FMT_RET(AL_FORMAT_71CHN32);
        }
        SetError("Unsupported float32 channel count\n");
        return AL_NONE;
    }
    if(bits == 64 && alIsExtensionPresent("AL_EXT_DOUBLE"))
    {
        if(channels == 1) CHECK_FMT_RET(AL_FORMAT_MONO_DOUBLE_EXT);
        if(channels == 2) CHECK_FMT_RET(AL_FORMAT_STEREO_DOUBLE_EXT);
        SetError("Unsupported double channel count\n");
        return AL_NONE;
    }
#undef CHECK_FMT_RET

    SetError("Unsupported float bit depth\n");
    return AL_NONE;
}

extern "C" {

/* Function: alureGetVersion
 *
 * Stores the major and minor version of the library. If either major or minor
 * are NULL, that value is not provided.
 */
ALURE_API void ALURE_APIENTRY alureGetVersion(ALuint *major, ALuint *minor)
{
    if(major) *major = ALURE_VER_MAJOR;
    if(minor) *minor = ALURE_VER_MINOR;
}

/* Function: alureGetErrorString
 *
 * Returns a string describing the last error encountered.
 */
ALURE_API const ALchar* ALURE_APIENTRY alureGetErrorString(void)
{
    const ALchar *ret = last_error;
    last_error = "No error";
    return ret;
}


/* Function: alureGetDeviceNames
 *
 * Gets an array of device name strings from OpenAL. This encapsulates
 * AL_ENUMERATE_ALL_EXT (if supported and 'all' is true) and standard
 * enumeration, with 'count' being set to the number of returned device
 * names.
 *
 * Returns:
 * An array of device name strings, or NULL on error.
 *
 * See Also:
 * <alureFreeDeviceNames>
 */
ALURE_API const ALCchar** ALURE_APIENTRY alureGetDeviceNames(ALCboolean all, ALCsizei *count)
{
    const ALCchar *list = NULL;
    if(all && alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"))
        list = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
    else
        list = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
    if(!list)
    {
        alcGetError(NULL);
        SetError("No device names found");
        return NULL;
    }

    const ALCchar *cur = list;
    ALuint retlistLen = 0;
    while(*cur)
    {
        cur += strlen(cur)+1;
        retlistLen++;
    }

    const ALCchar **retlist = new const ALCchar*[retlistLen+1];
    retlistLen = 0;
    cur = list;
    while(*cur)
    {
        ALCuint len = strlen(cur)+1;
        ALCchar *newstr = new ALCchar[len];

        memcpy(newstr, cur, len);
        cur += len;

        retlist[retlistLen] = newstr;
        retlistLen++;
    }
    retlist[retlistLen] = NULL;

    *count = retlistLen;
    return retlist;
}

/* Function: alureFreeDeviceNames
 *
 * Frees the device name array returned from alureGetDeviceNames.
 *
 * See Also:
 * <alureGetDeviceNames>
 */
ALURE_API ALvoid ALURE_APIENTRY alureFreeDeviceNames(const ALCchar **names)
{
    if(names)
    {
        for(ALCuint i = 0;names[i];i++)
            delete[] const_cast<ALCchar*>(names[i]);
        delete[] names;
    }
}


/* Function: alureInitDevice
 *
 * Opens the named device, creates a context with the given attributes, and
 * sets that context as current. The name and attribute list would be the same
 * as what's passed to alcOpenDevice and alcCreateContext respectively.
 *
 * Returns:
 * AL_FALSE on error.
 *
 * See Also:
 * <alureShutdownDevice>
 */
ALURE_API ALboolean ALURE_APIENTRY alureInitDevice(const ALCchar *name, const ALCint *attribs)
{
    ALCdevice *device = alcOpenDevice(name);
    if(!device)
    {
        alcGetError(NULL);

        SetError("Device open failed");
        return AL_FALSE;
    }

    ALCcontext *context = alcCreateContext(device, attribs);
    if(!context || alcMakeContextCurrent(context) == ALC_FALSE)
    {
        if(context)
            alcDestroyContext(context);
        alcCloseDevice(device);

        SetError("Context setup failed");
        return AL_FALSE;
    }
    alcGetError(device);

    return AL_TRUE;
}

/* Function: alureShutdownDevice
 *
 * Destroys the current context and closes its associated device.
 *
 * Returns:
 * AL_FALSE on error.
 *
 * See Also:
 * <alureInitDevice>
 */
ALURE_API ALboolean ALURE_APIENTRY alureShutdownDevice(void)
{
    ALCcontext *context = alcGetCurrentContext();
    ALCdevice *device = alcGetContextsDevice(context);
    if(!context || !device)
    {
        alcGetError(device);
        SetError("Failed to get current device");
        return AL_FALSE;
    }

    if(alcMakeContextCurrent(NULL) == ALC_FALSE)
    {
        alcGetError(NULL);
        SetError("Failed to unset current context");
        return AL_FALSE;
    }

    alcDestroyContext(context);
    alcCloseDevice(device);
    alcGetError(NULL);

    return AL_TRUE;
}


/* Function: alureGetSampleFormat
 *
 * Retrieves an OpenAL format for the given sample format. If bits is non-0,
 * floatbits must be 0, and if floatbits is non-0, bits must be 0. The
 * application should not rely on any particular format enum being returned as
 * it is dependant on the available extensions. The returned format will be
 * valid for the current context. Requires an active context.
 *
 * Returns:
 * An OpenAL format enum for the given sample format, or AL_NONE if one can't
 * be found.
 */
ALURE_API ALenum ALURE_APIENTRY alureGetSampleFormat(ALuint channels, ALuint bits, ALuint floatbits)
{
    if(alGetError() != AL_NO_ERROR)
    {
        SetError("Existing OpenAL error");
        return AL_NONE;
    }

    if(bits && floatbits)
    {
        SetError("Both bit-types specified");
        return AL_NONE;
    }

    if(bits)
        return GetSampleFormat(channels, bits, false);
    return GetSampleFormat(channels, floatbits, true);
}


/* Function: alureInstallDecodeCallbacks
 *
 * Installs callbacks to enable ALURE to handle more file types. The index is
 * the order that each given set of callbacks will be tried, starting at the
 * most negative number (INT_MIN) and going up. Negative indices will be tried
 * before the built-in decoders, and positive indices will be tried after.
 * Installing callbacks onto the same index multiple times will remove the
 * previous callbacks, and removing old callbacks won't affect any opened files
 * using them (they'll continue to use the old functions until properly closed,
 * although newly opened files will use the new ones). Passing NULL for all
 * callbacks is a valid way to remove an installed set, otherwise certain
 * callbacks must be specified. Callbacks that are not specified will assume
 * failure.
 *
 * Parameters:
 * open_file - This callback is expected to open the named file and prepare it
 *             for decoding. If the callbacks cannot decode the file, NULL
 *             should be returned to indicate failure. Upon success, a non-NULL
 *             handle must be returned, which will be used as a unique
 *             identifier for the decoder instance. This callback is required
 *             if open_memory is not specified.
 * open_memory - This callback behaves the same as open_file, except it takes a
 *               memory segment for input instead of a filename. The given
 *               memory will remain valid while the instance is open. This
 *               callback is required if open_file is not specified.
 * get_format - This callback is used to retrieve the format of the decoded
 *              data for the given instance. It is the responsibility of the
 *              function to make sure the returned format is valid for the
 *              current AL context (eg. don't return AL_FORMAT_QUAD16 if the
 *              AL_EXT_MCFORMATS extension isn't available). Returning 0 for
 *              samplerate or blocksize, or returning AL_NONE for format, will
 *              cause a failure. Returning AL_FALSE indicates failure. This
 *              callback is required.
 * decode - This callback is called to get more decoded data. Up to the
 *          specified amount of bytes should be written to the data pointer.
 *          The number of bytes written should be a multiple of the block size,
 *          otherwise an OpenAL error may occur during buffering. The function
 *          should return the number of bytes written. This callback is
 *          required.
 * rewind - This callback is for rewinding the instance so that the next decode
 *          calls for it will get audio data from the start of the sound file.
 *          If the stream fails to rewind, AL_FALSE should be returned.
 * close - This callback is called at the end of processing for a particular
 *         instance. The handle will not be used further and any associated
 *         data may be deleted.
 *
 * Returns:
 * AL_FALSE on error.
 */
ALURE_API ALboolean ALURE_APIENTRY alureInstallDecodeCallbacks(ALint index,
      void*     (*open_file)(const ALchar *filename),
      void*     (*open_memory)(const ALubyte *data, ALuint length),
      ALboolean (*get_format)(void *instance, ALenum *format, ALuint *samplerate, ALuint *blocksize),
      ALuint    (*decode)(void *instance, ALubyte *data, ALuint bytes),
      ALboolean (*rewind)(void *instance),
      void      (*close)(void *instance))
{
    if(!open_file && !open_memory && !get_format && !decode && !rewind && !close)
    {
        std::map<ALint,UserCallbacks>::iterator i = InstalledCallbacks.find(index);
        if(i != InstalledCallbacks.end())
            InstalledCallbacks.erase(i);
        return AL_TRUE;
    }

    if((!open_file && !open_memory) || !get_format || !decode)
    {
        SetError("Missing callback functions");
        return AL_FALSE;
    }

    UserCallbacks newcb;
    newcb.open_file = open_file;
    newcb.open_mem  = open_memory;
    newcb.get_fmt   = get_format;
    newcb.decode    = decode;
    newcb.rewind    = rewind;
    newcb.close     = close;

    InstalledCallbacks[index] = newcb;

    return AL_TRUE;
}


/* Function: alureSleep
 *
 * Rests the calling thread for the given number of seconds.
 *
 * Returns:
 * AL_FALSE on error.
 */
ALURE_API ALboolean ALURE_APIENTRY alureSleep(ALfloat duration)
{
    if(duration < 0.0f)
    {
        SetError("Invalid duration");
        return AL_FALSE;
    }

    ALuint seconds = (ALuint)duration;
    ALdouble rest = duration - (ALdouble)seconds;

#ifdef HAVE_NANOSLEEP

    struct timespec t, remainingTime;
    t.tv_sec = (time_t)seconds;
    t.tv_nsec = (long)(rest*1000000000);

    while(nanosleep(&t, &remainingTime) < 0 && errno == EINTR)
        t = remainingTime;

#elif defined(HAVE_WINDOWS_H)

    while(seconds > 0)
    {
        Sleep(1000);
        seconds--;
    }
    Sleep((DWORD)(rest * 1000));

#endif

    return AL_TRUE;
}


/* Function: alureGetProcAddress
 *
 * Returns a pointer for the named ALURE function.
 *
 * Returns:
 * NULL on error.
 *
 * *Version Added*: 1.1
 */
ALURE_API void* ALURE_APIENTRY alureGetProcAddress(const ALchar *funcname)
{
    static const struct {
        const char *name;
        void *func;
    } FunctionList[] = {
#define ADD_FUNCTION(x) { #x, (void*)x },
        ADD_FUNCTION(alureGetVersion)
        ADD_FUNCTION(alureGetErrorString)
        ADD_FUNCTION(alureGetDeviceNames)
        ADD_FUNCTION(alureFreeDeviceNames)
        ADD_FUNCTION(alureInitDevice)
        ADD_FUNCTION(alureShutdownDevice)
        ADD_FUNCTION(alureGetSampleFormat)
        ADD_FUNCTION(alureSleep)
        ADD_FUNCTION(alureCreateBufferFromFile)
        ADD_FUNCTION(alureCreateBufferFromMemory)
        ADD_FUNCTION(alureBufferDataFromFile)
        ADD_FUNCTION(alureBufferDataFromMemory)
        ADD_FUNCTION(alureCreateStreamFromFile)
        ADD_FUNCTION(alureCreateStreamFromMemory)
        ADD_FUNCTION(alureCreateStreamFromStaticMemory)
        ADD_FUNCTION(alureCreateStreamFromCallback)
        ADD_FUNCTION(alureGetStreamLength)
        ADD_FUNCTION(alureRewindStream)
        ADD_FUNCTION(alureDestroyStream)
        ADD_FUNCTION(alureSetStreamOrder)
        ADD_FUNCTION(alureSetStreamPatchset)
        ADD_FUNCTION(alureInstallDecodeCallbacks)
        ADD_FUNCTION(alureSetIOCallbacks)
        ADD_FUNCTION(alureGetProcAddress)
        ADD_FUNCTION(alurePlaySourceStream)
        ADD_FUNCTION(alurePlaySource)
        ADD_FUNCTION(alureStopSource)
#undef ADD_FUNCTION
        { NULL, NULL }
    };

    size_t i;
    for(i = 0;FunctionList[i].name;i++)
    {
        if(strcmp(FunctionList[i].name, funcname) == 0)
            break;
    }

    if(!FunctionList[i].name)
        SetError("Function not found");
    return FunctionList[i].func;
}

} // extern "C"
