/*++

Copyright (c) 2000  Microsoft Corporation

Module Name:

    result.h

Abstract:

    

Author:

    Hakki T. Bostanci (hakkib) 06-Apr-2000

Revision History:

--*/

#ifndef RESULT_H
#define RESULT_H

#ifndef ASSERT
#define ASSERT assert
#endif

#ifndef PCTSTR
#define PCTSTR LPCTSTR
#endif //PCTSTR

#if defined(_X86_)
    #define CURRENT_MACHINE_TYPE  IMAGE_FILE_MACHINE_I386
#elif defined(_MIPS_)
    #define CURRENT_MACHINE_TYPE  IMAGE_FILE_MACHINE_R4000
#elif defined(_ALPHA_)
    #define CURRENT_MACHINE_TYPE  IMAGE_FILE_MACHINE_ALPHA
#elif defined(_PPC_)
    #define CURRENT_MACHINE_TYPE  IMAGE_FILE_MACHINE_POWERPC
#elif defined(_AXP64_)
    #define CURRENT_MACHINE_TYPE  IMAGE_FILE_MACHINE_ALPHA64
#elif defined(_IA64_)
    #define CURRENT_MACHINE_TYPE  IMAGE_FILE_MACHINE_IA64
    #ifndef CONTEXT_CONTROL
        #pragma message("CONTEXT_CONTROL was not defined!!!")
        #define CONTEXT_CONTROL CONTEXT86_CONTROL
    #endif CONTEXT_CONTROL
#else
    #undef CURRENT_MACHINE_TYPE
#endif

//
// The CHECK() macro can be used to evaluate the result of APIs that use
// LastError. These APIs typically return a non-zero value if there is 
// no error and if there is an error, they return zero and SetLastError()
// with the extended error information.
//

#define CHECK(Expression)                                           \
	{															    \
		if ((Expression) == 0) {						            \
																    \
			throw CError(GetLastError() STAMP(_T(#Expression)));    \
		}														    \
	}															    \

//
// CHECK0 macro deals with the API functions that do not use the LastError 
// value. Typically registry APIs fall into this category, they directly 
// return the error code, or ERROR_SUCCESS if there is no error.
//

#define CHECK_REG(Expression)                                       \
	{															    \
		DWORD __dwResult = (DWORD) (Expression);				    \
																    \
		if (__dwResult != ERROR_SUCCESS) {						    \
																    \
			throw CError(__dwResult STAMP(_T(#Expression)));        \
		}														    \
	}															    \

//
// CHECK0 macro deals with the API functions that do not use the LastError 
// value. Typically registry APIs fall into this category, they directly 
// return the error code, or ERROR_SUCCESS if there is no error.
//

#define CHECK_HR(Expression)                                        \
	{															    \
		HRESULT __hr = Expression;				                    \
																    \
        if (__hr != S_OK)                                           \
        {														    \
			throw CError(__hr STAMP(_T(#Expression)));              \
		}														    \
	}															    \

//
// CHECK_LSA macro deals with the LSA API functions.
//

#define CHECK_LSA(Expression) \
	{															    \
		DWORD __dwResult = (DWORD) (Expression);				    \
																    \
		if (__dwResult != ERROR_SUCCESS) 						    \
        {                                                           \
            __dwResult = LsaNtStatusToWinError(__dwResult);         \
																    \
			throw CError(__dwResult STAMP(_T(#Expression)));        \
		}														    \
	}															    \

//
// On DEBUG builds, we include the location STAMP on the error message popup,
// i.e. we display the expression that raised the error, the module name and
// the line number 
//

#ifdef _CONSOLE
    #define ENDL _T("\n")
#else //_CONSOLE
    #define ENDL _T(", ")
#endif //_CONSOLE

#if defined(DEBUG) || defined(_DEBUG) || defined(DBG)
	#define STAMP(pExpr)	, pExpr, _T(__FILE__), __LINE__
	#define STAMP_DECL		, PCTSTR pExpr = _T(""), PCTSTR pFile = _T(""), INT nLine = 0
	#define STAMP_INIT      , m_pExpr(pExpr), m_pFile(pFile), m_nLine(nLine)
	#define STAMP_ARGS		, m_pExpr, m_pFile, (PCTSTR) m_nLine 
	#define STAMP_DEFINE	PCTSTR m_pExpr; PCTSTR m_pFile; INT m_nLine; mutable CONTEXT m_Context;
	#define STAMP_FORMAT	_T("%s") ENDL _T("%s: %d") ENDL
    #define STAMP_IOS	    << std::endl << rhs.m_pExpr << std::endl << rhs.m_pFile << _T(": ") << rhs.m_nLine
	#define STAMP_LENGTH	_tcslen(pExpr) + _tcslen(pFile) + 8
#else //DEBUG
	#define STAMP(pExpr)
	#define STAMP_DECL
	#define STAMP_INIT
	#define STAMP_ARGS
	#define STAMP_DEFINE
	#define STAMP_FORMAT
    #define STAMP_IOS
	#define STAMP_LENGTH 0
#endif //DEBUG


inline PTSTR _tcsdupl(LPCTSTR pStrSource)
{
    if (!pStrSource) {

        pStrSource = _T("");
    }

    PTSTR pStrDest = (PTSTR) ::LocalAlloc(
        LMEM_FIXED, 
        (_tcslen(pStrSource) + 1) * sizeof(TCHAR)
    );

    if (pStrDest) {

        _tcscpy(pStrDest, pStrSource);
    }

    return pStrDest;
}

inline PTSTR FormatMessageFromSystem(DWORD nNum)
{
    PTSTR pText = 0;

    DWORD dwResult = ::FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM | 
		FORMAT_MESSAGE_IGNORE_INSERTS |
		FORMAT_MESSAGE_MAX_WIDTH_MASK,
		0,
		nNum,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
		(PTSTR) &pText,
		0,
		0
	);

    if (pText == 0) {

        pText = _tcsdupl(_T("Unknown Error"));
    }

    return pText;
}

#ifdef _IOSTREAM_

// workaround for VC6 compiler bug (Q192539)

class CError;
std::ostream &operator <<(std::ostream &os, const CError &rhs);

#endif //_IOSTREAM_


//////////////////////////////////////////////////////////////////////////
//
// CError
//

class CError
{
public:
	CError(
		PTSTR pText
		STAMP_DECL
	) :
		m_nNum(0),
		m_pText(pText),
        m_bFree(false)
		STAMP_INIT
	{
#if (defined(DEBUG) || defined(_DEBUG) || defined(DBG)) && defined(CURRENT_MACHINE_TYPE)
        m_Context.ContextFlags = CONTEXT_CONTROL;
		GetThreadContext(GetCurrentThread(), &m_Context);
#endif
	}

	CError(
		DWORD nNum
		STAMP_DECL
	) :
		m_nNum(nNum),
		m_pText(0),
        m_bFree(false)
		STAMP_INIT
	{
#if (defined(DEBUG) || defined(_DEBUG) || defined(DBG)) && defined(CURRENT_MACHINE_TYPE)
        m_Context.ContextFlags = CONTEXT_CONTROL;
		GetThreadContext(GetCurrentThread(), &m_Context);
#endif
	}

    ~CError()
    {
        if (m_bFree) {
        
            LocalFree(m_pText);
        }
    }

    DWORD 
	Num() const 
	{
		return m_nNum;
	}

	PCTSTR 
	Text() const 
	{
        if (!m_pText) {

            m_pText = FormatMessageFromSystem(m_nNum);
            m_bFree = true;
        }

        return m_pText;
	}

    static
    void 
    AskDebugBreak()
    {
        if (MessageBox(
            0, 
            _T("Do you want to break into the debugger?"), 
            0, 
            MB_ICONQUESTION | MB_YESNO
        ) == IDYES) {

            DebugBreak();
        }
    }

#if (defined(DEBUG) || defined(_DEBUG) || defined(DBG)) && defined(CURRENT_MACHINE_TYPE) && defined(_IMAGEHLP_)

#define MAX_STACK_DEPTH   32
#define MAX_SYMBOL_LENGTH 256

    template <int N>
    struct CImagehlpSymbol : public IMAGEHLP_SYMBOL 
    {
        CImagehlpSymbol()
        {
            ZeroMemory(this, sizeof(*this));
            SizeOfStruct  = sizeof(IMAGEHLP_SYMBOL);
            MaxNameLength = N;
        }

    private:
        CHAR NameData[N-1];
    };

    struct CImagehlpLine : public IMAGEHLP_LINE
    {
        CImagehlpLine()
        {
            ZeroMemory(this, sizeof(*this));
            SizeOfStruct = sizeof(IMAGEHLP_LINE);
        }
    };

    struct CImagehlpModule : public IMAGEHLP_MODULE
    {
        CImagehlpModule()
        {
            ZeroMemory(this, sizeof(*this));
            SizeOfStruct = sizeof(IMAGEHLP_MODULE);
        }
    };

    void DumpStack(FILE *fout = stdout) const
    {
        PCONTEXT pContext = &m_Context;
        HANDLE   hProcess = GetCurrentProcess();
        HANDLE   hThread  = GetCurrentThread();

        SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
        SymInitialize(hProcess, 0, TRUE);

        STACKFRAME StackFrame = { 0 }; 

#if defined(_X86_)
        StackFrame.AddrPC.Offset       = pContext->Eip;
        StackFrame.AddrFrame.Offset    = pContext->Ebp;
        StackFrame.AddrStack.Offset    = pContext->Esp;
#elif defined(_MIPS_)
        StackFrame.AddrPC.Offset       = pContext->Fir;
        StackFrame.AddrFrame.Offset    = pContext->IntS6;
        StackFrame.AddrStack.Offset    = pContext->IntSp;
#elif defined(_ALPHA_)
        StackFrame.AddrPC.Offset       = pContext->Fir;
        StackFrame.AddrFrame.Offset    = pContext->IntFp;
        StackFrame.AddrStack.Offset    = pContext->IntSp;
#elif defined(_PPC_)
        StackFrame.AddrPC.Offset       = pContext->Iar;
        StackFrame.AddrFrame.Offset    = pContext->IntFp;
        StackFrame.AddrStack.Offset    = pContext->Gpr1;
#endif

        StackFrame.AddrPC.Mode         = AddrModeFlat;
        StackFrame.AddrFrame.Mode      = AddrModeFlat;
        StackFrame.AddrStack.Mode      = AddrModeFlat;

        for (
            int nStackWalkLevel = 0;
            nStackWalkLevel < MAX_STACK_DEPTH &&
            StackWalk(
                CURRENT_MACHINE_TYPE,
                hProcess,
                hThread,
                &StackFrame,                         
                pContext,                             
                0,  
                SymFunctionTableAccess,  
                SymGetModuleBase,              
                0
            );
            ++nStackWalkLevel
        ) {

            CImagehlpModule Module;
            SymGetModuleInfo(hProcess, StackFrame.AddrPC.Offset, &Module);

            DWORD dwDisplacement = 0;
            CImagehlpSymbol<MAX_SYMBOL_LENGTH> Symbol;
            SymGetSymFromAddr(hProcess, StackFrame.AddrPC.Offset, &dwDisplacement, &Symbol);

            CHAR szUnDSymbol[MAX_SYMBOL_LENGTH] = "";
            SymUnDName(&Symbol, szUnDSymbol, sizeof(szUnDSymbol));

            DWORD dwLineDisplacement = 0;
            CImagehlpLine Line;
            SymGetLineFromAddr(hProcess, StackFrame.AddrPC.Offset, &dwLineDisplacement, &Line);

            fprintf(
                fout,
                "%08x %08x %08x %08x %s!%s+0x%x (%s:%d)\n",
                StackFrame.Params[0],
                StackFrame.Params[1],
                StackFrame.Params[2],
                StackFrame.Params[3],
                Module.ModuleName,
                szUnDSymbol,
                dwDisplacement,
                Line.FileName, 
                Line.LineNumber
            );
        }

        SymCleanup(hProcess);
    }

#else
    
    void 
    DumpStack(PVOID pVoid = 0) const
    {
    }

#endif

	template <class F>
	int
	Print(F OutputFunction) const
	{
		OutputFunction(
			_T("Error 0x%08x: %s") ENDL STAMP_FORMAT, 
			Num(), 
			Text()
			STAMP_ARGS
		);

        return 1;
	}

#ifdef _IOSTREAM_

    friend std::ostream &operator <<(std::ostream &os, const CError &rhs)
    {
		return os <<
            _T("Error ") << rhs.Num() << _T(": ") << rhs.Text() 
            STAMP_IOS << std::endl;
    }

#endif //_IOSTREAM_

private:
	DWORD m_nNum;
	mutable PTSTR m_pText;
	mutable bool  m_bFree;
	STAMP_DEFINE;
};

#endif //RESULT_H