/**
 @file debugAssert.cpp

 Windows implementation of assertion routines.

 @maintainer Morgan McGuire, graphics3d.com
 
 @created 2001-08-26
 @edited  2009-06-02
 */

#include "G3D/debugAssert.h"
#include "G3D/platform.h"
#ifdef G3D_WINDOWS
    #include <tchar.h>
#endif
#include "G3D/format.h"
#include "G3D/prompt.h"
#include <string>
#include "G3D/debugPrintf.h"
#include "G3D/Log.h"

#include <cstdlib>

#ifdef _MSC_VER
    // disable: "C++ exception handler used"
#   pragma warning (push)
#   pragma warning (disable : 4530)
#endif

using namespace std;

namespace G3D { namespace _internal {

ConsolePrintHook _consolePrintHook;
AssertionHook _debugHook = _handleDebugAssert_;
AssertionHook _failureHook = _handleErrorCheck_;

#ifdef G3D_LINUX
#if 0 /* G3DFIX: Disabled to avoid requirement for X11 libraries */
    Display*      x11Display = NULL;
    Window        x11Window  = 0;
#endif
#endif


#ifdef G3D_WINDOWS
static void postToClipboard(const char *text) {
    if (OpenClipboard(NULL)) {
        HGLOBAL hMem = GlobalAlloc(GHND | GMEM_DDESHARE, strlen(text) + 1);
        if (hMem) {
            char *pMem = (char*)GlobalLock(hMem);
            strcpy(pMem, text);
            GlobalUnlock(hMem);

            EmptyClipboard();
            SetClipboardData(CF_TEXT, hMem);
        }

        CloseClipboard();
        GlobalFree(hMem);
    }
}
#endif

/**
 outTitle should be set before the call
 */
static void createErrorMessage(
    const char*         expression,
    const std::string&  message,
    const char*         filename,
    int                 lineNumber,
    std::string&        outTitle,
    std::string&        outMessage) {

    std::string le = "";
    const char* newline = "\n";

    #ifdef G3D_WINDOWS
        newline = "\r\n";

        // The last error value.  (Which is preserved across the call).
        DWORD lastErr = GetLastError();
    
        // The decoded message from FormatMessage
        LPTSTR formatMsg = NULL;

        if (NULL == formatMsg) {
            FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                          FORMAT_MESSAGE_IGNORE_INSERTS |
                          FORMAT_MESSAGE_FROM_SYSTEM,
                            NULL,
                            lastErr,
                            0,
                            (LPTSTR)&formatMsg,
                            0,
                            NULL);
        }

        // Make sure the message got translated into something.
        LPTSTR realLastErr;
        if (NULL != formatMsg) {
            realLastErr = formatMsg;
        } else {
            realLastErr = LPTSTR(_T("Last error code does not exist."));
        }

        if (lastErr != 0) {
            le = G3D::format("Last Error (0x%08X): %s\r\n\r\n", lastErr, (LPCSTR)realLastErr);
        }

        // Get rid of the allocated memory from FormatMessage.
        if (NULL != formatMsg) {
            LocalFree((LPVOID)formatMsg);
        }

        char modulePath[MAX_PATH];
        GetModuleFileNameA(NULL, modulePath, MAX_PATH);

        const char* moduleName = strrchr(modulePath, '\\');
        outTitle = outTitle + string(" - ") + string(moduleName ? (moduleName + 1) : modulePath);

    #else
        (void)outTitle;
    #endif

    // Build the message.
    outMessage =
        G3D::format("%s%s%sExpression: %s%s%s:%d%s%s%s", 
                 message.c_str(), newline, newline, expression, newline, 
                 filename, lineNumber, newline, newline, le.c_str());
}


bool _handleDebugAssert_(
    const char*         expression,
    const std::string&  message,
    const char*         filename,
    int                 lineNumber,
    bool                useGuiPrompt) {

    std::string dialogTitle = "Assertion Failure";
    std::string dialogText = "";
    createErrorMessage(expression, message, filename, lineNumber, dialogTitle, dialogText);

    #ifdef G3D_WINDOWS
        DWORD lastErr = GetLastError();
        postToClipboard(dialogText.c_str());
        debugPrintf("\n%s\n", dialogText.c_str());
    #endif

    const int cBreak        = 0;
    const int cIgnore       = 1;
    const int cAbort        = 2;

    static const char* choices[] = {"Debug", "Ignore", "Exit"};

    // Log the error
    Log::common()->print(std::string("\n**************************\n\n") + dialogTitle + "\n" + dialogText);

    int result = G3D::prompt(dialogTitle.c_str(), dialogText.c_str(), (const char**)choices, 3, useGuiPrompt);

#    ifdef G3D_WINDOWS
        // Put the incoming last error back.
        SetLastError(lastErr);
#    endif

    switch (result) {
    // -1 shouldn't actually occur because it means 
    // that we're in release mode.
    case -1:
    case cBreak:
        return true;
        break;

    case cIgnore:
        return false;
        break;
   
    case cAbort:
        exit(-1);
        break;
    }

    // Should never get here
    return false;
}


bool _handleErrorCheck_(
    const char*         expression,
    const std::string&  message,
    const char*         filename,
    int                 lineNumber,
    bool                useGuiPrompt) {

    std::string dialogTitle = "Critical Error";
    std::string dialogText = "";

    createErrorMessage(expression, message, filename, lineNumber, dialogTitle, dialogText);

    // Log the error
    Log::common()->print(std::string("\n**************************\n\n") + dialogTitle + "\n" + dialogText);
    #ifdef G3D_WINDOWS
        DWORD lastErr = GetLastError();
        (void)lastErr;
        postToClipboard(dialogText.c_str());
        debugPrintf("\n%s\n", dialogText.c_str());
    #endif

    static const char* choices[] = {"Ok"};

    const std::string& m = 
        std::string("An internal error has occured in this program and it will now close.  "
        "The specific error is below. More information has been saved in \"") +
            Log::getCommonLogFilename() + "\".\n\n" + dialogText;

    int result = G3D::prompt("Error", m.c_str(), (const char**)choices, 1, useGuiPrompt);
    (void)result;

    return true;
}


#ifdef G3D_WINDOWS
static HCURSOR oldCursor;
static RECT    oldCursorRect;
static POINT   oldCursorPos;
static int     oldShowCursorCount;
#endif

void _releaseInputGrab_() {
    #ifdef G3D_WINDOWS

        GetCursorPos(&oldCursorPos);

        // Stop hiding the cursor if the application hid it.
        oldShowCursorCount = ShowCursor(true) - 1;

        if (oldShowCursorCount < -1) {
            for (int c = oldShowCursorCount; c < -1; ++c) {
                ShowCursor(true);
            }
        }

        // Set the default cursor in case the application
        // set the cursor to NULL.
        oldCursor = GetCursor();
        SetCursor(LoadCursor(NULL, IDC_ARROW));

        // Allow the cursor full access to the screen
        GetClipCursor(&oldCursorRect);
        ClipCursor(NULL);
        
    #elif defined(G3D_LINUX)
#if 0 /* G3DFIX: Disabled to avoid requirement for X11 libraries */
        if (x11Display != NULL) {
            XUngrabPointer(x11Display, CurrentTime);
            XUngrabKeyboard(x11Display, CurrentTime);
            if (x11Window != 0) {
                //XUndefineCursor(x11Display, x11Window);
                // TODO: Note that we leak this cursor; it should be
                // freed in the restore code.
                Cursor c = XCreateFontCursor(x11Display, 68);
                XDefineCursor(x11Display, x11Window, c);
            }
            XSync(x11Display, false);           
            XAllowEvents(x11Display, AsyncPointer, CurrentTime);
            XFlush(x11Display);
        }
#endif
    #elif defined(G3D_OSX)
        // TODO: OS X
    #endif
}


void _restoreInputGrab_() {
    #ifdef G3D_WINDOWS

        // Restore the old clipping region
        ClipCursor(&oldCursorRect);

        SetCursorPos(oldCursorPos.x, oldCursorPos.y);

        // Restore the old cursor
        SetCursor(oldCursor);

        // Restore old visibility count
        if (oldShowCursorCount < 0) {
            for (int c = 0; c > oldShowCursorCount; --c) {
                ShowCursor(false);
            }
        }
        
    #elif defined(G3D_LINUX)
        // TODO: Linux
    #elif defined(G3D_OSX)
        // TODO: OS X
    #endif
}


}; // internal namespace
 
void setAssertionHook(AssertionHook hook) {
    G3D::_internal::_debugHook = hook;
}

AssertionHook assertionHook() {
    return     G3D::_internal::_debugHook;
}

void setFailureHook(AssertionHook hook) {
    G3D::_internal::_failureHook = hook;
}

AssertionHook failureHook() {
    return G3D::_internal::_failureHook;
}


void setConsolePrintHook(ConsolePrintHook h) {
    G3D::_internal::_consolePrintHook = h;
}

ConsolePrintHook consolePrintHook() {
    return G3D::_internal::_consolePrintHook;
}


std::string __cdecl debugPrint(const std::string& s) {
#   ifdef G3D_WINDOWS
        const int MAX_STRING_LEN = 1024;
    
        // Windows can't handle really long strings sent to
        // the console, so we break the string.
        if (s.size() < MAX_STRING_LEN) {
            OutputDebugStringA(s.c_str());
        } else {
            for (unsigned int i = 0; i < s.size(); i += MAX_STRING_LEN) {
                std::string sub = s.substr(i, MAX_STRING_LEN);
                OutputDebugStringA(sub.c_str());
            }
        }
#    else
        fprintf(stderr, "%s", s.c_str());
        fflush(stderr);
#    endif

     return s;
}

std::string __cdecl debugPrintf(const char* fmt ...) {
    va_list argList;
    va_start(argList, fmt);
    std::string s = G3D::vformat(fmt, argList);
    va_end(argList);

    return debugPrint(s);
//    return debugPrint(consolePrint(s));
}

std::string consolePrint(const std::string& s) {
    FILE* L = Log::common()->getFile();
    fprintf(L, "%s", s.c_str());

    if (consolePrintHook()) {
        consolePrintHook()(s);
    }

    fflush(L);
    return s;
}


std::string __cdecl consolePrintf(const char* fmt ...) {
    va_list argList;
    va_start(argList, fmt);
    std::string s = G3D::vformat(fmt, argList);
    va_end(argList);

    return consolePrint(s);
}

} // namespace

#ifdef _MSC_VER
#   pragma warning (pop)
#endif