/*
InsomniaSDL v2060507
By DarkRanger <darkranger.no-ip.org>
License: MIT License
*/
#include <stdio.h>
#include <stdlib.h>
#include <SDL3/SDL.h>

#ifdef LINUX
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/uinput.h>
#include <sys/ioctl.h>
#endif

#define APP_NAME "InsomniaSDL"
#define APP_VERSION "20260507"
#define TIME_SLEEP 5000

#ifdef LINUX
static void printFatalError(const char *msg) {
    if (errno) {
        perror(msg);
    }
    else {
        fprintf(stderr, "%s\n", msg);
    }

    exit(1);
}
#endif

static void printSDLError() {
    fprintf(stderr, "SDL Error: %s\n", SDL_GetError());
    exit(1);
}

static int randomInteger(int min, int max) {
    return SDL_rand(max - min + 1) + min;
}

#ifdef LINUX
static void init(int fd, int width, int height)
{
    static struct uinput_setup usetup;
    static struct uinput_abs_setup abs_setup;

    /*Enable mouse buttons are required, even though we are not triggering it at all.*/
    if (ioctl(fd, UI_SET_EVBIT, EV_KEY) == -1) {
        printFatalError("ioctl UI_SET_EVBIT EV_KEY");
    }
    if (ioctl(fd, UI_SET_KEYBIT, BTN_LEFT) == -1) {
        printFatalError("ioctl UI_SET_KEYBIT BTN_LEFT");
    }
    /******/

    if (ioctl(fd, UI_SET_EVBIT, EV_REL) == -1) {
        printFatalError("ioctl UI_SET_EVBIT EV_REL");
    }
    if (ioctl(fd, UI_SET_EVBIT, EV_ABS) == -1) {
        printFatalError("ioctl UI_SET_EVBIT EV_ABS");
    }
    if (ioctl(fd, UI_SET_ABSBIT, ABS_X) == -1) {
        printFatalError("ioctl UI_SET_ABSBIT ABS_X");
    }
    if (ioctl(fd, UI_SET_ABSBIT, ABS_Y) == -1) {
        printFatalError("ioctl UI_SET_ABSBIT ABS_Y");
    }
    if (ioctl(fd, UI_SET_RELBIT, REL_X) == -1) {
        printFatalError("ioctl UI_SET_RELBIT REL_X");
    }
    if (ioctl(fd, UI_SET_RELBIT, REL_Y) == -1) {
        printFatalError("ioctl UI_SET_RELBIT REL_Y");
    }

    abs_setup.code = ABS_X;
    abs_setup.absinfo.minimum = 0;
    abs_setup.absinfo.maximum = width;

    if (ioctl(fd, UI_ABS_SETUP, &abs_setup) == -1) {
        printFatalError("ioctl ABS_SETUP ABS_X");
    }

    abs_setup.code = ABS_Y;
    abs_setup.absinfo.minimum = 0;
    abs_setup.absinfo.maximum = height;

    if (ioctl(fd, UI_ABS_SETUP, &abs_setup) == -1) {
        printFatalError("ioctl ABS_SETUP ABS_Y");
    }

    memset(&usetup, 0, sizeof(usetup));
    usetup.id.bustype = BUS_USB;
    usetup.id.vendor = 0x1234;
    usetup.id.product = 0x5678;
    strcpy(usetup.name, APP_NAME);

    if (ioctl(fd, UI_DEV_SETUP, &usetup) == -1) {
        printFatalError("ioctl UI_DEV_SETUP BUS_USB");
    }

    if (ioctl(fd, UI_DEV_CREATE) == -1) {
        printFatalError("ioctl UI_DEV_CREATE");
    }

    SDL_Delay(100);
}

static void emit(int fd, int type, int code, int value) {
    struct input_event ie;
    ie.type = type;
    ie.code = code;
    ie.value = value;

    if (write(fd, &ie, sizeof(ie)) == -1) {
        printFatalError("Emit");
    }
}
#endif

static void moveMouse(int method, int fd, int width, int height, int x, int y) {
    if (method == 0) {
        if (!SDL_WarpMouseGlobal((float)x, (float)y)) {
            printSDLError();
        }
    }

#ifdef LINUX
    else if (method == 1) {
        fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);

        if (fd == -1) {
            printFatalError("open");
        }

        init(fd, width, height);
        emit(fd, EV_ABS, ABS_X, x);
        emit(fd, EV_ABS, ABS_Y, y);
        emit(fd, EV_SYN, SYN_REPORT, 0);
        SDL_Delay(100);

        if (ioctl(fd, UI_DEV_DESTROY) == -1) {
            printFatalError("ioctl UI_DEV_DESTROY");
        }

        close(fd);
    }
#endif

}

int main(int argc, char *argv[])
{
    int screenWidth = 640;
    int screenHeight = 480;
    int appWidth = 320;
    int appHeight = 240;
    int method = 0;
    int fd = 0;
    int quit = 0;
    int x = 0, y = 0;
    int minX = 0, minY = 0, maxX = 0, maxY = 0;
    int steps = 10;
    unsigned int lastTime = 0, currentTime = 0;
    SDL_Window* screen = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Event event;

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        printSDLError();
    }

    const char *os = SDL_GetPlatform();
    const char *video = SDL_GetCurrentVideoDriver();
    SDL_DisplayID display = SDL_GetPrimaryDisplay();
    const SDL_DisplayMode *mode = SDL_GetCurrentDisplayMode(display);

    if (mode == NULL) {
        printSDLError();
    }
    if (strcmp(os, "Linux") == 0 && strcmp(video, "wayland") == 0) {
        method = 1;
    }

    screenWidth = mode->w;
    screenHeight = mode->h;
    printf("v%s\n", APP_VERSION);
    printf("OS: %s, Video: %s %dx%d, Method: %d\n", os, video, mode->w, mode->h, method);
    screen = SDL_CreateWindow(APP_NAME, appWidth, appHeight, 0);

    if (screen == NULL) {
        printSDLError();
    }

    renderer = SDL_CreateRenderer(screen, NULL);
    /*To avoid high CPU load.*/
    SDL_SetRenderVSync(renderer, 1);

    if (renderer == NULL) {
        printSDLError();
    }

    if (!SDL_WarpMouseGlobal((float)100, (float)100)) {
        printSDLError();
    }

    minX = screenWidth / 3;
    maxX = (screenWidth / 3) * 2;
    minY = screenHeight / 3;
    maxY = (screenHeight / 3) * 2;
    x = minX;
    y = minY;
   
    while (quit == 0) {
        currentTime = SDL_GetTicks();

        if (currentTime > lastTime + TIME_SLEEP) {
            x = randomInteger(minX, maxX);
            y = randomInteger(minY, maxY);
            moveMouse(method, fd, screenWidth, screenHeight, x, y);
            lastTime = currentTime;
        }

        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_EVENT_QUIT:
                    quit = 1;
                    break;
            }
        }

        SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer);
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
        SDL_RenderDebugTextFormat(renderer, 10, 10, "%dx%d %d %d\n", screenWidth, screenHeight, x, y);
        SDL_RenderPresent(renderer);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(screen);
    SDL_Quit();
    return 0;
}
