#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

#include "glad/gl.h"
#include "opengl.h"
#include "file.h"
#include "dds_validate.h"

unsigned int compile(char const * vertex_source,
                     int vertex_source_size,
                     char const * geometry_source,
                     int geometry_source_size,
                     char const * fragment_source,
                     int fragment_source_size)
{
  int compile_status;
  char info_log[512];

  // program
  unsigned int shader_program = glCreateProgram();
  unsigned int vertex_shader = -1;
  unsigned int geometry_shader = -1;
  unsigned int fragment_shader = -1;

  // vertex shader
  vertex_shader = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertex_shader, 1, &vertex_source, &vertex_source_size);
  glCompileShader(vertex_shader);
  glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &compile_status);
  if (!compile_status) {
    glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
    fprintf(stderr, "vertex shader compile: %s\n", info_log);
  }
  glAttachShader(shader_program, vertex_shader);

  // geometry shader
  if (geometry_source_size != 0) {
    geometry_shader = glCreateShader(GL_GEOMETRY_SHADER);
    glShaderSource(geometry_shader, 1, &geometry_source, &geometry_source_size);
    glCompileShader(geometry_shader);
    glGetShaderiv(geometry_shader, GL_COMPILE_STATUS, &compile_status);
    if (!compile_status) {
      glGetShaderInfoLog(geometry_shader, 512, NULL, info_log);
      fprintf(stderr, "geometry shader compile: %s\n", info_log);
    }
    glAttachShader(shader_program, geometry_shader);
  }

  // fragment shader
  fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragment_shader, 1, &fragment_source, &fragment_source_size);
  glCompileShader(fragment_shader);
  glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &compile_status);
  if (!compile_status) {
    glGetShaderInfoLog(fragment_shader, 512, NULL, info_log);
    fprintf(stderr, "fragment shader compile: %s\n", info_log);
  }
  glAttachShader(shader_program, fragment_shader);

  // link shaders
  glLinkProgram(shader_program);
  glGetProgramiv(shader_program, GL_LINK_STATUS, &compile_status);
  if (!compile_status) {
    glGetProgramInfoLog(shader_program, 512, NULL, info_log);
    fprintf(stderr, "shader link: %s\n", info_log);
  }

  glDeleteShader(vertex_shader);
  if (geometry_source_size != 0)
    glDeleteShader(geometry_shader);
  glDeleteShader(fragment_shader);

  return shader_program;
}

unsigned int compile_from_files(char const * vertex_path,
                                char const * geometry_path,
                                char const * fragment_path)
{
  int vertex_source_size = 0;
  void * vertex_source = NULL;
  int geometry_source_size = 0;
  void * geometry_source = NULL;
  int fragment_source_size = 0;
  void * fragment_source = NULL;

  vertex_source = read_file(vertex_path, &vertex_source_size);
  assert(vertex_source != NULL);

  if (geometry_path != NULL) {
    geometry_source = read_file(geometry_path, &geometry_source_size);
    assert(geometry_source != NULL);
  }

  fragment_source = read_file(fragment_path, &fragment_source_size);
  assert(fragment_source != NULL);

  unsigned int program = compile((char const *)vertex_source, vertex_source_size,
                                 (char const *)geometry_source, geometry_source_size,
                                 (char const *)fragment_source, fragment_source_size);

  free(vertex_source);
  free(geometry_source);
  free(fragment_source);

  return program;
}

unsigned int load_uniform_buffer(char const * const path, int * out_size)
{
  unsigned int buffer;
  glGenBuffers(1, &buffer);
  glBindBuffer(GL_UNIFORM_BUFFER, buffer);

  int data_size;
  void * data = read_file(path, &data_size);
  assert(data != NULL);

  glBufferData(GL_UNIFORM_BUFFER, data_size, data, GL_STATIC_DRAW);
  free(data);

  glBindBuffer(GL_UNIFORM_BUFFER, 0);

  *out_size = data_size;

  return buffer;
}

void load_dds_texture_2D(char const * const path)
{
  fprintf(stderr, "load DDS texture: %s\n", path);

  int size;
  void * data = read_file(path, &size);
  assert(data != NULL);

  void * image_data;
  int image_size;
  DDS_FILE const * dds = dds_validate(data, size, &image_data, &image_size);

  glCompressedTexImage2D(GL_TEXTURE_2D,
                         0,
                         GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
                         dds->header.dwWidth,
                         dds->header.dwHeight,
                         0,
                         image_size,
                         image_data);

  free(data);
}
