#include "common.h"
#include "base/Trace.h"
#include "base/String.h"

#ifndef STAMP_BUILD
#define STAMP_BUILD "(unstamped)"
#endif

#ifdef NDEBUG
#define BUILD_TYPE "RELEASE"
#endif

#ifdef _DEBUG
#define BUILD_TYPE "DEBUG"
#endif

#ifndef BUILD_TYPE
#define BUILD_TYPE "UNKNOWN"
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

static const char* g_sCurrent = "(BASE)";
static const char* g_sFilter = "*>Aa";
static unsigned g_nLevel = 0;
static FILE* g_fileLog = stdout;

Trace::Scope::Scope(const char* s) :
    m_sOuter(0) {
    logPrint("> > %s",s);
    {
        m_sOuter = g_sCurrent;
        g_sCurrent = s;
        ++g_nLevel;
    }
}

Trace::Scope::Scope(const char* s1,const char* s2) :
    m_sOuter(0) {
    logPrint("> > %s [%s]",s1,s2);
    {
        m_sOuter = g_sCurrent;
        g_sCurrent = s1;
        ++g_nLevel;
    }
}

Trace::Scope::Scope(const char* s1,const char* s2,const char* s3) :
    m_sOuter(0) {
    logPrint("> > %s [%s:%s]",s1,s2,s3);
    {
        m_sOuter = g_sCurrent;
        g_sCurrent = s1;
        ++g_nLevel;
    }
}

Trace::Scope::~Scope() {
    const char* s;
    {
        --g_nLevel;
        s = g_sCurrent;
        g_sCurrent = m_sOuter;
    }
    logPrint("< < %s",s);
}

static inline int InFilter(char c) {
    return 0 != strchr(g_sFilter,c);
}

static inline char* AddPrefix(char* p) {
    *p++ = '\n';
    return p;
}

static inline char* AddIndent(char* p) {
    for (int n = 31 & g_nLevel; 0 < n; --n) {
        *p++ = ' ';
        *p++ = ' ';
    }
    return p;
}

static void LogWrite(const char* s) {
    if (g_fileLog) {
        fputs(s,g_fileLog);
    }
}

static base::String g_sLogBaseName;
static int g_ydayLog;
static time_t g_tFlushed;

static bool openLog(struct tm* tm) {
    // Add a time-specific extension.
    char s[80];
    ::sprintf(s,"-%04d-%02d-%02d-%02d-%02d-%02d.log",tm->tm_year + 1900,tm->tm_mon + 1,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);
    base::String sFilename = g_sLogBaseName;
    sFilename.strcat(s);

    g_fileLog = ::fopen(sFilename,"a");
    if (!g_fileLog) {
        return false;
    }
    g_ydayLog = tm->tm_yday;
    return true;
}

void Trace::initializeLog(const char* sName) {
    g_sLogBaseName = sName;

    time_t t = ::time(0);
    struct tm* tm = ::localtime(&t);

    if (!openLog(tm)) {
        return;
    }

    // Write who we are as the first entry.
    LogWrite("\n\n===== START LOG ====\n");
    LogWrite(ctime(&t));
    LogWrite("VERSION " STAMP_BUILD " " BUILD_TYPE);

    g_tFlushed = t;
    ::fflush(g_fileLog);
}

void Trace::rolloverLog() {
    time_t t = ::time(0);
    struct tm* tm = ::localtime(&t);
    if (g_ydayLog == tm->tm_yday) {
        if (10 < (t - g_tFlushed)) {
            g_tFlushed = t;
            ::fflush(g_fileLog);
        }
        return;
    }
    fclose(g_fileLog);
    openLog(tm);
}

void Trace::cleanupLog(void) {
    fclose(g_fileLog);
    g_fileLog = 0;
}

void Trace::setFilter(const char* s) {
    g_sFilter = strdup(s);
}

const char* Trace::getFilter() {
    return g_sFilter;
}

void Trace::logPrint(const char* s,...) {
    // skip if not logging
    if (!g_fileLog) {
        return;
    }
    // omit if not in filter
    if (!InFilter(*s++)) {
        return;
    }
    char sBuffer[8000];
    char* p = AddPrefix(sBuffer);
    // insert leading spaces as required
    if (' ' == *s) {
        ++s;
        p = AddIndent(p);
    }
    // format the arguments
    va_list a;
    va_start(a,s);
    vsprintf(p,s,a);
    va_end(a);
    // drop into log file
    LogWrite(sBuffer);
}

void Trace::logString(const char* s,const char* sValue) {
    // skip if not logging
    if (!g_fileLog) {
        return;
    }
    if (256 < strlen(sValue)) {
        logBuffer(s,sValue,strlen(sValue));
        return;
    }
    // omit if not in filter
    if (!InFilter(*s++)) {
        return;
    }
    char sBuffer[1000];
    char* p = AddPrefix(sBuffer);
    // insert leading spaces as required
    if (' ' == *s) {
        ++s;
        p = AddIndent(p);
    }
    // format the arguments
    sprintf(p,"%s : \"%s\"",s,sValue);
    // drop into log file
    LogWrite(sBuffer);
}

void Trace::logSection(const char* sName) {
    LogWrite("\n\n##################################################################\n##  ");
    LogWrite(sName);
    LogWrite("\n##################################################################\n");
}

static const char sHex[] = "0123456789ABCDEF";

void Trace::logBuffer(const char* sName,const char* sBuffer,unsigned cbBuffer) {
    // skip if not logging
    if (!g_fileLog) {
        return;
    }
    // omit if not in filter
    if (!InFilter(*sName++)) {
        return;
    }
    // mark start of buffer
    logPrint("*[BUFFER %s (%u bytes)]",sName,cbBuffer);
    // dump contents of buffer
    for (unsigned i = 0; i < cbBuffer;) {
        char sLine[256];
        char* s1 = sLine;
        sprintf(s1,"\n%05x:",i);
        s1 += strlen(s1);
        memset(s1,' ',50);
        char* s2 = s1 + 50;
        while (i < cbBuffer) {
            unsigned b = *(unsigned char*) (sBuffer + i);
            ++s1;
            *s1++ = sHex[15 & (b >> 4)];
            *s1++ = sHex[15 & b];
            *s2++ = (char) (((31 < b) && (b < 127)) ? b : '.');
            ++i;
            if (!(15 & i)) break;
        }
        *s2 = 0;
        LogWrite(sLine);
    }
    // mark end of buffer
    logPrint("*[END BUFFER %s]",sName);
}

void Trace::logText(const char* sName,const char* sBuffer) {
    // skip if not logging
    if (!g_fileLog) {
        return;
    }
    // omit if not in filter
    if (!InFilter(*sName++)) {
        return;
    }
    // mark start of buffer
    logPrint("*[TEXT %s]",sName);
    // dump contents of buffer
    LogWrite(sBuffer);
    // mark end of buffer
    logPrint("*[END TEXT %s]",sName);
}

void Trace::assertAt(const char* sFile,unsigned iLine,const char* sExpression) {
    Trace::logPrint("*%s:%u ASSERT FAILED: %s",sFile,iLine,sExpression);
}