#ifndef main_H
#define main_H

#ifdef _DEBUG
#    include <iostream>
#    include <sstream>
#    include <vector>
#    ifndef _LTMDB
#        define _CRTDBG_MAP_ALLOC
#        include <crtdbg.h>
#        include <stdlib.h>
#        define DEBUG_CLIENTBLOCK new (_CLIENT_BLOCK, __FILE__, __LINE__)
#        define new DEBUG_CLIENTBLOCK
#        define _LTMDB
#    endif
#endif
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#pragma warning(push)
#pragma warning(disable : 4091)
#include <DbgHelp.h>
#pragma warning(pop)

#define KSGStart __stdcall
#include "Globals.h"

namespace Framework
{
    //! Speichert die dem Programm vom Betriebssystem beim Start übergebenen
    //! Parameter
    struct Startparam
    {
        HINSTANCE hinst, hpinst;
        LPSTR cmd;
        int show;
    };

    //! Überschreibe diese Funktion. Sie wird vom Framework automatisch beim
    //! Start des Programmes aufgerufen \param p Die Parameter, die dem Programm
    //! beim Start vom Betriebssystem übergeben wurden
    int KSGStart Start(Startparam p);
} // namespace Framework

typedef BOOL(__stdcall* MINIDUMPWRITEDUMP)(HANDLE hProcess,
    DWORD dwPid,
    HANDLE hFile,
    MINIDUMP_TYPE DumpType,
    CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
    CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
    CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);

void createMinidump(struct _EXCEPTION_POINTERS* apExceptionInfo)
{
    HMODULE mhLib = ::LoadLibrary("dbghelp.dll");
    MINIDUMPWRITEDUMP pDump = mhLib ? (MINIDUMPWRITEDUMP)::GetProcAddress(
                                  mhLib, "MiniDumpWriteDump")
                                    : 0;

    HANDLE hFile = ::CreateFile("error_core_memory_dump.dmp",
        GENERIC_WRITE,
        FILE_SHARE_WRITE,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    _MINIDUMP_EXCEPTION_INFORMATION ExInfo;
    ExInfo.ThreadId = ::GetCurrentThreadId();
    ExInfo.ExceptionPointers = apExceptionInfo;
    ExInfo.ClientPointers = FALSE;

    if (pDump)
        pDump(GetCurrentProcess(),
            GetCurrentProcessId(),
            hFile,
            MiniDumpNormal,
            &ExInfo,
            NULL,
            NULL);
    ::CloseHandle(hFile);
}

LONG WINAPI unhandledHandler(struct _EXCEPTION_POINTERS* apExceptionInfo)
{
    createMinidump(apExceptionInfo);
    return EXCEPTION_CONTINUE_SEARCH;
}

#ifdef _DEBUG
template<typename TChar, typename TTraits> class OutputDebugStringBuf
    : public std::basic_stringbuf<TChar, TTraits>
{
public:
    explicit OutputDebugStringBuf()
        : _buffer(1)
    {
        __super::setg(nullptr, nullptr, nullptr);
        __super::setp(
            _buffer.data(), _buffer.data(), _buffer.data() + _buffer.size());
    }

    ~OutputDebugStringBuf() {}

    static_assert(
        std::is_same<TChar, char>::value || std::is_same<TChar, wchar_t>::value,
        "OutputDebugStringBuf only supports char and wchar_t types");

    int sync()
    try
    {
        MessageOutputer<TChar, TTraits>()(__super::pbase(), __super::pptr());
        __super::setp(
            _buffer.data(), _buffer.data(), _buffer.data() + _buffer.size());
        return 0;

    } catch (...)
    {
        return -1;
    }

    int overflow(int c = TTraits::eof())
    {
        auto syncRet = sync();
        if (c != TTraits::eof())
        {
            _buffer[0] = c;
            __super::setp(_buffer.data(),
                _buffer.data() + 1,
                _buffer.data() + _buffer.size());
        }
        return syncRet == -1 ? TTraits::eof() : 0;
    }

private:
    std::vector<TChar> _buffer;

    template<typename TChar, typename TTraits> struct MessageOutputer;

    template<> struct MessageOutputer<char, std::char_traits<char>>
    {
        template<typename TIterator>
        void operator()(TIterator begin, TIterator end) const
        {
            std::string s(begin, end);
            OutputDebugStringA(s.c_str());
        }
    };

    template<> struct MessageOutputer<wchar_t, std::char_traits<wchar_t>>
    {
        template<typename TIterator>
        void operator()(TIterator begin, TIterator end) const
        {
            std::wstring s(begin, end);
            OutputDebugStringW(s.c_str());
        }
    };
};
#endif

#ifndef NO_MAIN
int WINAPI WinMain(_In_ HINSTANCE hinst,
    _In_opt_ HINSTANCE hpinst,
    _In_ LPSTR cmd,
    int _In_ show)
{
#    ifdef _DEBUG
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    OutputDebugStringBuf<char, std::char_traits<char>> charDebugOutput;
    std::streambuf* buf = std::cout.rdbuf();
    std::cout.rdbuf(&charDebugOutput);
#    endif
    SetUnhandledExceptionFilter(unhandledHandler);
    Framework::initFramework(hinst);
    Framework::Startparam stp;
    stp.hinst = hinst;
    stp.hpinst = hpinst;
    stp.cmd = cmd;
    stp.show = show;
    int ret = Framework::Start(stp);
    Framework::releaseFramework();
#    ifdef _DEBUG
    std::cout.rdbuf(buf);
#    endif
    return ret;
}
#endif

#endif