#include "config.h"
#include "minato_debug.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

////////////////////////////////////////////////////////////////////////////////
// エラーチェックつきのstrdup
////////////////////////////////////////////////////////////////////////////////
ALLOCATED char* mystrdup(char* str)
{
    char* result;
    
    result = strdup(str);
    if(result == NULL) {
        fprintf(stderr, "False in allocating memory.");
        exit(1);
    }
   
    return result;
}

////////////////////////////////////////////////////////////////////////////////
// エラーチェックつきのmalloc
////////////////////////////////////////////////////////////////////////////////
ALLOCATED void* mymalloc(size_t size)
{
    void* result;

    result = malloc(size);
    if(result == NULL) {
        fprintf(stderr, "False in allocating memory.");
        exit(1);
    }
   
    return result;
}

////////////////////////////////////////////////////////////////////////////////
// エラーチェックつきのremalloc
////////////////////////////////////////////////////////////////////////////////
ALLOCATED void* myrealloc(void* ptr, size_t size)
{
    void* result;
    
    result = realloc(ptr, size);
    if(result == NULL) {
        fprintf(stderr, "False in allocating memory.");
        exit(1);
    }
    return result;
}

#ifdef DEBUG

#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <sys/time.h>

///////////////////////////////////////////////////////////////////////////////
// デバッグ用ログ関数 - 初期化
///////////////////////////////////////////////////////////////////////////////
static FILE* gFile;
static int gIndent;

void Log_Begin(BOOL clear)
{
    char path[258];
    char* homepath;

    /// ready file path ///   
    homepath = getenv("HOME");
    if(homepath == NULL) {
        fprintf(stderr, "$HOME is NULL");
        exit(1);
    }
   
    sprintf(path, "%s/exe.log", homepath);

    /// trunc file ///
    if(clear) {
        const int des = open(path, O_TRUNC|O_CREAT, 0666);
        if(des < 0) {
            fprintf(stderr, "file open error");
            exit(1);
        }
        close(des);
    }

    /// go ///
    gFile = fopen(path, "w");
    gIndent = 0;
}

///////////////////////////////////////////////////////////////////////////////
// デバッグ用ログ関数 - 解放
///////////////////////////////////////////////////////////////////////////////
void Log_End()
{
    fclose(gFile);
}

///////////////////////////////////////////////////////////////////////////////
// デバッグ用ログ関数 - 書き込み
///////////////////////////////////////////////////////////////////////////////
void Log_Write(char* str, ...)
{
    char buf[32768];
   
    va_list args;
    va_start(args, str);
    vsprintf(buf, str, args);
    va_end(args);

    fprintf(gFile, buf);
    fflush(gFile);
}

///////////////////////////////////////////////////////////////////////////////
// デバッグ用ログ関数 - 書き込み 改行あり
///////////////////////////////////////////////////////////////////////////////
int Log_Writeln(char* str, ...)
{
    char buf[32768];
   
    va_list args;
    va_start(args, str);
    vsprintf(buf, str, args);
    va_end(args);

    fprintf(gFile, buf);
    fprintf(gFile, "\n");
    fflush(gFile);

    return 1;
}

///////////////////////////////////////////////////////////////////////////////
// デバッグ用ログ関数 - メモリイメージ書き込み
///////////////////////////////////////////////////////////////////////////////
void Log_Dump(void* buf, int size, BOOL addr)
{
    int i;

    for(i=0; i<size; i++) {
        if(i%2 == 0) Log_Write(" ");
        if(i%16 == 0) {
            Log_Write("\n");
         
            if(addr) Log_Write("%08x: ", i);
        }
         
        Log_Write("%02x", ((unsigned char*)buf)[i]);
    }
      
    Log_Write("\n");
}
   
///////////////////////////////////////////////////////////////////////////////
// デバッグ用ログ関数 - 関数開始
///////////////////////////////////////////////////////////////////////////////
void Log_TraceBegin(const char* file_name, int line, const char* func_name)
{
    int i;

    for(i=0; i<gIndent; i++) Log_Write("    ");
    Log_Write("%s %d:%s() {\n", file_name, line, func_name);
   
    gIndent++;
}
   
///////////////////////////////////////////////////////////////////////////////
// デバッグ用ログ関数 - 関数終了
///////////////////////////////////////////////////////////////////////////////
void Log_TraceEnd()
{
    int i;

    for(i=0; i<gIndent; i++) Log_Write("    ");
    Log_Write("}\n");
   
    gIndent--;
}

///////////////////////////////////////////////////////////////////////////////
// メモリーリークチェック用定義
///////////////////////////////////////////////////////////////////////////////
#define NAME_SIZE 32

typedef struct _sMallocEntry
{
    void* mMemory;

    char mFileName[NAME_SIZE];
    int mLine;
    char mFuncName[NAME_SIZE];

    struct _sMallocEntry* mNextEntry;
} sMallocEntry;
      
#define ARRAY_SIZE 65535
static sMallocEntry* gMallocEntries[ARRAY_SIZE];

inline void release_entry(void* memory, const char* file_name, int line, const char* func_name)
{
    sMallocEntry* entry;
    const int hash = (int)memory % ARRAY_SIZE;
    
    entry = gMallocEntries[hash];
    if(entry->mMemory == memory) { 
        sMallocEntry* next_entry = entry->mNextEntry;
        free(entry);
        gMallocEntries[hash] = next_entry;
        return ;
    }
    else {
        while(entry->mNextEntry) {
            if(entry->mNextEntry->mMemory == memory) {
                sMallocEntry* next_entry = entry->mNextEntry->mNextEntry;
                free(entry->mNextEntry);
                entry->mNextEntry = next_entry;

                return;
            }
            entry = entry->mNextEntry;
        }
    }

    fprintf(stderr, "\tinvalid free at file: %s line:%d function:%s() addr:%x\n", entry->mFileName, entry->mLine, entry->mFuncName, entry->mMemory);
}

///////////////////////////////////////////////////////////////////////////////
// メモリーリークチェック用開始
///////////////////////////////////////////////////////////////////////////////
void CheckMemLeak_Begin()
{
    memset(gMallocEntries, 0, sizeof(sMallocEntry*)*ARRAY_SIZE);
}
   
///////////////////////////////////////////////////////////////////////////////
// メモリーリークチェック用解放
///////////////////////////////////////////////////////////////////////////////
void CheckMemLeak_End()
{
    int i;

    fprintf(stderr, "Detecting memory leak...\n");
                         
    for(i=0; i<ARRAY_SIZE; i++) {
        sMallocEntry* entry = gMallocEntries[i];
      
        while(entry) {
            fprintf(stderr, "\tDetected!! at file: %s line:%d function:%s() addr:%x\n"
                                             , entry->mFileName, entry->mLine
                                             , entry->mFuncName, entry->mMemory);
            entry = entry->mNextEntry;
        }
    }

    fprintf(stderr, "done.\n");
}

///////////////////////////////////////////////////////////////////////////////
// メモリーリークチェック用メモリーアロケート
///////////////////////////////////////////////////////////////////////////////
ALLOCATED void* CheckMemLeak_Malloc(size_t size, const char* file_name, int line, const char* func_name)
{
    sMallocEntry* entry;
    int i;
    int hash;
    
    entry = (sMallocEntry*)mymalloc(sizeof(sMallocEntry));

    for(i=0; i<NAME_SIZE-1; i++) {
         if(file_name[i]) entry->mFileName[i] = file_name[i];
    }
    entry->mFileName[i] = 0;
   
    entry->mLine = line;

    for(i=0; i<NAME_SIZE-1; i++) {
         if(func_name[i]) entry->mFuncName[i] = func_name[i];
    }
    entry->mFuncName[i] = 0;

    entry->mMemory = mymalloc(size);
   
    hash = (int)entry->mMemory % ARRAY_SIZE;
    entry->mNextEntry = gMallocEntries[hash];
    gMallocEntries[hash] = entry;

    return entry->mMemory;
}

///////////////////////////////////////////////////////////////////////////////
// メモリーリークチェック用メモリーアロケート
///////////////////////////////////////////////////////////////////////////////
ALLOCATED void* CheckMemLeak_Realloc(void* memory, size_t size, const char* file_name, int line, const char* func_name)
{
    sMallocEntry* entry;
    int hash;

    /// delete old entry ///
    if(memory) release_entry(memory, file_name, line, func_name);

    /// add new entry ///
    entry = (sMallocEntry*)mymalloc(sizeof(sMallocEntry));
      
    memcpy(entry->mFileName, file_name, NAME_SIZE);
    entry->mLine = line;
    memcpy(entry->mFuncName, func_name, NAME_SIZE);

    entry->mMemory = realloc(memory, size);
    if(entry->mMemory == NULL) {
        fprintf(stderr,"false in allocating memory.");
        exit(1);
    }
   
    hash = (int)entry->mMemory % ARRAY_SIZE;
    entry->mNextEntry = gMallocEntries[hash];
    gMallocEntries[hash] = entry;

    return entry->mMemory;
}

///////////////////////////////////////////////////////////////////////////////
// メモリーリークチェック用メモリーアロケート
///////////////////////////////////////////////////////////////////////////////
ALLOCATED char* CheckMemLeak_Strdup(char* str, const char* file_name, int line, const char* func_name)
{
    char* result;

    result = (char*)CheckMemLeak_Malloc(sizeof(char)*(strlen(str)+1), file_name, line, func_name);
  
    strcpy(result, str);

    return result;
}
   
///////////////////////////////////////////////////////////////////////////////
// メモリーリークチェック用メモリー解放
///////////////////////////////////////////////////////////////////////////////
void CheckMemLeak_Free(void* memory, const char* file_name, int line, const char* func_name)
{
    release_entry(memory, file_name, line, func_name);
    free(memory);
}

#endif
