#include "dmlib.h"
#include <errno.h>


int      dmVerbosity = 0;
char     *dmProgName = NULL,
         *dmProgDesc = NULL,
         *dmProgVersion = NULL,
         *dmProgAuthor = NULL,
         *dmProgLicense = NULL;


void dmInitProg(char *name, char *desc, char *version, char *author, char *license)
{
    dmProgName     = name;
    dmProgDesc     = desc;
    dmProgVersion  = version;

    if (author)
        dmProgAuthor = author;
    else
        dmProgAuthor = DM_PROG_AUTHOR;

    if (license)
        dmProgLicense = license;
    else
        dmProgLicense = DM_PROG_LICENSE;

}


void dmPrintBanner(FILE *outFile, const char *name, const char *usage)
{
    fprintf(outFile,
            "\n%s v%s (%s)\n"
            "%s\n"
            "%s\n"
            "Usage: %s %s\n",
            dmProgName, dmProgVersion, dmProgDesc,
            dmProgAuthor, dmProgLicense, name, usage);
}


void dmMsgVA(int level, const char *fmt, va_list ap)
{
    if (dmVerbosity >= level)
    {
        fprintf(stderr, "%s: ", dmProgName);
        vfprintf(stderr, fmt, ap);
    }
}


void dmPrintVA(int level, const char *fmt, va_list ap)
{
    if (dmVerbosity >= level)
    {
        vfprintf(stderr, fmt, ap);
    }
}


void dmErrorVA(const char *fmt, va_list ap)
{
    fprintf(stderr, "%s: ", dmProgName);
    vfprintf(stderr, fmt, ap);
}


void dmError(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    dmErrorVA(fmt, ap);
    va_end(ap);
}


void dmMsg(int level, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    dmMsgVA(level, fmt, ap);
    va_end(ap);
}


void dmPrint(int level, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    dmPrintVA(level, fmt, ap);
    va_end(ap);
}


/* Memory handling routines
 */
void *dmMalloc(size_t l)
{
    return malloc(l);
}


void *dmMalloc0(size_t l)
{
    return calloc(1, l);
}


void *dmCalloc(size_t n, size_t l)
{
    return calloc(n, l);
}


void *dmRealloc(void *p, size_t l)
{
    return realloc(p, l);
}


void dmFree(void *p)
{
    /* Check for NULL pointers for portability due to some libc
     * implementations not handling free(NULL) too well.
     */
    if (p) free(p);
}


BOOL dmGetIntVal(const char *s, int *i)
{
    if (s[0] == '$')
    {
        if (sscanf(&s[1], "%x", i) < 1)
            return FALSE;
    }
    else if (s[0] == '0' && s[1] == 'x')
    {
        if (sscanf(&s[2], "%x", i) < 1)
            return FALSE;
    }
    else
    {
        if (sscanf(s, "%u", i) < 1)
            return FALSE;
    }
    return TRUE;
}


/* Error handling
 */
#define DM_SYSTEM_ERRORS 100000

int dmGetErrno()
{
    return DM_SYSTEM_ERRORS + errno;
}


const char *dmErrorStr(int error)
{
    if (error >= DM_SYSTEM_ERRORS)
        return strerror(error - DM_SYSTEM_ERRORS);
    
    switch (error)
    {
        case DMERR_OK:             return "No error";
        case DMERR_FOPEN:          return "File open error";
        case DMERR_FREAD:          return "Read error";
        case DMERR_FWRITE:         return "Write error";
        case DMERR_FSEEK:          return "Seek error";
        case DMERR_NOT_FOUND:      return "Resource not found";

        case DMERR_INVALID_DATA:   return "Invalid data";
        case DMERR_MALLOC:         return "Memory allocation failure";
        case DMERR_ALREADY_INIT:   return "Already initialized";
        case DMERR_INIT_FAIL:      return "Initialization failed";
        case DMERR_INVALID_ARGS:   return "Invalid arguments";

        case DMERR_NULLPTR:        return "NULL pointer";
        case DMERR_NOT_SUPPORTED:  return "Operation not supported";
        case DMERR_OUT_OF_DATA:    return "Out of data";
        case DMERR_EXTRA_DATA:     return "Extra data";
        case DMERR_BOUNDS:         return "Bounds check failed";

        case DMERR_NOTPACK:        return "File is not a PACK";
        case DMERR_VERSION:        return "Unsupported PACK version";
        case DMERR_INVALID:        return "Invalid data, corrupted file";
        case DMERR_COMPRESSION:    return "Error in compression";

        default:                   return "Unknown error";
    }
}


#ifdef DM_MUTEX_DEBUG

static DMMutexLock * dmGetMutexThreadIDLock(DMMutex *mutex)
{
    Uint32 id = SDL_ThreadID();
    int i;
    for (i = 0; i < 8; i++)
    {
        DMMutexLock *lock = &(mutex->locks[i]);
        if (lock->used && lock->id == id)
            return lock;
    }
    return NULL;
}

static void dmPrintMutexLocks(DMMutex *mutex, const char *state, const char *file, const int line)
{
    int i;

    fprintf(stderr,
    "----------------------\n"
    "%s --> %p @ %s:%d\n"
    "Current thread: %d\n"
    "Mutex         : %p (created @ %s:%d)\n",
    state, mutex, file, line,
    SDL_ThreadID(), mutex, mutex->cr_file, mutex->cr_line);

    for (i = 0; i < 8; i++)
    {
        DMMutexLock *lock = &(mutex->locks[i]);
        if (lock->used)
        {
            fprintf(stderr,
            "Lock #%d: thread=%d, state=%d\n",
            i, lock->id, lock->state);
        }
    }    
}

int dmDOMutexLock(DMMutex *mutex, const char *file, const int line)
{
    if (mutex != NULL)
    {
        dmPrintMutexLocks(mutex, "LOCKING", file, line);
        
        DMMutexLock *lock = dmGetMutexThreadIDLock(mutex);
        if (lock != NULL)
        {
            int res;
            if (lock->state == 0)
                res = SDL_mutexP(mutex->m);
            else
                res = 1;
            lock->state++;
            fprintf(stderr, "LOCKING %p @ thread=%d done [1].\n", mutex, SDL_ThreadID());
            return res;
        }
        else
        {
            int i;
            for (i = 0; i < 8; i++)
            {
                DMMutexLock *lock = &(mutex->locks[i]);
                if (!lock->used)
                {
                    int res;
                    lock->used = TRUE;
                    lock->id = SDL_ThreadID();
                    lock->state++;
                    res = SDL_mutexP(mutex->m);
                    fprintf(stderr, "LOCKING %p @ thread=%d done [2].\n", mutex, SDL_ThreadID());
                    return res;
                }
            }
            return -2;
        }
    }
    return -1;
}


int dmDOMutexUnlock(DMMutex *mutex, const char *file, const int line)
{
    if (mutex != NULL)
    {
        dmPrintMutexLocks(mutex, "UN-LOCKING", file, line);

        DMMutexLock *lock = dmGetMutexThreadIDLock(mutex);
        if (lock != NULL)
        {
            int res;
            lock->state--;
            if (lock->state == 0)
                res = SDL_mutexV(mutex->m);
            else
                res = lock->state;
            return res;
        }
        else
        {
            return -2;
        }
    }
    return -1;
}


DMMutex * dmDOCreateMutex(const char *file, const int line)
{
    DMMutex *mutex = dmMalloc0(sizeof(DMMutex));
    if (mutex == NULL)
        return NULL;
    mutex->cr_file = dm_strdup(file);
    mutex->cr_line = line;
    mutex->m = SDL_CreateMutex();
    return mutex;
}


void dmDestroyMutex(DMMutex *mutex)
{
    if (mutex != NULL)
    {
        SDL_DestroyMutex(mutex->m);
        dmFree(mutex);
    }
}

#endif
