#ifdef MKXPZ_ESSENTIALS_DEBUG
#include <SDL.h>

#ifdef __WIN32__
#include <SDL_syswm.h>
#include <windows.h>
#else
#include <cstring>
#endif


#include "util/mINI.h"
#include "eventthread.h"
#include "system/fake-api.h"
#include "filesystem/filesystem.h"
#include "input/input.h"
#include "system/system.h"
#include "sharedstate.h"

namespace ini = mINI;

// Essentials, without edits, needs Win32API. Two problems with that:

// 1. Not all Win32API functions work here, and
// 2. It uses Windows libraries.

// So, even on Windows, we need to switch out some of those functions
// with our own in order to trick the game into doing what we want.

// On Windows, we just have to worry about functions that involve
// thread IDs and window handles mostly. On anything else, every
// function that an Essentials game uses has to be impersonated.

// If you're lucky enough to be using a crossplatform library, you
// could just load that since MiniFFI should work anywhere, but
// every single system or windows-specific function you use has
// to be intercepted.

// This would also apply if you were trying to load a macOS .dylib
// from Windows or a linux .so from macOS. It's just not gonna work,
// chief.

// There's probably not much reason to change this outside of
// improving compatibility with games in general, since if you just
// want to fix one specific game and you're smart enough to add to
// this then you're also smart enough to either use MiniFFI on a
// library made for your platform or, better yet, just write the
// functionality into MKXP and don't use MiniFFI/Win32API at all.

// A lot of functions here will probably be bound directly to Ruby
// eventually (so that you can just call Graphics.screenshot or
// something instead of having to make a messy Win32API call
// that *now* has to run entirely different function to even work)

PREFABI DWORD MKXP_GetCurrentThreadId(void) NOP_VAL(DUMMY_VAL)

PREFABI DWORD
MKXP_GetWindowThreadProcessId(HWND hWnd, LPDWORD lpdwProcessId)
NOP_VAL(DUMMY_VAL)

PREFABI HWND MKXP_FindWindowEx(HWND hWnd, HWND hWndChildAfter,
                               LPCSTR lpszClass, LPCSTR lpszWindow)
#ifdef __WIN32__
{
    SDL_SysWMinfo wm;
    SDL_GetWindowWMInfo(shState->sdlWindow(), &wm);
    return wm.info.win.window;
}
#else
NOP_VAL((HWND)DUMMY_VAL)
#endif

PREFABI DWORD MKXP_GetForegroundWindow(void) {
    if (SDL_GetWindowFlags(shState->sdlWindow()) & SDL_WINDOW_INPUT_FOCUS) {
        return DUMMY_VAL;
    }
    return 0;
}

PREFABI BOOL MKXP_GetClientRect(HWND hWnd, LPRECT lpRect) {
    SDL_GetWindowSize(shState->sdlWindow(), (int *)&lpRect->right,
                      (int *)&lpRect->bottom);
    return true;
}

// You would think that you could just call GetCursorPos
// and ScreenToClient with the window handle and lppoint
// and be fine, but nope
PREFABI BOOL MKXP_GetCursorPos(LPPOINT lpPoint) {
    SDL_GetGlobalMouseState((int *)&lpPoint->x, (int *)&lpPoint->y);
    return true;
}

PREFABI BOOL MKXP_ScreenToClient(HWND hWnd, LPPOINT lpPoint) {
    lpPoint->x = shState->input().mouseX();
    lpPoint->y = shState->input().mouseY();
    return true;
}

PREFABI BOOL MKXP_SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y,
                               int cx, int cy, UINT uFlags) {
    // The game calls resize_screen which will automatically
    // ... well, resize the screen
    // shState->eThread().requestWindowResize(cx, cy);
    
    shState->eThread().requestWindowReposition(X, Y);
    return true;
}

PREFABI BOOL MKXP_SetWindowTextA(HWND hWnd, LPCSTR lpString) {
    shState->eThread().requestWindowRename((const char *)lpString);
    return true;
}

PREFABI BOOL MKXP_GetWindowRect(HWND hWnd, LPRECT lpRect) {
    int cur_x, cur_y, cur_w, cur_h;
    SDL_GetWindowPosition(shState->sdlWindow(), &cur_x, &cur_y);
    SDL_GetWindowSize(shState->sdlWindow(), &cur_w, &cur_h);
    lpRect->left = cur_x;
    lpRect->right = cur_x + cur_w + 1;
    lpRect->top = cur_y;
    lpRect->bottom = cur_y + cur_h + 1;
    return true;
}

// Shift key with GetKeyboardState doesn't work for whatever reason,
// so Windows needs this too
#define ks(sc) shState->eThread().keyStates[SDL_SCANCODE_##sc]
PREFABI BOOL MKXP_GetKeyboardState(PBYTE lpKeyState) {
#ifdef __WIN32__
    bool rc = GetKeyboardState(lpKeyState);
    if (rc) {
        lpKeyState[VK_LSHIFT] = ks(LSHIFT) << 7;
        lpKeyState[VK_RSHIFT] = ks(RSHIFT) << 7;
        lpKeyState[VK_SHIFT] =
        (lpKeyState[VK_LSHIFT] || lpKeyState[VK_RSHIFT]) ? 0x80 : 0;
    }
    return rc;
#else
    for (int i = 254; i > 0; i--) {
        lpKeyState[i] = (MKXP_GetAsyncKeyState(i)) ? 0x80 : 0;
    }
    return true;
#endif
}

// =========================================
// macOS / Linux only stuff starts here
// =========================================

#ifndef __WIN32__

PREFABI VOID MKXP_RtlMoveMemory(VOID *Destination, VOID *Source,
                                SIZE_T Length) {
    // I have no idea if this is a good idea or not
    // or why it's even necessary in the first place,
    // getting rid of this is priority #1
    memcpy(Destination, Source, Length);
}

// I don't know who's more crazy, them for writing this stuff
// or me for being willing to put it here.

// Probably me.

PREFABI HMODULE MKXP_LoadLibrary(LPCSTR lpLibFileName) {
    return SDL_LoadObject(lpLibFileName);
}

PREFABI BOOL MKXP_FreeLibrary(HMODULE hLibModule) {
    SDL_UnloadObject(hLibModule);
    return true;
}

// Luckily, Essentials only cares about the high-order bit,
// so SDL's keystates will work perfectly fine
PREFABI SHORT MKXP_GetAsyncKeyState(int vKey) {
    SHORT result;
    switch (vKey) {
        case 0x10: // Any Shift
            result = (ks(LSHIFT) || ks(RSHIFT)) ? 0x8000 : 0;
            break;
            
        case 0x11: // Any Ctrl
            result = (ks(LCTRL) || ks(RCTRL)) ? 0x8000 : 0;
            break;
            
        case 0x12: // Any Alt
            result = (ks(LALT) || ks(RALT)) ? 0x8000 : 0;
            break;
            
        case 0x1: // Mouse button 1
            result = (SDL_GetMouseState(0, 0) & SDL_BUTTON(1)) ? 0x8000 : 0;
            break;
            
        case 0x2: // Mouse button 2
            result = (SDL_GetMouseState(0, 0) & SDL_BUTTON(3)) ? 0x8000 : 0;
            break;
            
        case 0x4: // Middle mouse
            result = (SDL_GetMouseState(0, 0) & SDL_BUTTON(2)) ? 0x8000 : 0;
            break;
            
        default:
            try {
                // Use EventThread instead of Input because
                // Input.update typically gets overridden
                result = shState->eThread().keyStates[vKeyToScancode[vKey]] << 15;
            } catch (...) {
                result = 0;
            }
            break;
    }
    return result;
}
#undef ks

PREFABI BOOL
MKXP_GetSystemPowerStatus(LPSYSTEM_POWER_STATUS lpSystemPowerStatus) {
    int seconds, percent;
    SDL_PowerState ps;
    ps = SDL_GetPowerInfo(&seconds, &percent);
    
    // Setting ACLineStatus
    if (ps == SDL_POWERSTATE_UNKNOWN) {
        lpSystemPowerStatus->ACLineStatus = 0xFF;
    } else {
        lpSystemPowerStatus->ACLineStatus =
        (ps == SDL_POWERSTATE_ON_BATTERY) ? 1 : 0;
    }
    
    // Setting BatteryFlag
    if (ps == SDL_POWERSTATE_ON_BATTERY) {
        if (percent == -1) {
            lpSystemPowerStatus->BatteryFlag = 0xFF;
        } else if (percent >= 33) {
            lpSystemPowerStatus->BatteryFlag = 1;
        } else if (4 < percent && percent < 33) {
            lpSystemPowerStatus->BatteryFlag = 2;
        } else {
            lpSystemPowerStatus->BatteryFlag = 4;
        }
    } else if (ps == SDL_POWERSTATE_CHARGING) {
        lpSystemPowerStatus->BatteryFlag = 8;
    } else if (ps == SDL_POWERSTATE_NO_BATTERY) {
        lpSystemPowerStatus->BatteryFlag = (BYTE)128;
    } else {
        lpSystemPowerStatus->BatteryFlag = 0xFF;
    }
    
    // Setting BatteryLifePercent
    lpSystemPowerStatus->BatteryLifePercent =
    (percent != -1) ? (BYTE)percent : 0xFF;
    
    // Setting SystemStatusFlag
    lpSystemPowerStatus->SystemStatusFlag = 0;
    
    // Setting BatteryLifeTime
    lpSystemPowerStatus->BatteryLifeTime = seconds;
    
    // Setting BatteryFullLifeTime
    lpSystemPowerStatus->BatteryFullLifeTime = -1;
    
    return true;
}

PREFABI BOOL MKXP_ShowWindow(HWND hWnd, int nCmdShow) NOP_VAL(true);

// This only currently supports getting screen width/height
// Not really motivated to do the other ones when I'll be
// extending Ruby at a later time anyway
PREFABI int MKXP_GetSystemMetrics(int nIndex) {
    SDL_DisplayMode dm = {0};
    int rc = SDL_GetDesktopDisplayMode(
                                       SDL_GetWindowDisplayIndex(shState->sdlWindow()), &dm);
    if (!rc) {
        switch (nIndex) {
            case 0:
                return dm.w;
                break;
                
            case 1:
                return dm.h;
                break;
                
            default:
                return -1;
                break;
        }
    }
    
    return -1;
}

PREFABI HWND MKXP_SetCapture(HWND hWnd) NOP_VAL((HWND)DUMMY_VAL);

PREFABI BOOL MKXP_ReleaseCapture(void) NOP_VAL(true);

PREFABI int MKXP_ShowCursor(BOOL bShow) NOP_VAL(DUMMY_VAL);

PREFABI DWORD MKXP_GetPrivateProfileString(LPCTSTR lpAppName, LPCTSTR lpKeyName,
                                           LPCTSTR lpDefault,
                                           LPTSTR lpReturnedString, DWORD nSize,
                                           LPCTSTR lpFileName) {
    if (mkxp_fs::fileExists(lpFileName)) {
        ini::INIStructure data;
        
        if (ini::INIFile(lpFileName).read(data)) {
            if (data.has(lpAppName) && data[lpAppName].has(lpKeyName))
                strncpy(lpReturnedString, data[lpAppName][lpKeyName].c_str(), nSize);
        } else {
            strncpy(lpReturnedString, lpDefault, nSize);
        }
    }
    
    return strlen(lpReturnedString);
}

// Does not handle sublanguages, only returns a few
// common languages

// Use MKXP.user_language instead

PREFABI short // I know it's a LANGID but I don't care
MKXP_GetUserDefaultLangID(void) {
    char buf[50];
    strncpy(buf, mkxp_sys::getSystemLanguage().c_str(), sizeof(buf));
    
    for (int i = 0; i < strlen(buf); i++) {
        if (buf[i] == '_') {
            buf[i] = 0;
            break;
        }
    }
    
#define MATCH(l, c)                                                            \
if (!strcmp(l, buf))                                                         \
return (c & 0x3ff);
    MATCH("ja", 0x11);
    MATCH("en", 0x09);
    MATCH("fr", 0x0c);
    MATCH("it", 0x10);
    MATCH("de", 0x07);
    MATCH("es", 0x0a);
    MATCH("ko", 0x12);
    MATCH("pt", 0x16);
    MATCH("zh", 0x04);
#undef MATCH
    return 0x09;
}

PREFABI BOOL MKXP_GetUserName(LPSTR lpBuffer, LPDWORD pcbBuffer) {
    if (*pcbBuffer < 1)
        return false;
    strncpy(lpBuffer, mkxp_sys::getUserName().c_str(), *pcbBuffer);
    return true;
}

PREFABI BOOL MKXP_RegisterHotKey(HWND hWnd, int id, UINT fsModifiers, UINT vk)
NOP_VAL(true);

PREFABI LONG MKXP_SetWindowLong(HWND hWnd, int nIndex, LONG dwNewLong) {
    if (nIndex == -16) {
        if (dwNewLong == 0) {
            shState->eThread().requestFullscreenMode(true);
        } else if (dwNewLong == 0x14ca0000) {
            shState->eThread().requestFullscreenMode(false);
        }
    }
    return DUMMY_VAL;
};

#endif
#endif
