#include "app/gfx/arena.h"

#include <SDL.h>
#include <algorithm>

#include "app/gfx/backend/irenderer.h"
#include "util/log.h"
#include "util/sdl_deleter.h"

namespace yaze {
namespace gfx {

void Arena::Initialize(IRenderer* renderer) { renderer_ = renderer; }

Arena& Arena::Get() {
  static Arena instance;
  return instance;
}

Arena::Arena() {
  layer1_buffer_.fill(0);
  layer2_buffer_.fill(0);
}

Arena::~Arena() {
  // Use the safe shutdown method that handles cleanup properly
  Shutdown();
}



void Arena::QueueTextureCommand(TextureCommandType type, Bitmap* bitmap) {
  texture_command_queue_.push_back({type, bitmap});
}

void Arena::ProcessTextureQueue(IRenderer* renderer) {
  // Use provided renderer if available, otherwise use stored renderer
  IRenderer* active_renderer = renderer ? renderer : renderer_;
  
  if (!active_renderer) {
    // Arena not initialized yet - defer processing
    return;
  }
  
  if (texture_command_queue_.empty()) {
    return;
  }

  // Performance optimization: Batch process textures with limits
  // Process up to 8 texture operations per frame to avoid frame drops
  constexpr size_t kMaxTexturesPerFrame = 8;
  size_t processed = 0;
  
  auto it = texture_command_queue_.begin();
  while (it != texture_command_queue_.end() && processed < kMaxTexturesPerFrame) {
    const auto& command = *it;
    bool should_remove = true;
    
    // CRITICAL: Replicate the exact short-circuit evaluation from working code
    // We MUST check command.bitmap AND command.bitmap->surface() in one expression
    // to avoid dereferencing invalid pointers
    
    switch (command.type) {
      case TextureCommandType::CREATE: {
        // Create a new texture and update it with bitmap data
        // Use short-circuit evaluation - if bitmap is invalid, never call ->surface()
        if (command.bitmap && command.bitmap->surface() &&
            command.bitmap->surface()->format &&
            command.bitmap->is_active() && 
            command.bitmap->width() > 0 && command.bitmap->height() > 0) {
          
          try {
            auto texture = active_renderer->CreateTexture(command.bitmap->width(), 
                                                          command.bitmap->height());
            if (texture) {
              command.bitmap->set_texture(texture);
              active_renderer->UpdateTexture(texture, *command.bitmap);
              processed++;
            } else {
              should_remove = false;  // Retry next frame
            }
          } catch (...) {
            LOG_ERROR("Arena", "Exception during texture creation");
            should_remove = true;  // Remove bad command
          }
        }
        break;
      }
      case TextureCommandType::UPDATE: {
        // Update existing texture with current bitmap data
        if (command.bitmap->texture() && 
            command.bitmap->surface() && command.bitmap->surface()->format &&
            command.bitmap->is_active()) {
          try {
            active_renderer->UpdateTexture(command.bitmap->texture(), *command.bitmap);
            processed++;
          } catch (...) {
            LOG_ERROR("Arena", "Exception during texture update");
          }
        }
        break;
      }
      case TextureCommandType::DESTROY: {
        if (command.bitmap->texture()) {
          try {
            active_renderer->DestroyTexture(command.bitmap->texture());
            command.bitmap->set_texture(nullptr);
            processed++;
          } catch (...) {
            LOG_ERROR("Arena", "Exception during texture destruction");
          }
        }
        break;
      }
    }
    
    if (should_remove) {
      it = texture_command_queue_.erase(it);
    } else {
      ++it;
    }
  }
}

SDL_Surface* Arena::AllocateSurface(int width, int height, int depth, int format) {
  // Try to get a surface from the pool first
  for (auto it = surface_pool_.available_surfaces_.begin(); 
       it != surface_pool_.available_surfaces_.end(); ++it) {
    auto& info = surface_pool_.surface_info_[*it];
    if (std::get<0>(info) == width && std::get<1>(info) == height && 
        std::get<2>(info) == depth && std::get<3>(info) == format) {
      SDL_Surface* surface = *it;
      surface_pool_.available_surfaces_.erase(it);
      return surface;
    }
  }

  // Create new surface if none available in pool
  Uint32 sdl_format = GetSnesPixelFormat(format);
  SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, sdl_format);
  
  if (surface) {
    auto surface_ptr = std::unique_ptr<SDL_Surface, util::SDL_Surface_Deleter>(surface);
    surfaces_[surface] = std::move(surface_ptr);
    surface_pool_.surface_info_[surface] = std::make_tuple(width, height, depth, format);
  }
  
  return surface;
}

void Arena::FreeSurface(SDL_Surface* surface) {
  if (!surface) return;
  
  // Return surface to pool if space available
  if (surface_pool_.available_surfaces_.size() < surface_pool_.MAX_POOL_SIZE) {
    surface_pool_.available_surfaces_.push_back(surface);
  } else {
    // Remove from tracking maps
    surface_pool_.surface_info_.erase(surface);
    surfaces_.erase(surface);
  }
}

void Arena::Shutdown() {
  // Process any remaining batch updates before shutdown
  ProcessTextureQueue(renderer_);
  
  // Clear pool references first to prevent reuse during shutdown
  surface_pool_.available_surfaces_.clear();
  surface_pool_.surface_info_.clear();
  texture_pool_.available_textures_.clear();
  texture_pool_.texture_sizes_.clear();
  
  // CRITICAL FIX: Clear containers in reverse order to prevent cleanup issues
  // This ensures that dependent resources are freed before their dependencies
  textures_.clear();
  surfaces_.clear();
  
  // Clear any remaining queue items
  texture_command_queue_.clear();
}

void Arena::NotifySheetModified(int sheet_index) {
  if (sheet_index < 0 || sheet_index >= 223) {
    LOG_WARN("Arena", "Invalid sheet index %d, ignoring notification", sheet_index);
    return;
  }
  
  auto& sheet = gfx_sheets_[sheet_index];
  if (!sheet.is_active() || !sheet.surface()) {
    LOG_DEBUG("Arena", "Sheet %d not active or no surface, skipping notification", sheet_index);
    return;
  }
  
  // Queue texture update so changes are visible in all editors
  if (sheet.texture()) {
    QueueTextureCommand(TextureCommandType::UPDATE, &sheet);
    LOG_DEBUG("Arena", "Queued texture update for modified sheet %d", sheet_index);
  } else {
    // Create texture if it doesn't exist
    QueueTextureCommand(TextureCommandType::CREATE, &sheet);
    LOG_DEBUG("Arena", "Queued texture creation for modified sheet %d", sheet_index);
  }
}


}  // namespace gfx
}  // namespace yaze