/*
 * Copyright (c) 2005 Massachusetts Institute of Technology
 *
 * 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.
 */

/* $Id$ */

#include<windows.h>
#include<utils.h>
#include<malloc.h>
#include<stdio.h>
#include<strsafe.h>
#include<assert.h>

#define HASHSIZE 1151
#define ALLOCBLOCK 1024

#define HASHPTR(p) (((size_t) (p)) % HASHSIZE)

typedef struct tag_allocation {
    const char * file;
    int    line;
    size_t size;
    void * ptr;
#ifdef _WIN32
    DWORD  thread;
#endif

    LDCL(struct tag_allocation);
} allocation;

static allocation * ht[HASHSIZE];

static allocation * next_alloc = NULL;
static size_t       idx_next_alloc = 0;
static allocation * free_alloc = NULL;

typedef struct tag_thread_info {
#ifdef _WIN32
    DWORD thread;
#else
#error Unsupported platform
#endif
    wchar_t name[128];
    wchar_t creator[128];

    const char * file;
    int line;

    LDCL(struct tag_thread_info);
} thread_info;

static thread_info * threads = NULL;

static hashtable fn_hash;

static CRITICAL_SECTION cs_alloc;
static LONG ctr = 0;
static int  perf_ready = 0;

static DWORD init_thread = 0;

static khm_int32 hash_stringA(const void * vs) {
    /* DJB algorithm */

    khm_int32 hv = 13331;
    char * c;

    for (c = (char *) vs; *c; c++) {
        hv = ((hv << 5) + hv) + (khm_int32) *c;
    }

    return (hv & KHM_INT32_MAX);
}

static khm_int32 hash_string_compA(const void * vs1,
                                   const void * vs2) {
    return strcmp((const char *) vs1, (const char *) vs2);
}

static void perf_once(void) {
    if (InterlockedIncrement(&ctr) == 1) {
        InitializeCriticalSection(&cs_alloc);
        ZeroMemory(ht, sizeof(ht));

        next_alloc = malloc(sizeof(allocation) * ALLOCBLOCK);
        assert(next_alloc);
        idx_next_alloc = 0;
        free_alloc = NULL;

        ZeroMemory(&fn_hash, sizeof(fn_hash));
        fn_hash.n = 13;
        fn_hash.hash = hash_stringA;
        fn_hash.comp = hash_string_compA;
        fn_hash.bins = calloc(sizeof(hash_bin *), fn_hash.n);

        perf_ready = 1;
    } else {
        DWORD this_thread = GetCurrentThreadId();

        while(!perf_ready &&
              init_thread != this_thread) {
            Sleep(0);           /* relinquish control to the thread
                                   that is initializing the alloc
                                   data. */
        }
    }
}

static allocation * get_allocation(void) {
    allocation * a;

    LPOP(&free_alloc, &a);
    if (!a) {
        if (idx_next_alloc == ALLOCBLOCK) {
            next_alloc = malloc(sizeof(allocation) * ALLOCBLOCK);
            assert(next_alloc);
            idx_next_alloc = 0;
        }

        a = &next_alloc[idx_next_alloc];
        idx_next_alloc++;
    }

    return a;
}

#define MAXCB_STR 32768

KHMEXP wchar_t *
perf_wcsdup(const char * file, int line, const wchar_t * str) {
    size_t cb;
    wchar_t * dest;

    if (FAILED(StringCbLength(str, MAXCB_STR, &cb)))
        return NULL;
    cb += sizeof(wchar_t);

    dest = (wchar_t *) perf_malloc(file, line, cb);
    StringCbCopy(dest, cb, str);

    return dest;
}

KHMEXP char *
perf_strdup(const char * file, int line, const char * str) {
    size_t cb;
    char * dest;

    if (FAILED(StringCbLengthA(str, MAXCB_STR, &cb)))
        return NULL;
    cb += sizeof(char);

    dest = (char *) perf_malloc(file, line, cb);
    StringCbCopyA(dest, cb, str);

    return dest;
}

KHMEXP void *
perf_calloc(const char * file, int line, size_t num, size_t size) {
    void * ptr;
    size_t tsize;

    tsize = num * size;

    ptr = perf_malloc(file,line,tsize);

    if (ptr) {
        ZeroMemory(ptr, tsize);
    }

    return ptr;
}

KHMEXP void * 
perf_malloc(const char * file, int line, size_t s) {
    allocation * a;
    void * ptr;
    size_t h;
    char * fn_copy = NULL;

    perf_once();

    assert(s > 0);

    EnterCriticalSection(&cs_alloc);
    a = get_allocation();

    ptr = malloc(s);

    assert(ptr);                /* TODO: handle this gracefully */

    if (file[0] == '.' && file[1] == '\\')
        file += 2;

    fn_copy = hash_lookup(&fn_hash, file);
    if (fn_copy == NULL) {

        size_t cblen = 0;
        if (FAILED(StringCbLengthA(file, MAX_PATH * sizeof(char),
                                   &cblen)))
            fn_copy = NULL;
        else {
            fn_copy = malloc(cblen + sizeof(char));
            if (fn_copy) {
                hash_bin * b;
                int hv;

                StringCbCopyA(fn_copy, cblen + sizeof(char), file);

                hv = fn_hash.hash(fn_copy) % fn_hash.n;

                b = malloc(sizeof(*b));
                b->data = fn_copy;
                b->key = fn_copy;
                LINIT(b);
                LPUSH(&fn_hash.bins[hv], b);
            }
        }
    }

    a->file = fn_copy;
    a->line = line;
    a->size = s;
    a->ptr = ptr;
#ifdef _WIN32
    a->thread = GetCurrentThreadId();
#endif

    h = HASHPTR(ptr);

    LPUSH(&ht[h], a);
    LeaveCriticalSection(&cs_alloc);

    return ptr;
}

KHMEXP void *
perf_realloc(const char * file, int line, void * data, size_t s) {
    void * n_data;
    allocation * a;
    size_t h;

    if (data == NULL)
        return perf_malloc(file, line, s);

    perf_once();
    h = HASHPTR(data);

    n_data = realloc(data, s);

    assert(n_data);

    EnterCriticalSection(&cs_alloc);
    for (a = ht[h]; a; a = LNEXT(a)) {
        if (a->ptr == data)
            break;
    }

    assert(a);

    LDELETE(&ht[h], a);

    a->size = s;
    a->ptr = n_data;

    h = HASHPTR(n_data);
    LPUSH(&ht[h], a);
    LeaveCriticalSection(&cs_alloc);

    return n_data;
}

KHMEXP void
perf_free  (void * b) {
    size_t h;
    allocation * a;

    perf_once();
    h = HASHPTR(b);

    EnterCriticalSection(&cs_alloc);
    for(a = ht[h]; a; a = LNEXT(a)) {
        if (a->ptr == b)
            break;
    }

    assert(a);

    LDELETE(&ht[h], a);
    LPUSH(&free_alloc, a);
    LeaveCriticalSection(&cs_alloc);
}

KHMEXP void KHMAPI
perf_dump(FILE * f) {
    size_t i;
    allocation * a;
    size_t total = 0;
    thread_info * t;

    perf_once();

    EnterCriticalSection(&cs_alloc);

    fprintf(f, "p00\t*** Threads ***\n");
    fprintf(f, "p00\tFile\tLine\tThread\tName\tCreated by\n");

    for (t = threads; t; t = LNEXT(t)) {
        fprintf(f, "p01\t%s\t%6d\t%6d\t%S\t%S\n",
                t->file, t->line, t->thread,
                t->name, t->creator);
    }

    fprintf(f, "p02\t--- End Threads ---\n");

    fprintf(f, "p10\t*** Leaked allocations list ***\n");
    fprintf(f, "p11\tFile\tLine\tThread\tSize\tAddress\n");

    for (i=0; i < HASHSIZE; i++) {
        for (a = ht[i]; a; a = LNEXT(a)) {
            fprintf(f, "p12\t%s\t%6d\t%6d\t%6d\t0x%p\n", a->file, a->line,
		    a->thread, a->size, a->ptr);
            total += a->size;
        }
    }

    fprintf(f, "p20\t----------------------------------------\n");
    fprintf(f, "p21\tTotal\t\t%d\n", total);
    fprintf(f, "p22\t----------------- End ------------------\n");

    LeaveCriticalSection(&cs_alloc);
}

KHMEXP void
perf_set_thread_desc(const char * file, int line,
                     const wchar_t * name, const wchar_t * creator) {
    thread_info * t;
    char * fn_copy;

    perf_once();

    t = malloc(sizeof(*t));
    ZeroMemory(t, sizeof(*t));

#ifdef _WIN32
    t->thread = GetCurrentThreadId();
#else
#error Unsupported platform
#endif

    StringCbCopy(t->name, sizeof(t->name), name);
    if (creator)
        StringCbCopy(t->creator, sizeof(t->creator), creator);

    if (file[0] == '.' && file[1] == '\\')
        file += 2;

    EnterCriticalSection(&cs_alloc);

    fn_copy = hash_lookup(&fn_hash, file);
    if (fn_copy == NULL) {
        size_t cblen = 0;
        if (FAILED(StringCbLengthA(file, MAX_PATH * sizeof(char),
                                   &cblen)))
            fn_copy = NULL;
        else {
            fn_copy = malloc(cblen + sizeof(char));
            if (fn_copy) {
                hash_bin * b;
                int hv;

                StringCbCopyA(fn_copy, cblen + sizeof(char), file);

                hv = fn_hash.hash(fn_copy) % fn_hash.n;

                b = malloc(sizeof(*b));
                b->data = fn_copy;
                b->key = fn_copy;
                LINIT(b);
                LPUSH(&fn_hash.bins[hv], b);
            }
        }
    }

    t->file = fn_copy;
    t->line = line;

    LPUSH(&threads, t);
    LeaveCriticalSection(&cs_alloc);
}
