#include <gtk/gtk.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

static int serial_fd = -1;
static GtkWidget *drawing_area = NULL;
static int canvas_width = 600;
static int canvas_height = 600;

typedef enum { PRIM_LINE, PRIM_CIRCLE, PRIM_TEXT, PRIM_POLY } PrimType;

#define MAX_POLY_POINTS 8

typedef struct {
    PrimType type;
    double x1, y1, x2, y2, r; // Used by line/circle
    double px[MAX_POLY_POINTS]; // Universal polygon coordinate points tracking arrays
    double py[MAX_POLY_POINTS];
    int pt_count;
    double red, green, blue;
    char text_content[64];
} DrawingPrimitive;

#define MAX_PRIMITIVES 3000
#define NUM_LAYERS 3

static DrawingPrimitive layers[NUM_LAYERS][MAX_PRIMITIVES];
static int layer_counts[NUM_LAYERS] = {0, 0, 0};
static int active_write_layer = 0;

static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;

static void clear_layer(int layer_idx) {
    if (layer_idx >= 0 && layer_idx < NUM_LAYERS) {
        pthread_mutex_lock(&queue_mutex);
        layer_counts[layer_idx] = 0;
        pthread_mutex_unlock(&queue_mutex);
    }
}

static void add_primitive_to_active(DrawingPrimitive p) {
    pthread_mutex_lock(&queue_mutex);
    int idx = layer_counts[active_write_layer];
    if (idx < MAX_PRIMITIVES) {
        layers[active_write_layer][idx] = p;
        layer_counts[active_write_layer]++;
    }
    pthread_mutex_unlock(&queue_mutex);
}

static void on_draw(GtkDrawingArea *da, cairo_t *cr, int width, int height, gpointer data) {
    cairo_set_source_rgb(cr, 0.08, 0.09, 0.10);
    cairo_paint(cr);

    pthread_mutex_lock(&queue_mutex);
    for (int l = 0; l < NUM_LAYERS; l++) {
        for (int i = 0; i < layer_counts[l]; i++) {
            DrawingPrimitive p = layers[l][i];
            cairo_set_source_rgb(cr, p.red, p.green, p.blue);

            if (p.type == PRIM_LINE) {
                cairo_set_line_width(cr, 1.0);
                cairo_move_to(cr, p.x1, p.y1);
                cairo_line_to(cr, p.x2, p.y2);
                cairo_stroke(cr);
            } else if (p.type == PRIM_CIRCLE) {
                cairo_set_line_width(cr, 3.0);
                cairo_arc(cr, p.x1, p.y1, p.r, 0, 2 * G_PI);
                cairo_stroke(cr);
            } else if (p.type == PRIM_TEXT) {
                cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
                cairo_set_font_size(cr, 16.0);
                cairo_text_extents_t extents;
                cairo_text_extents(cr, p.text_content, &extents);
                cairo_move_to(cr, p.x1 - (extents.width / 2.0), p.y1 + (extents.height / 2.0));
                cairo_show_text(cr, p.text_content);
            } else if (p.type == PRIM_POLY && p.pt_count > 2) {
                // 1. Build vector path layout geometry
                cairo_move_to(cr, p.px[0], p.py[0]);
                for (int pt = 1; pt < p.pt_count; pt++) {
                    cairo_line_to(cr, p.px[pt], p.py[pt]);
                }
                cairo_close_path(cr);

                // 2. Solid color background fill mask execution (Hides lines drawn underneath!)
                cairo_set_source_rgb(cr, 0.08, 0.09, 0.10); // Matches workspace slate background
                cairo_fill_preserve(cr);

                // 3. Render bright edge highlight line contours on top
                cairo_set_source_rgb(cr, p.red, p.green, p.blue);
                cairo_set_line_width(cr, 1.0);
                cairo_stroke(cr);
            }
        }
    }
    pthread_mutex_unlock(&queue_mutex);
}

static void on_resize(GtkDrawingArea *da, int width, int height, gpointer data) {
    static int last_w = -1, last_h = -1;
    if (width != last_w || height != last_h) {
        canvas_width = width;
        canvas_height = height;
        last_w = width;
        last_h = height;
        if (serial_fd != -1) {
            char msg[64];
            snprintf(msg, sizeof(msg), "CANVAS_SIZE:%d,%d\n", width, height);
            write(serial_fd, msg, strlen(msg));
        }
    }
}

static gboolean process_command_idle(gpointer data) {
    char *raw = (char *)data;

    if (strncmp(raw, "CLEAR_LAYER:", 12) == 0) {
        clear_layer(atoi(raw + 12));
    } else if (strncmp(raw, "LAYER:", 6) == 0) {
        active_write_layer = atoi(raw + 6);
        if (active_write_layer < 0 || active_write_layer >= NUM_LAYERS) active_write_layer = 0;
    } else if (strcmp(raw, "REFRESH") == 0) {
        if (drawing_area != NULL) gtk_widget_queue_draw(drawing_area);
    } else if (strncmp(raw, "CIRCLE:", 7) == 0) {
        DrawingPrimitive p; p.type = PRIM_CIRCLE;
        if (sscanf(raw + 7, "%lf,%lf,%lf,%lf,%lf,%lf", &p.x1, &p.y1, &p.r, &p.red, &p.green, &p.blue) == 6) {
            add_primitive_to_active(p);
        }
    } else if (strncmp(raw, "LINE:", 5) == 0) {
        DrawingPrimitive p; p.type = PRIM_LINE;
        if (sscanf(raw + 5, "%lf,%lf,%lf,%lf,%lf,%lf,%lf", &p.x1, &p.y1, &p.x2, &p.y2, &p.red, &p.green, &p.blue) == 7) {
            add_primitive_to_active(p);
        }
    } else if (strncmp(raw, "TEXT:", 5) == 0) {
        DrawingPrimitive p; p.type = PRIM_TEXT;
        char txt_buf[64] = {0};
        if (sscanf(raw + 5, "%lf,%lf,%lf,%lf,%lf,%63s", &p.x1, &p.y1, &p.red, &p.green, &p.blue, txt_buf) == 6) {
            strncpy(p.text_content, txt_buf, sizeof(p.text_content) - 1);
            add_primitive_to_active(p);
        }
    } else if (strncmp(raw, "FILL_POLY:", 10) == 0) {
        // Universal parser routine handling variable multi-sided shapes [1]
        DrawingPrimitive p; p.type = PRIM_POLY;
        char *token;
        char *str = g_strdup(raw + 10);
        char *saveptr;

        int step = 0;
        int pt_idx = 0;
        token = strtok_r(str, ",", &saveptr);
        
        if (token != NULL) {
            p.pt_count = atoi(token);
            if (p.pt_count > MAX_POLY_POINTS) p.pt_count = MAX_POLY_POINTS;

            while ((token = strtok_r(NULL, ",", &saveptr)) != NULL) {
                if (pt_idx < p.pt_count) {
                    if (step == 0) {
                        p.px[pt_idx] = atof(token);
                        step = 1;
                    } else {
                        p.py[pt_idx] = atof(token);
                        pt_idx++;
                        step = 0;
                    }
                } else {
                    // Collect color variables
                    if (step == 0) { p.red = atof(token); step++; }
                    else if (step == 1) { p.green = atof(token); step++; }
                    else if (step == 2) { p.blue = atof(token); break; }
                }
            }
            if (pt_idx == p.pt_count) {
                add_primitive_to_active(p);
            }
        }
        g_free(str);
    }

    g_free(raw);
    return G_SOURCE_REMOVE;
}

static void *serial_listener_thread(void *arg) {
    char buffer[512];
    int index = 0;
    while (serial_fd != -1) {
        char byte;
        if (read(serial_fd, &byte, 1) > 0) {
            if (byte == '\n' || byte == '\r') {
                if (index > 0) {
                    buffer[index] = '\0';
                    if (strcmp(buffer, "READY_PING?") == 0) {
                        char ack_byte = 0x06;
                        write(serial_fd, &ack_byte, 1);
                        tcdrain(serial_fd); 
                    } else {
                        g_idle_add(process_command_idle, g_strdup(buffer));
                    }
                    index = 0;
                }
            } else if (index < sizeof(buffer) - 1) {
                buffer[index++] = byte;
            }
        }
    }
    return NULL;
}

static int on_command_line(GApplication *app, GApplicationCommandLine *cmdline, gpointer user_data) {
    int argc;
    char **argv = g_application_command_line_get_arguments(cmdline, &argc);
    if (argc < 2) { g_strfreev(argv); return 1; }

    serial_fd = open(argv[1], O_RDWR | O_NOCTTY, 0);
    if (serial_fd < 0) { perror("Port fail"); g_strfreev(argv); return 1; }

    struct termios tty;
    if (tcgetattr(serial_fd, &tty) != 0) { g_strfreev(argv); return 1; }

    cfsetospeed(&tty, B9600); cfsetispeed(&tty, B9600);
    cfmakeraw(&tty);

    tty.c_cc[VMIN]  = 1; tty.c_cc[VTIME] = 0;
    tcflush(serial_fd, TCIOFLUSH);
    if (tcsetattr(serial_fd, TCSANOW, &tty) != 0) { g_strfreev(argv); return 1; }

    pthread_t id; pthread_create(&id, NULL, serial_listener_thread, NULL); pthread_detach(id);

    GtkWidget *win = gtk_application_window_new(GTK_APPLICATION(app));
    gtk_window_set_title(GTK_WINDOW(win), "Universal Remote Display Terminal");
    gtk_window_set_default_size(GTK_WINDOW(win), canvas_width, canvas_height);

    drawing_area = gtk_drawing_area_new();
    gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(drawing_area), on_draw, NULL, NULL);
    g_signal_connect(drawing_area, "resize", G_CALLBACK(on_resize), NULL);

    gtk_window_set_child(GTK_WINDOW(win), drawing_area);
    gtk_window_present(GTK_WINDOW(win));

    g_strfreev(argv);
    return 0;
}

int main(int argc, char **argv) {
    GtkApplication *app = gtk_application_new("com.embedded.universaldisplay", G_APPLICATION_HANDLES_COMMAND_LINE);
    g_signal_connect(app, "command-line", G_CALLBACK(on_command_line), NULL);
    return g_application_run(G_APPLICATION(app), argc, argv);
}

