#include "SDL_log.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <cstdio>
#include <cstdlib>
#include <frostbite2D/graphics/texture.h>
#include <frostbite2D/resource/asset.h>
#include <glad/glad.h>

namespace frostbite2D {

Texture::Texture(int width, int height, uint32_t id)
    : width_(width), height_(height), textureID_(id) {}

Texture::~Texture() {
  if (textureID_ != 0) {
    glDeleteTextures(1, &textureID_);
    textureID_ = 0;
  }
}

Ptr<Texture> Texture::loadFromFile(const std::string& path) {
  Asset& asset = Asset::get();
  std::string resolvedPath = asset.resolveAssetPath(path);
  std::vector<uint8> fileData;

  if (!asset.readBinaryFile(path, fileData)) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                 "Failed to read texture file: %s", resolvedPath.c_str());
    return nullptr;
  }

  if (fileData.empty()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Texture file is empty: %s",
                 resolvedPath.c_str());
    return nullptr;
  }

  SDL_RWops *rw =
      SDL_RWFromConstMem(fileData.data(), static_cast<int>(fileData.size()));
  if (!rw) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create RWops: %s",
                 SDL_GetError());
    return nullptr;
  }

  SDL_Surface *surface = IMG_Load_RW(rw, 1);
  if (!surface) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load image: %s",
                 IMG_GetError());
    SDL_FreeRW(rw);
    return nullptr;
  }

  GLenum format;
  int channels;
  if (surface->format->BytesPerPixel == 4) {
    format = GL_RGBA;
    channels = 4;
  } else {
    format = GL_RGB;
    channels = 3;
  }

  int width = surface->w;
  int height = surface->h;

  uint32_t textureID;
  glGenTextures(1, &textureID);
  glBindTexture(GL_TEXTURE_2D, textureID);

  glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format,
               GL_UNSIGNED_BYTE, surface->pixels);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
  glBindTexture(GL_TEXTURE_2D, 0);

  SDL_FreeSurface(surface);

  auto texture = Ptr<Texture>(new Texture(width, height, textureID));
  texture->path_ = resolvedPath;
  texture->channels_ = channels;
  
  return texture;
}

Ptr<Texture> Texture::createFromMemory(const uint8* data, int width, int height, int channels) {
  if (!data) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create texture from memory: null data");
    return nullptr;
  }
  
  uint32_t format = channels == 4 ? GL_RGBA : GL_RGB;
  
  uint32_t textureID;
  glGenTextures(1, &textureID);
  glBindTexture(GL_TEXTURE_2D, textureID);
  
  glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
  
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
  glBindTexture(GL_TEXTURE_2D, 0);
  
  auto texture = Ptr<Texture>(new Texture(width, height, textureID));
  texture->channels_ = channels;
  
  return texture;
}

Ptr<Texture> Texture::createEmpty(int width, int height) {
  uint32_t textureID;
  glGenTextures(1, &textureID);
  glBindTexture(GL_TEXTURE_2D, textureID);
  
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
  
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
  glBindTexture(GL_TEXTURE_2D, 0);
  
  return Ptr<Texture>(new Texture(width, height, textureID));
}

void Texture::bind(uint32_t slot) {
  glActiveTexture(GL_TEXTURE0 + slot);
  glBindTexture(GL_TEXTURE_2D, textureID_);
}

void Texture::unbind() {
  glBindTexture(GL_TEXTURE_2D, 0);
}

void Texture::setWrapMode(uint32_t wrapS, uint32_t wrapT) {
  bind();
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);
  unbind();
}

void Texture::setFilterMode(uint32_t minFilter, uint32_t magFilter) {
  bind();
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
  unbind();
}

}
