#include "pkgi.h"
#include "pkgi_style.h"

#define INITGUID
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <dwmapi.h>
#include <intrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <shlwapi.h>
#include <wininet.h>
#include <wincodec.h>

#pragma comment (lib, "shlwapi.lib")
#pragma comment (lib, "dwmapi.lib")
#pragma comment (lib, "wininet.lib")
#pragma comment (lib, "windowscodecs.lib")

#define Assert(cond) do { if (!(cond)) __debugbreak(); } while (0)

static HINTERNET g_inet;

static HWND g_hwnd;
static HDC g_dc;
static HDC g_memdc;
static HBRUSH g_brush;
static HPEN g_pen;

static LARGE_INTEGER g_time_freq;
static LARGE_INTEGER g_time;

static CRITICAL_SECTION g_dialog_lock;

static char g_input_text[256];
static int g_input_text_active;

static uint32_t g_button_frame_count;

struct pkgi_http
{
    HANDLE handle;
    uint64_t size;
    uint64_t offset;

    HINTERNET conn;
};

static pkgi_http g_http[4];

#define PKGI_FOLDER "pkgi"
#define PKGI_APP_FOLDER "app"

#define PKGI_DIALOG_MESSAGE 1
#define PKGI_DIALOG_ERROR 2
#define PKGI_DIALOG_PROGRESS 3

#define GDI_COLOR(c) RGB((c)&0xff, (c>>8)&0xff, (c>>16)&0xff)

static LRESULT CALLBACK pkgi_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_ERASEBKGND:
        return 1;

    case WM_PAINT:
        ValidateRect(hwnd, NULL);
        return 0;
    }

    return DefWindowProcW(hwnd, msg, wparam, lparam);
}

void pkgi_log(const char* msg, ...)
{
    va_list args;
    va_start(args, msg);
    vprintf(msg, args);
    printf("\n");
    va_end(args);
}

int pkgi_snprintf(char* buffer, uint32_t size, const char* msg, ...)
{
    va_list args;
    va_start(args, msg);
    int len = vsnprintf(buffer, size - 1, msg, args);
    va_end(args);
    buffer[len] = 0;
    return len;
}

void pkgi_vsnprintf(char* buffer, uint32_t size, const char* msg, va_list args)
{
    int len = vsnprintf(buffer, size - 1, msg, args);
    buffer[len] = 0;
}

char* pkgi_strstr(const char* str, const char* sub)
{
    return StrStrA(str, sub);
}

int pkgi_stricontains(const char* str, const char* sub)
{
    return StrStrIA(str, sub) != NULL;
}

int pkgi_stricmp(const char* a, const char* b)
{
    return stricmp(a, b);
}

void pkgi_strncpy(char* dst, uint32_t size, const char* src)
{
    strncpy(dst, src, size);
}

char* pkgi_strrchr(const char* str, char ch)
{
    return strrchr(str, ch);
}

void pkgi_memcpy(void* dst, const void* src, uint32_t size)
{
    memcpy(dst, src, size);
}

void pkgi_memmove(void* dst, const void* src, uint32_t size)
{
    memmove(dst, src, size);
}

int pkgi_memequ(const void* a, const void* b, uint32_t size)
{
    return memcmp(a, b, size) == 0;
}

int pkgi_is_unsafe_mode(void)
{
    return 1;
}

int pkgi_ok_button(void)
{
    return PKGI_BUTTON_X;
}

int pkgi_cancel_button(void)
{
    return PKGI_BUTTON_O;
}

void pkgi_dialog_lock(void)
{
    EnterCriticalSection(&g_dialog_lock);
}

void pkgi_dialog_unlock(void)
{
    LeaveCriticalSection(&g_dialog_lock);
}

void pkgi_dialog_input_text(const char* title, const char* text)
{
    printf("%s (default=%s): ", title, text);
    g_input_text_active = 1;
}

int pkgi_dialog_input_update(void)
{
    if (g_input_text_active)
    {
        gets_s(g_input_text, sizeof(g_input_text));
        g_input_text_active = 0;
        return g_input_text[0] != 0;
    }
    return 0;
}

void pkgi_dialog_input_get_text(char* text, uint32_t size)
{
    strncpy(text, g_input_text, size);
}

void pkgi_start(void)
{
    InitializeCriticalSection(&g_dialog_lock);

    HRESULT hr = CoInitializeEx(NULL, 0);
    Assert(SUCCEEDED(hr));

    g_inet = InternetOpenW(L"pkgi", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    Assert(g_inet);

    WNDCLASSEXW wc =
    {
        .cbSize = sizeof(wc),
        .style = CS_OWNDC,
        .lpfnWndProc = pkgi_window_proc,
        .lpszClassName = L"pkg_simulator",
    };

    ATOM atom = RegisterClassExW(&wc);
    Assert(atom);

    RECT r = { 0, 0, VITA_WIDTH, VITA_HEIGHT };

    DWORD style = WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
    DWORD exstyle = WS_EX_APPWINDOW;
    BOOL ok = AdjustWindowRectEx(&r, style, FALSE, exstyle);
    Assert(ok);

    g_hwnd = CreateWindowExW(
        exstyle, wc.lpszClassName, L"pkgi simulator", style | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top,
        NULL, NULL, NULL, NULL);
    Assert(g_hwnd);

    g_dc = GetDC(g_hwnd);
    Assert(g_dc);

    BITMAPINFO bmi =
    {
        .bmiHeader =
        {
            .biSize = sizeof(bmi.bmiHeader),
            .biWidth = VITA_WIDTH,
            .biHeight = -VITA_HEIGHT,
            .biPlanes = 1,
            .biBitCount = 32,
            .biCompression = BI_RGB,
        },
    };

    g_memdc = CreateCompatibleDC(g_dc);
    Assert(g_memdc);

    void* bits = NULL;
    HBITMAP bmp = CreateDIBSection(g_memdc, &bmi, 0, &bits, NULL, 0);
    Assert(bmp);

    SelectObject(g_memdc, bmp);

    AddFontResourceW(L"ltn0.otf");

    LOGFONTW log =
    {
        .lfHeight = -18,
        .lfWeight = FW_NORMAL,
        .lfOutPrecision = OUT_TT_ONLY_PRECIS,
        .lfQuality = CLEARTYPE_QUALITY,
        .lfFaceName = L"SCE Rodin Cattleya LATIN",
    };

    HFONT font = CreateFontIndirectW(&log);
    Assert(font);

    g_brush = GetStockObject(DC_BRUSH);
    g_pen = GetStockObject(DC_PEN);
    SelectObject(g_memdc, font);
    SelectObject(g_memdc, g_brush);
    SelectObject(g_memdc, g_pen);
    SetBkMode(g_memdc, TRANSPARENT);

    QueryPerformanceFrequency(&g_time_freq);
    QueryPerformanceCounter(&g_time);
}

int pkgi_update(pkgi_input* input)
{
    MSG msg;
    while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT)
        {
            return 0;
        }
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    uint32_t previous = input->down;

    input->down = 0;
    if (GetActiveWindow() == g_hwnd)
    {
        if (GetAsyncKeyState(VK_UP) >> 15) input->down |= PKGI_BUTTON_UP;
        if (GetAsyncKeyState(VK_DOWN) >> 15) input->down |= PKGI_BUTTON_DOWN;
        if (GetAsyncKeyState(VK_LEFT) >> 15) input->down |= PKGI_BUTTON_LEFT;
        if (GetAsyncKeyState(VK_RIGHT) >> 15) input->down |= PKGI_BUTTON_RIGHT;
        if (GetAsyncKeyState(VK_END) >> 15) input->down |= PKGI_BUTTON_X;
        if (GetAsyncKeyState(VK_NEXT) >> 15) input->down |= PKGI_BUTTON_O;
        if (GetAsyncKeyState(VK_HOME) >> 15) input->down |= PKGI_BUTTON_T;
        if (GetAsyncKeyState(VK_DELETE) >> 15) input->down |= PKGI_BUTTON_S;
        if (GetAsyncKeyState(VK_RETURN) >> 15) input->down |= PKGI_BUTTON_START;
        if (GetAsyncKeyState(VK_SPACE) >> 15) input->down |= PKGI_BUTTON_SELECT;
        if (GetAsyncKeyState(VK_OEM_6) >> 15) input->down |= PKGI_BUTTON_RT; // [
        if (GetAsyncKeyState(VK_OEM_4) >> 15) input->down |= PKGI_BUTTON_LT; // ]
    }

    input->pressed = input->down & ~previous;
    input->active = input->pressed;

    if (input->down == previous)
    {
        if (g_button_frame_count >= 10)
        {
            input->active = input->down;
        }
        g_button_frame_count++;
    }
    else
    {
        g_button_frame_count = 0;
    }

    LARGE_INTEGER time;
    QueryPerformanceCounter(&time);
    input->delta = 1000000 * (time.QuadPart - g_time.QuadPart) / g_time_freq.QuadPart;
    g_time = time;

    return 1;
}

void pkgi_swap(void)
{
    BitBlt(g_dc, 0, 0, VITA_WIDTH, VITA_HEIGHT, g_memdc, 0, 0, SRCCOPY);
    DwmFlush();
}

void pkgi_end(void)
{
    InternetCloseHandle(g_inet);
    DeleteCriticalSection(&g_dialog_lock);
}

int pkgi_battery_present()
{
    return 1;
}

int pkgi_bettery_get_level()
{
    return 66;
}

int pkgi_battery_is_low()
{
    return 0;
}

int pkgi_battery_is_charging()
{
    return 0;
}

uint64_t pkgi_get_free_space(void)
{
    ULARGE_INTEGER available;
    BOOL ok = GetDiskFreeSpaceExW(NULL, &available, NULL, NULL);
    Assert(ok);
    return available.QuadPart;
}

const char* pkgi_get_config_folder(void)
{
    return PKGI_FOLDER;
}

const char* pkgi_get_temp_folder(void)
{
    return PKGI_FOLDER;
}

const char* pkgi_get_app_folder(void)
{
    return PKGI_APP_FOLDER;
}

int pkgi_is_incomplete(const char* titleid)
{
    char path[256];
    pkgi_snprintf(path, sizeof(path), "%s/%s.resume", pkgi_get_temp_folder(), titleid);

    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);

    DWORD attrib = GetFileAttributesW(wpath);
    if (attrib == INVALID_FILE_ATTRIBUTES)
    {
        DWORD err = GetLastError();
        if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND)
        {
            return 0;
        }
        Assert(0);
    }

    return !!(attrib & FILE_ATTRIBUTE_ARCHIVE);
}

int pkgi_is_installed(const char* titleid)
{
    char path[256];
    pkgi_snprintf(path, sizeof(path), "%s/%s", pkgi_get_app_folder(), titleid);

    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);

    DWORD attrib = GetFileAttributesW(wpath);
    if (attrib == INVALID_FILE_ATTRIBUTES)
    {
        DWORD err = GetLastError();
        if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND)
        {
            return 0;
        }
        Assert(0);
    }

    return !!(attrib & FILE_ATTRIBUTE_DIRECTORY);
}

int pkgi_install(const char* titleid)
{
    Sleep(2000);

    CreateDirectoryW(L"app", NULL);

    char src[256];
    pkgi_snprintf(src, sizeof(src), "%s/%.9s", pkgi_get_temp_folder(), titleid);
    char dst[256];
    pkgi_snprintf(dst, sizeof(dst), "%s/%.9s", pkgi_get_app_folder(), titleid);

    WCHAR wsrc[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, src, -1, wsrc, MAX_PATH);
    WCHAR wdst[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, dst, -1, wdst, MAX_PATH);

    return MoveFileW(wsrc, wdst) == FALSE ? 0 : 1;
}

uint32_t pkgi_time_msec()
{
    return GetTickCount();
}

static DWORD WINAPI pkgi_win32_thread(void* arg)
{
    pkgi_thread_entry* start = arg;
    start();
    return 0;
}

void pkgi_start_thread(const char* name, pkgi_thread_entry* start)
{
    PKGI_UNUSED(name);
    HANDLE h = CreateThread(NULL, 0, &pkgi_win32_thread, start, 0, NULL);
    Assert(h);
}

void pkgi_sleep(uint32_t msec)
{
    Sleep(msec);
}

int pkgi_load(const char* name, void* data, uint32_t max)
{
    WCHAR wname[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH);

    HANDLE f = CreateFileW(wname, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (f == INVALID_HANDLE_VALUE)
    {
        return -1;
    }

    DWORD read;
    BOOL ok = ReadFile(f, data, max, &read, NULL);
    CloseHandle(f);

    return ok ? read : -1;
}

int pkgi_save(const char* name, const void* data, uint32_t size)
{
    WCHAR wname[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH);

    HANDLE f = CreateFileW(wname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
    if (f == INVALID_HANDLE_VALUE)
    {
        return 0;
    }

    DWORD written;
    BOOL ok = WriteFile(f, data, size, &written, NULL);
    CloseHandle(f);

    return ok && written == size;
}

void pkgi_lock_process(void)
{
}

void pkgi_unlock_process(void)
{
}

pkgi_texture pkgi_load_png_raw(const void* data, uint32_t size)
{
    (void)size;

    HBITMAP bmp = NULL;

    WCHAR exepath[MAX_PATH];
    GetModuleFileNameW(NULL, exepath, MAX_PATH);
    WCHAR* last = StrRChrIW(exepath, NULL, L'\\');
    *last = 0;

    WCHAR path[MAX_PATH];
    int cnt = wsprintfW(path, L"%s\\..\\..\\..\\assets\\", exepath);

    MultiByteToWideChar(CP_UTF8, 0, data, -1, path + cnt, MAX_PATH - cnt);

    HRESULT hr;

    IWICImagingFactory* factory;
    hr = CoCreateInstance(&CLSID_WICImagingFactory, 0, CLSCTX_INPROC_SERVER, &IID_IWICImagingFactory, (LPVOID*)&factory);
    if (SUCCEEDED(hr))
    {
        IWICBitmapDecoder* decoder;
        hr = IWICImagingFactory_CreateDecoderFromFilename(factory, path, 0, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder);
        if (SUCCEEDED(hr))
        {
            IWICBitmapFrameDecode* frame;
            hr = IWICBitmapDecoder_GetFrame(decoder, 0, &frame);
            if (SUCCEEDED(hr))
            {
                IWICFormatConverter* converter;
                hr = IWICImagingFactory_CreateFormatConverter(factory, &converter);
                if (SUCCEEDED(hr))
                {
                    hr = IWICFormatConverter_Initialize(converter, (IWICBitmapSource*)frame, &GUID_WICPixelFormat32bppBGRA,
                                                        WICBitmapDitherTypeNone, 0, 0.0, WICBitmapPaletteTypeCustom);
                    if (SUCCEEDED(hr))
                    {
                        UINT width, height;
                        hr = IWICFormatConverter_GetSize(converter, &width, &height);
                        if (SUCCEEDED(hr))
                        {
                            BITMAPINFO bmi =
                            {
                                .bmiHeader =
                                {
                                    .biSize = sizeof(bmi.bmiHeader),
                                    .biWidth = width,
                                    .biHeight = -(LONG)height,
                                    .biPlanes = 1,
                                    .biBitCount = 32,
                                    .biCompression = BI_RGB,
                                },
                            };

                            HDC dc = GetDC(NULL);
                            Assert(dc);

                            void* bits;
                            bmp = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);
                            Assert(bmp);

                            ReleaseDC(NULL, dc);

                            hr = IWICFormatConverter_CopyPixels(converter, 0, width * 4, width * height * 4, bits);
                            Assert(SUCCEEDED(hr));
                        }
                    }
                    IWICFormatConverter_Release(converter);
                }
                IWICBitmapFrameDecode_Release(frame);
            }
            IWICBitmapDecoder_Release(decoder);
        }
        IWICImagingFactory_Release(factory);
    }

    Assert(bmp);

    return (pkgi_texture)bmp;
}

void pkgi_draw_texture(pkgi_texture texture, int x, int y)
{
    HBITMAP bmp = (HBITMAP)texture;
    BITMAP bitmap;
    GetObjectW(bmp, sizeof(bitmap), &bitmap);

    HDC dc = CreateCompatibleDC(NULL);
    SelectObject(dc, bmp);

    BitBlt(g_memdc, x, y, bitmap.bmWidth, bitmap.bmHeight, dc, 0, 0, SRCCOPY);

    DeleteObject(dc);
}

void pkgi_clip_set(int x, int y, int w, int h)
{
    SelectClipRgn(g_memdc, NULL);
    IntersectClipRect(g_memdc, x, y, x + w, y + h);
}

void pkgi_clip_remove(void)
{
    SelectClipRgn(g_memdc, NULL);
}

void pkgi_draw_rect(int x, int y, int w, int h, uint32_t color)
{
    RECT rect = { x, y, x + w, y + h };

    SetDCBrushColor(g_memdc, GDI_COLOR(color));
    FillRect(g_memdc, &rect, g_brush);
}

void pkgi_draw_text(int x, int y, uint32_t color, const char* text)
{
    WCHAR wtext[1024];
    int wlen = MultiByteToWideChar(CP_UTF8, 0, text, -1, wtext, _countof(wtext));

    SetTextColor(g_memdc, GDI_COLOR(color));
    ExtTextOutW(g_memdc, x, y, 0, NULL, wtext, wlen, NULL);
}

int pkgi_text_width(const char* text)
{
    WCHAR wtext[1024];
    int wlen = MultiByteToWideChar(CP_UTF8, 0, text, -1, wtext, _countof(wtext));

    RECT r = { 0, 0, 65536, 65536 };
    DrawTextW(g_memdc, wtext, wlen, &r, DT_CALCRECT | DT_NOCLIP);

    return r.right - r.left;
}

int pkgi_text_height(const char* text)
{
    WCHAR wtext[1024];
    int wlen = MultiByteToWideChar(CP_UTF8, 0, text, -1, wtext, _countof(wtext));

    RECT r = { 0, 0, 65536, 65536 };
    DrawTextW(g_memdc, wtext, wlen, &r, DT_CALCRECT | DT_NOCLIP);

    return r.bottom - r.top;
}

pkgi_http* pkgi_http_get(const char* url, const char* content, uint64_t offset)
{
    pkgi_http* http = NULL;
    for (size_t i = 0; i < 4; i++)
    {
        if (g_http[i].handle == NULL && g_http[i].conn == NULL)
        {
            http = g_http + i;
            break;
        }
    }

    if (http == NULL)
    {
        LOG("too many simultaneous http requests");
        return NULL;
    }

    HANDLE handle = INVALID_HANDLE_VALUE;
    if (content)
    {
        char path[MAX_PATH];
        strcpy(path, pkgi_get_temp_folder());
        strcat(path, strrchr(url, '/'));

        WCHAR wpath[MAX_PATH];
        MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);

        handle = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
        if (handle == INVALID_HANDLE_VALUE)
        {
            LOG("%s not found, trying shorter path", path);
            pkgi_snprintf(path, sizeof(path), "%s/%s.pkg", pkgi_get_temp_folder(), content);

            MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);
            handle = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
        }
    }

    if (handle == INVALID_HANDLE_VALUE)
    {
        WCHAR headers[256];
        if (offset != 0)
        {
            wsprintfW(headers, L"Range: bytes=%I64u-", offset);
        }

        WCHAR wurl[MAX_PATH];
        MultiByteToWideChar(CP_UTF8, 0, url, -1, wurl, MAX_PATH);

        DWORD flags = INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_SECURE;
        HINTERNET conn = InternetOpenUrlW(g_inet, wurl, offset ? headers : NULL, (DWORD)-1, flags, 0);
        if (conn)
        {
            http->conn = conn;
        }
        else
        {
            http = NULL;
        }
    }
    else
    {
        LARGE_INTEGER size;
        BOOL ok = GetFileSizeEx(handle, &size);
        Assert(ok);

        if (offset != 0)
        {
            LARGE_INTEGER seek = { .QuadPart = offset };
            ok = SetFilePointerEx(handle, seek, NULL, FILE_BEGIN);
            Assert(ok);
        }

        http->handle = handle;
        http->size = size.QuadPart - offset;
        http->offset = 0;
    }

    Sleep(300);

    return http;
}

int pkgi_http_response_length(pkgi_http* http, int64_t* length)
{
    if (http->conn)
    {
        DWORD status;
        DWORD status_len = sizeof(status);
        if (HttpQueryInfoA(http->conn, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &status_len, NULL))
        {
            LOG("http status code = %d", status);

            if (status == 200 || status == 206)
            {
                char str[64];
                DWORD len = sizeof(str);
                if (HttpQueryInfoA(http->conn, HTTP_QUERY_CONTENT_LENGTH, str, &len, NULL))
                {
                    sscanf(str, "%I64d", length);
                    LOG("http response length = %lld", length);
                    return 1;
                }
                else
                {
                    if (GetLastError() == ERROR_HTTP_HEADER_NOT_FOUND)
                    {
                        LOG("http response has no content length (or chunked encoding)");
                        *length = 0;
                        return 1;
                    }

                    LOG("error retrieving content length response header");
                    return 0;
                }
            }

            return 0;
        }
        else
        {
            LOG("cannot get http status code");
            return 0;
        }
    }
    else
    {
        *length = (int64_t)http->size;
        return 1;
    }
}

int pkgi_http_read(pkgi_http* http, void* buffer, uint32_t size)
{
    DWORD read;

    if (http->conn)
    {
        if (!InternetReadFile(http->conn, buffer, size, &read))
        {
            return -(int)GetLastError();
        }
        return read;
    }
    else
    {
        if (size > http->size - http->offset)
        {
            size = (uint32_t)(http->size - http->offset);
        }

        //if (size > 1024)
        //{
        //    uint32_t temp = 1024 + (rand()%102400);
        //    if (temp < size)
        //    {
        //        size = temp;
        //    }
        //}

        BOOL ok = ReadFile(http->handle, buffer, size, &read, NULL);
        if (!ok)
        {
            return -1;
        }
        http->offset += read;

        Sleep(1);
    }

    return read;
}

void pkgi_http_close(pkgi_http* http)
{
    if (http->conn)
    {
        InternetCloseHandle(http->conn);
        http->conn = NULL;
    }
    else
    {
        CloseHandle(http->handle);
        http->handle = NULL;
    }
}

int pkgi_mkdirs(char* path)
{
    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);

    WCHAR* ptr = wpath;
    while (*ptr)
    {
        while (*ptr && *ptr != L'/')
        {
            ptr++;
        }
        WCHAR last = *ptr;
        *ptr = 0;
        BOOL ok = CreateDirectoryW(wpath, NULL);
        if (!ok && GetLastError() != ERROR_ALREADY_EXISTS)
        {
            return 0;
        }
        if (last == 0)
        {
            break;
        }
        *ptr++ = last;
    }

    // Sleep(10);
    return 1;
}

void pkgi_rm(const char* file)
{
    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, file, -1, wpath, MAX_PATH);

    if (!DeleteFileW(wpath))
    {
        LOG("cannot delete %s file", file);
    }
}

int64_t pkgi_get_size(const char* path)
{
    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);

    WIN32_FILE_ATTRIBUTE_DATA data;
    if (GetFileAttributesExW(wpath, GetFileExInfoStandard, &data))
    {
        Sleep(10);
        return ((uint64_t)data.nFileSizeHigh << 32) | data.nFileSizeLow;
    }

    return -1;
}

void* pkgi_create(const char* path)
{
    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);

    HANDLE f = CreateFileW(wpath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (f == INVALID_HANDLE_VALUE)
    {
        return NULL;
    }

    return f;
}

void* pkgi_append(const char* path)
{
    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);

    HANDLE f = CreateFileW(wpath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (f == INVALID_HANDLE_VALUE)
    {
        return NULL;
    }

    SetFilePointer(f, 0, NULL, FILE_END);

    return f;
}

void*pkgi_openrw(const char* path)
{
    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);

    HANDLE f = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (f == INVALID_HANDLE_VALUE)
    {
        return NULL;
    }

    return f;
}

int pkgi_read(void* f, void* buffer, uint32_t size)
{
    DWORD read;
    BOOL ok = ReadFile(f, buffer, size, &read, NULL);
    return ok ? read : -1;
}

int pkgi_write(void* f, const void* buffer, uint32_t size)
{
    DWORD written;
    BOOL ok = WriteFile(f, buffer, size, &written, NULL);
    return ok && written == size;
}

void pkgi_close(void* f)
{
    CloseHandle(f);
}
