#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;
  drawCallCount_ = 0;
  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;
  }

  Renderer& renderer = Renderer::get();
  glm::mat4 mat = transform.matrix;
  Camera* camera = renderer.getCamera();
  Vec2 snapOffset = Vec2::Zero();
  if (camera && renderer.shouldSnapVertices()) {
    glm::vec4 referencePos(quad.vertices[0].position.x,
                           quad.vertices[0].position.y, 0.0f, 1.0f);
    glm::vec4 transformedReference = mat * referencePos;
    Vec2 referenceWorld(transformedReference.x, transformedReference.y);
    snapOffset = camera->snapWorldPosition(referenceWorld) - referenceWorld;
  }

  Vertex transformedVertices[4];
  float minX = 0.0f;
  float maxX = 0.0f;
  float minY = 0.0f;
  float maxY = 0.0f;
  for (int i = 0; i < 4; i++) {
    transformedVertices[i] = quad.vertices[i];
    glm::vec4 pos(transformedVertices[i].position.x, transformedVertices[i].position.y,
                  0.0f, 1.0f);
    glm::vec4 transformed = mat * pos;
    transformedVertices[i].position.x = transformed.x + snapOffset.x;
    transformedVertices[i].position.y = transformed.y + snapOffset.y;

    if (i == 0) {
      minX = maxX = transformedVertices[i].position.x;
      minY = maxY = transformedVertices[i].position.y;
      continue;
    }

    minX = std::min(minX, transformedVertices[i].position.x);
    maxX = std::max(maxX, transformedVertices[i].position.x);
    minY = std::min(minY, transformedVertices[i].position.y);
    maxY = std::max(maxY, transformedVertices[i].position.y);
  }

  if (camera && renderer.isCullingEnabled()) {
    Vec2 cameraPos = camera->getRenderPosition();
    float safeZoom = std::max(camera->getZoom(), 0.01f);
    float cullPadding = 0.5f / safeZoom;
    Rect viewRect(cameraPos.x - cullPadding, cameraPos.y - cullPadding,
                  camera->getVisibleWidth() + cullPadding * 2.0f,
                  camera->getVisibleHeight() + cullPadding * 2.0f);
    Rect quadBounds(minX, minY, maxX - minX, maxY - minY);
    if (!viewRect.intersects(quadBounds)) {
      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);

  constexpr size_t kVerticesPerQuad = 4;
  constexpr size_t kIndicesPerQuad = 6;
  if (currentBatch_.vertices.size() + kVerticesPerQuad > MAX_VERTICES ||
      currentBatch_.indices.size() + kIndicesPerQuad > MAX_INDICES) {
    flushCurrentBatch();
    currentBatch_.key = newKey;
    currentBatch_.shader = shader;
    currentBatch_.texture = texture;
  }

  if (currentBatch_.vertices.size() + kVerticesPerQuad > MAX_VERTICES ||
      currentBatch_.indices.size() + kIndicesPerQuad > MAX_INDICES) {
    static bool overflowWarningLogged = false;
    if (!overflowWarningLogged) {
      SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                  "Batch overflow: dropping quad (max %d quads per draw batch)",
                  MAX_QUADS);
      overflowWarningLogged = true;
    }
    return;
  }

  uint16_t indexOffset = static_cast<uint16_t>(currentBatch_.vertices.size());
  for (int i = 0; i < 4; i++) {
    currentBatch_.vertices.push_back(transformedVertices[i]);
  }

  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) {
    Renderer& renderer = Renderer::get();
    bool preferPixelArtSampling = renderer.shouldUsePixelArtSampling();
    currentBatch_.texture->bind(0, preferPixelArtSampling);
  }
  
  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);
  drawCallCount_++;
  
  currentBatch_.vertices.clear();
  currentBatch_.indices.clear();
  currentBatch_.shader = nullptr;
  currentBatch_.texture.Reset();
}

}
