#include "graphics/texture.h"
#include "util.h"
#include "utility/logger.h"

bool Texture::loadTexture(const char *imagePath) {
  SDL_Surface *buffer = IMG_Load(imagePath);
  if (!buffer)
    ERROR_LOG("Failed to load image file: {}", imagePath);
  // UTIL::flip_surface(buffer);

  glGenTextures(1, &ID);
  /*
  GLenum error = glGetError();
  if(error != GL_NO_ERROR) {
          std::cout << "OpenGL error: " << error << std::endl;
  }
  */

  glBindTexture(GL_TEXTURE_2D, ID);

  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, buffer->w, buffer->h, 0, GL_RGBA,
               GL_UNSIGNED_BYTE, buffer->pixels);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glGenerateMipmap(ID);

  textureWidth = buffer->w;
  textureHeight = buffer->h;

  SDL_FreeSurface(buffer);

  return true;
}

void Texture::bind() {
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, ID);
}

Texture::~Texture() { glDeleteTextures(1, &ID); }

bool TextureArray::loadTextures(std::vector<const char *> imagePaths) {
  std::vector<SDL_Surface *> surfaces;
  surfaces.resize(imagePaths.size());
  // Fill surfaces vector
  for (int i = 0; i < imagePaths.size(); ++i) {
    surfaces[i] = IMG_Load(imagePaths[i]);
    if (!surfaces[i])
      ERROR_LOG("Failed to load image file: {}", imagePaths[i]);
  }
  if (!adjustCanvasSizes(surfaces))
    ERROR_LOG(
        "Failed to adjust canvas size of images! \n Make sure to check that "
        "every tileset has square dimensions! (512x512, 756x756 ... etc)",
        NULL);
  if (surfaces.empty())
    ERROR_LOG("No surfaces created!", NULL);
  numOfLayers = imagePaths.size();

  glGenTextures(1, &ID);
  glBindTexture(GL_TEXTURE_2D_ARRAY, ID);

  // Creating the texture array all of our textures will live in.
  // adjustCanvasSizes makes every image the same size, so we can just use the
  // first surface in the list of surfaces
  glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, surfaces[0]->w, surfaces[0]->h,
               (GLsizei)numOfLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

  for (int layer = 0; layer < numOfLayers; ++layer) {
    glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, surfaces[layer]->w,
                    surfaces[layer]->h, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                    surfaces[layer]->pixels);
  }
  glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

  glGenerateMipmap(GL_TEXTURE_2D_ARRAY);

  for (auto &surface : surfaces)
    SDL_FreeSurface(surface);
  surfaces.clear();

  return true;
}

void TextureArray::bind() {
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D_ARRAY, ID);
}

bool TextureArray::adjustCanvasSizes(std::vector<SDL_Surface *> &surfaces) {
  int maxWidth = 0;
  int maxHeight = 0;
  for (auto &surface : surfaces) {
    if (surface->w != surface->h)
      ERROR_LOG("Image must be a square!", NULL);

    if (surface->w > maxWidth)
      maxWidth = surface->w;
    if (surface->h > maxHeight)
      maxHeight = surface->h;
    textures.push_back(TextureData({surface->w, surface->h}));
  }
  for (int i = 0; i < surfaces.size(); ++i) {
    SDL_Surface *canvas = SDL_CreateRGBSurface(
        0, maxWidth, maxHeight, surfaces[i]->format->BitsPerPixel,
        surfaces[i]->format->Rmask, surfaces[i]->format->Gmask,
        surfaces[i]->format->Bmask, surfaces[i]->format->Amask);

    SDL_FillRect(canvas, NULL, SDL_MapRGBA(canvas->format, 0, 0, 0, 0));

    SDL_BlitSurface(surfaces[i], NULL, canvas, NULL);

    SDL_FreeSurface(surfaces[i]);
    surfaces[i] = canvas;
  }

  canvasWidth = maxWidth;
  canvasHeight = maxHeight;

  return true;
}

TextureArray::~TextureArray() { glDeleteTextures(1, &ID); }
