#include "SDL_log.h"
#include <SDL2/SDL.h>
#include <frostbite2D/graphics/batch.h>
#include <frostbite2D/graphics/renderer.h>
#include <glad/glad.h>

namespace frostbite2D {

Batch::Batch() {
  currentBatch_.vertices.reserve(MAX_VERTICES);
  currentBatch_.indices.reserve(MAX_INDICES);
}

Batch::~Batch() {
  shutdown();
}

bool Batch::init() {
  glGenVertexArrays(1, &vao_);
  glGenBuffers(1, &vbo_);
  glGenBuffers(1, &ibo_);
  
  setupMesh();
  
  SDL_Log("Batch system initialized");
  return true;
}

void Batch::shutdown() {
  if (vao_ != 0) {
    glDeleteVertexArrays(1, &vao_);
    vao_ = 0;
  }
  if (vbo_ != 0) {
    glDeleteBuffers(1, &vbo_);
    vbo_ = 0;
  }
  if (ibo_ != 0) {
    glDeleteBuffers(1, &ibo_);
    ibo_ = 0;
  }
  
  currentBatch_.vertices.clear();
  currentBatch_.indices.clear();
  batches_.clear();
}

void Batch::setupMesh() {
  glBindVertexArray(vao_);
  
  glBindBuffer(GL_ARRAY_BUFFER, vbo_);
  glBufferData(GL_ARRAY_BUFFER, MAX_VERTICES * sizeof(Vertex), nullptr, GL_DYNAMIC_DRAW);
  
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_INDICES * sizeof(uint16), nullptr, GL_DYNAMIC_DRAW);
  
  int stride = sizeof(Vertex);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, (void*)0);
  
  glEnableVertexAttribArray(1);
  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (void*)offsetof(Vertex, texCoord));
  
  glEnableVertexAttribArray(2);
  glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, stride, (void*)offsetof(Vertex, r));
  
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindVertexArray(0);
}

void Batch::begin() {
  begun_ = true;
  currentBatch_.vertices.clear();
  currentBatch_.indices.clear();
  currentBatch_.key = {0, 0, 0};
  currentBatch_.shader = nullptr;
  currentBatch_.texture.Reset();
}

void Batch::end() {
  if (begun_) {
    flush();
    begun_ = false;
  }
}

void Batch::submitQuad(const Quad& quad, const Transform2D& transform,
                     Ptr<Texture> texture, Shader* shader,
                     BlendMode blendMode) {
  if (!begun_) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Batch not started, call begin() first");
    return;
  }
  
  uint32_t textureID = texture ? texture->getID() : 0;
  uint32_t shaderID = shader ? shader->getID() : 0;
  BatchKey newKey = {shaderID, textureID, (uint32_t)blendMode};
  
  flushIfNeeded(newKey, shader, texture);
  
  glm::mat4 mat = transform.matrix;
  uint16_t indexOffset = static_cast<uint16_t>(currentBatch_.vertices.size());
  
  for (int i = 0; i < 4; i++) {
    Vertex v = quad.vertices[i];
    glm::vec4 pos(v.position.x, v.position.y, 0.0f, 1.0f);
    glm::vec4 transformed = mat * pos;
    v.position.x = transformed.x;
    v.position.y = transformed.y;
    
    currentBatch_.vertices.push_back(v);
  }
  
  currentBatch_.indices.push_back(indexOffset);
  currentBatch_.indices.push_back(static_cast<uint16_t>(indexOffset + 1));
  currentBatch_.indices.push_back(static_cast<uint16_t>(indexOffset + 2));
  currentBatch_.indices.push_back(static_cast<uint16_t>(indexOffset + 1));
  currentBatch_.indices.push_back(static_cast<uint16_t>(indexOffset + 3));
  currentBatch_.indices.push_back(static_cast<uint16_t>(indexOffset + 2));
}

void Batch::flush() {
  if (!currentBatch_.vertices.empty()) {
    flushCurrentBatch();
  }
}

void Batch::flushIfNeeded(const BatchKey& newKey, Shader* shader, Ptr<Texture> texture) {
  if (currentBatch_.indices.empty()) {
    currentBatch_.key = newKey;
    currentBatch_.shader = shader;
    currentBatch_.texture = texture;
    return;
  }
  
  if (currentBatch_.key.shaderID != newKey.shaderID ||
      currentBatch_.key.textureID != newKey.textureID ||
      currentBatch_.key.blendMode != newKey.blendMode) {
    flushCurrentBatch();
    currentBatch_.key = newKey;
    currentBatch_.shader = shader;
    currentBatch_.texture = texture;
  }
}

void Batch::flushCurrentBatch() {
  if (currentBatch_.vertices.empty()) {
    return;
  }
  
  // Apply the batch's blend state right before drawing so per-sprite
  // `SetBlendMode()` changes actually affect the GL pipeline.
  Renderer::get().setupBlendMode(static_cast<BlendMode>(currentBatch_.key.blendMode));

  glBindVertexArray(vao_);
  
  // 绑定着色器和纹理
  if (currentBatch_.shader) {
    currentBatch_.shader->use();
    // 设置纹理采样器 uniform 变量
    currentBatch_.shader->setTexture("u_texture", 0);
  }
  if (currentBatch_.texture) {
    currentBatch_.texture->bind(0);
  }
  
  glBindBuffer(GL_ARRAY_BUFFER, vbo_);
  glBufferSubData(GL_ARRAY_BUFFER, 0, 
                  currentBatch_.vertices.size() * sizeof(Vertex), 
                  currentBatch_.vertices.data());
  
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
  glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, 
                  currentBatch_.indices.size() * sizeof(uint16), 
                  currentBatch_.indices.data());
  
  glDrawElements(GL_TRIANGLES, 
                 static_cast<GLsizei>(currentBatch_.indices.size()), 
                  GL_UNSIGNED_SHORT, 0);
  
  currentBatch_.vertices.clear();
  currentBatch_.indices.clear();
  currentBatch_.shader = nullptr;
  currentBatch_.texture.Reset();
}

}
