#include <malloc.h>   /* for alloca() */


/************************************************************/
/* errno and GetLastError support */

#include "misc_thread_common.h"

static DWORD cffi_tls_index = TLS_OUT_OF_INDEXES;

BOOL WINAPI DllMain(HINSTANCE hinstDLL,
                    DWORD     reason_for_call,
                    LPVOID    reserved)
{
    LPVOID p;

    switch (reason_for_call) {

    case DLL_THREAD_DETACH:
        if (cffi_tls_index != TLS_OUT_OF_INDEXES) {
            p = TlsGetValue(cffi_tls_index);
            if (p != NULL) {
                TlsSetValue(cffi_tls_index, NULL);
                cffi_thread_shutdown(p);
            }
        }
        break;

    default:
        break;
    }
    return TRUE;
}

static void init_cffi_tls(void)
{
    if (cffi_tls_index == TLS_OUT_OF_INDEXES) {
        cffi_tls_index = TlsAlloc();
        if (cffi_tls_index == TLS_OUT_OF_INDEXES)
            PyErr_SetString(PyExc_WindowsError, "TlsAlloc() failed");
    }
}

static struct cffi_tls_s *get_cffi_tls(void)
{
    LPVOID p = TlsGetValue(cffi_tls_index);

    if (p == NULL) {
        p = malloc(sizeof(struct cffi_tls_s));
        if (p == NULL)
            return NULL;
        memset(p, 0, sizeof(struct cffi_tls_s));
        TlsSetValue(cffi_tls_index, p);
    }
    return (struct cffi_tls_s *)p;
}

#ifdef USE__THREAD
# error "unexpected USE__THREAD on Windows"
#endif

static void save_errno(void)
{
    int current_err = errno;
    int current_lasterr = GetLastError();
    struct cffi_tls_s *p = get_cffi_tls();
    if (p != NULL) {
        p->saved_errno = current_err;
        p->saved_lasterror = current_lasterr;
    }
    /* else: cannot report the error */
}

static void restore_errno(void)
{
    struct cffi_tls_s *p = get_cffi_tls();
    if (p != NULL) {
        SetLastError(p->saved_lasterror);
        errno = p->saved_errno;
    }
    /* else: cannot report the error */
}

/************************************************************/


#if PY_MAJOR_VERSION >= 3
static PyObject *b_getwinerror(PyObject *self, PyObject *args, PyObject *kwds)
{
    int err = -1;
    int len;
    WCHAR *s_buf = NULL; /* Free via LocalFree */
    PyObject *v, *message;
    static char *keywords[] = {"code", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i", keywords, &err))
        return NULL;

    if (err == -1) {
        struct cffi_tls_s *p = get_cffi_tls();
        if (p == NULL)
            return PyErr_NoMemory();
        err = p->saved_lasterror;
    }

    len = FormatMessageW(
        /* Error API error */
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,           /* no message source */
        err,
        MAKELANGID(LANG_NEUTRAL,
        SUBLANG_DEFAULT), /* Default language */
        (LPWSTR) &s_buf,
        0,              /* size not used */
        NULL);          /* no args */
    if (len==0) {
        /* Only seen this in out of mem situations */
        message = PyUnicode_FromFormat("Windows Error 0x%X", err);
    } else {
        /* remove trailing cr/lf and dots */
        while (len > 0 && (s_buf[len-1] <= L' ' || s_buf[len-1] == L'.'))
            s_buf[--len] = L'\0';
        message = PyUnicode_FromWideChar(s_buf, len);
    }
    if (message != NULL)
        v = Py_BuildValue("(iO)", err, message);
    else
        v = NULL;
    LocalFree(s_buf);
    return v;
}
#else
static PyObject *b_getwinerror(PyObject *self, PyObject *args, PyObject *kwds)
{
    int err = -1;
    int len;
    char *s;
    char *s_buf = NULL; /* Free via LocalFree */
    char s_small_buf[40]; /* Room for "Windows Error 0xFFFFFFFFFFFFFFFF" */
    PyObject *v;
    static char *keywords[] = {"code", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i", keywords, &err))
        return NULL;

    if (err == -1) {
        struct cffi_tls_s *p = get_cffi_tls();
        if (p == NULL)
            return PyErr_NoMemory();
        err = p->saved_lasterror;
    }

    len = FormatMessage(
        /* Error API error */
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,           /* no message source */
        err,
        MAKELANGID(LANG_NEUTRAL,
        SUBLANG_DEFAULT), /* Default language */
        (LPTSTR) &s_buf,
        0,              /* size not used */
        NULL);          /* no args */
    if (len==0) {
        /* Only seen this in out of mem situations */
        sprintf(s_small_buf, "Windows Error 0x%X", err);
        s = s_small_buf;
        s_buf = NULL;
    } else {
        s = s_buf;
        /* remove trailing cr/lf and dots */
        while (len > 0 && (s[len-1] <= ' ' || s[len-1] == '.'))
            s[--len] = '\0';
    }
    v = Py_BuildValue("(is)", err, s);
    LocalFree(s_buf);
    return v;
}
#endif


/************************************************************/
/* Emulate dlopen()&co. from the Windows API */

#define RTLD_LAZY   0
#define RTLD_NOW    0
#define RTLD_GLOBAL 0
#define RTLD_LOCAL  0

static void *dlopen(const char *filename, int flag)
{
    return (void *)LoadLibraryA(filename);
}

static void *dlopenW(const wchar_t *filename)
{
    return (void *)LoadLibraryW(filename);
}

static void *dlsym(void *handle, const char *symbol)
{
    void *address = GetProcAddress((HMODULE)handle, symbol);
#ifndef MS_WIN64
    if (!address) {
        /* If 'symbol' is not found, then try '_symbol@N' for N in
           (0, 4, 8, 12, ..., 124).  Unlike ctypes, we try to do that
           for any symbol, although in theory it should only be done
           for __stdcall functions.
        */
        int i;
        char *mangled_name = alloca(1 + strlen(symbol) + 1 + 3 + 1);
        if (!mangled_name)
            return NULL;
        for (i = 0; i < 32; i++) {
            sprintf(mangled_name, "_%s@%d", symbol, i * 4);
            address = GetProcAddress((HMODULE)handle, mangled_name);
            if (address)
                break;
        }
    }
#endif
    return address;
}

static int dlclose(void *handle)
{
    return FreeLibrary((HMODULE)handle) ? 0 : -1;
}

static const char *dlerror(void)
{
    static char buf[32];
    DWORD dw = GetLastError(); 
    if (dw == 0)
        return NULL;
    sprintf(buf, "error 0x%x", (unsigned int)dw);
    return buf;
}
