#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#include "../include/chroma.h"

// Global logging level
static int log_level = 0; // 0=ERROR, 1=WARN, 2=INFO, 3=DEBUG

// Initialize chroma state
int chroma_init(chroma_state_t *state) {
  if (!state) {
    return CHROMA_ERROR_INIT;
  }

  // Initialize all fields to zero
  memset(state, 0, sizeof(chroma_state_t));

  // Set initial state
  state->running = false;
  state->initialized = false;
  state->egl_display = EGL_NO_DISPLAY;
  state->egl_context = EGL_NO_CONTEXT;

  // Initialize stb_image
  chroma_image_init_stb();

  state->initialized = true;
  chroma_log("INFO", "Chroma state initialized");

  return CHROMA_OK;
}

// Cleanup chroma state
void chroma_cleanup(chroma_state_t *state) {
  if (!state || !state->initialized) {
    return;
  }

  chroma_log("INFO", "Cleaning up chroma state");

  // Stop the main loop
  state->running = false;

  // Clean up all images
  chroma_images_cleanup(state);

  // Clean up EGL
  chroma_egl_cleanup(state);

  // Clean up Wayland
  chroma_wayland_disconnect(state);

  // Clean up configuration
  chroma_config_free(&state->config);

  state->initialized = false;
  chroma_log("INFO", "Chroma cleanup complete");
}

// Assign wallpaper to an output
static int assign_wallpaper_to_output(chroma_state_t *state,
                                      chroma_output_t *output) {
  if (!state || !output || !output->active) {
    return CHROMA_ERROR_INIT;
  }

  // Get image path for this output
  const char *image_path = chroma_config_get_image_for_output(
      &state->config, output->name ? output->name : "unknown");
  if (!image_path) {
    chroma_log("WARN", "No wallpaper configured for output %u (%s)", output->id,
               output->name ? output->name : "unknown");
    return CHROMA_ERROR_CONFIG;
  }

  // Load or get cached image
  chroma_image_t *image = chroma_image_get_or_load(state, image_path);
  if (!image) {
    chroma_log("ERROR", "Failed to load image for output %u: %s", output->id,
               image_path);
    return CHROMA_ERROR_IMAGE;
  }

  // Assign image to output
  output->image = image;

  // Create surface if it doesn't exist
  if (!output->surface) {
    int ret = chroma_surface_create(state, output);
    if (ret != CHROMA_OK) {
      chroma_log("ERROR", "Failed to create surface for output %u", output->id);
      return ret;
    }
  }

  // Render wallpaper
  int ret = chroma_render_wallpaper(state, output);
  if (ret != CHROMA_OK) {
    chroma_log("ERROR", "Failed to render wallpaper for output %u", output->id);
    return ret;
  }

  chroma_log("INFO", "Assigned wallpaper to output %u (%s): %s", output->id,
             output->name ? output->name : "unknown", image_path);

  return CHROMA_OK;
}

// Assign wallpapers to all active outputs
static int assign_wallpapers_to_all_outputs(chroma_state_t *state) {
  if (!state) {
    return CHROMA_ERROR_INIT;
  }

  int success_count = 0;
  int error_count = 0;

  for (int i = 0; i < state->output_count; i++) {
    chroma_output_t *output = &state->outputs[i];

    if (!output->active) {
      chroma_log("DEBUG", "Skipping inactive output %u", output->id);
      continue;
    }

    if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
      success_count++;
    } else {
      error_count++;
    }
  }

  chroma_log("INFO", "Wallpaper assignment complete: %d success, %d errors",
             success_count, error_count);

  return (success_count > 0) ? CHROMA_OK : CHROMA_ERROR_IMAGE;
}

// Handle output configuration complete event
void handle_output_done(chroma_state_t *state, chroma_output_t *output) {
  if (!state || !output) {
    return;
  }

  chroma_log("INFO",
             "Output %u (%s) configuration complete: %dx%d@%d, scale=%d",
             output->id, output->name ? output->name : "unknown", output->width,
             output->height, 0, output->scale);

  /* Assign wallpaper to this output */
  if (assign_wallpaper_to_output(state, output) != CHROMA_OK) {
    chroma_log("ERROR", "Failed to assign wallpaper to output %u", output->id);
  }
}

// Process Wayland events
static int process_wayland_events(chroma_state_t *state) {
  if (!state || !state->display) {
    return CHROMA_ERROR_WAYLAND;
  }

  /* Dispatch pending events */
  if (wl_display_dispatch_pending(state->display) == -1) {
    chroma_log("ERROR", "Failed to dispatch pending Wayland events: %s",
               strerror(errno));
    return CHROMA_ERROR_WAYLAND;
  }

  /* Read events from the server */
  if (wl_display_read_events(state->display) == -1) {
    chroma_log("ERROR", "Failed to read Wayland events: %s", strerror(errno));
    return CHROMA_ERROR_WAYLAND;
  }

  /* Dispatch the read events */
  if (wl_display_dispatch_pending(state->display) == -1) {
    chroma_log("ERROR", "Failed to dispatch read Wayland events: %s",
               strerror(errno));
    return CHROMA_ERROR_WAYLAND;
  }

  return CHROMA_OK;
}

// Main event loop
int chroma_run(chroma_state_t *state) {
  if (!state || !state->initialized) {
    return CHROMA_ERROR_INIT;
  }

  chroma_log("INFO", "Starting main event loop");
  state->running = true;

  // Initial wallpaper assignment
  chroma_log("INFO", "Performing initial wallpaper assignment");
  assign_wallpapers_to_all_outputs(state);

  // Main event loop
  while (state->running && !chroma_should_quit) {
    // Dispatch any pending events first
    if (wl_display_dispatch_pending(state->display) == -1) {
      chroma_log("ERROR", "Failed to dispatch pending events: %s",
                 strerror(errno));
      break;
    }

    // Prepare to read events
    if (wl_display_prepare_read(state->display) == -1) {
      chroma_log("ERROR", "Failed to prepare Wayland display for reading");
      break;
    }

    // Flush outgoing requests
    if (wl_display_flush(state->display) == -1) {
      chroma_log("ERROR", "Failed to flush Wayland display: %s",
                 strerror(errno));
      wl_display_cancel_read(state->display);
      break;
    }

    // Get the display file descriptor
    int fd = wl_display_get_fd(state->display);
    if (fd == -1) {
      chroma_log("ERROR", "Failed to get Wayland display file descriptor");
      wl_display_cancel_read(state->display);
      break;
    }

    // Use select() to wait for events with timeout
    fd_set readfds;
    struct timeval timeout;

    FD_ZERO(&readfds);
    FD_SET(fd, &readfds);

    timeout.tv_sec = 1; // 1 second timeout
    timeout.tv_usec = 0;

    int select_result = select(fd + 1, &readfds, NULL, NULL, &timeout);

    if (select_result == -1) {
      if (errno == EINTR) {
        // Interrupted by signal, check if we should quit
        wl_display_cancel_read(state->display);
        continue;
      }
      chroma_log("ERROR", "select() failed: %s", strerror(errno));
      wl_display_cancel_read(state->display);
      break;
    }

    if (select_result == 0) {
      // Timeout - no events available
      wl_display_cancel_read(state->display);
      continue;
    }

    // Events are available
    if (FD_ISSET(fd, &readfds)) {
      if (process_wayland_events(state) != CHROMA_OK) {
        break;
      }
    } else {
      wl_display_cancel_read(state->display);
    }
  }

  state->running = false;
  chroma_log("INFO", "Main event loop ended");

  return CHROMA_OK;
}

// Error code to string conversion
const char *chroma_error_string(chroma_error_t error) {
  switch (error) {
  case CHROMA_OK:
    return "Success";
  case CHROMA_ERROR_INIT:
    return "Initialization error";
  case CHROMA_ERROR_WAYLAND:
    return "Wayland error";
  case CHROMA_ERROR_EGL:
    return "EGL error";
  case CHROMA_ERROR_IMAGE:
    return "Image loading error";
  case CHROMA_ERROR_CONFIG:
    return "Configuration error";
  case CHROMA_ERROR_MEMORY:
    return "Memory allocation error";
  default:
    return "Unknown error";
  }
}

// Logging function
void chroma_log(const char *level, const char *format, ...) {
  va_list args;
  char timestamp[32];
  struct timeval tv;
  struct tm *tm_info;

  // Get current time
  gettimeofday(&tv, NULL);
  tm_info = localtime(&tv.tv_sec);

  // Format timestamp
  snprintf(timestamp, sizeof(timestamp), "%04d-%02d-%02d %02d:%02d:%02d.%03d",
           tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
           tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec,
           (int)(tv.tv_usec / 1000));

  // Print log message
  printf("[%s] %s: ", timestamp, level);

  va_start(args, format);
  vprintf(format, args);
  va_end(args);

  printf("\n");
  fflush(stdout);
}

// Set log level
void chroma_set_log_level(int level) { log_level = level; }

// Get log level
int chroma_get_log_level(void) { return log_level; }

// Handle configuration reload (SIGHUP)
int chroma_reload_config(chroma_state_t *state, const char *config_file) {
  if (!state) {
    return CHROMA_ERROR_INIT;
  }

  chroma_log("INFO", "Reloading configuration");

  // Free current configuration
  chroma_config_free(&state->config);

  // Load new configuration
  int ret = chroma_config_load(&state->config, config_file);
  if (ret != CHROMA_OK) {
    chroma_log("ERROR", "Failed to reload configuration: %s",
               chroma_error_string(ret));
    return ret;
  }

  // Reassign wallpapers with new configuration
  ret = assign_wallpapers_to_all_outputs(state);
  if (ret != CHROMA_OK) {
    chroma_log("ERROR", "Failed to reassign wallpapers after config reload");
    return ret;
  }

  chroma_log("INFO", "Configuration reloaded successfully");
  return CHROMA_OK;
}

// Check if an output needs wallpaper update
static bool output_needs_update(chroma_output_t *output) {
  if (!output || !output->active) {
    return false;
  }

  // Check if output has no surface or image assigned
  if (!output->surface || !output->image) {
    return true;
  }

  // Check if image is no longer loaded
  if (!output->image->loaded) {
    return true;
  }

  return false;
}

// Update outputs that need wallpaper refresh
int chroma_update_outputs(chroma_state_t *state) {
  if (!state) {
    return CHROMA_ERROR_INIT;
  }

  int updated_count = 0;

  for (int i = 0; i < state->output_count; i++) {
    chroma_output_t *output = &state->outputs[i];

    if (output_needs_update(output)) {
      if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
        updated_count++;
      }
    }
  }

  if (updated_count > 0) {
    chroma_log("INFO", "Updated wallpapers for %d outputs", updated_count);
  }

  return CHROMA_OK;
}

// Get statistics
void chroma_get_stats(chroma_state_t *state, int *active_outputs,
                      int *loaded_images) {
  if (!state) {
    if (active_outputs)
      *active_outputs = 0;
    if (loaded_images)
      *loaded_images = 0;
    return;
  }

  int active = 0;
  for (int i = 0; i < state->output_count; i++) {
    if (state->outputs[i].active) {
      active++;
    }
  }

  int loaded = 0;
  for (int i = 0; i < state->image_count; i++) {
    if (state->images[i].loaded) {
      loaded++;
    }
  }

  if (active_outputs)
    *active_outputs = active;
  if (loaded_images)
    *loaded_images = loaded;
}
