/*  Arduino Radar Clock on CrowPanel 2.1inch-HMI ESP32 Rotary Display 480*480 by mircemk, October 2025*/

#include <Arduino.h>
#include <Arduino_GFX_Library.h>
#include <time.h>
#include <WiFi.h>

// WiFi credentials
const char* ssid = "*****";     // Replace with your WiFi SSID
const char* password = "*****";  // Replace with your WiFi password

// NTP Server settings
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 3600;    // Change according to your timezone (3600 = GMT+1)
const int   daylightOffset_sec = 3600;

/* ======== DISPLAY CONFIGURATION ======== */
#define TYPE_SEL   7      // ST7701 init table
#define PCLK_NEG   1      // 1 = falling edge
#define TIMING_SET 1      // 1 = wider safe porches

/* --- Backlight PIN --- */
#define BL_PIN 6

/* --- SPI for ST7701 init --- */
#define PANEL_CS  16
#define PANEL_SCK  2
#define PANEL_SDA  1

/* --- Rotary Encoder Pins --- */
#define ENCODER_A_PIN 42
#define ENCODER_B_PIN 44

/* --- Display Timing --- */
#if TIMING_SET == 0
  static const int HFP=20, HPW=10, HBP=10;
  static const int VFP=8, VPW=10, VBP=10;
#else
  static const int HFP=40, HPW=8,  HBP=40;
  static const int VFP=20, VPW=8,  VBP=20;
#endif

/* ======== RADAR CONFIGURATION ======== */
#define DISPLAY_WIDTH 480
#define DISPLAY_HEIGHT 480
#define CENTER_X 240
#define CENTER_Y 240
#define RADAR_RADIUS 200
#define FRAME_START 200   
#define FRAME_WIDTH 5     
#define BORDER_WIDTH 5    
#define SWEEP_SPEED 3

/* ======== COLOR SCHEMES ======== */
// Color definitions for different schemes (RGB565)
typedef struct {
  uint16_t green_color;
  uint16_t dim_green_color;
  uint16_t sweep_color;
  uint16_t grid_color;
  uint16_t frame_color;
  uint16_t text_color;
  uint16_t trail_color;
  uint16_t black;
} ColorScheme;

// Scheme 1: Classic Green (original)
const ColorScheme SCHEME_GREEN = {
  0x07E0,  // green_color
  0x03A0,  // dim_green_color
  0x07FF,  // sweep_color (cyan)
  0x03E0,  // grid_color
  0x07E0,  // frame_color
  0x07E0,  // text_color
  0x0320,  // trail_color
  0x0000   // black
};

// Scheme 2: Red
const ColorScheme SCHEME_RED = {
  0xF800,  // green_color -> red
  0x7800,  // dim_green_color -> dark red
  0xF810,  // sweep_color -> bright red
  0xF800,  // grid_color -> red
  0xF800,  // frame_color -> red
  0xF800,  // text_color -> red
  0x7800,  // trail_color -> dark red
  0x0000   // black
};

// Scheme 3: Blue (like second vector link - digital radar)
const ColorScheme SCHEME_BLUE = {
  0x001F,  // green_color -> blue
  0x0010,  // dim_green_color -> dark blue
  0x041F,  // sweep_color -> bright blue
  0x001F,  // grid_color -> blue
  0x001F,  // frame_color -> blue
  0x001F,  // text_color -> blue
  0x0010,  // trail_color -> dark blue
  0x0000   // black
};

// Scheme 4: Yellow-Orange
const ColorScheme SCHEME_YELLOW_ORANGE = {
  0xFDA0,  // green_color -> yellow-orange
  0xFB00,  // dim_green_color -> dark yellow-orange
  0xFEA0,  // sweep_color -> bright yellow-orange
  0xFDA0,  // grid_color -> yellow-orange
  0xFDA0,  // frame_color -> yellow-orange
  0xFDA0,  // text_color -> yellow-orange
  0xFB00,  // trail_color -> dark yellow-orange
  0x0000   // black
};

// Scheme 5: White
const ColorScheme SCHEME_WHITE = {
  0xFFFF,  // green_color -> white
  0xDEFB,  // dim_green_color -> light gray
  0xFFFF,  // sweep_color -> white
  0xFFFF,  // grid_color -> white
  0xFFFF,  // frame_color -> white
  0xFFFF,  // text_color -> white
  0xBDF7,  // trail_color -> medium gray
  0x0000   // black
};

const ColorScheme* colorSchemes[] = {
  &SCHEME_GREEN,
  &SCHEME_RED, 
  &SCHEME_BLUE,
  &SCHEME_YELLOW_ORANGE,
  &SCHEME_WHITE
};

#define NUM_COLOR_SCHEMES 5

/* ======== GLOBAL VARIABLES ======== */
Arduino_DataBus *panelBus = nullptr;
Arduino_ESP32RGBPanel *rgbpanel = nullptr;
Arduino_RGB_Display *gfx = nullptr;

static float current_angle = 0;
static uint16_t *frameBuffer = nullptr;
static uint16_t *staticFrameBuffer = nullptr;

// Color scheme management
int currentColorScheme = 0;
bool colorSchemeChanged = true;

// Rotary encoder variables
volatile int encoderPos = 0;
int lastEncoderPos = 0;
portMUX_TYPE encoderMux = portMUX_INITIALIZER_UNLOCKED;

// Startup sequence state
enum StartupState {
  SHOW_TITLE,
  SHOW_CONNECTING,
  SHOW_CLOCK
};
StartupState currentStartupState = SHOW_TITLE;
unsigned long startupStartTime = 0;

// WiFi connection state
bool wifiConnecting = false;
bool wifiConnected = false;
unsigned long lastWiFiCheck = 0;
const unsigned long WIFI_CHECK_INTERVAL = 1000;

// Display stability
bool displayStable = false;

// Encoder interrupt service routine
void IRAM_ATTR encoderISR() {
  portENTER_CRITICAL_ISR(&encoderMux);
  
  static uint8_t old_AB = 0;
  // Grey code
  // 0b00: 0
  // 0b01: 1
  // 0b11: 2
  // 0b10: 3
  const int8_t enc_states[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
  
  old_AB <<= 2; // Remember previous state
  old_AB |= (digitalRead(ENCODER_A_PIN) ? (1 << 1) : 0) | (digitalRead(ENCODER_B_PIN) ? (1 << 0) : 0);
  
  encoderPos += enc_states[(old_AB & 0x0f)];
  
  portEXIT_CRITICAL_ISR(&encoderMux);
}

void handleEncoder() {
  portENTER_CRITICAL(&encoderMux);
  int currentPos = encoderPos;
  portEXIT_CRITICAL(&encoderMux);
  
  if (currentPos != lastEncoderPos && currentStartupState == SHOW_CLOCK) {
    if (currentPos > lastEncoderPos) {
      // Clockwise rotation - next color scheme
      currentColorScheme = (currentColorScheme + 1) % NUM_COLOR_SCHEMES;
    } else {
      // Counter-clockwise rotation - previous color scheme
      currentColorScheme = (currentColorScheme - 1 + NUM_COLOR_SCHEMES) % NUM_COLOR_SCHEMES;
    }
    
    colorSchemeChanged = true;
    Serial.printf("Color scheme changed to: %d\n", currentColorScheme + 1);
    
    lastEncoderPos = currentPos;
  }
}

/* ======== STARTUP SCREEN FUNCTIONS ======== */
void showTitleScreen() {
  // Use direct display drawing for maximum stability
  gfx->fillScreen(SCHEME_GREEN.black);
  
  // Draw "RADAR CLOCK" - larger text
  gfx->setTextColor(SCHEME_GREEN.text_color);
  gfx->setTextSize(4);
  
  // Calculate position for "RADAR CLOCK"
  String radarText = "RADAR CLOCK";
  int16_t x1, y1;
  uint16_t w, h;
  gfx->getTextBounds(radarText, 0, 0, &x1, &y1, &w, &h);
  int radarX = (DISPLAY_WIDTH - w) / 2;
  int radarY = CENTER_Y - 60;
  
  gfx->setCursor(radarX, radarY);
  gfx->print(radarText);
  
  // Draw "by" - smaller text
  gfx->setTextSize(2);
  String byText = "by";
  gfx->getTextBounds(byText, 0, 0, &x1, &y1, &w, &h);
  int byX = (DISPLAY_WIDTH - w) / 2;
  int byY = CENTER_Y;
  
  gfx->setCursor(byX, byY);
  gfx->print(byText);
  
  // Draw "Mircemk" - medium text
  gfx->setTextSize(3);
  String nameText = "Mircemk";
  gfx->getTextBounds(nameText, 0, 0, &x1, &y1, &w, &h);
  int nameX = (DISPLAY_WIDTH - w) / 2;
  int nameY = CENTER_Y + 40;
  
  gfx->setCursor(nameX, nameY);
  gfx->print(nameText);
}

void showConnectingScreen() {
  // Use direct display drawing for stability during WiFi connection
  gfx->fillScreen(SCHEME_GREEN.black);
  
  // Draw "connecting..." text
  gfx->setTextColor(SCHEME_GREEN.text_color);
  gfx->setTextSize(3);
  
  String connectingText = "connecting...";
  int16_t x1, y1;
  uint16_t w, h;
  gfx->getTextBounds(connectingText, 0, 0, &x1, &y1, &w, &h);
  int textX = (DISPLAY_WIDTH - w) / 2;
  int textY = CENTER_Y;
  
  gfx->setCursor(textX, textY);
  gfx->print(connectingText);
  
  // Draw the display immediately using the frame buffer method for stability
  if (frameBuffer) {
    gfx->draw16bitRGBBitmap(0, 0, frameBuffer, DISPLAY_WIDTH, DISPLAY_HEIGHT);
  }
}

void updateConnectingAnimation() {
  static unsigned long lastDotUpdate = 0;
  static bool dotVisible = false;
  
  unsigned long currentTime = millis();
  if (currentTime - lastDotUpdate >= 500) { // Blink every 500ms
    lastDotUpdate = currentTime;
    dotVisible = !dotVisible;
    
    // Update dot without redrawing entire screen
    if (dotVisible) {
      gfx->fillRect(DISPLAY_WIDTH - 30, CENTER_Y + 40, 10, 10, SCHEME_GREEN.text_color);
    } else {
      gfx->fillRect(DISPLAY_WIDTH - 30, CENTER_Y + 40, 10, 10, SCHEME_GREEN.black);
    }
    
    // Update only the changed area
    gfx->draw16bitRGBBitmap(DISPLAY_WIDTH - 30, CENTER_Y + 40, 
                           &frameBuffer[CENTER_Y * DISPLAY_WIDTH + (DISPLAY_WIDTH - 30)], 
                           10, 10);
  }
}

bool connectToWiFi() {
  Serial.print("Connecting to WiFi");
  WiFi.begin(ssid, password);
  
  unsigned long startTime = millis();
  const unsigned long timeout = 30000; // 30 seconds timeout
  
  while (WiFi.status() != WL_CONNECTED && millis() - startTime < timeout) {
    delay(500);
    Serial.print(".");
    
    // Update connecting animation
    updateConnectingAnimation();
  }
  
  Serial.println();
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("WiFi connected!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    return true;
  } else {
    Serial.println("WiFi connection failed!");
    return false;
  }
}

void updateStartupSequence() {
  unsigned long currentTime = millis();
  unsigned long elapsedTime = currentTime - startupStartTime;
  
  switch(currentStartupState) {
    case SHOW_TITLE:
      if (elapsedTime >= 2000) { // Show title for 2 seconds
        currentStartupState = SHOW_CONNECTING;
        startupStartTime = currentTime;
        showConnectingScreen();
        Serial.println("Showing connecting screen...");
        
        // Start WiFi connection
        wifiConnecting = true;
        WiFi.begin(ssid, password);
      }
      break;
      
    case SHOW_CONNECTING:
      // Update connecting animation
      updateConnectingAnimation();
      
      // Check WiFi status periodically
      if (currentTime - lastWiFiCheck >= WIFI_CHECK_INTERVAL) {
        lastWiFiCheck = currentTime;
        
        if (WiFi.status() == WL_CONNECTED) {
          wifiConnecting = false;
          wifiConnected = true;
          currentStartupState = SHOW_CLOCK;
          startupStartTime = currentTime;
          Serial.println("WiFi connected! Showing clock...");
          
          // Initialize the clock display
          initClockDisplay();
          displayStable = true;
        }
      }
      break;
      
    case SHOW_CLOCK:
      // Clock is now running in main loop
      break;
  }
}

/* ======== DRAWING FUNCTIONS ======== */
void draw_line_to_buffer(uint16_t *buffer, int x0, int y0, int x1, int y1, uint16_t color) {
    int dx = abs(x1 - x0);
    int dy = abs(y1 - y0);
    int sx = (x0 < x1) ? 1 : -1;
    int sy = (y0 < y1) ? 1 : -1;
    int err = dx - dy;

    while (true) {
        if (x0 >= 0 && x0 < DISPLAY_WIDTH && y0 >= 0 && y0 < DISPLAY_HEIGHT) {
            buffer[y0 * DISPLAY_WIDTH + x0] = color;
        }

        if (x0 == x1 && y0 == y1) break;
        
        int e2 = 2 * err;
        if (e2 > -dy) {
            err -= dy;
            x0 += sx;
        }
        if (e2 < dx) {
            err += dx;
            y0 += sy;
        }
    }
}

void draw_rectangle_to_buffer(uint16_t *buffer, int x1, int y1, int x2, int y2, uint16_t color) {
    for (int y = y1; y <= y2; y++) {
        for (int x = x1; x <= x2; x++) {
            if (x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) {
                buffer[y * DISPLAY_WIDTH + x] = color;
            }
        }
    }
}

void draw_digit_to_buffer(uint16_t *buffer, int x, int y, int digit, uint16_t color) {
    // Increased sizes (3x original)
    int width = 18;  // Was 6
    int height = 24; // Was 8
    
    // Clear background for larger digit
    draw_rectangle_to_buffer(buffer, x-9, y-12, x+9, y+12, colorSchemes[currentColorScheme]->black);
    
    switch(digit) {
        case 0:
            draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top
            draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom
            draw_line_to_buffer(buffer, x-6, y-9, x-6, y+9, color); // left
            draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right
            break;
        case 1:
            draw_line_to_buffer(buffer, x, y-9, x, y+9, color); // vertical
            draw_line_to_buffer(buffer, x-3, y-9, x, y-9, color); // top
            break;
        case 2:
            draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top
            draw_line_to_buffer(buffer, x+6, y-9, x+6, y, color); // right top
            draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle
            draw_line_to_buffer(buffer, x-6, y, x-6, y+9, color); // left bottom
            draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom
            break;
        case 3:
            draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top
            draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right
            draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle
            draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom
            break;
        case 4:
            draw_line_to_buffer(buffer, x-6, y-9, x-6, y, color); // left top
            draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle
            draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right
            break;
        case 5:
            draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top
            draw_line_to_buffer(buffer, x-6, y-9, x-6, y, color); // left top
            draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle
            draw_line_to_buffer(buffer, x+6, y, x+6, y+9, color); // right bottom
            draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom
            break;
        case 6:
            draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top
            draw_line_to_buffer(buffer, x-6, y-9, x-6, y+9, color); // left
            draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle
            draw_line_to_buffer(buffer, x+6, y, x+6, y+9, color); // right bottom
            draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom
            break;
        case 7:
            draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top
            draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right
            break;
        case 8:
            draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top
            draw_line_to_buffer(buffer, x-6, y-9, x-6, y+9, color); // left
            draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right
            draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle
            draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom
            break;
        case 9:
            draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top
            draw_line_to_buffer(buffer, x-6, y-9, x-6, y, color); // left top
            draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right
            draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle
            draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom
            break;
    }
}

void draw_text_to_buffer(uint16_t *buffer, int x, int y, const char* text, uint16_t color) {
    int char_width = 24;  // Increased from 8 to 24 for larger spacing
    int pos_x = x;
    
    while (*text) {
        char c = *text++;
        
        if (c >= '0' && c <= '9') {
            draw_digit_to_buffer(buffer, pos_x, y, c - '0', color);
        }
        else if (c == ':') {
            // Draw larger colon
            buffer[(y - 6) * DISPLAY_WIDTH + pos_x] = color;
            buffer[(y - 5) * DISPLAY_WIDTH + pos_x] = color;
            buffer[(y + 5) * DISPLAY_WIDTH + pos_x] = color;
            buffer[(y + 6) * DISPLAY_WIDTH + pos_x] = color;
        }
        else if (c == '/') {
            // Draw larger slash
            for (int i = -9; i <= 9; i++) {
                int px = pos_x + i;
                int py = y - i;
                if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) {
                    buffer[py * DISPLAY_WIDTH + px] = color;
                }
            }
        }
        pos_x += char_width;
    }
}

void update_time_display() {
    struct tm timeinfo;
    if(!getLocalTime(&timeinfo)) {
        Serial.println("Failed to obtain time");
        return;
    }
    
    // Calculate positions
    // Font height is 24, so three heights = 72 pixels
    int time_y = CENTER_Y - 72;  // Move up by three font heights
    int date_y = CENTER_Y + 72;  // Move down by three font heights
    int time_x = CENTER_X - 90;  // Keep the same horizontal position
    int date_x = CENTER_X - 105; // Keep the same horizontal position
    
    // Clear previous time area - precise clearing
    // Height of clearing = font height (24) + 2 pixels margin
    // Width of clearing = 8 digits * 24 pixels width + 4 pixels margin
    for (int y = time_y - 13; y < time_y + 13; y++) {
        for (int x = time_x - 2; x < time_x + (8 * 24) + 2; x++) {
            if (x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) {
                frameBuffer[y * DISPLAY_WIDTH + x] = colorSchemes[currentColorScheme]->black;
            }
        }
    }
    
    // Format time string
    char timeStr[9];
    sprintf(timeStr, "%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
    
    // Draw time
    draw_text_to_buffer(frameBuffer, time_x, time_y, timeStr, colorSchemes[currentColorScheme]->text_color);
    
    // Clear previous date area - precise clearing
    // Height of clearing = font height (24) + 2 pixels margin
    // Width of clearing = 10 digits * 24 pixels width + 4 pixels margin
    for (int y = date_y - 13; y < date_y + 13; y++) {
        for (int x = date_x - 2; x < date_x + (10 * 24) + 2; x++) {
            if (x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) {
                frameBuffer[y * DISPLAY_WIDTH + x] = colorSchemes[currentColorScheme]->black;
            }
        }
    }
    
    // Format date string
    char dateStr[11];
    sprintf(dateStr, "%02d/%02d/%04d", timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900);
    
    // Draw date
    draw_text_to_buffer(frameBuffer, date_x, date_y, dateStr, colorSchemes[currentColorScheme]->text_color);
}

void redrawStaticElements() {
    // Clear static frame buffer
    for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) {
        staticFrameBuffer[i] = colorSchemes[currentColorScheme]->black;
    }
    
    // Redraw all static elements with new colors
    draw_frame_border();
    draw_radar_grid();
    
    // Copy to main frame buffer
    for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) {
        frameBuffer[i] = staticFrameBuffer[i];
    }
    
    colorSchemeChanged = false;
    Serial.println("Static elements redrawn with new color scheme");
}

/* ======== DISPLAY INITIALIZATION ======== */
void init_display() {
    Serial.println("Initializing display...");
    
    pinMode(BL_PIN, OUTPUT);
    digitalWrite(BL_PIN, HIGH);
    delay(100);

    // Initialize rotary encoder pins
    pinMode(ENCODER_A_PIN, INPUT_PULLUP);
    pinMode(ENCODER_B_PIN, INPUT_PULLUP);
    
    // Attach interrupts for rotary encoder
    attachInterrupt(digitalPinToInterrupt(ENCODER_A_PIN), encoderISR, CHANGE);
    attachInterrupt(digitalPinToInterrupt(ENCODER_B_PIN), encoderISR, CHANGE);

    panelBus = new Arduino_SWSPI(
        GFX_NOT_DEFINED, PANEL_CS, PANEL_SCK, PANEL_SDA, GFX_NOT_DEFINED
    );

    rgbpanel = new Arduino_ESP32RGBPanel(
        40, 7, 15, 41,
        46, 3, 8, 18, 17,
        14, 13, 12, 11, 10, 9,
        5, 45, 48, 47, 21,
        1, 50, 10, 50,
        1, 30, 10, 30,
        PCLK_NEG, 8000000UL
    );

#if TYPE_SEL == 7
    gfx = new Arduino_RGB_Display(
        DISPLAY_WIDTH, DISPLAY_HEIGHT, rgbpanel, 0, true,
        panelBus, GFX_NOT_DEFINED,
        st7701_type7_init_operations, sizeof(st7701_type7_init_operations)
    );
#else
    gfx = new Arduino_RGB_Display(
        DISPLAY_WIDTH, DISPLAY_HEIGHT, rgbpanel, 0, true,
        panelBus, GFX_NOT_DEFINED,
        st7701_type5_init_operations, sizeof(st7701_type5_init_operations)
    );
#endif

    Serial.println("Starting display begin...");
    bool ok = gfx->begin(16000000);
    Serial.printf("Display begin: %s\n", ok ? "OK" : "FAILED");
    
    if (!ok) {
        Serial.println("Display initialization failed!");
        while(1) delay(1000);
    }
    
    // Allocate main frame buffer with extra margin for safety
    frameBuffer = (uint16_t*)ps_malloc(DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t) + 32);
    if (!frameBuffer) {
        Serial.println("Frame buffer allocation failed!");
        while(1) delay(1000);
    }
    
    // Allocate static frame buffer for unchanging elements with extra margin
    staticFrameBuffer = (uint16_t*)ps_malloc(DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t) + 32);
    if (!staticFrameBuffer) {
        Serial.println("Static frame buffer allocation failed!");
        while(1) delay(1000);
    }
    
    // Clear both buffers to black
    for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) {
        frameBuffer[i] = colorSchemes[currentColorScheme]->black;
        staticFrameBuffer[i] = colorSchemes[currentColorScheme]->black;
    }
    
    Serial.println("Display initialized successfully");
}

void initClockDisplay() {
    // Draw static elements to static buffer
    draw_frame_border();
    draw_radar_grid();
    
    // Copy static elements to main frame buffer
    for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) {
        frameBuffer[i] = staticFrameBuffer[i];
    }
    
    // Init and get the time
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
    
    Serial.println("Clock display initialized");
}

void draw_circle_to_buffer(uint16_t *buffer, int center_x, int center_y, int radius, uint16_t color) {
    int x = radius;
    int y = 0;
    int err = 0;

    while (x >= y) {
        buffer[(center_y + y) * DISPLAY_WIDTH + (center_x + x)] = color;
        buffer[(center_y + x) * DISPLAY_WIDTH + (center_x + y)] = color;
        buffer[(center_y + x) * DISPLAY_WIDTH + (center_x - y)] = color;
        buffer[(center_y + y) * DISPLAY_WIDTH + (center_x - x)] = color;
        buffer[(center_y - y) * DISPLAY_WIDTH + (center_x + x)] = color;
        buffer[(center_y - x) * DISPLAY_WIDTH + (center_x + y)] = color;
        buffer[(center_y - x) * DISPLAY_WIDTH + (center_x - y)] = color;
        buffer[(center_y - y) * DISPLAY_WIDTH + (center_x - x)] = color;

        y++;
        err += 1 + 2 * y;
        if (2 * (err - x) + 1 > 0) {
            x--;
            err += 1 - 2 * x;
        }
    }
}

void draw_arc_to_buffer(uint16_t *buffer, int center_x, int center_y, int radius, int start_angle, int end_angle, uint16_t color) {
    // Convert angles to radians
    float start_rad = start_angle * PI / 180.0;
    float end_rad = end_angle * PI / 180.0;
    
    // Draw arc by stepping through angles
    for (float angle = start_rad; angle <= end_rad; angle += 0.01) {
        int x = center_x + radius * cos(angle);
        int y = center_y + radius * sin(angle);
        if (x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) {
            buffer[y * DISPLAY_WIDTH + x] = color;
        }
    }
}

void draw_radial_scale_marks() {
    for (int angle = 0; angle < 360; angle += 10) {
        if ((angle >= 355 || angle <= 5) ||
            (angle >= 85 && angle <= 95) ||
            (angle >= 175 && angle <= 185) ||
            (angle >= 265 && angle <= 275)) {
            continue;
        }
        
        float rad = angle * PI / 180.0;
        int inner_x = CENTER_X + (FRAME_START + FRAME_WIDTH) * sin(rad);
        int inner_y = CENTER_Y - (FRAME_START + FRAME_WIDTH) * cos(rad);
        int outer_x = CENTER_X + (FRAME_START + FRAME_WIDTH + 12) * sin(rad);
        int outer_y = CENTER_Y - (FRAME_START + FRAME_WIDTH + 12) * cos(rad);
        
        draw_line_to_buffer(staticFrameBuffer, inner_x, inner_y, outer_x, outer_y, colorSchemes[currentColorScheme]->frame_color);
    }
    
    for (int angle = 0; angle < 360; angle += 30) {
        if (angle % 90 == 0) continue;
        if ((angle >= 355 || angle <= 5) ||
            (angle >= 85 && angle <= 95) ||
            (angle >= 175 && angle <= 185) ||
            (angle >= 265 && angle <= 275)) {
            continue;
        }
        
        float rad = angle * PI / 180.0;
        int inner_x = CENTER_X + (FRAME_START + FRAME_WIDTH) * sin(rad);
        int inner_y = CENTER_Y - (FRAME_START + FRAME_WIDTH) * cos(rad);
        int outer_x = CENTER_X + (FRAME_START + FRAME_WIDTH + 20) * sin(rad);
        int outer_y = CENTER_Y - (FRAME_START + FRAME_WIDTH + 20) * cos(rad);
        
        draw_line_to_buffer(staticFrameBuffer, inner_x, inner_y, outer_x, outer_y, colorSchemes[currentColorScheme]->frame_color);
    }
}

void draw_cardinal_directions() {
    const int gap_size = 10;
    
    // North
    int north_x = CENTER_X;
    int north_y = CENTER_Y - FRAME_START - FRAME_WIDTH - 20;
    for (int dx = -12; dx <= 12; dx++) {
        for (int dy = -12; dy <= 12; dy++) {
            int px = north_x + dx;
            int py = north_y + dy;
            if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) {
                staticFrameBuffer[py * DISPLAY_WIDTH + px] = colorSchemes[currentColorScheme]->black;
            }
        }
    }
    draw_line_to_buffer(staticFrameBuffer, north_x - 8, north_y + 8, north_x - 8, north_y - 8, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, north_x + 8, north_y + 8, north_x + 8, north_y - 8, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, north_x - 8, north_y - 8, north_x + 8, north_y + 8, colorSchemes[currentColorScheme]->text_color);
    
    // South
    int south_x = CENTER_X;
    int south_y = CENTER_Y + FRAME_START + FRAME_WIDTH + 20;
    for (int dx = -12; dx <= 12; dx++) {
        for (int dy = -12; dy <= 12; dy++) {
            int px = south_x + dx;
            int py = south_y + dy;
            if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) {
                staticFrameBuffer[py * DISPLAY_WIDTH + px] = colorSchemes[currentColorScheme]->black;
            }
        }
    }
    draw_line_to_buffer(staticFrameBuffer, south_x - 8, south_y - 8, south_x + 8, south_y - 8, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, south_x - 8, south_y - 8, south_x - 8, south_y, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, south_x - 8, south_y, south_x + 8, south_y, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, south_x + 8, south_y, south_x + 8, south_y + 8, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, south_x - 8, south_y + 8, south_x + 8, south_y + 8, colorSchemes[currentColorScheme]->text_color);
    
    // East
    int east_x = CENTER_X + FRAME_START + FRAME_WIDTH + 20;
    int east_y = CENTER_Y;
    for (int dx = -12; dx <= 12; dx++) {
        for (int dy = -12; dy <= 12; dy++) {
            int px = east_x + dx;
            int py = east_y + dy;
            if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) {
                staticFrameBuffer[py * DISPLAY_WIDTH + px] = colorSchemes[currentColorScheme]->black;
            }
        }
    }
    draw_line_to_buffer(staticFrameBuffer, east_x - 8, east_y - 8, east_x + 8, east_y - 8, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, east_x - 8, east_y, east_x + 8, east_y, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, east_x - 8, east_y + 8, east_x + 8, east_y + 8, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, east_x - 8, east_y - 8, east_x - 8, east_y + 8, colorSchemes[currentColorScheme]->text_color);
    
    // West
    int west_x = CENTER_X - FRAME_START - FRAME_WIDTH - 20;
    int west_y = CENTER_Y;
    for (int dx = -12; dx <= 12; dx++) {
        for (int dy = -12; dy <= 12; dy++) {
            int px = west_x + dx;
            int py = west_y + dy;
            if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) {
                staticFrameBuffer[py * DISPLAY_WIDTH + px] = colorSchemes[currentColorScheme]->black;
            }
        }
    }
    draw_line_to_buffer(staticFrameBuffer, west_x - 8, west_y - 8, west_x - 4, west_y + 8, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, west_x - 4, west_y + 8, west_x, west_y - 4, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, west_x, west_y - 4, west_x + 4, west_y + 8, colorSchemes[currentColorScheme]->text_color);
    draw_line_to_buffer(staticFrameBuffer, west_x + 4, west_y + 8, west_x + 8, west_y - 8, colorSchemes[currentColorScheme]->text_color);
}

void draw_frame_border() {
    const int gap_size = 10;
    
    for (int r = FRAME_START; r < FRAME_START + FRAME_WIDTH; r++) {
        draw_arc_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, 
                           gap_size, 90 - gap_size, colorSchemes[currentColorScheme]->frame_color);
        draw_arc_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, 
                           90 + gap_size, 180 - gap_size, colorSchemes[currentColorScheme]->frame_color);
        draw_arc_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, 
                           180 + gap_size, 270 - gap_size, colorSchemes[currentColorScheme]->frame_color);
        draw_arc_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, 
                           270 + gap_size, 360 - gap_size, colorSchemes[currentColorScheme]->frame_color);
    }
    
    draw_radial_scale_marks();
    draw_cardinal_directions();
}

void draw_radar_grid() {
    for (int r = 50; r <= RADAR_RADIUS; r += 50) {
        draw_circle_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, colorSchemes[currentColorScheme]->grid_color);
    }
    
    draw_line_to_buffer(staticFrameBuffer, CENTER_X - RADAR_RADIUS, CENTER_Y, CENTER_X + RADAR_RADIUS, CENTER_Y, colorSchemes[currentColorScheme]->grid_color);
    draw_line_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y - RADAR_RADIUS, CENTER_X, CENTER_Y + RADAR_RADIUS, colorSchemes[currentColorScheme]->grid_color);
}

void draw_radar_sweep() {
    float rad = current_angle * PI / 180.0;
    int end_x = CENTER_X + RADAR_RADIUS * sin(rad);
    int end_y = CENTER_Y - RADAR_RADIUS * cos(rad);
    
    for (int r = 0; r <= RADAR_RADIUS; r++) {
        int trail_x = CENTER_X + r * sin(rad);
        int trail_y = CENTER_Y - r * cos(rad);
        
        int dx = trail_x - CENTER_X;
        int dy = trail_y - CENTER_Y;
        if (dx * dx + dy * dy <= RADAR_RADIUS * RADAR_RADIUS) {
            uint16_t trail_color = colorSchemes[currentColorScheme]->trail_color;
            
            if (r == RADAR_RADIUS) {
                frameBuffer[trail_y * DISPLAY_WIDTH + trail_x] = colorSchemes[currentColorScheme]->sweep_color;
            } else {
                frameBuffer[trail_y * DISPLAY_WIDTH + trail_x] = trail_color;
            }
        }
    }
    
    current_angle += SWEEP_SPEED;
    if (current_angle >= 360) {
        current_angle = 0;
        
        for (int y = 0; y < DISPLAY_HEIGHT; y++) {
            for (int x = 0; x < DISPLAY_WIDTH; x++) {
                int dx = x - CENTER_X;
                int dy = y - CENTER_Y;
                if (dx * dx + dy * dy <= RADAR_RADIUS * RADAR_RADIUS) {
                    frameBuffer[y * DISPLAY_WIDTH + x] = colorSchemes[currentColorScheme]->black;
                }
            }
        }
        
        for (int y = 0; y < DISPLAY_HEIGHT; y++) {
            for (int x = 0; x < DISPLAY_WIDTH; x++) {
                int dx = x - CENTER_X;
                int dy = y - CENTER_Y;
                if (dx * dx + dy * dy <= RADAR_RADIUS * RADAR_RADIUS) {
                    frameBuffer[y * DISPLAY_WIDTH + x] = staticFrameBuffer[y * DISPLAY_WIDTH + x];
                }
            }
        }
    }
}

void setup() {
    Serial.begin(115200);
    Serial.println("\n\nStarting ESP32S3 Radar Clock");
    
    // Increase stability by setting WiFi to static mode
    WiFi.mode(WIFI_STA);
    WiFi.setAutoReconnect(true);
    WiFi.persistent(true);
    
    // Initialize display
    init_display();
    
    // Show title screen
    startupStartTime = millis();
    showTitleScreen();
    Serial.println("Showing title screen...");
}

void loop() {
    // Update startup sequence
    updateStartupSequence();
    
    // Only run clock functions when in clock mode
    if (currentStartupState == SHOW_CLOCK) {
        // Check for encoder rotation
        handleEncoder();
        
        // Redraw static elements if color scheme changed
        if (colorSchemeChanged) {
            redrawStaticElements();
        }
        
        // Update radar sweep
        draw_radar_sweep();
        
        // Update time display
        update_time_display();
        
        // Update display
        gfx->draw16bitRGBBitmap(0, 0, frameBuffer, DISPLAY_WIDTH, DISPLAY_HEIGHT);
    }
    
    delay(50); // ~20 FPS
}