#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <EGL/egl.h>
#include <GLES2/gl2.h>

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

// Vertex shader for simple texture rendering
static const char *vertex_shader_source =
    "#version 120\n"
    "attribute vec2 position;\n"
    "attribute vec2 texcoord;\n"
    "varying vec2 v_texcoord;\n"
    "void main() {\n"
    "    gl_Position = vec4(position, 0.0, 1.0);\n"
    "    v_texcoord = texcoord;\n"
    "}\n";

// Fragment shader for simple texture rendering
static const char *fragment_shader_source =
    "#version 120\n"
    "varying vec2 v_texcoord;\n"
    "uniform sampler2D texture;\n"
    "void main() {\n"
    "    gl_FragColor = texture2D(texture, v_texcoord);\n"
    "}\n";

// Vertices for a fullscreen quad
static const float vertices[] = {
    // Position  Texcoord
    -1.0f, -1.0f, 0.0f, 1.0f, // Bottom left
    1.0f,  -1.0f, 1.0f, 1.0f, // Bottom right
    1.0f,  1.0f,  1.0f, 0.0f, // Top right
    -1.0f, 1.0f,  0.0f, 0.0f, // Top left
};

static const unsigned int indices[] = {
    0, 1, 2, // First triangle
    2, 3, 0  // Second triangle
};

// Shader compilation helper
static GLuint compile_shader(GLenum type, const char *source) {
  GLuint shader = glCreateShader(type);
  if (!shader) {
    chroma_log("ERROR", "Failed to create shader");
    return 0;
  }

  glShaderSource(shader, 1, &source, NULL);
  glCompileShader(shader);

  GLint status;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
  if (status != GL_TRUE) {
    char log[512];
    glGetShaderInfoLog(shader, sizeof(log), NULL, log);
    chroma_log("ERROR", "Shader compilation failed: %s", log);
    glDeleteShader(shader);
    return 0;
  }

  return shader;
}

// Shader program creation helper
static GLuint create_shader_program(void) {
  GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_source);
  if (!vertex_shader) {
    return 0;
  }

  GLuint fragment_shader =
      compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source);
  if (!fragment_shader) {
    glDeleteShader(vertex_shader);
    return 0;
  }

  GLuint program = glCreateProgram();
  if (!program) {
    chroma_log("ERROR", "Failed to create shader program");
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    return 0;
  }

  glAttachShader(program, vertex_shader);
  glAttachShader(program, fragment_shader);
  glLinkProgram(program);

  GLint status;
  glGetProgramiv(program, GL_LINK_STATUS, &status);
  if (status != GL_TRUE) {
    char log[512];
    glGetProgramInfoLog(program, sizeof(log), NULL, log);
    chroma_log("ERROR", "Shader program linking failed: %s", log);
    glDeleteProgram(program);
    program = 0;
  }

  glDeleteShader(vertex_shader);
  glDeleteShader(fragment_shader);

  return program;
}

// EGL configuration selection
static int choose_egl_config(EGLDisplay display, EGLConfig *config) {
  EGLint attributes[] = {EGL_SURFACE_TYPE,
                         EGL_WINDOW_BIT,
                         EGL_RED_SIZE,
                         8,
                         EGL_GREEN_SIZE,
                         8,
                         EGL_BLUE_SIZE,
                         8,
                         EGL_ALPHA_SIZE,
                         8,
                         EGL_RENDERABLE_TYPE,
                         EGL_OPENGL_BIT,
                         EGL_NONE};

  EGLint num_configs;
  if (!eglChooseConfig(display, attributes, config, 1, &num_configs)) {
    chroma_log("ERROR", "Failed to choose EGL config: 0x%04x", eglGetError());
    return -1;
  }

  if (num_configs == 0) {
    chroma_log("ERROR", "No suitable EGL configs found");
    return -1;
  }

  return 0;
}

// EGL initialization
int chroma_egl_init(chroma_state_t *state) {
  if (!state || !state->display) {
    return CHROMA_ERROR_INIT;
  }

  // Get EGL display
  state->egl_display = eglGetDisplay((EGLNativeDisplayType)state->display);
  if (state->egl_display == EGL_NO_DISPLAY) {
    chroma_log("ERROR", "Failed to get EGL display: 0x%04x", eglGetError());
    return CHROMA_ERROR_EGL;
  }

  // Initialize EGL
  EGLint major, minor;
  if (!eglInitialize(state->egl_display, &major, &minor)) {
    chroma_log("ERROR", "Failed to initialize EGL: 0x%04x", eglGetError());
    return CHROMA_ERROR_EGL;
  }

  chroma_log("INFO", "EGL initialized: version %d.%d", major, minor);

  // Bind OpenGL API
  if (!eglBindAPI(EGL_OPENGL_API)) {
    chroma_log("ERROR", "Failed to bind OpenGL API: 0x%04x", eglGetError());
    chroma_egl_cleanup(state);
    return CHROMA_ERROR_EGL;
  }

  // Choose EGL config
  if (choose_egl_config(state->egl_display, &state->egl_config) != 0) {
    chroma_egl_cleanup(state);
    return CHROMA_ERROR_EGL;
  }

  // Create EGL context
  EGLint context_attributes[] = {EGL_CONTEXT_MAJOR_VERSION, 2,
                                 EGL_CONTEXT_MINOR_VERSION, 1, EGL_NONE};

  state->egl_context = eglCreateContext(state->egl_display, state->egl_config,
                                        EGL_NO_CONTEXT, context_attributes);
  if (state->egl_context == EGL_NO_CONTEXT) {
    chroma_log("ERROR", "Failed to create EGL context: 0x%04x", eglGetError());
    chroma_egl_cleanup(state);
    return CHROMA_ERROR_EGL;
  }

  chroma_log("INFO", "EGL context created successfully");
  return CHROMA_OK;
}

// EGL cleanup
void chroma_egl_cleanup(chroma_state_t *state) {
  if (!state) {
    return;
  }

  if (state->egl_display != EGL_NO_DISPLAY) {
    eglMakeCurrent(state->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
                   EGL_NO_CONTEXT);

    if (state->egl_context != EGL_NO_CONTEXT) {
      eglDestroyContext(state->egl_display, state->egl_context);
      state->egl_context = EGL_NO_CONTEXT;
    }

    eglTerminate(state->egl_display);
    state->egl_display = EGL_NO_DISPLAY;
  }

  chroma_log("INFO", "EGL cleaned up");
}

// Create surface for output
int chroma_surface_create(chroma_state_t *state, chroma_output_t *output) {
  if (!state || !output || !state->compositor || !state->layer_shell) {
    return CHROMA_ERROR_INIT;
  }

  // Create Wayland surface
  output->surface = wl_compositor_create_surface(state->compositor);
  if (!output->surface) {
    chroma_log("ERROR", "Failed to create Wayland surface for output %u",
               output->id);
    return CHROMA_ERROR_WAYLAND;
  }

  // Create layer surface for wallpaper
  output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
      state->layer_shell, output->surface, output->wl_output,
      ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "chroma-wallpaper");

  if (!output->layer_surface) {
    chroma_log("ERROR", "Failed to create layer surface for output %u",
               output->id);
    chroma_surface_destroy(output);
    return CHROMA_ERROR_WAYLAND;
  }

  // Configure layer surface
  zwlr_layer_surface_v1_set_size(output->layer_surface, output->width,
                                 output->height);
  zwlr_layer_surface_v1_set_anchor(output->layer_surface,
                                   ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
                                       ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
                                       ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
                                       ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
  zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1);
  zwlr_layer_surface_v1_set_keyboard_interactivity(
      output->layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE);

  // Add layer surface listener
  zwlr_layer_surface_v1_add_listener(
      output->layer_surface, &chroma_layer_surface_listener_impl, output);

  // Commit surface to trigger configure event
  wl_surface_commit(output->surface);

  // Wait for configure event
  wl_display_roundtrip(state->display);

  // Create EGL window
  output->egl_window =
      wl_egl_window_create(output->surface, output->width, output->height);
  if (!output->egl_window) {
    chroma_log("ERROR", "Failed to create EGL window for output %u",
               output->id);
    chroma_surface_destroy(output);
    return CHROMA_ERROR_EGL;
  }

  // Create EGL surface
  output->egl_surface =
      eglCreateWindowSurface(state->egl_display, state->egl_config,
                             (EGLNativeWindowType)output->egl_window, NULL);
  if (output->egl_surface == EGL_NO_SURFACE) {
    chroma_log("ERROR", "Failed to create EGL surface for output %u: 0x%04x",
               output->id, eglGetError());
    chroma_surface_destroy(output);
    return CHROMA_ERROR_EGL;
  }

  chroma_log("INFO", "Created surface for output %u (%dx%d)", output->id,
             output->width, output->height);

  return CHROMA_OK;
}

// Destroy surface
void chroma_surface_destroy(chroma_output_t *output) {
  if (!output) {
    return;
  }

  if (output->egl_surface != EGL_NO_SURFACE) {
    eglDestroySurface(eglGetCurrentDisplay(), output->egl_surface);
    output->egl_surface = EGL_NO_SURFACE;
  }

  if (output->egl_window) {
    wl_egl_window_destroy(output->egl_window);
    output->egl_window = NULL;
  }

  if (output->layer_surface) {
    zwlr_layer_surface_v1_destroy(output->layer_surface);
    output->layer_surface = NULL;
  }

  if (output->surface) {
    wl_surface_destroy(output->surface);
    output->surface = NULL;
  }

  chroma_log("DEBUG", "Destroyed surface for output %u", output->id);
}

// Create texture from image data
static GLuint create_texture_from_image(chroma_image_t *image) {
  if (!image || !image->loaded || !image->data) {
    return 0;
  }

  GLuint texture;
  glGenTextures(1, &texture);
  glBindTexture(GL_TEXTURE_2D, texture);

  // Set texture parameters
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  // Upload texture data
  GLenum format = (image->channels == 4) ? GL_RGBA : GL_RGB;
  glTexImage2D(GL_TEXTURE_2D, 0, format, image->width, image->height, 0, format,
               GL_UNSIGNED_BYTE, image->data);

  glBindTexture(GL_TEXTURE_2D, 0);

  return texture;
}

// Render wallpaper to output
int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
  if (!state || !output || !output->image || !output->image->loaded) {
    return CHROMA_ERROR_INIT;
  }

  // Make context current
  if (!eglMakeCurrent(state->egl_display, output->egl_surface,
                      output->egl_surface, state->egl_context)) {
    chroma_log("ERROR", "Failed to make EGL context current: 0x%04x",
               eglGetError());
    return CHROMA_ERROR_EGL;
  }

  // Set viewport
  glViewport(0, 0, output->width, output->height);

  // Clear screen
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  // Create shader program (should be cached in real implementation)
  GLuint program = create_shader_program();
  if (!program) {
    return CHROMA_ERROR_EGL;
  }

  // Use shader program
  glUseProgram(program);

  // Create and bind texture
  GLuint texture = create_texture_from_image(output->image);
  if (!texture) {
    chroma_log("ERROR", "Failed to create texture for output %u", output->id);
    glDeleteProgram(program);
    return CHROMA_ERROR_EGL;
  }

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, texture);
  glUniform1i(glGetUniformLocation(program, "texture"), 0);

  // Create vertex buffer objects (should be cached in real implementation)
  GLuint vbo, ebo;
  glGenBuffers(1, &vbo);
  glGenBuffers(1, &ebo);

  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
               GL_STATIC_DRAW);

  // Set vertex attributes
  GLint position_attr = glGetAttribLocation(program, "position");
  GLint texcoord_attr = glGetAttribLocation(program, "texcoord");

  glEnableVertexAttribArray(position_attr);
  glVertexAttribPointer(position_attr, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
                        (void *)0);

  glEnableVertexAttribArray(texcoord_attr);
  glVertexAttribPointer(texcoord_attr, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
                        (void *)(2 * sizeof(float)));

  // Draw
  glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

  // Cleanup
  glDeleteBuffers(1, &vbo);
  glDeleteBuffers(1, &ebo);
  glDeleteTextures(1, &texture);
  glDeleteProgram(program);

  // Swap buffers
  if (!eglSwapBuffers(state->egl_display, output->egl_surface)) {
    chroma_log("ERROR", "Failed to swap buffers for output %u: 0x%04x",
               output->id, eglGetError());
    return CHROMA_ERROR_EGL;
  }

  // Commit surface
  wl_surface_commit(output->surface);

  chroma_log("DEBUG", "Rendered wallpaper to output %u", output->id);
  return CHROMA_OK;
}