#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <regex.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>

typedef struct {
char active_hex[32];
int width, height;
int x, y;
int ancestor_x, ancestor_y;
int ancestor_width, ancestor_height;
} Geometry;

static void rstrip(char *s) {
size_t n = strlen(s);
while (n > 0 && (s[n-1] == '\n' || s[n-1] == '\r' || isspace((unsigned char)s[n-1]))) {
s[--n] = '\0';
}
}

static int run_command(const char *cmd, char *out, size_t outsz) {
    FILE *fp = popen(cmd, "r");
    if (!fp) return -1;
    size_t total = 0;
    if (out && outsz > 0) out[0] = '\0';
    char buf[512];
    while (fgets(buf, sizeof(buf), fp)) {
        size_t len = strlen(buf);
if (out && total + len + 1 < outsz) {
	memcpy(out + total, buf, len);
	total += len;
	out[total] = '\0';
}
    }
    int rc = pclose(fp);
    return rc;
}

static int parse_first_geometry(const char *line, int *w, int *h, int *x, int *y) {
    regex_t re;
    if (regcomp(&re, "([0-9]+)x([0-9]+)\\+([0-9]+)\\+([0-9]+)", REG_EXTENDED)) {
        return 0;
    }
    regmatch_t m[5];
    int ok = 0;
    if (regexec(&re, line, 5, m, 0) == 0) {
        char a[32], b[32], c[32], d[32];
        int l0 = m[1].rm_eo - m[1].rm_so;
        int l1 = m[2].rm_eo - m[2].rm_so;
        int l2 = m[3].rm_eo - m[3].rm_so;
        int l3 = m[4].rm_eo - m[4].rm_so;
        if (l0 < (int)sizeof(a) && l1 < (int)sizeof(b) && l2 < (int)sizeof(c) && l3 < (int)sizeof(d)) {
            memcpy(a, line + m[1].rm_so, l0); a[l0] = '\0';
            memcpy(b, line + m[2].rm_so, l1); b[l1] = '\0';
            memcpy(c, line + m[3].rm_so, l2); c[l2] = '\0';
            memcpy(d, line + m[4].rm_so, l3); d[l3] = '\0';
            *w = atoi(a); *h = atoi(b); *x = atoi(c); *y = atoi(d);
            ok = 1;
        }
    }
    regfree(&re);
    return ok;
}

static int is_top_level_line(const char *line) {
    int count = 0;
    const char *p = line;
    while (*p == ' ') { count++; p++; }
    if (count == 5 && *p != ' ' && *p != '\0') return 1;
    return 0;
}

static int get_active_window_hex(char *hexbuf, size_t sz) {
    char out[128] = {0};
    if (run_command("xdotool getactivewindow", out, sizeof(out)) != 0) return 0;
    rstrip(out);
    if (out[0] == '\0') return 0;
    unsigned long id = strtoul(out, NULL, 10);
    snprintf(hexbuf, sz, "0x%lx", id);
    return 1;
}


static int get_geometry(Geometry *g) {
    memset(g, 0, sizeof(*g));
    if (!get_active_window_hex(g->active_hex, sizeof(g->active_hex))) {
        return 0;
    }
    FILE *fp = popen("xwininfo -root -tree", "r");
    if (!fp) return 0;
    char line[2048];
    int in_block = 0;
    int found_active = 0;
    int ancestor_w = 0, ancestor_h = 0, ancestor_x = 0, ancestor_y = 0;
    int w = 0, h = 0, x = 0, y = 0;
while (fgets(line, sizeof(line), fp)) {
rstrip(line);
if (is_top_level_line(line)) {
	if (in_block && found_active) {
	break;
	}
	in_block = 1;
	found_active = 0;
	int tw, th, tx, ty;
	if (parse_first_geometry(line, &tw, &th, &tx, &ty)) {
	ancestor_w = tw; ancestor_h = th; ancestor_x = tx; ancestor_y = ty;
	}
	continue;
}
if (in_block) {
	if (strstr(line, g->active_hex)) {
	int tw, th, tx, ty;
	if (parse_first_geometry(line, &tw, &th, &tx, &ty)) {
		w = tw; h = th; x = tx; y = ty;
		found_active = 1;
	}
	}
}
}
    pclose(fp);
    if (!in_block || !found_active) {
        return 0;
    }
    g->width = w; g->height = h; g->x = x; g->y = y;
    g->ancestor_width = ancestor_w; g->ancestor_height = ancestor_h;
    g->ancestor_x = ancestor_x; g->ancestor_y = ancestor_y;
    return 1;
}



static int get_screen_dims(int *screen_w, int *screen_h) {
    FILE *fp = popen("xdpyinfo", "r");
    if (!fp) return 0;
    char line[1024];
    int ok = 0;
    while (fgets(line, sizeof(line), fp)) {
        char *p = strstr(line, "dimensions:");
if (p) {
	int w = 0, h = 0;
	p += strlen("dimensions:");
	while (*p && isspace((unsigned char)*p)) p++;
	if (sscanf(p, "%dx%d", &w, &h) == 2) {
	*screen_w = w; *screen_h = h;
	ok = 1;
	break;
	}
}
    }
    pclose(fp);
    return ok;
}



static void move_resize(const char *win_hex, int x, int y, int w, int h) {
    char cmd[256];
    snprintf(cmd, sizeof(cmd), "xdotool windowmove %s %d %d", win_hex, x, y);
    system(cmd);
    snprintf(cmd, sizeof(cmd), "xdotool windowsize %s %d %d", win_hex, w, h);
    system(cmd);
}



static void unmaximize_if_needed(int screen_w, int screen_h, const Geometry *geom, const char *action) {
    if (strcmp(action, "fullscreen") != 0 &&
        geom->ancestor_width == screen_w && geom->ancestor_height == screen_h) {
        system("wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz");
    }
}

static void handle_action(const char *action,int screen_w, int screen_h,int half_screen_w, int half_screen_h) {
    if (!(strcmp(action, "center") == 0 || strcmp(action, "fullscreen") == 0 ||
          strcmp(action, "left") == 0 || strcmp(action, "right") == 0 ||
          strcmp(action, "up") == 0 || strcmp(action, "down") == 0)) {
        return;
    }
    Geometry geom;
    if (!get_geometry(&geom)) {
        fprintf(stderr, "get_geometry failed\n");
        return;
    }
int overhead_x = geom.ancestor_width - geom.width;
int overhead_y = geom.ancestor_height - geom.height;
int full_width = screen_w - overhead_x;
int full_height = screen_h - overhead_y;
int half_width = half_screen_w - overhead_x;
int half_height = half_screen_h - overhead_y;
int x_new = 0, y_new = 0, w_new = 0, h_new = 0;
    unmaximize_if_needed(screen_w, screen_h, &geom, action);
    if (strcmp(action, "center") == 0) {
        w_new = (int)(0.75 * screen_w);
        h_new = (int)(0.75 * screen_h);
        x_new = (screen_w - w_new - overhead_x) / 2;
        y_new = (screen_h - h_new - overhead_y) / 2;
    } else if (strcmp(action, "fullscreen") == 0) {
        x_new = 0; y_new = 0;
        w_new = full_width; h_new = full_height;
    } else if (strcmp(action, "left") == 0 || strcmp(action, "right") == 0) {
        x_new = (strcmp(action, "left") == 0) ? 0 : half_screen_w;
        w_new = half_width;
        int condA = (geom.width <= half_width) &&
                    (geom.ancestor_x == x_new) &&
                    (geom.height <= half_height);
        int condB = (geom.height == full_height);
        if (condA || condB) {
            y_new = 0; h_new = full_height;
        } else {
            y_new = (geom.ancestor_y < half_screen_h) ? 0 : half_screen_h;
            h_new = half_height;
        }
    } else if (strcmp(action, "up") == 0 || strcmp(action, "down") == 0) {
        y_new = (strcmp(action, "up") == 0) ? 0 : half_screen_h;
        h_new = half_height;

        int condA = (geom.height <= half_height) &&
                    (geom.ancestor_y == y_new) &&
                    (geom.width >= half_width);
        int condB = (geom.width == full_width);
        if (condA || condB) {
            x_new = 0; w_new = full_width;
        } else {
            x_new = (geom.ancestor_x < half_screen_w) ? 0 : half_screen_w;
            w_new = half_width;
        }
    }
    move_resize(geom.active_hex, x_new, y_new, w_new, h_new);
}

static void build_fifo_path(char *out, size_t outsz) {
    const char *home = getenv("HOME");
    if (!home) home = ".";
    snprintf(out, outsz, "%s/.tiler.sock", home);
}

int main(void) {
    char fifo_path[PATH_MAX];
    build_fifo_path(fifo_path, sizeof(fifo_path));
    unlink(fifo_path);
    if (mkfifo(fifo_path, 0666) == -1) {
        perror("mkfifo");
        return 1;
    }
    int screen_w = 0, screen_h = 0;
    if (!get_screen_dims(&screen_w, &screen_h)) {
        fprintf(stderr, "Could not parse screen dimensions\n");
        return 1;
    }
    int half_screen_w = screen_w / 2;
    int half_screen_h = screen_h / 2;
    printf("[tilerd] Listening on %s...\n", fifo_path);
    fflush(stdout);
    int wfd = open(fifo_path, O_WRONLY | O_NONBLOCK);
    (void)wfd;
    while (1) {
        int fd = open(fifo_path, O_RDONLY | O_NONBLOCK);
        if (fd == -1) {
            perror("open");
            usleep(200000);
            continue;
        }
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
        int sel = select(fd + 1, &readfds, NULL, NULL, &tv);
        if (sel > 0 && FD_ISSET(fd, &readfds)) {
            char buf[256];
            ssize_t n = read(fd, buf, sizeof(buf) - 1);
            if (n > 0) {
                buf[n] = '\0';
                char *saveptr = NULL;
                char *tok = strtok_r(buf, "\n", &saveptr);
                while (tok) {
                    char action[64];
                    snprintf(action, sizeof(action), "%s", tok);
                    rstrip(action);
                    if (action[0] != '\0') {
                        handle_action(action, screen_w, screen_h, half_screen_w, half_screen_h);
                    }
                    tok = strtok_r(NULL, "\n", &saveptr);
                }
            }
        }
        close(fd);
    }
    return 0;
}
