#include <raylib.h>
#include <rlgl.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include <signal.h>

static volatile bool running = true;

static void handle_signal(int sig) {
    (void)sig;
    running = false;
}

#define WIDTH  1920
#define HEIGHT 1080

#define BALL_RADIUS 100.0f
#define INFLUENCE   200.0f

typedef struct {
    const char* name;
    Vector2 pos;
    Vector2 origin;
    float angle;
    float angle_step;
} Star;

static Star stars[] = {
    { .name = "star_1.png", .pos = {   4,  528 }, .origin = {165, 166}, .angle = 90.0, .angle_step = -0.025 },
    { .name = "star_2.png", .pos = { 215,  809 }, .origin = {82,  83},  .angle = 90.0, .angle_step = 0.05   },
    { .name = "star_3.png", .pos = { 333,  781 }, .origin = {25,  31},  .angle = 90.0, .angle_step = 0.15   },
    { .name = "star_4.png", .pos = { 956,  405 }, .origin = {166, 167}, .angle = 90.0, .angle_step = -0.1   },
    { .name = "star_5.png", .pos = { 1164, 570 }, .origin = {66,  70},  .angle = 90.0, .angle_step = 0.075  },
};

#define STARS_COUNT (int)(sizeof(stars) / sizeof(stars[0]))

static char *hyprland_socket_path;

static Vector2 hyprland_get_cursor_position() {
    Vector2 pos = {0, 0};

    int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        TraceLog(LOG_ERROR, "socket: %s", strerror(errno));
        return pos;
    }

    struct sockaddr_un addr = {0};
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, hyprland_socket_path, sizeof(addr.sun_path) - 1);

    if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        TraceLog(LOG_ERROR, "connect %s: %s", hyprland_socket_path, strerror(errno));
        close(sock_fd);
        return pos;
    }

    const char *message = "/cursorpos";
    if (send(sock_fd, message, strlen(message), 0) == -1) {
        TraceLog(LOG_ERROR, "send: %s", strerror(errno));
        close(sock_fd);
        return pos;
    }

    char buffer[256] = {0};
    ssize_t bytes_received = recv(sock_fd, buffer, sizeof(buffer) - 1, 0);
    if (bytes_received == -1) {
        TraceLog(LOG_ERROR, "recv: %s", strerror(errno));
        close(sock_fd);
        return pos;
    }

    // format is x, y so parse here
    buffer[bytes_received] = '\0';

    if (sscanf(buffer, "%f, %f", &pos.x, &pos.y) != 2) {
        TraceLog(LOG_ERROR, "Failed to parse cursor position");
    }

    close(sock_fd);
    return pos;
}

static const char *resolve_asset_path(const char *path) {
    if (FileExists(path)) return path;
    const char *system_path = TextFormat("/usr/local/share/pkl-wallpaper/%s", path);
    if (FileExists(system_path)) return system_path;
    return path;
}

static Texture2D load_texture_from_file(const char *file_path) {
    Texture2D texture = LoadTexture(resolve_asset_path(file_path));
    if (texture.id == 0) {
        TraceLog(LOG_ERROR, "Failed to load texture from file: %s", file_path);
        abort();
    }
    return texture;
}

static Shader load_shader_from_file(const char *vs_path, const char *fs_path) {
    Shader shader = LoadShader(resolve_asset_path(vs_path), resolve_asset_path(fs_path));
    if (shader.id == 0) {
        TraceLog(LOG_ERROR, "Failed to load shader from files: %s, %s", vs_path, fs_path);
        abort();
    }
    return shader;
}

static const char* get_window_title() {
    return getenv("DEBUG") != NULL ? "pkl-wallpaper-debug" : "pkl-wallpaper";
}

static char* get_hyprland_socket_path() {
    return strdup(TextFormat("%s/hypr/%s/.socket.sock", getenv("XDG_RUNTIME_DIR"), getenv("HYPRLAND_INSTANCE_SIGNATURE")));
}

int main(void) {
    InitWindow(WIDTH, HEIGHT, get_window_title());
    SetWindowState(FLAG_WINDOW_RESIZABLE);
    SetTargetFPS(60);
    SetExitKey(KEY_NULL);

    signal(SIGTERM, handle_signal);
    signal(SIGINT, handle_signal);

    // initialize hyprland socket path
    hyprland_socket_path = get_hyprland_socket_path();

    // initialize textures
    Texture2D distanceFieldTex = load_texture_from_file("assets/textures/background_distance_field.png");
    Texture2D backgroundTex    = load_texture_from_file("assets/textures/background_transparent.png");
    Texture2D starTextures[STARS_COUNT] = {0};
    for (int i = 0; i < STARS_COUNT; i++) {
        starTextures[i] = load_texture_from_file(TextFormat("assets/textures/%s", stars[i].name));
    }

    RenderTexture2D target = LoadRenderTexture(WIDTH, HEIGHT);

    // initialize shader
    Shader shader = load_shader_from_file("assets/shaders/basic.vs", "assets/shaders/distance_field.fs");

    int resolutionLoc       = GetShaderLocation(shader, "resolution");
    int mousePosLoc         = GetShaderLocation(shader, "mousePos");
    int ballRadiusLoc       = GetShaderLocation(shader, "ballRadius");
    int shapeInfluenceLoc   = GetShaderLocation(shader, "shapeInfluence");
    int distanceFieldTexLoc = GetShaderLocation(shader, "distanceFieldTex");

    Vector2 resolution = {WIDTH, HEIGHT};
    SetShaderValue(shader, resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);

    float ballRadius = BALL_RADIUS;
    SetShaderValue(shader, ballRadiusLoc, &ballRadius, SHADER_UNIFORM_FLOAT);

    float shapeInfluence = INFLUENCE;
    SetShaderValue(shader, shapeInfluenceLoc, &shapeInfluence, SHADER_UNIFORM_FLOAT);

    int textureUnit = 1;
    SetShaderValue(shader, distanceFieldTexLoc, &textureUnit, SHADER_UNIFORM_INT);

    while (running) {
        int screenWidth = GetScreenWidth();
        int screenHeight = GetScreenHeight();

        if (IsWindowResized()) {
            UnloadRenderTexture(target);
            target = LoadRenderTexture(screenWidth, screenHeight);

            Vector2 resolution = {screenWidth, screenHeight};
            SetShaderValue(shader, resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
        }

        Vector2 hyprCursor = hyprland_get_cursor_position();
        Vector2 mousePos = {hyprCursor.x, (float)screenHeight - hyprCursor.y};
        SetShaderValue(shader, mousePosLoc, &mousePos, SHADER_UNIFORM_VEC2);

        BeginDrawing();

        rlActiveTextureSlot(1);
        rlEnableTexture(distanceFieldTex.id);
        rlActiveTextureSlot(0);

        BeginShaderMode(shader);
        DrawTextureRec(target.texture, (Rectangle){0, 0, screenWidth, -screenHeight}, (Vector2){0, 0}, WHITE);
        EndShaderMode();

        DrawTextureRec(backgroundTex, (Rectangle){0, 0, screenWidth, screenHeight}, (Vector2){0, 0}, WHITE);

        for (int i = 0; i < STARS_COUNT; i++) {
            Star* star = &stars[i];
            star->angle = fmodf(star->angle + star->angle_step, 360.0f);

            Texture2D tex = starTextures[i];
            Rectangle source = {0, 0, (float)tex.width, (float)tex.height};
            Rectangle dest   = {star->pos.x, star->pos.y, (float)tex.width, (float)tex.height};
            DrawTexturePro(tex, source, dest, star->origin, star->angle, WHITE);
        }

        EndDrawing();
    }

    UnloadRenderTexture(target);
    UnloadTexture(distanceFieldTex);
    UnloadTexture(backgroundTex);
    for (int i = 0; i < STARS_COUNT; i++) {
        UnloadTexture(starTextures[i]);
    }
    UnloadShader(shader);
    CloseWindow();

    free(hyprland_socket_path);

    return 0;
}
