/*
 *         Author:  John Ryland (jryland), jryland@xiaofrog.com
 *        Company:  InvertedLogic
 */

#define _DEBUG_MEMORY_OFF_ // Avoid recursion
#include <stdio.h>
#include <stdlib.h>
#include "Iterators.h"
#include "DebugMemory.h"


class AllocationRecord
{
public:
    AllocationRecord() { ptr = 0; }
    AllocationRecord(void *p, int s, const char *f, int l, int fl) {
        ptr = p;
        size = s;
        file = f;
        line = l;
        flags = fl;
    }
    void *ptr;
    int size;
    const char *file;
    int line;
    int flags;
};


class IntRecord {
public:
    IntRecord() { value = 0; }
    IntRecord(const char *k, int x) { key = k; value = x; }
    const char *key;
    int value;
};


Dict<AllocationRecord>   records;
Dict<IntRecord>          fileLineTotals;
Dict<IntRecord>          fileTotals;


int debug_mem_log_level = 0;


void DebugMemory::logMemoryAllocations(int level)
{
    debug_mem_log_level = level;
}


#define normal_log             if ( debug_mem_log_level > 0 ) printf
#define verbose_log            if ( debug_mem_log_level > 1 ) printf


void DebugMemory::showMemoryLeaks()
{
#ifdef DEBUG
    if ( records.count() == 0 )
        printf("No leaks found.\n");
    else
        foreach (AllocationRecord rec, records)
            printf("allocation from %s, line %i, leaked %i bytes at %p.\n", rec.file, rec.line, rec.size, rec.ptr);
#else
    printf("Rebuild in DEBUG mode to enable memory logging.\n");
#endif
}


void DebugMemory::showMemoryAllocations()
{
#ifdef DEBUG
    foreach (IntRecord rec, fileLineTotals)
        printf("file and line %s allocated %i bytes\n", rec.key, rec.value);
    foreach (IntRecord rec, fileTotals)
        printf("file %s allocated %i bytes\n", rec.key, rec.value);
#else
    printf("Rebuild in DEBUG mode to enable memory logging.\n");
#endif
}


const char *debug_mem_file = 0;
int debug_mem_line = 0;


void *DebugMemory::addLog(int type, void *p, unsigned int nNum, unsigned int size, const char *file, int line)
{
#ifdef DEBUG
    const char *operation[] = { "-", "calloc", "valloc", "realloc", "reallocf", "malloc", "new", "new[]", "new", "new[]", "free", "delete", "delete[]" };
    const int typeMap[] = { 0, 1, 1, 1, 1, 1, 2, 3, 2, 3, 1, 2, 3 };
    char tmpLoc[1024];
    const char *loc = tmpLoc;
    if (!file) {
        loc = "unknown location";
        file = "unknown";
        line = -1;
    } else
        snprintf(tmpLoc, 1024, "%s, line %i", file, line);
    void *ptr = 0;
    if ( type == 8 || type == 9 || type == 11 || type == 12 )
        debug_mem_file = 0;
    switch (type)
    {
        case 0: debug_mem_file = file; debug_mem_line = line; return 0;
        case 1: ptr = calloc(nNum, size); size *= nNum; break;
        case 2: ptr = valloc(size); break;
        case 3: ptr = realloc(p, size); if (!ptr) return 0; break;
        case 4: ptr = reallocf(p, size); break;
        case 5: case 6: case 7: case 8:
        case 9: ptr = malloc(size); break;
        case 10: case 11: case 12: free(p); break;
        default: return 0;
    }
    normal_log("Debug: doing %s ", operation[type]); 
    if ( p ) normal_log("of ptr %p ", p);
    if ( size ) normal_log("of size %i ", size);
    normal_log("from %s.\n", loc);
/*
    if ( type == 10 || type == 11 || type == 12 ) {
        normal_log("Debug: doing %s of ptr %p from %s.\n", operation[type], p, loc); 
    } else if ( type == 3 || type == 4 ) {
        normal_log("Debug: doing %s of ptr %p of size %i from %s.\n", operation[type], p, size, loc);
    } else
        normal_log("Debug: doing %s of size %i from %s.\n", operation[type], size, loc);
*/
    if ( type == 3 || type == 4 || type == 10 || type == 11 || type == 12 ) {
        if (p) {
            char key[32];
            sprintf(key, "%p", p);
            AllocationRecord rec = records.find(key);
            //const AllocationRecord &rec = records.find(key);
            if ( type == 3 || type == 4 )
                normal_log("Debug: realloc'd memory was previously at %p and allocated %i bytes from %s, line %i\n", rec.ptr, rec.size, rec.file ? rec.file : "-", rec.line);
            if ( rec.ptr ) {
                if ( rec.flags != typeMap[type] ) {
                    printf("Unmatched allocation type for ptr %p from %s.\n", p, loc);
                    printf("eg Used free on pointer allocated with new, or malloc and delete\n");
                    printf("new[] and delete, or new and delete[], or new and realloc etc.\n");
                    records.remove(key); // Treat as warning and still proceed as if this could be okay.
                } else {
                    records.remove(key);
                    verbose_log("removed allocation record for ptr %p\n", p);
                }
            } else {
                printf("Delete/free of unmatched allocation for ptr %p from %s.\n", p, loc);
            }
        }
    }
    
    if ( type == 10 || type == 11 || type == 12 )
        return 0;
/*
    if ( type == 3 || type == 4 ) {
        char key[32];
        sprintf(key, "%p", p);
        const AllocationRecord &rec = records.find(key);
        normal_log("Debug: realloc'd memory was previously at %p and allocated %i bytes from %s, line %i\n", rec.ptr, rec.size, rec.file, rec.line);
    }
*/
    char key[32];
    sprintf(key, "%p", ptr);
    records.append(key, AllocationRecord(ptr, size, file, line, typeMap[type]));
    verbose_log("added allocation record for ptr %p\n", ptr);
    int x = fileLineTotals.find(loc).value;
    if (x)
        fileLineTotals.remove(loc);
    fileLineTotals.append(loc, IntRecord(strdup(loc), x + size));
    int y = fileTotals.find(file).value;
    if (y)
        fileTotals.remove(file);
    fileTotals.append(file, IntRecord(strdup(file), y + size));
    return ptr;
#endif
}


#ifdef DEBUG
void *operator new    (size_t size) { return DebugMemory::addLog(8, 0, 0, size, debug_mem_file, debug_mem_line); }
void *operator new[]  (size_t size) { return DebugMemory::addLog(9, 0, 0, size, debug_mem_file, debug_mem_line); }
void operator delete  (void *p)     { DebugMemory::addLog(11, p, 0, 0, debug_mem_file, debug_mem_line); }
void operator delete[](void *p)     { DebugMemory::addLog(12, p, 0, 0, debug_mem_file, debug_mem_line); }
#endif


