
#ifdef _WIN32
#include <windows.h>
#endif

#ifdef _MSC_VER
#else
#include <strings.h>
#endif

#include <stdio.h>
#include <stdarg.h>
#include <time.h>   /* So we can seed the random number generator based on time. */
#include <errno.h>

#if !defined(_WIN32)
#include <dirent.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <dlfcn.h>
#endif

#include <stddef.h> /* For size_t. */

/* Sized types. Prefer built-in types. Fall back to stdint. */
#ifdef _MSC_VER
    #if defined(__clang__)
        #pragma GCC diagnostic push
        #pragma GCC diagnostic ignored "-Wlanguage-extension-token"
        #pragma GCC diagnostic ignored "-Wlong-long"
        #pragma GCC diagnostic ignored "-Wc++11-long-long"
    #endif
    typedef   signed __int8  dr_int8;
    typedef unsigned __int8  dr_uint8;
    typedef   signed __int16 dr_int16;
    typedef unsigned __int16 dr_uint16;
    typedef   signed __int32 dr_int32;
    typedef unsigned __int32 dr_uint32;
    typedef   signed __int64 dr_int64;
    typedef unsigned __int64 dr_uint64;
    #if defined(__clang__)
        #pragma GCC diagnostic pop
    #endif
#else
    #define MA_HAS_STDINT
    #include <stdint.h>
    typedef int8_t   dr_int8;
    typedef uint8_t  dr_uint8;
    typedef int16_t  dr_int16;
    typedef uint16_t dr_uint16;
    typedef int32_t  dr_int32;
    typedef uint32_t dr_uint32;
    typedef int64_t  dr_int64;
    typedef uint64_t dr_uint64;
#endif

#ifdef MA_HAS_STDINT
    typedef uintptr_t dr_uintptr;
#else
    #if defined(_WIN32)
        #if defined(_WIN64)
            typedef dr_uint64 dr_uintptr;
        #else
            typedef dr_uint32 dr_uintptr;
        #endif
    #elif defined(__GNUC__)
        #if defined(__LP64__)
            typedef dr_uint64 dr_uintptr;
        #else
            typedef dr_uint32 dr_uintptr;
        #endif
    #else
        typedef dr_uint64 dr_uintptr;   /* Fallback. */
    #endif
#endif

typedef dr_uint8    dr_bool8;
typedef dr_uint32   dr_bool32;
#define DR_TRUE     1
#define DR_FALSE    0

typedef void* dr_handle;
typedef void* dr_ptr;
typedef void (* dr_proc)(void);

#if defined(SIZE_MAX)
    #define DR_SIZE_MAX SIZE_MAX
#else
    #define DR_SIZE_MAX 0xFFFFFFFF
#endif

/*
Return Values:
  0:  Success
  22: EINVAL
  34: ERANGE

Not using symbolic constants for errors because I want to avoid #including errno.h
*/
int dr_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src)
{
    size_t i;

    if (dst == 0) {
        return 22;
    }
    if (dstSizeInBytes == 0) {
        return 34;
    }
    if (src == 0) {
        dst[0] = '\0';
        return 22;
    }

    for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) {
        dst[i] = src[i];
    }

    if (i < dstSizeInBytes) {
        dst[i] = '\0';
        return 0;
    }

    dst[0] = '\0';
    return 34;
}

int dr_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
{
    size_t maxcount;
    size_t i;

    if (dst == 0) {
        return 22;
    }
    if (dstSizeInBytes == 0) {
        return 34;
    }
    if (src == 0) {
        dst[0] = '\0';
        return 22;
    }

    maxcount = count;
    if (count == ((size_t)-1) || count >= dstSizeInBytes) {        /* -1 = _TRUNCATE */
        maxcount = dstSizeInBytes - 1;
    }

    for (i = 0; i < maxcount && src[i] != '\0'; ++i) {
        dst[i] = src[i];
    }

    if (src[i] == '\0' || i == count || count == ((size_t)-1)) {
        dst[i] = '\0';
        return 0;
    }

    dst[0] = '\0';
    return 34;
}

int dr_strcat_s(char* dst, size_t dstSizeInBytes, const char* src)
{
    char* dstorig;

    if (dst == 0) {
        return 22;
    }
    if (dstSizeInBytes == 0) {
        return 34;
    }
    if (src == 0) {
        dst[0] = '\0';
        return 22;
    }

    dstorig = dst;

    while (dstSizeInBytes > 0 && dst[0] != '\0') {
        dst += 1;
        dstSizeInBytes -= 1;
    }

    if (dstSizeInBytes == 0) {
        return 22;  /* Unterminated. */
    }


    while (dstSizeInBytes > 0 && src[0] != '\0') {
        *dst++ = *src++;
        dstSizeInBytes -= 1;
    }

    if (dstSizeInBytes > 0) {
        dst[0] = '\0';
    } else {
        dstorig[0] = '\0';
        return 34;
    }

    return 0;
}

int dr_strncat_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
{
    char* dstorig;

    if (dst == 0) {
        return 22;
    }
    if (dstSizeInBytes == 0) {
        return 34;
    }
    if (src == 0) {
        return 22;
    }

    dstorig = dst;

    while (dstSizeInBytes > 0 && dst[0] != '\0') {
        dst += 1;
        dstSizeInBytes -= 1;
    }

    if (dstSizeInBytes == 0) {
        return 22;  /* Unterminated. */
    }


    if (count == ((size_t)-1)) {        /* _TRUNCATE */
        count = dstSizeInBytes - 1;
    }

    while (dstSizeInBytes > 0 && src[0] != '\0' && count > 0) {
        *dst++ = *src++;
        dstSizeInBytes -= 1;
        count -= 1;
    }

    if (dstSizeInBytes > 0) {
        dst[0] = '\0';
    } else {
        dstorig[0] = '\0';
        return 34;
    }

    return 0;
}

/*
String Helpers
*/
int dr_append_path(char* dst, size_t dstSize, const char* base, const char* other)
{
    int err;
    size_t  len;

    /* TODO: Return the correct error codes here. */
    if (dst == NULL) {
        return -1;
    }
    if (base == NULL || other == NULL) {
        return -1;
    }

    err = dr_strcpy_s(dst, dstSize, base);
    if (err != 0) {
        return err;
    }

    len = strlen(dst);
    if (len > 0) {
        /* Append the slash if required. */
        if (dst[len-1] != '/' && dst[len-1] != '\\') {
            err = dr_strcat_s(dst, dstSize, "/");
            if (err != 0) {
                dst[0] = '\0';
                return err;
            }

            len += 1;   /* +1 to the length to account for the slash. */
        }
    }

    err = dr_strcat_s(dst, dstSize, other);
    if (err != 0) {
        dst[0] = '\0';
        return err;
    }

    /* Success. */
    return 0;
}

const char* dr_path_file_name(const char* path)
{
    const char* fileName = path;

    if (path == NULL) {
        return NULL;
    }

    /* We just loop through the path until we find the last slash. */
    while (path[0] != '\0') {
        if (path[0] == '/' || path[0] == '\\') {
            fileName = path;
        }

        path += 1;
    }

    /* At this point the file name is sitting on a slash, so just move forward. */
    while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) {
        fileName += 1;
    }

    return fileName;
}

const char* dr_extension(const char* path)
{
    const char* extension = path;
    const char* lastoccurance = NULL;

    if (path == NULL) {
        return NULL;
    }

    /* Just find the last '.' and return. */
    while (extension[0] != '\0') {
        if (extension[0] == '.') {
            extension    += 1;
            lastoccurance = extension;
        }

        extension += 1;
    }

    return (lastoccurance != 0) ? lastoccurance : extension;
}

dr_bool32 dr_extension_equal(const char* path, const char* extension)
{
    const char* ext1;
    const char* ext2;

    if (path == NULL || extension == NULL) {
        return 0;
    }

    ext1 = extension;
    ext2 = dr_extension(path);

#ifdef _MSC_VER
    return _stricmp(ext1, ext2) == 0;
#else
    return strcasecmp(ext1, ext2) == 0;
#endif
}




/*
File Iterator

dr_file_iterator state;
dr_file_iterator* pFile = dr_file_iterator_begin("the/folder/path", &state);
while (pFile != NULL) {
    // Do something with pFile.
    pFile = dr_file_iterator_next(pFile);
}

Limitations:
  - Only supports file paths up to 256 characters.
*/
typedef struct
{
    char folderPath[256];
    char relativePath[256]; /* Relative to the original folder path. */
    char absolutePath[256]; /* Concatenation of folderPath and relativePath. */
    dr_bool32 isDirectory;
#ifdef _WIN32
    HANDLE hFind;
#else
    DIR* dir;
#endif
} dr_file_iterator;

dr_file_iterator* dr_file_iterator_begin(const char* pFolderPath, dr_file_iterator* pState)
{
#ifdef _WIN32
    char searchQuery[MAX_PATH];
    unsigned int searchQueryLength;
    WIN32_FIND_DATAA ffd;
    HANDLE hFind;
#else
    DIR* dir;
#endif

    if (pState == NULL) {
        return NULL;
    }

    memset(pState, 0, sizeof(*pState));

    if (pFolderPath == NULL) {
        return NULL;
    }

#ifdef _WIN32
    dr_strcpy_s(searchQuery, sizeof(searchQuery), pFolderPath);

    searchQueryLength = (unsigned int)strlen(searchQuery);
    if (searchQueryLength >= MAX_PATH - 3) {
        return NULL; /* Path is too long. */
    }

    searchQuery[searchQueryLength + 0] = '\\';
    searchQuery[searchQueryLength + 1] = '*';
    searchQuery[searchQueryLength + 2] = '\0';

    hFind = FindFirstFileA(searchQuery, &ffd);
    if (hFind == INVALID_HANDLE_VALUE) {
        return NULL; /* Failed to begin search. */
    }

    /* Skip past "." and ".." directories. */
    while (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) {
        if (!FindNextFileA(hFind, &ffd)) {
            FindClose(hFind);
            return NULL;    /* Couldn't find anything. */
        }
    }

    pState->hFind = hFind;


    if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
        pState->isDirectory = 1;
    } else {
        pState->isDirectory = 0;
    }

    dr_strcpy_s(pState->relativePath, sizeof(pState->relativePath), ffd.cFileName);
#else
    dir = opendir(pFolderPath);
    if (dir == NULL) {
        return NULL;    /* Couldn't find anything. */
    }

    /* Select the first file. */
    for (;;) {
        struct dirent* info;
        struct stat fileinfo;
        char filePath[4096];

        info = readdir(dir);
        if (info == NULL) {
            closedir(dir);
            return NULL;
        }

        if (strcmp(info->d_name, ".") == 0 || strcmp(info->d_name, "..") == 0) {
            continue;   /* Skip past "." and ".." directories. */
        }

        dr_strcpy_s(pState->relativePath, sizeof(pState->relativePath), info->d_name);
        dr_append_path(filePath, sizeof(filePath), pFolderPath, pState->relativePath);

        if (stat(filePath, &fileinfo) != 0) {
            continue;
        }

        if (S_ISDIR(fileinfo.st_mode)) {
            pState->isDirectory = 1;
        } else {
            pState->isDirectory = 0;
        }

        break;
    }

    pState->dir = dir;
#endif

    /* Getting here means everything was successful. We can now set some state before returning. */
    dr_strcpy_s(pState->folderPath, sizeof(pState->folderPath), pFolderPath);
    dr_append_path(pState->absolutePath, sizeof(pState->absolutePath), pState->folderPath, pState->relativePath);

    return pState;
}

dr_file_iterator* dr_file_iterator_next(dr_file_iterator* pState)
{
#ifdef _WIN32
    WIN32_FIND_DATAA ffd;
#endif

    if (pState == NULL) {
        return NULL;
    }

#ifdef _WIN32
    if (!FindNextFileA(pState->hFind, &ffd)) {
        /* Couldn't find anything else. */
        FindClose(pState->hFind);
        pState->hFind = NULL;
        return NULL;
    }

    /* Found something. */
    if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
        pState->isDirectory = 1;
    } else {
        pState->isDirectory = 0;
    }

    dr_strcpy_s(pState->relativePath, sizeof(pState->relativePath), ffd.cFileName);
#else
    /* Enter a loop here so we can skip past "." and ".." directories. */
    for (;;) {
        struct dirent* info;
        struct stat fileinfo;
        char filePath[4096];

        info = readdir(pState->dir);
        if (info == NULL) {
            closedir(pState->dir);
            pState->dir = NULL;
            return NULL;
        }

        if (strcmp(info->d_name, ".") == 0 || strcmp(info->d_name, "..") == 0) {
            continue;   /* Skip past "." and ".." directories. */
        }

        dr_strcpy_s(pState->relativePath, sizeof(pState->relativePath), info->d_name);
        dr_append_path(filePath, sizeof(filePath), pState->folderPath, pState->relativePath);

        /*printf("Done: %s\n", pState->relativePath);*/

        if (stat(filePath, &fileinfo) != 0) {
            continue;
        }

        if (S_ISDIR(fileinfo.st_mode)) {
            pState->isDirectory = 1;
        } else {
            pState->isDirectory = 0;
        }

        break;
    }
#endif

    /* Success */
    dr_append_path(pState->absolutePath, sizeof(pState->absolutePath), pState->folderPath, pState->relativePath);
    return pState;
}

void dr_file_iterator_end(dr_file_iterator* pState)
{
    if (pState == NULL) {
        return;
    }

#ifdef _WIN32
    FindClose(pState->hFind);
    pState->hFind = NULL;
#else
    closedir(pState->dir);
    pState->dir = NULL;
#endif
}


/*
File Management

Free file data with free().
*/
static int dr_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode)
{
#if _MSC_VER && _MSC_VER >= 1400
    errno_t err;
#endif

    if (ppFile != NULL) {
        *ppFile = NULL;  /* Safety. */
    }

    if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) {
        return -1;  /* Invalid args. */
    }

#if _MSC_VER && _MSC_VER >= 1400
    err = fopen_s(ppFile, pFilePath, pOpenMode);
    if (err != 0) {
        return err;
    }
#else
#if defined(_WIN32) || defined(__APPLE__)
    *ppFile = fopen(pFilePath, pOpenMode);
#else
    #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE)
        *ppFile = fopen64(pFilePath, pOpenMode);
    #else
        *ppFile = fopen(pFilePath, pOpenMode);
    #endif
#endif
    if (*ppFile == NULL) {
        return errno;
    }
#endif

    return 0;
}

void* dr_open_and_read_file_with_extra_data(const char* pFilePath, size_t* pFileSizeOut, size_t extraBytes)
{
    FILE* pFile;
    size_t fileSize;
    size_t bytesRead;
    void* pFileData;

    /* Safety. */
    if (pFileSizeOut) {
        *pFileSizeOut = 0;   
    }

    if (pFilePath == NULL) {
        return NULL;
    }

    /* TODO: Use 64-bit versions of the FILE APIs. */

    if (dr_fopen(&pFile, pFilePath, "rb") != 0) {
        return NULL;
    }

    fseek(pFile, 0, SEEK_END);
    fileSize = ftell(pFile);
    fseek(pFile, 0, SEEK_SET);

    if (fileSize + extraBytes > DR_SIZE_MAX) {
        fclose(pFile);
        return NULL;    /* File is too big. */
    }

    pFileData = malloc((size_t)fileSize + extraBytes);    /* <-- Safe cast due to the check above. */
    if (pFileData == NULL) {
        fclose(pFile);
        return NULL;    /* Failed to allocate memory for the file. Good chance the file is too big. */
    }

    bytesRead = fread(pFileData, 1, (size_t)fileSize, pFile);
    if (bytesRead != fileSize) {
        free(pFileData);
        fclose(pFile);
        return NULL;    /* Failed to read every byte from the file. */
    }

    fclose(pFile);

    if (pFileSizeOut) {
        *pFileSizeOut = (size_t)fileSize;
    }

    return pFileData;
}

void* dr_open_and_read_file(const char* pFilePath, size_t* pFileSizeOut)
{
    return dr_open_and_read_file_with_extra_data(pFilePath, pFileSizeOut, 0);
}


dr_bool32 dr_argv_is_set(int argc, char** argv, const char* value)
{
    int iarg;
    for (iarg = 0; iarg < argc; ++iarg) {
        if (strcmp(argv[iarg], value) == 0) {
            return DR_TRUE;
        }
    }

    return DR_FALSE;
}


int dr_vprintf_fixed(int width, const char* const format, va_list args)
{
    int i;
    int len;
    char buffer[4096];

    if (width <= 0) {
        return -1;  /* Width cannot be negative or 0. */
    }

    if ((unsigned int)width > sizeof(buffer)) {
        return -1;  /* Width is too big. */
    }
    
    /* We need to print this into a string (truncated). */
#if (defined(_MSC_VER) && _MSC_VER > 1200) || ((defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500) || defined(_ISOC99_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L))
    len = vsnprintf(buffer, width+1, format, args);
#else
    len = vsprintf(buffer, format, args);
    if (len > width) {
        len = width;
    }
    
    buffer[len] = '\0';
#endif

    printf("%s", buffer);
    for (i = len; i < width; ++i) {
        printf(" ");
    }
    

    return len;
}

int dr_printf_fixed(int width, const char* const format, ...)
{
    int result;

    va_list args;
    va_start(args, format);
    {
        result = dr_vprintf_fixed(width, format, args);
    }
    va_end(args);

    return result;
}

int dr_vprintf_fixed_with_margin(int width, int margin, const char* const format, va_list args)
{
    int i;

    /* Margin. */
    for (i = 0; i < margin; ++i) {
        printf(" ");
    }

    return dr_vprintf_fixed(width - margin, format, args);
}

int dr_printf_fixed_with_margin(int width, int margin, const char* const format, ...)
{
    int result;
    
    va_list args;
    va_start(args, format);
    {
        result = dr_vprintf_fixed_with_margin(width, margin, format, args);
    }
    va_end(args);

    return result;
}



#ifdef _WIN32
static LARGE_INTEGER g_DRTimerFrequency = {{0}};
double dr_timer_now()
{
    LARGE_INTEGER counter;

    if (g_DRTimerFrequency.QuadPart == 0) {
        QueryPerformanceFrequency(&g_DRTimerFrequency);
    }

    QueryPerformanceCounter(&counter);

    return counter.QuadPart / (double)g_DRTimerFrequency.QuadPart;
}
#else
#if _POSIX_C_SOURCE >= 199309L
    #if defined(CLOCK_MONOTONIC)
        #define MA_CLOCK_ID CLOCK_MONOTONIC
    #else
        #define MA_CLOCK_ID CLOCK_REALTIME
    #endif
    double dr_timer_now()
    {
        struct timespec newTime;
        clock_gettime(CLOCK_MONOTONIC, &newTime);

        return ((newTime.tv_sec * 1000000000LL) + newTime.tv_nsec) / 1000000000.0;
    }
#else
    double dr_timer_now()
    {
        struct timeval newTime;
        gettimeofday(&newTime, NULL);

        return ((newTime.tv_sec * 1000000) + newTime.tv_usec) / 1000000.0;
    }
#endif
#endif


float dr_scale_to_range_f32(float x, float lo, float hi)
{
    return lo + x*(hi-lo);
}


#define DR_LCG_M   2147483647
#define DR_LCG_A   48271
#define DR_LCG_C   0
static int g_drLCG;

void dr_seed(int seed)
{
    g_drLCG = seed;
}

int dr_rand_s32()
{
    int lcg = g_drLCG;
    int r = (DR_LCG_A * lcg + DR_LCG_C) % DR_LCG_M;
    g_drLCG = r;
    return r;
}

unsigned int dr_rand_u32()
{
    return (unsigned int)dr_rand_s32();
}

double dr_rand_f64()
{
    return dr_rand_s32() / (double)0x7FFFFFFF;
}

float dr_rand_f32()
{
    return (float)dr_rand_f64();
}

float dr_rand_range_f32(float lo, float hi)
{
    return dr_scale_to_range_f32(dr_rand_f32(), lo, hi);
}

int dr_rand_range_s32(int lo, int hi)
{
    if (lo == hi) {
        return lo;
    }

    return lo + dr_rand_u32() / (0xFFFFFFFF / (hi - lo + 1) + 1);
}



void dr_pcm_s32_to_f32(void* dst, const void* src, dr_uint64 count)
{
    float* dst_f32 = (float*)dst;
    const dr_int32* src_s32 = (const dr_int32*)src;

    dr_uint64 i;
    for (i = 0; i < count; i += 1) {
        double x = src_s32[i];

#if 0
        x = x + 2147483648.0;
        x = x * 0.0000000004656612873077392578125;
        x = x - 1;
#else
        x = x / 2147483648.0;
#endif

        dst_f32[i] = (float)x;
    }
}

void dr_pcm_s32_to_s16(void* dst, const void* src, dr_uint64 count)
{
    dr_int16* dst_s16 = (dr_int16*)dst;
    const dr_int32* src_s32 = (const dr_int32*)src;

    dr_uint64 i;
    for (i = 0; i < count; i += 1) {
        dr_int32 x = src_s32[i];
        x = x >> 16;
        dst_s16[i] = (dr_int16)x;
    }
}




dr_handle dr_dlopen(const char* filename)
{
    dr_handle handle;

#ifdef _WIN32
    handle = (dr_handle)LoadLibraryA(filename);
#else
    handle = (dr_handle)dlopen(filename, RTLD_NOW);
#endif

    return handle;
}

void dr_dlclose(dr_handle handle)
{
#ifdef _WIN32
    FreeLibrary((HMODULE)handle);
#else
    dlclose((void*)handle);
#endif
}

dr_proc dr_dlsym(dr_handle handle, const char* symbol)
{
    dr_proc proc;

#ifdef _WIN32
    proc = (dr_proc)GetProcAddress((HMODULE)handle, symbol);
#else
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wpedantic"
#endif
    proc = (dr_proc)dlsym((void*)handle, symbol);
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
    #pragma GCC diagnostic pop
#endif
#endif

    return proc;
}
