/***************************************************************************
 *                                                                          *
 *  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
 *                                                                          *
 ***************************************************************************/
#pragma once

#include "glmCoreAPI.h"

#if defined(_MSC_VER)

/**********************************************************************
 *
 * StackWalker.h
 *
 *
 *
 * LICENSE (http://www.opensource.org/licenses/bsd-license.php)
 *
 *   Copyright (c) 2005-2009, Jochen Kalmbach
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without modification,
 *   are permitted provided that the following conditions are met:
 *
 *   Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *   Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *   Neither the name of Jochen Kalmbach nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 *   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * **********************************************************************/

#include "glmWindows.h"
#include <sstream>
namespace glm
{
    class StackWalkerInternal; // forward
    class StackWalker
    {
    public:
        typedef enum ExceptType
        {
            NonExcept = 0, // RtlCaptureContext
            AfterExcept = 1,
            AfterCatch = 2, // get_current_exception_context
        } ExceptType;

        typedef enum StackWalkOptions
        {
            // No addition info will be retrieved
            // (only the address is available)
            RetrieveNone = 0,

            // Try to get the symbol-name
            RetrieveSymbol = 1,

            // Try to get the line for this symbol
            RetrieveLine = 2,

            // Try to retrieve the module-infos
            RetrieveModuleInfo = 4,

            // Also retrieve the version for the DLL/EXE
            RetrieveFileVersion = 8,

            // Contains all the above
            RetrieveVerbose = 0xF,

            // Generate a "good" symbol-search-path
            SymBuildPath = 0x10,

            // Also use the public Microsoft-Symbol-Server
            SymUseSymSrv = 0x20,

            // Contains all the above "Sym"-options
            SymAll = 0x30,

            // Contains all options (default)
            OptionsAll = 0x3F
        } StackWalkOptions;

        StackWalker(ExceptType extype, int options = OptionsAll, PEXCEPTION_POINTERS exp = NULL);

        StackWalker(int options = OptionsAll, // 'int' is by design, to combine the enum-flags
                    LPCSTR szSymPath = NULL,
                    DWORD dwProcessId = GetCurrentProcessId(),
                    HANDLE hProcess = GetCurrentProcess());

        StackWalker(DWORD dwProcessId, HANDLE hProcess);

        virtual ~StackWalker();

        bool SetSymPath(LPCSTR szSymPath);

        bool SetTargetProcess(DWORD dwProcessId, HANDLE hProcess);

        PCONTEXT GetCurrentExceptionContext();

    private:
        bool Init(ExceptType extype, int options, LPCSTR szSymPath, DWORD dwProcessId,
                  HANDLE hProcess, PEXCEPTION_POINTERS exp = NULL);

    public:
        typedef BOOL(__stdcall* PReadProcessMemoryRoutine)(
            HANDLE hProcess,
            DWORD64 qwBaseAddress,
            PVOID lpBuffer,
            DWORD nSize,
            LPDWORD lpNumberOfBytesRead,
            LPVOID pUserData // optional data, which was passed in "ShowCallstack"
        );

        BOOL LoadModules();

        BOOL ShowCallstack(
            HANDLE hThread = GetCurrentThread(),
            const CONTEXT* context = NULL,
            PReadProcessMemoryRoutine readMemoryFunction = NULL,
            LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback
        );

        BOOL ShowObject(LPVOID pObject);

    protected:
        enum
        {
            STACKWALK_MAX_NAMELEN = 1024
        }; // max name length for found symbols

    protected:
        // Entry for each Callstack-Entry
        typedef struct CallstackEntry
        {
            DWORD64 offset; // if 0, we have no valid entry
            CHAR name[STACKWALK_MAX_NAMELEN];
            CHAR undName[STACKWALK_MAX_NAMELEN];
            CHAR undFullName[STACKWALK_MAX_NAMELEN];
            DWORD64 offsetFromSmybol;
            DWORD offsetFromLine;
            DWORD lineNumber;
            CHAR lineFileName[STACKWALK_MAX_NAMELEN];
            DWORD symType;
            LPCSTR symTypeString;
            CHAR moduleName[STACKWALK_MAX_NAMELEN];
            DWORD64 baseOfImage;
            CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
        } CallstackEntry;

        typedef enum CallstackEntryType
        {
            firstEntry,
            nextEntry,
            lastEntry
        } CallstackEntryType;

        virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
        virtual void OnLoadModule(LPCSTR img,
                                  LPCSTR mod,
                                  DWORD64 baseAddr,
                                  DWORD size,
                                  DWORD result,
                                  LPCSTR symType,
                                  LPCSTR pdbName,
                                  ULONGLONG fileVersion);
        virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry& entry);
        virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
        virtual void OnOutput(LPCSTR szText);

        StackWalkerInternal* m_sw;
        HANDLE m_hProcess;
        DWORD m_dwProcessId;
        BOOL m_modulesLoaded;
        LPSTR m_szSymPath;

        int m_options;
        int m_MaxRecursionCount;

        static BOOL __stdcall myReadProcMem(HANDLE hProcess,
                                            DWORD64 qwBaseAddress,
                                            PVOID lpBuffer,
                                            DWORD nSize,
                                            LPDWORD lpNumberOfBytesRead);
        std::stringstream _stream;
        friend StackWalkerInternal;
    }; // class StackWalker

    // The "ugly" assembler-implementation is needed for systems before XP
    // If you have a new PSDK and you only compile for XP and later, then you can use
    // the "RtlCaptureContext"
    // Currently there is no define which determines the PSDK-Version...
    // So we just use the compiler-version (and assumes that the PSDK is
    // the one which was installed by the VS-IDE)

    // INFO: If you want, you can use the RtlCaptureContext if you only target XP and later...
    //       But I currently use it in x64/IA64 environments...
    // #if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400)

#if defined(_M_IX86)
#ifdef CURRENT_THREAD_VIA_EXCEPTION
// TODO: The following is not a "good" implementation,
// because the callstack is only valid in the "__except" block...
#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags)                   \
    do                                                                              \
    {                                                                               \
        memset(&c, 0, sizeof(CONTEXT));                                             \
        EXCEPTION_POINTERS* pExp = NULL;                                            \
        __try                                                                       \
        {                                                                           \
            throw 0;                                                                \
        }                                                                           \
        __except (((pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER   \
                                                      : EXCEPTION_EXECUTE_HANDLER)) \
        {                                                                           \
        }                                                                           \
        if (pExp != NULL)                                                           \
            memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT));                       \
        c.ContextFlags = contextFlags;                                              \
    } while (0);
#else
// clang-format off
// The following should be enough for walking the callstack...
#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
  do                                                              \
  {                                                               \
    memset(&c, 0, sizeof(CONTEXT));                               \
    c.ContextFlags = contextFlags;                                \
    __asm    call x                                               \
    __asm x: pop eax                                              \
    __asm    mov c.Eip, eax                                       \
    __asm    mov c.Ebp, ebp                                       \
    __asm    mov c.Esp, esp                                       \
  } while (0)
// clang-format on
#endif

#else

// The following is defined for x86 (XP and higher), x64 and IA64:
#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
    do                                                            \
    {                                                             \
        memset(&c, 0, sizeof(CONTEXT));                           \
        c.ContextFlags = contextFlags;                            \
        RtlCaptureContext(&c);                                    \
    } while (0);
#endif
} // namespace glm

#else

namespace glm
{
    class StackWalker
    {
    public:
        void ShowCallstack();
    };
} // namespace glm

#endif
