/**
 * @file SnakeUi.c
 * @brief LVGL widget implementation for the Snake game
 */
#include "SnakeUi.h"
#include "SnakeLogic.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <tt_lvgl_keyboard.h>

// Forward declarations
static void game_play_event(lv_event_t* e);
static void snake_timer_cb(lv_timer_t* timer);
static void delete_event(lv_event_t* e);
static void reenter_key_mode_event(lv_event_t* e);
static void snake_draw(snake_game_t* game);
static void snake_create_segment_objects(snake_game_t* game);
static void snake_delete_segment_objects(snake_game_t* game);

// Static flag to ensure srand is only called once
static bool srand_initialized = false;

/**
 * @brief Free all resources for the snake game object
 */
static void delete_event(lv_event_t* e) {
    lv_obj_t* obj = lv_event_get_target_obj(e);
    snake_game_t* game = (snake_game_t*)lv_obj_get_user_data(obj);

    if (game) {
        // Stop timer first
        if (game->timer) {
            lv_timer_delete(game->timer);
            game->timer = NULL;
        }

        // Restore edit mode and remove from group before cleanup
        if (tt_lvgl_hardware_keyboard_is_available()) {
            lv_group_t* group = lv_group_get_default();
            if (group) lv_group_set_editing(group, false);
            lv_group_remove_obj(game->container);
        }

        // Delete LVGL objects for snake segments
        snake_delete_segment_objects(game);

        // Free snake body linked list
        snake_free_body(game->head);
        game->head = NULL;

        // Food object is a child of container, will be deleted automatically
        // Container is a child of obj, will be deleted automatically

        lv_free(game);
        lv_obj_set_user_data(obj, NULL);
    }
}

/**
 * @brief Re-enter key mode when the game area is clicked after Q/Esc exit
 */
static void reenter_key_mode_event(lv_event_t* e) {
    lv_obj_t* container = lv_event_get_current_target_obj(e);
    lv_group_t* group = lv_group_get_default();
    if (!group) return;
    if (lv_obj_get_group(container) == NULL) {
        lv_group_add_obj(group, container);
    }
    lv_group_focus_obj(container);
    lv_group_set_editing(group, true);
}

/**
 * @brief Delete LVGL objects for all snake segments
 */
static void snake_delete_segment_objects(snake_game_t* game) {
    if (!game || !game->head) return;

    snake_segment_t* segment = game->head;
    while (segment != NULL) {
        if (segment->obj) {
            lv_obj_delete(segment->obj);
            segment->obj = NULL;
        }
        segment = segment->next;
    }
}

/**
 * @brief Create LVGL objects for all snake segments
 */
static void snake_create_segment_objects(snake_game_t* game) {
    if (!game || !game->head || !game->container) return;

    snake_segment_t* segment = game->head;
    bool is_head = true;

    while (segment != NULL) {
        segment->obj = lv_obj_create(game->container);
        lv_obj_set_size(segment->obj, game->cell_size - 2, game->cell_size - 2);
        lv_obj_set_style_radius(segment->obj, SNAKE_CELL_RADIUS, LV_PART_MAIN);
        lv_obj_set_style_border_width(segment->obj, 0, LV_PART_MAIN);

        if (is_head) {
            lv_obj_set_style_bg_color(segment->obj, SNAKE_HEAD_COLOR, LV_PART_MAIN);
            is_head = false;
        } else {
            lv_obj_set_style_bg_color(segment->obj, SNAKE_BODY_COLOR, LV_PART_MAIN);
        }

        // Position will be set by snake_draw()
        segment = segment->next;
    }
}

/**
 * @brief Update LVGL object positions based on game state
 */
static void snake_draw(snake_game_t* game) {
    if (!game || !game->container) return;

    // Update snake segment positions
    snake_segment_t* segment = game->head;
    while (segment != NULL) {
        if (segment->obj) {
            lv_coord_t px = segment->x * game->cell_size + 1;
            lv_coord_t py = segment->y * game->cell_size + 1;
            lv_obj_set_pos(segment->obj, px, py);
        }
        segment = segment->next;
    }

    // Update food position
    if (game->food) {
        lv_coord_t fx = game->food_x * game->cell_size + 1;
        lv_coord_t fy = game->food_y * game->cell_size + 1;
        lv_obj_set_pos(game->food, fx, fy);
    }
}

/**
 * @brief Timer callback - moves snake and updates display
 */
static void snake_timer_cb(lv_timer_t* timer) {
    snake_game_t* game = (snake_game_t*)lv_timer_get_user_data(timer);
    if (!game || game->game_over || game->paused) return;

    // Move snake
    bool move_ok = snake_move(game);

    if (!move_ok) {
        // Collision occurred, game over
        game->game_over = true;
        lv_obj_send_event(game->widget, LV_EVENT_VALUE_CHANGED, NULL);
        return;
    }

    // Check food collision
    if (snake_check_food_collision(game)) {
        // Grow snake and update score
        if (!snake_grow(game->head)) {
            // Memory allocation failed - treat as game over
            game->game_over = true;
            lv_obj_send_event(game->widget, LV_EVENT_VALUE_CHANGED, NULL);
            return;
        }
        game->score++;
        game->length++;

        // Create LVGL object for new segment
        snake_segment_t* tail = game->head;
        while (tail->next != NULL) {
            tail = tail->next;
        }
        if (tail && !tail->obj && game->container) {
            tail->obj = lv_obj_create(game->container);
            if (!tail->obj) {
                game->game_over = true;
                lv_obj_send_event(game->widget, LV_EVENT_VALUE_CHANGED, NULL);
                return;
            }
            lv_obj_set_size(tail->obj, game->cell_size - 2, game->cell_size - 2);
            lv_obj_set_style_radius(tail->obj, SNAKE_CELL_RADIUS, LV_PART_MAIN);
            lv_obj_set_style_border_width(tail->obj, 0, LV_PART_MAIN);
            lv_obj_set_style_bg_color(tail->obj, SNAKE_BODY_COLOR, LV_PART_MAIN);
        }

        // Spawn new food - if fails, grid is full (win!)
        if (!snake_spawn_food(game)) {
            // Hide food since there's no valid position
            if (game->food) {
                lv_obj_add_flag(game->food, LV_OBJ_FLAG_HIDDEN);
            }
            // Player won - end the game
            game->game_over = true;
            lv_obj_send_event(game->widget, LV_EVENT_VALUE_CHANGED, NULL);
            return;
        }

        // Increase speed (decrease timer period) as snake grows
        uint32_t foods_eaten = game->length - SNAKE_INITIAL_LENGTH;
        uint32_t speed_reduction = foods_eaten * SNAKE_SPEED_DECREASE_MS;
        uint32_t new_period = SNAKE_GAME_SPEED_MS;
        if (speed_reduction < (SNAKE_GAME_SPEED_MS - SNAKE_MIN_SPEED_MS)) {
            new_period = SNAKE_GAME_SPEED_MS - speed_reduction;
        } else {
            new_period = SNAKE_MIN_SPEED_MS;
        }
        lv_timer_set_period(game->timer, new_period);

        // Notify score change
        lv_obj_send_event(game->widget, LV_EVENT_VALUE_CHANGED, NULL);
    }

    // Update display
    snake_draw(game);
}

/**
 * @brief Event callback for game play (gesture/key)
 */
static void game_play_event(lv_event_t* e) {
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t* obj = (lv_obj_t*)lv_event_get_user_data(e);
    snake_game_t* game = (snake_game_t*)lv_obj_get_user_data(obj);

    if (!game || game->game_over) return;

    if (code == LV_EVENT_GESTURE) {
        lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_active());
        switch (dir) {
            case LV_DIR_TOP:
                snake_set_direction(game, SNAKE_DIR_UP);
                break;
            case LV_DIR_BOTTOM:
                snake_set_direction(game, SNAKE_DIR_DOWN);
                break;
            case LV_DIR_LEFT:
                snake_set_direction(game, SNAKE_DIR_LEFT);
                break;
            case LV_DIR_RIGHT:
                snake_set_direction(game, SNAKE_DIR_RIGHT);
                break;
            default:
                break;
        }
    } else if (code == LV_EVENT_CLICKED) {
        lv_indev_t* indev = lv_indev_active();
        if (indev) {
            lv_point_t point;
            lv_indev_get_point(indev, &point);

            lv_area_t coords;
            lv_obj_get_coords(game->container, &coords);

            lv_coord_t w = coords.x2 - coords.x1 + 1;
            lv_coord_t h = coords.y2 - coords.y1 + 1;
            lv_coord_t cx = coords.x1 + w / 2;
            lv_coord_t cy = coords.y1 + h / 2;

            lv_coord_t rx = point.x - cx;
            lv_coord_t ry = point.y - cy;

            if (rx != 0 || ry != 0) {
                if (abs(rx) * h > abs(ry) * w) {
                    // Horizontal touch
                    if (rx > 0) {
                        snake_set_direction(game, SNAKE_DIR_RIGHT);
                    } else {
                        snake_set_direction(game, SNAKE_DIR_LEFT);
                    }
                } else {
                    // Vertical touch
                    if (ry > 0) {
                        snake_set_direction(game, SNAKE_DIR_DOWN);
                    } else {
                        snake_set_direction(game, SNAKE_DIR_UP);
                    }
                }
            }
        }
    } else if (code == LV_EVENT_KEY) {
        uint32_t key = lv_event_get_key(e);
        // Arrow keys, WASD, and punctuation keys for cardputer
        switch (key) {
            case LV_KEY_UP:
            case 'w':
            case 'W':
            case ';':
                snake_set_direction(game, SNAKE_DIR_UP);
                break;
            case LV_KEY_DOWN:
            case 's':
            case 'S':
            case '.':
                snake_set_direction(game, SNAKE_DIR_DOWN);
                break;
            case LV_KEY_LEFT:
            case 'a':
            case 'A':
            case ',':
                snake_set_direction(game, SNAKE_DIR_LEFT);
                break;
            case LV_KEY_RIGHT:
            case 'd':
            case 'D':
            case '/':
                snake_set_direction(game, SNAKE_DIR_RIGHT);
                break;
            case LV_KEY_ESC:
            case 'q':
            case 'Q': {
                // Exit key/edit mode - remove from group so navigation is restored
                // Re-entry: tap/click the game area to return to key mode
                lv_group_t* grp = lv_group_get_default();
                if (grp) lv_group_set_editing(grp, false);
                lv_group_remove_obj(lv_event_get_current_target_obj(e));
                break;
            }
            default:
                break;
        }
    }
}

/**
 * @brief Create a new Snake game widget
 */
lv_obj_t* snake_create(lv_obj_t* parent, uint16_t cell_size, bool wall_collision) {
    // Create main object
    lv_obj_t* obj = lv_obj_create(parent);
    if (!obj) return NULL;

    // Allocate game state
    snake_game_t* game = (snake_game_t*)lv_malloc(sizeof(snake_game_t));
    if (!game) {
        lv_obj_delete(obj);
        return NULL;
    }
    memset(game, 0, sizeof(snake_game_t));
    lv_obj_set_user_data(obj, game);

    // Store widget reference for events
    game->widget = obj;

    // Initialize game state
    game->cell_size = cell_size;
    game->score = 0;
    game->length = SNAKE_INITIAL_LENGTH;
    game->direction = SNAKE_DIR_RIGHT;
    game->next_direction = SNAKE_DIR_RIGHT;
    game->game_over = false;
    game->paused = false;
    game->wall_collision_enabled = wall_collision;

    // Set up main object
    lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));
    lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_set_style_pad_all(obj, 0, LV_PART_MAIN);
    lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN);
    lv_obj_set_style_bg_opa(obj, LV_OPA_TRANSP, LV_PART_MAIN);

    // Create game container (the actual playing field)
    game->container = lv_obj_create(obj);
    lv_obj_remove_flag(game->container, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_set_style_bg_color(game->container, SNAKE_BG_COLOR, LV_PART_MAIN);
    lv_obj_set_style_border_width(game->container, 1, LV_PART_MAIN);
    lv_obj_set_style_border_color(game->container, SNAKE_GRID_COLOR, LV_PART_MAIN);
    lv_obj_set_style_pad_all(game->container, 0, LV_PART_MAIN);
    lv_group_remove_obj(game->container);
    lv_obj_remove_flag(game->container, LV_OBJ_FLAG_GESTURE_BUBBLE);

    // Calculate grid dimensions based on available space (non-square)
    lv_coord_t available_w = lv_obj_get_content_width(parent) - 6;
    lv_coord_t available_h = lv_obj_get_content_height(parent) - 6;

    if (available_w < 50) available_w = 50;
    if (available_h < 50) available_h = 50;

    // Calculate how many cells fit in each dimension
    game->grid_width = available_w / cell_size;
    game->grid_height = available_h / cell_size;

    // Ensure minimum grid size (3x3 minimum for playable game)
    if (game->grid_width < 3) game->grid_width = 3;
    if (game->grid_height < 3) game->grid_height = 3;

    // Calculate actual field size
    lv_coord_t field_w = game->cell_size * game->grid_width;
    lv_coord_t field_h = game->cell_size * game->grid_height;

    lv_obj_set_size(game->container, field_w, field_h);
    lv_obj_center(game->container);

    // Initialize snake body at center
    int16_t start_x = game->grid_width / 2;
    int16_t start_y = game->grid_height / 2;
    game->head = snake_init_body(SNAKE_INITIAL_LENGTH, start_x, start_y);
    if (!game->head) {
        lv_obj_set_user_data(obj, NULL);
        lv_free(game);
        lv_obj_delete(obj);
        return NULL;
    }

    // Create LVGL objects for snake segments
    snake_create_segment_objects(game);

    // Create food object
    game->food = lv_obj_create(game->container);
    lv_obj_set_size(game->food, game->cell_size - 2, game->cell_size - 2);
    lv_obj_set_style_radius(game->food, game->cell_size / 2, LV_PART_MAIN);
    lv_obj_set_style_border_width(game->food, 0, LV_PART_MAIN);
    lv_obj_set_style_bg_color(game->food, SNAKE_FOOD_COLOR, LV_PART_MAIN);

    // Initialize random seed only once
    if (!srand_initialized) {
        srand((unsigned int)time(NULL));
        srand_initialized = true;
    }

    // Spawn initial food
    snake_spawn_food(game);

    // Initial draw
    snake_draw(game);

    // Add event callbacks for touch gestures and keyboard
    lv_obj_add_event_cb(game->container, game_play_event, LV_EVENT_GESTURE, obj);
    lv_obj_add_event_cb(game->container, game_play_event, LV_EVENT_KEY, obj);
    lv_obj_add_event_cb(game->container, game_play_event, LV_EVENT_CLICKED, obj);
    lv_obj_add_event_cb(obj, delete_event, LV_EVENT_DELETE, NULL);

    // Set up keyboard focus if available
    if (tt_lvgl_hardware_keyboard_is_available()) {
        lv_group_t* group = lv_group_get_default();
        if (group) {
            lv_group_add_obj(group, game->container);
            lv_obj_add_event_cb(game->container, reenter_key_mode_event, LV_EVENT_CLICKED, NULL);
            lv_group_focus_obj(game->container);
            lv_group_set_editing(group, true);
        }
    }

    // Start game timer
    game->timer = lv_timer_create(snake_timer_cb, SNAKE_GAME_SPEED_MS, game);

    return obj;
}

/**
 * @brief Reset the game to start a new game
 */
void snake_set_new_game(lv_obj_t* obj) {
    snake_game_t* game = (snake_game_t*)lv_obj_get_user_data(obj);
    if (!game) return;

    // Stop timer during reset
    if (game->timer) {
        lv_timer_pause(game->timer);
    }

    // Delete old snake segment objects
    snake_delete_segment_objects(game);

    // Free old snake body
    snake_free_body(game->head);
    game->head = NULL;

    // Reset game state
    game->score = 0;
    game->length = SNAKE_INITIAL_LENGTH;
    game->direction = SNAKE_DIR_RIGHT;
    game->next_direction = SNAKE_DIR_RIGHT;
    game->game_over = false;
    game->paused = false;

    // Create new snake body at center
    int16_t start_x = game->grid_width / 2;
    int16_t start_y = game->grid_height / 2;
    game->head = snake_init_body(SNAKE_INITIAL_LENGTH, start_x, start_y);

    if (!game->head) {
        // Memory allocation failed - keep game in over state
        game->game_over = true;
        lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL);
        return;
    }

    // Create new segment objects
    snake_create_segment_objects(game);

    // Spawn new food and unhide it
    if (snake_spawn_food(game)) {
        if (game->food) {
            lv_obj_remove_flag(game->food, LV_OBJ_FLAG_HIDDEN);
        }
    }

    // Draw
    snake_draw(game);

    // Resume timer
    if (game->timer) {
        lv_timer_resume(game->timer);
    }

    // Notify change
    lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL);
}

/**
 * @brief Get the current score
 */
uint16_t snake_get_score(lv_obj_t* obj) {
    snake_game_t* game = (snake_game_t*)lv_obj_get_user_data(obj);
    if (!game) return 0;
    return game->score;
}

/**
 * @brief Get the current snake length
 */
uint16_t snake_get_length(lv_obj_t* obj) {
    snake_game_t* game = (snake_game_t*)lv_obj_get_user_data(obj);
    if (!game) return 0;
    return game->length;
}

/**
 * @brief Check if game is over
 */
bool snake_get_game_over(lv_obj_t* obj) {
    snake_game_t* game = (snake_game_t*)lv_obj_get_user_data(obj);
    if (!game) return true;
    return game->game_over;
}
