////////////////////////////////////////////////////////////////////
//                                                                //
//  This file contains the assert macros that are used in DEBUG.  //
//                                                                //
////////////////////////////////////////////////////////////////////


#ifndef __POPASSERT_H__
#define __POPASSERT_H__


  #ifdef NDEBUG // ASSERTIONS DISABLED

    #define POP_ASSERT(test, cause)
    #define POP_ILLEGAL(cause)
    #define POP_DEBUG(message)

  #else         // ASSERTIONS ENABLED

    #ifdef WIN32         // Under Windows

      // To take off conversion warning "conversion from 'size_t' to
      // 'int', possible loss of data"
      #pragma warning(disable: 4267)
      // To take off STL warning "identifier was truncated to 'number'
      // characters in the debug information"
      #pragma warning(disable: 4786)

      // To use SignalObjectAndWait()
      //#define _WIN32_WINNT 0x0502 // Windows Server 2003
      #define _WIN32_WINNT 0x0501 // Windows XP
      //#define _WIN32_WINNT 0x0500 // Windows 2000
      //#define _WIN32_WINNT 0x0400 // Windows NT 4.0

      #include <iostream>
      #include <sstream>
      #include <string>
      #include <vector>
      #include <windows.h>
      // imagehlp.h must be compiled with 8-byte packing, but does not
      // enforce it.
      #pragma pack( push, before_imagehlp, 8 )
      #include <imagehlp.h>
      #pragma pack( pop, before_imagehlp )
      #pragma comment ( lib , "imagehlp.lib" )


      namespace AssertStack {

      struct StackData
      {
        HANDLE hThread;
        std::vector<std::string> vFunc;
        HANDLE mutex;
        std::ostream& o;
        std::string strFunc;
  
        StackData(std::ostream& os)
          : hThread(0), vFunc(),
            mutex(CreateMutex(0,true,0)), o(os), strFunc("")
        {
          DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
                          GetCurrentProcess(), &hThread,
                          0, false, DUPLICATE_SAME_ACCESS);
        }

        ~StackData()
        {
          if (hThread)
            CloseHandle(hThread);
          if (mutex)
            CloseHandle(mutex);
        }

        bool Valid() const { return (mutex) && (hThread); }
      };

      inline void GetStack(StackData *pStackData)
      {
        const int IMGSYMLEN     = sizeof(IMAGEHLP_SYMBOL);
        const int MAXSYMNAMELEN = 1024;

        const int MACHINETYPE   = IMAGE_FILE_MACHINE_I386;
        // const int MACHINETYPE   = IMAGE_FILE_MACHINE_IA64;
        // const int MACHINETYPE   = IMAGE_FILE_MACHINE_AMD64;

        const HANDLE hProcess   = GetCurrentProcess();

        const HANDLE hThread = pStackData->hThread;
        std::ostream& o = pStackData->o;

        DWORD symOptions;
        STACKFRAME stackFrame;
        memset(&stackFrame, 0, sizeof(stackFrame));
        CONTEXT context;
        memset(&context, 0, sizeof(context));

        IMAGEHLP_MODULE module;
        memset(&module, 0, sizeof(IMAGEHLP_MODULE));
        module.SizeOfStruct = sizeof(IMAGEHLP_MODULE);
        IMAGEHLP_SYMBOL *pSymbol
          = (IMAGEHLP_SYMBOL*) new char[IMGSYMLEN+MAXSYMNAMELEN];
        memset(pSymbol, 0, IMGSYMLEN+MAXSYMNAMELEN);

        char undecoratedName[MAXSYMNAMELEN];
        char undecoratedFullName[MAXSYMNAMELEN];

        symOptions  = SymGetOptions();
        symOptions &= ~SYMOPT_UNDNAME;
        SymSetOptions(symOptions);

        if (!SymInitialize(hProcess, NULL, true))
        {
          o << "No stack trace because of an error in SymInitialize()\n";
          goto end00;
        }

        WaitForSingleObject(pStackData->mutex, INFINITE);

        if ( (DWORD) (-1) == SuspendThread(hThread) )
        {
          o << "No stack trace because of an error in SuspendThread()\n";
          goto end01;
        }

        context.ContextFlags = CONTEXT_FULL;
        if (!GetThreadContext(hThread, &context))
        {
          o << "No stack trace because of an error in GetThreadContext()\n";
          goto end02;
        }

        stackFrame.AddrPC.Offset    = context.Eip;
        stackFrame.AddrPC.Mode      = AddrModeFlat;
        stackFrame.AddrFrame.Offset = context.Ebp;
        stackFrame.AddrFrame.Mode   = AddrModeFlat;

        pSymbol->SizeOfStruct  = IMGSYMLEN;
        pSymbol->MaxNameLength = MAXSYMNAMELEN;

        while(StackWalk(MACHINETYPE, hProcess, hThread, &stackFrame,
                        NULL, NULL, NULL, NULL, NULL))
        {
          if (SymGetSymFromAddr(hProcess, stackFrame.AddrPC.Offset,
                                NULL, pSymbol))
          {
            UnDecorateSymbolName(pSymbol->Name, undecoratedName,
                                 MAXSYMNAMELEN, UNDNAME_NAME_ONLY);
            UnDecorateSymbolName(pSymbol->Name, undecoratedFullName,
                                 MAXSYMNAMELEN, UNDNAME_COMPLETE);

            if (pStackData->strFunc == "")
              pStackData->strFunc = undecoratedName;

            std::string strName = pSymbol->Name;
            std::string strUndecName = undecoratedName;
            std::string strUndecFullName = undecoratedFullName;

            std::string strResult = strName;

            if (strName != strUndecName)
            {
              strResult += "\n\t";
              strResult += strUndecName;
            }

            if (strName != strUndecFullName)
            {
              strResult += "\n\t";
              strResult += strUndecFullName;
            }

            // File from which the symbol comes
            if (SymGetModuleInfo(hProcess,
                                 stackFrame.AddrPC.Offset,
                                 &module))
            {
              strResult += "\n\tIn ";
              strResult += module.LoadedImageName;;
            }

            pStackData->vFunc.push_back(strResult + '\n');
          }
        }

      end02:
        if ( (DWORD) (-1) == ResumeThread(hThread) )
          std::cerr << "Error in ResumeThread()\nUnpredictable results.\n";
      end01:
        if (!SymCleanup(hProcess))
          std::cerr << "Error in SymCleanup()\nUnpredicatble results.\n";
      end00:
        delete [] (char *) pSymbol;
      }

      }; // End of namespace AssertStack



      #define GEN_POP(title, firstline, reason)                    \
      {                                                            \
        std::ostringstream o;                                      \
        o << firstline                                             \
          << "REASON: " << reason << std::endl                     \
          << "FILE NAME: " << __FILE__ << std::endl                \
          << "LINE: " << __LINE__ << std::endl;                    \
                                                                   \
        AssertStack::StackData s(o);                               \
        if (!s.Valid())                                            \
        {                                                          \
          o << "No stack trace because of "                        \
               "an error in thread functions()\n";                 \
        }                                                          \
        else                                                       \
        {                                                          \
          HANDLE hThread =                                         \
            CreateThread(0, 0,                                     \
                         (LPTHREAD_START_ROUTINE)                  \
                           &AssertStack::GetStack,                 \
                         &s,                                       \
                         0, 0);                                    \
          SignalObjectAndWait(s.mutex, hThread, INFINITE, false);  \
          CloseHandle(hThread);                                    \
        }                                                          \
                                                                   \
        int nbFrames = s.vFunc.size();                             \
                                                                   \
        if (nbFrames > 1)                                          \
        {                                                          \
          s.strFunc = s.vFunc[1];                                  \
          nbFrames -= 3;                                           \
          o << "FUNCTION: " << s.strFunc << std::endl              \
            << nbFrames << " frames in stack trace." << std::endl; \
          for (int i = 1; i < nbFrames; i++)                       \
            o << nbFrames - i << ") " << s.vFunc[i] << std::endl;  \
        }                                                          \
                                                                   \
        o << "Abort execution? (cancel to break)" << std::endl;    \
                                                                   \
        switch(MessageBoxA(NULL,                                   \
                           o.str().c_str(),                        \
                           title,                                  \
                           MB_YESNOCANCEL))                        \
        {                                                          \
        case IDYES: abort(); break;                                \
        case IDCANCEL: DebugBreak(); break;                        \
        case IDNO:                                                 \
        default:                                                   \
          break;                                                   \
        }                                                          \
      }

    #elif defined(__linux__) // Under Linux

      #include <stdio.h>
      #include <unistd.h>
      #include <termios.h>
      #include <sys/ioctl.h>
      #include <sys/time.h>
      #include <sys/types.h>
      #include <sys/poll.h>

      // getch() macro
      #define __ASSERT_GETCH(character)                            \
        {                                                          \
          struct termios oldsettings, newsettings;                 \
          fflush(stdout);                                          \
          int error;                                               \
          tcgetattr(STDIN_FILENO, &oldsettings);                   \
          newsettings = oldsettings;                               \
          newsettings.c_lflag &= (~ICANON);                        \
          newsettings.c_lflag &= (~ECHO);                          \
          newsettings.c_cc[VTIME] = 0;                             \
          newsettings.c_cc[VMIN] = 1;                              \
                                                                   \
          error = tcsetattr(STDIN_FILENO, TCSANOW, &newsettings);  \
          if (error == 0)                                          \
            {                                                      \
              error  = read(STDIN_FILENO, &character, 1);          \
              error += tcsetattr(STDIN_FILENO,                     \
                                 TCSAFLUSH,                        \
                                 &oldsettings);                    \
            }                                                      \
                                                                   \
          if (error != 1)                                          \
            character = -1;                                        \
        }


      #include <iostream>    // for cout and cerr
      #include <sstream>     // for ostringstream
      #include <execinfo.h>  // for backtrace
      #include <sys/wait.h>  // for WEXITSTATUS
      #include <signal.h>    // for raise

      #define GEN_POP(title, firstline, reason)                    \
        {                                                          \
          std::ostringstream o, o2;                                \
          const int __IDABORT    = 'y';                            \
          const int __IDCONTINUE = 'n';                            \
          const int __IDBREAK    = 'b';                            \
                                                                   \
          o << "xmessage -buttons Abort:" << __IDABORT             \
            << ",Continue:"               << __IDCONTINUE          \
            << ",Break:"                  << __IDBREAK             \
            << ' ' << "\"";                                        \
                                                                   \
          o2 << firstline                                          \
             << "REASON: " << reason << std::endl                  \
             << "FILE NAME: " << __FILE__ << std::endl             \
             << "LINE: " << __LINE__ << std::endl                  \
             << "FUNCTION: " << __PRETTY_FUNCTION__ << std::endl;  \
                                                                   \
          void *array[1024];                                       \
                                                                   \
          int size = backtrace (array, 10);                        \
          char **strings = backtrace_symbols (array, size);        \
                                                                   \
          o2 << size-2 << " frames in stack trace." << std::endl;  \
                                                                   \
          for (int i = 0; i < size-2; i++)                         \
            o2 << strings[i] << std::endl;                         \
          free(strings);                                           \
                                                                   \
          o2 << "Abort execution?";                                \
                                                                   \
          o << o2.str() << '\"' << std::endl;                      \
                                                                   \
          int retvalue = system(o.str().c_str());                  \
                                                                   \
          switch(WEXITSTATUS(retvalue))                            \
            {                                                      \
            case __IDABORT: abort(); break;                        \
            case __IDBREAK: raise(SIGTRAP); break;                 \
            case __IDCONTINUE: break;                              \
            default:                                               \
              std::cout << title << std::endl                      \
                        << o2.str() << " (Yes/No/Break)"           \
                        << std::endl;                              \
                                                                   \
              __ASSERT_GETCH(retvalue);                            \
                                                                   \
              switch(retvalue & 0xff)                              \
                {                                                  \
                case __IDABORT: abort(); break;                    \
                case __IDBREAK: raise(SIGTRAP); break;             \
                case __IDCONTINUE: break;                          \
                default: break;                                    \
                }                                                  \
              break;                                               \
            }                                                      \
        }

    #else // Not Windows or Linux

      #error "OS not supported"

    #endif

      // Assertion macros

      #define POP_ASSERT(test, reason)                             \
        {                                                          \
          if (!(test))                                             \
            {                                                      \
              GEN_POP("ASSERTION FAILED",                          \
                      "FAILED TEST: " << #test << std::endl,       \
                      reason);                                     \
            }                                                      \
        }

      #define POP_ILLEGAL(reason)                                  \
        {                                                          \
          GEN_POP("ILLEGAL called", "", reason);                   \
        }

      #define POP_DEBUG(message)                                   \
        {                                                          \
          std::cerr << message << std::endl;                       \
        }

  #endif  // DEBUG

#endif  // POPASSERT_H

// Other macros to define: WARNING, INFO, DBGOP, PRECONDITION, POSTCONDITION
