#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"

#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

#include <ifaddrs.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

#define DISP_BUF_SIZE (320 * 240)

LV_IMG_DECLARE(logo);

/* ===================== UI Objects ===================== */

static lv_obj_t *hours_label;
static lv_obj_t *minutes_label;
static lv_obj_t *seconds_label;
static lv_obj_t *colon1_label;
static lv_obj_t *colon2_label;

static lv_obj_t *eth_ip_label;
static lv_obj_t *wifi_ip_label;
static lv_obj_t *custom_text_label;

static volatile bool show_custom_text = false;
static volatile bool custom_text_blink = false;
static volatile bool custom_text_visible = true;
static char custom_text_buf[256] = {0};

#define LVGL_CMD_FIFO "/tmp/lvgl_cmd"
#define LOGO_SHOW_SECONDS 5

/* ===================== Dark Theme ===================== */

static void apply_dark_theme(void)
{
    lv_obj_t *scr = lv_disp_get_scr_act(NULL);
    lv_obj_set_style_bg_color(scr, lv_color_black(), 0);
    lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
}

/* ===================== Time UI ===================== */

static void create_time_labels(lv_obj_t *parent)
{
    static lv_style_t style;
    lv_style_init(&style);
    lv_style_set_text_font(&style, &lv_font_montserrat_48);
    lv_style_set_text_color(&style, lv_color_white());

    hours_label = lv_label_create(parent);
    minutes_label = lv_label_create(parent);
    seconds_label = lv_label_create(parent);
    colon1_label = lv_label_create(parent);
    colon2_label = lv_label_create(parent);

    lv_obj_add_style(hours_label, &style, 0);
    lv_obj_add_style(minutes_label, &style, 0);
    lv_obj_add_style(seconds_label, &style, 0);
    lv_obj_add_style(colon1_label, &style, 0);
    lv_obj_add_style(colon2_label, &style, 0);

    lv_label_set_text(colon1_label, ":");
    lv_label_set_text(colon2_label, ":");

	lv_obj_align(hours_label, LV_ALIGN_CENTER, -80, -25);
	lv_obj_align_to(colon1_label, hours_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0);
	lv_obj_align_to(minutes_label, colon1_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0);
	lv_obj_align_to(colon2_label, minutes_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0);
	lv_obj_align_to(seconds_label, colon2_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0);

	
    lv_obj_add_flag(hours_label, LV_OBJ_FLAG_HIDDEN);
    lv_obj_add_flag(minutes_label, LV_OBJ_FLAG_HIDDEN);
    lv_obj_add_flag(seconds_label, LV_OBJ_FLAG_HIDDEN);
    lv_obj_add_flag(colon1_label, LV_OBJ_FLAG_HIDDEN);
    lv_obj_add_flag(colon2_label, LV_OBJ_FLAG_HIDDEN);
}

static void update_time_labels(int h, int m, int s)
{
    char buf[4];
    snprintf(buf, sizeof(buf), "%02d", h);
    lv_label_set_text(hours_label, buf);
    snprintf(buf, sizeof(buf), "%02d", m);
    lv_label_set_text(minutes_label, buf);
    snprintf(buf, sizeof(buf), "%02d", s);
    lv_label_set_text(seconds_label, buf);

    lv_obj_align_to(colon1_label, hours_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0);
    lv_obj_align_to(minutes_label, colon1_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0);
    lv_obj_align_to(colon2_label, minutes_label, LV_ALIGN_OUT_RIGHT_MID, 6, 0);
    lv_obj_align_to(seconds_label, colon2_label, LV_ALIGN_OUT_RIGHT_MID,6, 0);
}

static void create_custom_text_label(lv_obj_t *parent)
{
    static lv_style_t style;
    lv_style_init(&style);
    lv_style_set_text_font(&style, &lv_font_montserrat_48);
    lv_style_set_text_color(&style, lv_color_white());

    custom_text_label = lv_label_create(parent);
    lv_obj_add_style(custom_text_label, &style, 0);
    lv_label_set_text(custom_text_label, "");
    lv_obj_align(custom_text_label, LV_ALIGN_CENTER, 0, -25);
    lv_obj_add_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN);
}

static void show_time_row(bool visible)
{
    if(visible) {
        lv_obj_clear_flag(hours_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_clear_flag(minutes_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_clear_flag(seconds_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_clear_flag(colon1_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_clear_flag(colon2_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_add_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN);
    } else {
        lv_obj_add_flag(hours_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_add_flag(minutes_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_add_flag(seconds_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_add_flag(colon1_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_add_flag(colon2_label, LV_OBJ_FLAG_HIDDEN);
        lv_obj_clear_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN);
    }
}

static void apply_show_text_async(void *arg)
{
    const char *text = (const char *)arg;
    show_custom_text = true;
    custom_text_visible = true;
    lv_label_set_text(custom_text_label, text ? text : "");
    lv_obj_align(custom_text_label, LV_ALIGN_CENTER, 0, -25);
    show_time_row(false);
    lv_obj_clear_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN);
}

static void apply_show_ap_async(void *arg)
{
    (void)arg;
    show_custom_text = true;
    custom_text_blink = true;
    custom_text_visible = true;
    lv_label_set_text(custom_text_label, "AP");
    lv_obj_align(custom_text_label, LV_ALIGN_CENTER, 0, -25);
    show_time_row(false);
    lv_obj_clear_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN);
}

static void apply_show_time_async(void *arg)
{
    (void)arg;
    show_custom_text = false;
    custom_text_blink = false;
    custom_text_visible = true;
    show_time_row(true);
}

static void *fifo_thread(void *arg)
{
    (void)arg;
    unlink(LVGL_CMD_FIFO);
    if (mkfifo(LVGL_CMD_FIFO, 0666) < 0 && errno != EEXIST) {
        perror("mkfifo lvgl_cmd");
        return NULL;
    }

    while (1) {
        int fd = open(LVGL_CMD_FIFO, O_RDONLY);
        if (fd < 0) {
            usleep(200000);
            continue;
        }

        FILE *fp = fdopen(fd, "r");
        if (!fp) {
            close(fd);
            usleep(200000);
            continue;
        }

        char line[256];
        while (fgets(line, sizeof(line), fp)) {
            size_t len = strlen(line);
            while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
                line[--len] = '\0';
            }

            if (strcmp(line, "show_ap") == 0) {
                lv_async_call(apply_show_ap_async, NULL);
            } else if (strcmp(line, "show_conn") == 0) {
                strncpy(custom_text_buf, "Conn", sizeof(custom_text_buf) - 1);
                custom_text_buf[sizeof(custom_text_buf) - 1] = '\0';
                custom_text_blink = true;
                lv_async_call(apply_show_text_async, custom_text_buf);
            } else if (strcmp(line, "show_err0") == 0) {
                strncpy(custom_text_buf, "Err0", sizeof(custom_text_buf) - 1);
                custom_text_buf[sizeof(custom_text_buf) - 1] = '\0';
                custom_text_blink = false;
                lv_async_call(apply_show_text_async, custom_text_buf);
            } else if (strncmp(line, "show_pin:", 9) == 0) {
                strncpy(custom_text_buf, line + 9, sizeof(custom_text_buf) - 1);
                custom_text_buf[sizeof(custom_text_buf) - 1] = '\0';
                custom_text_blink = true;
                lv_async_call(apply_show_text_async, custom_text_buf);
            } else if (strncmp(line, "show_text:", 10) == 0) {
                strncpy(custom_text_buf, line + 10, sizeof(custom_text_buf) - 1);
                custom_text_buf[sizeof(custom_text_buf) - 1] = '\0';
                custom_text_blink = false;
                lv_async_call(apply_show_text_async, custom_text_buf);
            } else if (strcmp(line, "show_time") == 0) {
                lv_async_call(apply_show_time_async, NULL);
            }
        }

        fclose(fp);
    }

    return NULL;
}

/* ===================== IP UI ===================== */

static void create_ip_labels(lv_obj_t *parent)
{
    static lv_style_t style;
    lv_style_init(&style);
    lv_style_set_text_font(&style, &lv_font_montserrat_20);
    lv_style_set_text_color(&style, lv_color_white());

    eth_ip_label = lv_label_create(parent);
    wifi_ip_label = lv_label_create(parent);

    lv_obj_add_style(eth_ip_label, &style, 0);
    lv_obj_add_style(wifi_ip_label, &style, 0);

    lv_label_set_text(eth_ip_label, "ETH : --");
    lv_label_set_text(wifi_ip_label, "WiFi: --");

    lv_obj_align(eth_ip_label, LV_ALIGN_CENTER, 0, 35);
    lv_obj_align(wifi_ip_label, LV_ALIGN_CENTER, 0, 60);

    lv_obj_add_flag(eth_ip_label, LV_OBJ_FLAG_HIDDEN);
    lv_obj_add_flag(wifi_ip_label, LV_OBJ_FLAG_HIDDEN);
}

/* ===================== IP Logic ===================== */

static void get_ip(const char *ifname, char *out, size_t len)
{
    struct ifaddrs *ifaddr, *ifa;
    out[0] = 0;

    if (getifaddrs(&ifaddr))
        return;

    for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr)
            continue;
        if (ifa->ifa_addr->sa_family == AF_INET &&
            strcmp(ifa->ifa_name, ifname) == 0) {

            inet_ntop(AF_INET,
                      &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr,
                      out, len);
            break;
        }
    }
    freeifaddrs(ifaddr);
}

static void update_ip_ui(void)
{
    char eth[32] = {0}, wifi[32] = {0};
    get_ip("enP4p65s0", eth, sizeof(eth));
    if (eth[0] == '\0') {
        get_ip("eth0", eth, sizeof(eth));
    }
    get_ip("wlan0", wifi, sizeof(wifi));

    if (eth[0]) {
        lv_label_set_text_fmt(eth_ip_label, "ETH : %s", eth);
    } else {
        lv_label_set_text(eth_ip_label, "ETH : Not Connected");
    }

    if (wifi[0]) {
        lv_label_set_text_fmt(wifi_ip_label, "WiFi: %s", wifi);
    } else {
        lv_label_set_text(wifi_ip_label, "WiFi: Not Connected");
    }
}


/* ===================== Netlink Thread ===================== */

static void *netlink_thread(void *arg)
{
    int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

    struct sockaddr_nl addr = {
        .nl_family = AF_NETLINK,
        .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR,
    };

    bind(sock, (struct sockaddr *)&addr, sizeof(addr));

    char buf[4096];
    while (1) {
        recv(sock, buf, sizeof(buf), 0);
        lv_async_call((lv_async_cb_t)update_ip_ui, NULL);
    }
    return NULL;
}

/* ===================== LVGL Tick ===================== */

uint32_t custom_tick_get(void)
{
    static uint64_t start = 0;
    struct timeval tv;
    gettimeofday(&tv, NULL);
    uint64_t now = tv.tv_sec * 1000ULL + tv.tv_usec / 1000;
    if (!start)
        start = now;
    return now - start;
}

/* ===================== Main ===================== */

int main(void)
{
    lv_init();
    fbdev_init();

    static lv_color_t buf[DISP_BUF_SIZE];
    static lv_disp_draw_buf_t draw_buf;
    lv_disp_draw_buf_init(&draw_buf, buf, NULL, DISP_BUF_SIZE);

    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.draw_buf = &draw_buf;
    disp_drv.flush_cb = fbdev_flush;
    disp_drv.hor_res = 320;
    disp_drv.ver_res = 240;
    lv_disp_drv_register(&disp_drv);

    apply_dark_theme();

    lv_obj_t *scr = lv_disp_get_scr_act(NULL);

    lv_obj_t *img = lv_img_create(scr);
    lv_img_set_src(img, &logo);
    lv_obj_center(img);

    create_time_labels(scr);
    create_custom_text_label(scr);
    create_ip_labels(scr);

    pthread_t tid;
    pthread_create(&tid, NULL, netlink_thread, NULL);
    pthread_detach(tid);

    pthread_t fifo_tid;
    pthread_create(&fifo_tid, NULL, fifo_thread, NULL);
    pthread_detach(fifo_tid);

    time_t start = time(NULL);
    int last_sec = -1;
    bool shown = false;

    while (1) {
        time_t now = time(NULL);

        if (!shown && now - start >= LOGO_SHOW_SECONDS) {
            lv_obj_add_flag(img, LV_OBJ_FLAG_HIDDEN);

            show_time_row(true);

            lv_obj_clear_flag(eth_ip_label, LV_OBJ_FLAG_HIDDEN);
            lv_obj_clear_flag(wifi_ip_label, LV_OBJ_FLAG_HIDDEN);

            update_ip_ui();
            shown = true;
        }

        struct tm *tm = localtime(&now);
        if (!show_custom_text && tm->tm_sec != last_sec) {
            update_time_labels(tm->tm_hour, tm->tm_min, tm->tm_sec);
            last_sec = tm->tm_sec;
        }

        static uint32_t last_blink_ms = 0;
        uint32_t now_ms = lv_tick_get();
        if (custom_text_blink && now_ms - last_blink_ms >= 500) {
            last_blink_ms = now_ms;
            custom_text_visible = !custom_text_visible;
            if (custom_text_visible) {
                lv_obj_clear_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN);
            } else {
                lv_obj_add_flag(custom_text_label, LV_OBJ_FLAG_HIDDEN);
            }
        }

        lv_timer_handler();
        usleep(10000);
    }
}
