#include "Helpers.h"
#include <windows.h>
#include <wincrypt.h>
#include <algorithm>
#include <stdexcept>
using namespace std;
using namespace SMX;

namespace {
    function<void(const string &log)> g_LogCallback = [](const string &log) {
        printf("%6.3f: %s\n", GetMonotonicTime(), log.c_str());
    };
};

void SMX::Log(string s)
{
    g_LogCallback(s);
}

void SMX::Log(wstring s)
{
    Log(WideStringToUTF8(s));
}

void SMX::SetLogCallback(function<void(const string &log)> callback)
{
    g_LogCallback = callback;
}

const DWORD MS_VC_EXCEPTION = 0x406D1388;  
#pragma pack(push,8)  
typedef struct tagTHREADNAME_INFO  
{  
    DWORD dwType; // Must be 0x1000.  
    LPCSTR szName; // Pointer to name (in user addr space).  
    DWORD dwThreadID; // Thread ID (-1=caller thread).  
    DWORD dwFlags; // Reserved for future use, must be zero.  
} THREADNAME_INFO;  

#pragma pack(pop)  
void SMX::SetThreadName(DWORD iThreadId, const string &name)
{

#ifdef _MSC_VER
    THREADNAME_INFO info;  
    info.dwType = 0x1000;  
    info.szName = name.c_str();  
    info.dwThreadID = iThreadId;
    info.dwFlags = 0;
#pragma warning(push)  
#pragma warning(disable: 6320 6322)  
    __try{   
        RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);  
    }  
    __except (EXCEPTION_EXECUTE_HANDLER) {
    }  
#pragma warning(pop)
#endif //_MSC_VER
}  

void SMX::StripCrnl(wstring &s)
{
    while(s.size() && (s[s.size()-1] == '\r' || s[s.size()-1] == '\n'))
        s.erase(s.size()-1);
}

wstring SMX::GetErrorString(int err)
{
    wchar_t buf[1024] = L"";
    FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 0, err, 0, buf, sizeof(buf), NULL);

    // Fix badly formatted strings returned by FORMAT_MESSAGE_FROM_SYSTEM.
    wstring sResult = buf;
    StripCrnl(sResult);
    return sResult;
}

string SMX::vssprintf(const char *szFormat, va_list argList)
{
    int iChars = vsnprintf(NULL, 0, szFormat, argList);
    if(iChars == -1)
        return string("Error formatting string: ") + szFormat;

    string sStr;
    sStr.resize(iChars+1);
    vsnprintf((char *) sStr.data(), iChars+1, szFormat, argList);
    sStr.resize(iChars);

    return sStr;
}

string SMX::ssprintf(const char *fmt, ...)
{
    va_list va;
    va_start(va, fmt);
    return vssprintf(fmt, va);
}

wstring SMX::wvssprintf(const wchar_t *szFormat, va_list argList)
{
    int iChars = _vsnwprintf(NULL, 0, szFormat, argList);
    if(iChars == -1)
        return wstring(L"Error formatting string: ") + szFormat;

    wstring sStr;
    sStr.resize(iChars+1);
    _vsnwprintf((wchar_t *) sStr.data(), iChars+1, szFormat, argList);
    sStr.resize(iChars);

    return sStr;
}

wstring SMX::wssprintf(const wchar_t *fmt, ...)
{
    va_list va;
    va_start(va, fmt);
    return wvssprintf(fmt, va);
}

string SMX::BinaryToHex(const void *pData_, int iNumBytes)
{
    const unsigned char *pData = (const unsigned char *) pData_;
    string s;
    for(int i=0; i<iNumBytes; i++)
    {
        unsigned val = pData[i];
        s += ssprintf("%02x", val);
    }
    return s;
}

string SMX::BinaryToHex(const string &sString)
{
    return BinaryToHex(sString.data(), sString.size());
}

bool SMX::GetRandomBytes(void *pData, int iBytes)
{
    HCRYPTPROV hCryptProvider = 0;
    if (!CryptAcquireContext(&hCryptProvider, NULL, MS_DEF_PROV, PROV_RSA_FULL, (CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET)) && 
        !CryptAcquireContext(&hCryptProvider, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET | CRYPT_NEWKEYSET))
        return 0;

    bool bSuccess = !!CryptGenRandom(hCryptProvider, iBytes, (uint8_t *) pData);
    CryptReleaseContext(hCryptProvider, 0);
    return bSuccess;
}

// Monotonic timer code from https://stackoverflow.com/questions/24330496.
// Why is this hard?
//
// This code has backwards compatibility to XP, but we only officially support and
// test back to Windows 7, so that code path isn't tested.
typedef struct _KSYSTEM_TIME {
    ULONG LowPart;
    LONG High1Time;
    LONG High2Time;
} KSYSTEM_TIME;
#define KUSER_SHARED_DATA 0x7ffe0000
#define InterruptTime ((KSYSTEM_TIME volatile*)(KUSER_SHARED_DATA + 0x08))
#define InterruptTimeBias ((ULONGLONG volatile*)(KUSER_SHARED_DATA + 0x3b0))

namespace {
    LONGLONG ReadInterruptTime()
    {
        // Reading the InterruptTime from KUSER_SHARED_DATA is much better than
        // using GetTickCount() because it doesn't wrap, and is even a little quicker.
        // This works on all Windows NT versions (NT4 and up).
        LONG timeHigh;
        ULONG timeLow;
        do {
            timeHigh = InterruptTime->High1Time;
            timeLow = InterruptTime->LowPart;
        } while (timeHigh != InterruptTime->High2Time);
        LONGLONG now = ((LONGLONG)timeHigh << 32) + timeLow;
        static LONGLONG d = now;
        return now - d;
    }

    LONGLONG ScaleQpc(LONGLONG qpc)
    {
        // We do the actual scaling in fixed-point rather than floating, to make sure
        // that we don't violate monotonicity due to rounding errors.  There's no
        // need to cache QueryPerformanceFrequency().
        LARGE_INTEGER frequency;
        QueryPerformanceFrequency(&frequency);
        double fraction = 10000000/double(frequency.QuadPart);
        LONGLONG denom = 1024;
        LONGLONG numer = max(1LL, (LONGLONG)(fraction*denom + 0.5));
        return qpc * numer / denom;
    }

    ULONGLONG ReadUnbiasedQpc()
    {
        // We remove the suspend bias added to QueryPerformanceCounter results by
        // subtracting the interrupt time bias, which is not strictly speaking legal,
        // but the units are correct and I think it's impossible for the resulting
        // "unbiased QPC" value to go backwards.
        LONGLONG interruptTimeBias, qpc;
        do {
            interruptTimeBias = *InterruptTimeBias;
            LARGE_INTEGER counter;
            QueryPerformanceCounter(&counter);
            qpc = counter.QuadPart;
        } while (interruptTimeBias != static_cast<LONGLONG>(*InterruptTimeBias));
        static std::pair<LONGLONG,LONGLONG> d(qpc, interruptTimeBias);
        return ScaleQpc(qpc - d.first) - (interruptTimeBias - d.second);
    }

    bool Win7OrLater()
    {
        static int iWin7OrLater = -1;
        if(iWin7OrLater != -1)
            return bool(iWin7OrLater);

        OSVERSIONINFOW ver = {};
        ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
        GetVersionExW(&ver);
        iWin7OrLater = (ver.dwMajorVersion > 6 || (ver.dwMajorVersion == 6 && ver.dwMinorVersion >= 1));
        return bool(iWin7OrLater);
    }
}

/// getMonotonicTime() returns the time elapsed since the application's first
/// call to getMonotonicTime(), in 100ns units.  The values returned are
/// guaranteed to be monotonic.  The time ticks in 15ms resolution and advances
/// during suspend on XP and Vista, but we manage to avoid this on Windows 7
/// and 8, which also use a high-precision timer.  The time does not wrap after
/// 49 days.
double SMX::GetMonotonicTime()
{
    // On Windows XP and earlier, QueryPerformanceCounter is not monotonic so we
    // steer well clear of it; on Vista, it's just a bit slow.
    uint64_t iTime = Win7OrLater()? ReadUnbiasedQpc() : ReadInterruptTime();
    return iTime / 10000000.0;
}

void SMX::GenerateRandom(void *pOut, int iSize)
{
    // These calls shouldn't fail.
    HCRYPTPROV cryptProv;
    if(!CryptAcquireContextW(&cryptProv, nullptr,
        L"Microsoft Base Cryptographic Provider v1.0",
        PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
        throw runtime_error("CryptAcquireContext error");

    if(!CryptGenRandom(cryptProv, iSize, (BYTE *) pOut)) 
        throw runtime_error("CryptGenRandom error");

    if(!CryptReleaseContext(cryptProv, 0))
        throw runtime_error("CryptReleaseContext error");
}

string SMX::WideStringToUTF8(wstring s)
{
    if(s.empty())
        return "";

    int iBytes = WideCharToMultiByte( CP_ACP, 0, s.data(), s.size(), NULL, 0, NULL, FALSE );

    string ret;
    ret.resize(iBytes);
    WideCharToMultiByte( CP_ACP, 0, s.data(), s.size(), (char *) ret.data(), iBytes, NULL, FALSE );

    return ret;
}

const char *SMX::CreateError(string error)
{
    // Store the string in a static so it doesn't get deallocated.
    static string buf;
    buf = error;
    return buf.c_str();
}

SMX::AutoCloseHandle::AutoCloseHandle(HANDLE h)
{
    handle = h;
}

SMX::AutoCloseHandle::~AutoCloseHandle()
{
    if(handle != INVALID_HANDLE_VALUE)
        CloseHandle(handle);
}

SMX::Mutex::Mutex()
{
    m_hLock = CreateMutex(NULL, false, NULL);
}

SMX::Mutex::~Mutex()
{
    CloseHandle(m_hLock);
}

void SMX::Mutex::Lock()
{
    WaitForSingleObject(m_hLock, INFINITE);
    m_iLockedByThread = GetCurrentThreadId();
}

void SMX::Mutex::Unlock()
{
    m_iLockedByThread = 0;
    ReleaseMutex(m_hLock);
}

void SMX::Mutex::AssertNotLockedByCurrentThread()
{
    if(m_iLockedByThread == GetCurrentThreadId())
        throw runtime_error("Expected to not be locked");
}

void SMX::Mutex::AssertLockedByCurrentThread()
{
    if(m_iLockedByThread != GetCurrentThreadId())
        throw runtime_error("Expected to be locked");
}

SMX::LockMutex::LockMutex(SMX::Mutex &mutex):
    m_Mutex(mutex)
{
    m_Mutex.AssertNotLockedByCurrentThread();
    m_Mutex.Lock();
}

SMX::LockMutex::~LockMutex()
{
    m_Mutex.AssertLockedByCurrentThread();
    m_Mutex.Unlock();
}

// This is a helper to let the config tool open a window, which has no freopen.
// This isn't exposed in SMX.h.
// extern "C" __declspec(dllexport) void SMX_Internal_OpenConsole()
// {
//     AllocConsole();
//     freopen("CONOUT$","wb", stdout);
//     freopen("CONOUT$","wb", stderr);
// }
