#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>

static const char *ZIP_URL = "https://github.com/StitcyYT/Complex-X-/archive/refs/heads/main.zip";

struct ZipEntry {
    char name[512];
    unsigned long compSize;
    unsigned long uncompSize;
    unsigned short method;
};

static long read_u32(const unsigned char *p) {
    return p[0] | (p[1] << 8) | (p[2] << 16) | ((long)p[3] << 24);
}

static unsigned short read_u16(const unsigned char *p) {
    return (unsigned short)(p[0] | (p[1] << 8));
}

static int make_dir_tree(const char *filepath) {
    char tmp[1024];
    char *p;
    snprintf(tmp, sizeof(tmp), "%s", filepath);
    for (p = tmp + 1; *p; p++) {
        if (*p == '\\' || *p == '/') {
            *p = 0;
            CreateDirectoryA(tmp, NULL);
            *p = '\\';
        }
    }
    return 0;
}

static int extract_zip(const char *zipPath, const char *destDir) {
    FILE *f = fopen(zipPath, "rb");
    unsigned char hdr[30];
    unsigned char *comp = NULL, *uncomp = NULL;
    int ret = 0;

    if (!f) return 0;

    while (1) {
        if (fread(hdr, 1, 30, f) != 30) break;
        if (read_u32(hdr) != 0x04034b50) break;

        unsigned short flags = read_u16(hdr + 6);
        unsigned short method = read_u16(hdr + 8);
        unsigned long compSize = read_u32(hdr + 18);
        unsigned long uncompSize = read_u32(hdr + 22);
        unsigned short nameLen = read_u16(hdr + 26);
        unsigned short extraLen = read_u16(hdr + 28);

        char name[512];
        if (nameLen >= (unsigned short)sizeof(name)) {
            fseek(f, nameLen + extraLen + compSize, SEEK_CUR);
            continue;
        }
        if (fread(name, 1, nameLen, f) != nameLen) break;
        name[nameLen] = 0;
        fseek(f, extraLen, SEEK_CUR);

        if (name[0] == 0) break;

        if (name[strlen(name) - 1] == '/') {
            continue;
        }

        char outPath[1024];
        snprintf(outPath, sizeof(outPath), "%s\\%s", destDir, name);
        for (char *p = outPath; *p; p++) if (*p == '/') *p = '\\';
        make_dir_tree(outPath);

        comp = (unsigned char*)malloc(compSize + 1);
        if (!comp) break;
        if (fread(comp, 1, compSize, f) != compSize) { free(comp); comp = NULL; break; }

        FILE *out = fopen(outPath, "wb");
        if (!out) { free(comp); comp = NULL; break; }

        if (method == 0) {
            fwrite(comp, 1, compSize, out);
        } else if (method == 8) {
            uncomp = (unsigned char*)malloc(uncompSize + 1);
            if (uncomp) {
                z_stream zs;
                memset(&zs, 0, sizeof(zs));
                if (inflateInit2(&zs, -MAX_WBITS) == Z_OK) {
                    zs.next_in = comp;
                    zs.avail_in = compSize;
                    zs.next_out = uncomp;
                    zs.avail_out = uncompSize;
                    if (inflate(&zs, Z_FINISH) == Z_STREAM_END)
                        fwrite(uncomp, 1, uncompSize, out);
                    inflateEnd(&zs);
                }
                free(uncomp);
                uncomp = NULL;
            }
        }

        fclose(out);
        free(comp);
        comp = NULL;

        if ((flags & 1) && compSize == 0xffffffff) break;
    }

    fclose(f);
    if (comp) free(comp);
    ret = 1;
    return ret;
}

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nShowCmd) {
    char temp[MAX_PATH], zipPath[MAX_PATH], extractDir[MAX_PATH];
    char exeSrc[MAX_PATH], desk[MAX_PATH], dest[MAX_PATH];
    char cmd[1024];
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;

    GetTempPathA(MAX_PATH, temp);
    snprintf(zipPath, sizeof(zipPath), "%s\\complexx_repo.zip", temp);
    snprintf(extractDir, sizeof(extractDir), "%s\\complexx_extract", temp);
    SHGetFolderPathA(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, desk);

    if (FAILED(URLDownloadToFileA(NULL, ZIP_URL, zipPath, 0, NULL))) {
        MessageBoxA(NULL, "Failed to download Complexx Raycaster.", "Complexx Raycaster", MB_OK | MB_ICONERROR);
        return 1;
    }

    CreateDirectoryA(extractDir, NULL);

    if (!extract_zip(zipPath, extractDir)) {
        DeleteFileA(zipPath);
        MessageBoxA(NULL, "Failed to unpack.", "Complexx Raycaster", MB_OK | MB_ICONERROR);
        return 1;
    }

    DeleteFileA(zipPath);

    snprintf(exeSrc, sizeof(exeSrc), "%s\\Complex-X--main\\build\\raycaster.exe", extractDir);
    snprintf(dest, sizeof(dest), "%s\\Complexx Raycaster.exe", desk);

    if (!CopyFileA(exeSrc, dest, FALSE)) {
        char err[256];
        snprintf(err, sizeof(err), "Copy failed (error %lu).", GetLastError());
        MessageBoxA(NULL, err, "Complexx Raycaster", MB_OK | MB_ICONERROR);
        return 1;
    }

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    snprintf(cmd, sizeof(cmd), "cmd /c rd /s /q \"%s\"", extractDir);
    CreateProcessA(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
    WaitForSingleObject(pi.hProcess, 10000);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    MessageBoxA(NULL, "Complexx Raycaster is ready! Check your Desktop.", "Complexx Raycaster", MB_OK | MB_ICONINFORMATION);
    return 0;
}
