/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2006  Ladislav Vaiz <ok1zia@nagano.cz>
    and authors of web browser Links 0.96

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include "header.h"

static GHashTable *mbs;
static MUTEX_DEFINE(mbs);
#ifdef LEAK_DEBUG_LIST
char *mbs_file;
int mbs_line;
#endif


#ifdef LEAK_DEBUG
long mem_amount = 0;
long last_mem_amount = -1;
#ifdef LEAK_DEBUG_LIST
/*struct list_head memory_list = { &memory_list, &memory_list };*/
struct alloc_header memory_list = { &memory_list, &memory_list };
GMutex *memory_list_mutex;
char *memory_list_file;
int memory_list_line;
#endif
#endif


void check_memory_leaks(void)
{
#ifdef LEAK_DEBUG
    if (mem_amount) {
        free_all_itrms();
        fprintf(stderr, "\n\033[1mMemory leak by %ld bytes\033[0m\n", mem_amount);
#ifdef LEAK_DEBUG_LIST
        fprintf(stderr, "\nList of blocks: ");
        {
            int r = 0;
            struct alloc_header *ah;
            MUTEX_LOCK(memory_list);
            foreach (ah, memory_list) {
                fprintf(stderr, "%s%p:%d @ %s:%d", r ? ", ": "", (char *)ah + L_D_S, ah->size, ah->file, ah->line), r = 1;
                if (ah->comment) fprintf(stderr, ":\"%s\"", ah->comment);
            }
            fprintf(stderr, "\n");
            MUTEX_UNLOCK(memory_list);
        }
#endif
        force_dump();
    }
#endif
}

void init_debug(void){
    mbs=g_hash_table_new(g_direct_hash, g_direct_equal);
    MUTEX_INIT(mbs);
#ifdef LEAK_DEBUG_LIST
    MUTEX_INIT(memory_list);
#endif    
    
    //dbg("init_debug: sizeof(alloc_header)=%d, L_D_S=%d\n", sizeof(struct alloc_header), L_D_S);
	//zdebug_init(debug_type);
}

void free_debug(void){
	zdebug_free();

    g_hash_table_destroy(mbs);
    MUTEX_FREE(mbs);
#ifdef LEAK_DEBUG_LIST
    MUTEX_FREE(memory_list);
#endif
}



static void print_str_hash(gpointer key, gpointer value, gpointer data){
    dbg("\tkey='%s' \tvalue='%s'\n", key, value);
}

void dbg_str_hash(GHashTable *hash){
    g_hash_table_foreach(hash, print_str_hash, NULL);
}

#ifdef LEAK_DEBUG

void *debug_mem_alloc(char *file, int line, size_t size)
{
    void *p;
#ifdef LEAK_DEBUG
    struct alloc_header *ah;
#endif
#ifdef LEAK_DEBUG_LIST
    gpointer orig_key, value;
    char *c;
#endif    
    
    if (!size) return DUMMY;
#ifdef LEAK_DEBUG
    mem_amount += size;
    size += L_D_S;
#endif
    if (!(p = xmalloc(size + MCHK_LEAD))) {
        error("ERROR: out of memory (malloc returned NULL)\n");
        return NULL;
    }
#ifdef LEAK_DEBUG
    ah = p;
    p = (char *)p + L_D_S;
    ah->size = size - L_D_S;
#ifdef LEAK_DEBUG_LIST
//    fprintf(stderr, "malloc(%d)=%p p=%p L_D_S=%d\n", size + MCHK_LEAD, ah, p, L_D_S);
    for (c = (char *)p - MCHK_LEAD; c != p; c++) {
        *c = MCHK_PATTERN;
//        fprintf(stderr, "%p=%02x\n", c, (unsigned char)*c);

    }
    for (c = (char *)p + ah->size; c != (char *)p + ah->size + MCHK_TRAIL; c++){
        *c = MCHK_PATTERN;
//        fprintf(stderr, "%p=%02x\n", c, (unsigned char)*c);
    }
    ah->file = file;
    ah->line = line;
    ah->comment = NULL;
    MUTEX_LOCK(memory_list);
    add_to_list(memory_list, ah);
    MUTEX_UNLOCK(memory_list);

    MUTEX_LOCK(mbs);
    if (g_hash_table_lookup_extended(mbs, (char*)p, &orig_key, &value)){
        int cnt=(vint)value;
        if (value!=0){
            free_all_itrms();
            fprintf(stderr, " alloc: already allocated(%d): %p:%d @ %s:%d\n", cnt, p, ah->size, ah->file, ah->line);
            force_dump();
        }
    }
    g_hash_table_insert(mbs, p, (gpointer)1);
    MUTEX_UNLOCK(mbs);
#endif
#endif
/*    fprintf(stderr, "debuf_mem_alloc(%s:%d, %d)=%p\n", file, line, size, p);*/
    return p;
}

void debug_mem_free(char *file, int line, void *p)
{
#ifdef LEAK_DEBUG
    void *xp;
    struct alloc_header *ah;
#endif
#ifdef LEAK_DEBUG_LIST
    gpointer orig_key, value;
    char *c;
#endif    

    
    if (p == DUMMY) return;
    if (!p) {
        errfile = file, errline = line, internal_error("mem_free(NULL)");
        return;
    }
#ifdef LEAK_DEBUG
    xp = p;
    p = (char *)p - L_D_S;
    ah = p;
#ifdef LEAK_DEBUG_LIST
    for (c = (char *)xp - MCHK_LEAD; c != xp; c++) {
        if (*c == MCHK_PATTERN) continue;
        fprintf(stderr, " free: corrupted leading block %p:%d @ %s:%d  ", xp, ah->size, ah->file, ah->line);
        for (c = (char *)xp - MCHK_LEAD; c != xp; c++) fprintf(stderr, " %02x", (unsigned char)*c);
        fprintf(stderr, "\n");
        break;
    }
    for (c = (char *)xp + ah->size; c != (char *)xp + ah->size + MCHK_TRAIL; c++) {
        if (*c == MCHK_PATTERN) continue;
        fprintf(stderr, " free: corrupted trailing block %p:%d @ %s:%d  ", xp, ah->size, ah->file, ah->line);
        for (c = (char *)xp - MCHK_LEAD; c != xp; c++) fprintf(stderr, " %02x", (unsigned char)*c);
        fprintf(stderr, "\n");
        break;
    }
    MUTEX_LOCK(mbs);
    if (g_hash_table_lookup_extended(mbs, xp, &orig_key, &value)){
        int cnt=(vint)value;
        if (cnt!=1){
            MUTEX_UNLOCK(mbs);
            free_all_itrms();
            fprintf(stderr, " free: usage count is %d: %p:%d @ %s:%d\n", cnt, xp, ah->size, ah->file, ah->line);
            force_dump();
        }
        g_hash_table_remove(mbs, xp);
        g_hash_table_insert(mbs, xp, (gpointer)0);
    }
    MUTEX_UNLOCK(mbs);
    MUTEX_LOCK(memory_list);
    del_from_list(ah);
    MUTEX_UNLOCK(memory_list);
    if (ah->comment) free(ah->comment);
#endif
    mem_amount -= ah->size;
#endif
    /*fprintf(stderr, "debuf_mem_free(%s:%d)=%p\n", file, line, p);*/
    xfree(p);
}

void *debug_mem_realloc(char *file, int line, void *p, size_t size)
{
#ifdef LEAK_DEBUG
    struct alloc_header *ah;
#endif
#ifdef LEAK_DEBUG_LIST
    gpointer orig_key, value;
    char *c;
#endif    


    if (p == DUMMY) return debug_mem_alloc(file, line, size);
    if (!p) {
        errfile = file, errline = line, internal_error("mem_realloc(NULL, %d)", size);
        return NULL;
    }
    if (!size) {
        debug_mem_free(file, line, p);
        return DUMMY;
    }
#ifdef LEAK_DEBUG_LIST    
    ah = (struct alloc_header*)((char*)p - L_D_S);
    for (c = (char *)p - MCHK_LEAD; c != p; c++) {
        if (*c == MCHK_PATTERN) continue;
        fprintf(stderr, " free: corrupted leading block %p:%d @ %s:%d  ", p, ah->size, ah->file, ah->line);
        for (c = (char *)p - MCHK_LEAD; c != p; c++) fprintf(stderr, " %02x", (unsigned char)*c);
        fprintf(stderr, "\n");
        break;
    }
    for (c = (char *)p + ah->size; c != (char *)p + ah->size + MCHK_TRAIL; c++) {
        if (*c == MCHK_PATTERN) continue;
        fprintf(stderr, " free: corrupted trailing block %p:%d @ %s:%d  ", p, ah->size, ah->file, ah->line);
        for (c = (char *)p - MCHK_LEAD; c != p; c++) fprintf(stderr, " %02x", (unsigned char)*c);
        fprintf(stderr, "\n");
        break;
    }
    MUTEX_LOCK(mbs);
    if (g_hash_table_lookup_extended(mbs, p, &orig_key, &value)){
        int cnt=(vint)value;
        if (cnt!=1){
			dbg("debug_mem_realloc: cnt=%d\n", cnt);
			MUTEX_UNLOCK(mbs);
            free_all_itrms();
            fprintf(stderr, " realloc1: usage count is %d: %p @ %s:%d\n", cnt, p, file, line);
            force_dump();
        }
        g_hash_table_remove(mbs, p);
        g_hash_table_insert(mbs, p, (gpointer)0);
    }
    MUTEX_UNLOCK(mbs);
#endif    
    if (!(p = xrealloc((char *)p - L_D_S, size + L_D_S + MCHK_TRAIL))) {
        error("ERROR: out of memory (realloc returned NULL)\n");
        return NULL;
    }
#ifdef LEAK_DEBUG
    ah = p;
    mem_amount += size - ah->size;
    ah->size = size;
    p = (char *)p + L_D_S;
#ifdef LEAK_DEBUG_LIST
//    fprintf(stderr, "realloc(%d)=%p p=%p L_D_S=%d\n", size + MCHK_LEAD, ah, p, L_D_S);
    for (c = (char *)p - MCHK_LEAD; c != p; c++) {
        *c = MCHK_PATTERN;
//        fprintf(stderr, "%p=%02x\n", c, (unsigned char)*c);

    }
    for (c = (char *)p + ah->size; c != (char *)p + ah->size + MCHK_TRAIL; c++){
        *c = MCHK_PATTERN;
//        fprintf(stderr, "%p=%02x\n", c, (unsigned char)*c);
    }
    ah->prev->next = ah;
    ah->next->prev = ah;
    
    MUTEX_LOCK(mbs);
    if (g_hash_table_lookup_extended(mbs, p, &orig_key, &value)){
        int cnt=(vint)value;
        if (value!=0){
            free_all_itrms();
            fprintf(stderr, " realloc2: already allocated(%d): %p:%d @ %s:%d (allocated %s:%d)\n", cnt, (char *)ah + L_D_S, ah->size, file, line, ah->file, ah->line);
            force_dump();
        }
    }
    g_hash_table_insert(mbs, p, (gpointer)1);
    MUTEX_UNLOCK(mbs);
#endif
#endif
    return p;
}

void set_mem_comment(void *p, char *c, int l)
{
#ifdef LEAK_DEBUG_LIST
    struct alloc_header *ah = (struct alloc_header *)((char *)p - L_D_S);
    if (ah->comment) free(ah->comment);
    if ((ah->comment = malloc(l + 1))) memcpy(ah->comment, c, l), ah->comment[l] = 0;
#endif
}


void *debug_g_new0(char *file, int line, size_t len){
    gchar *c;

    c=debug_mem_alloc(file, line, len);
    if (c==DUMMY) return NULL;
    memset(c, 0, len);
    return c;
}

void *debug_g_new(char *file, int line, size_t len){
    gchar *c;

    c=debug_mem_alloc(file, line, len);
    if (c==DUMMY) return NULL;
    memset(c, 0, len);
    return c;
}

gchar *debug_g_strdup(char *file, int line, const gchar *str){
    gchar *c;
    
    if (!str) return NULL;
    c=debug_mem_alloc(file, line, strlen(str)+1);
    if (c==DUMMY) return NULL;
    strcpy(c, str);
    return c;
}

gchar *debug_g_strndup(char *file, int line, gchar *str, int len){
    gchar *c;
    int l;
    
    if (!str) return NULL;
    l=strlen(str);
    if (l>len) l=len;
    c=debug_mem_alloc(file, line, l+1);
    if (c==DUMMY) return NULL;
    strncpy(c, str, l);
    c[l]='\0';
    return c;
}

#undef g_strdup_vprintf
#undef g_free

gchar *debug_g_strdup_printf(char *file, int line, gchar *fmt, ...){
    va_list l;
    //int len;
    char *c,*d;

    va_start(l, fmt);
  /*  len=g_printf_string_upper_bound(fmt, l);
    c=debug_mem_alloc(file, line, len);
    if (c==DUMMY) return NULL;
    g_vsprintf(c, fmt, l);*/

    d = g_strdup_vprintf(fmt, l);
    c = debug_g_strdup(file, line, d);
    g_free(d);

    va_end(l);
    return c;
}

gchar *debug_g_strdup_vprintf(char *file, int line, gchar *fmt, va_list args){
//  int len;
    char *c,*d;

/*    len=g_printf_string_upper_bound(fmt, args);
    c=debug_mem_alloc(file, line, len);
    if (c==DUMMY) return NULL;
    g_vsprintf(c, fmt, args);*/
    
    d = g_strdup_vprintf(fmt, args);
    c = debug_g_strdup(file, line, d);
    g_free(d);
    return c;
}

gchar *debug_g_strconcat(char *file, int line, ...){
    va_list l;
    int len;
    char *c,*d;
    char *s;
    
    va_start(l, line);
    len=0;
    while(1){
        c=va_arg(l, char *);
        if (!c) break;
        len+=strlen(c);
    }
    va_end(l);
    s=debug_mem_alloc(file, line, len+1);
    if (s==DUMMY) return NULL;
    va_start(l, line);
    len=0;
    d=s;
    while(1){
        c=va_arg(l, char *);
        if (!c) break;
        strcpy(d, c);
        d+=strlen(c);
    }
    va_end(l);
    return s;
}
#endif


void sock_debug(int sock, char *m, ...){

    va_list l;
    FILE *f;

    return;
    
    f = fopen("tucnak.sockdbg", "at");
    if (!f) return;

    va_start(l, m);
    fprintf(f, "%5d: socket %3d ", getpid(), sock);
    vfprintf(f, m, l);
    fprintf(f,"\n");
    va_end(l);
    fclose(f);
}

