#include "stage_renderer.h"

#include "../core/game_config.h"

#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/graphics/types.h>
#include <frostbite2D/graphics/texture.h>

namespace ns_game {

namespace {

void drawRect(const frostbite2D::Rect& rect, const frostbite2D::Color& color) {
  frostbite2D::Renderer::get().drawQuad(rect, color);
}

void drawTexture(const frostbite2D::Rect& bounds,
                 const frostbite2D::Rect& sourceRect,
                 frostbite2D::Ptr<frostbite2D::Texture> texture,
                 const frostbite2D::Color& tint) {
  frostbite2D::Renderer& renderer = frostbite2D::Renderer::get();
  frostbite2D::Quad quad = frostbite2D::Quad::createTextured(
      bounds, sourceRect,
      frostbite2D::Vec2(static_cast<float>(texture->getWidth()),
                        static_cast<float>(texture->getHeight())),
      tint.r, tint.g, tint.b, tint.a, renderer.shouldShrinkSubTextureUVs());
  auto* shader = renderer.getShaderManager().getShader("sprite");
  if (shader) {
    shader->use();
  }
  renderer.getBatch().submitQuad(quad, frostbite2D::Transform2D::identity(),
                                 texture, shader,
                                 frostbite2D::BlendMode::Normal);
}

float worldWidthOf(const LevelDefinition& level) {
  return level.collision.levelRight - level.collision.levelLeft;
}

} // namespace

void StageRenderer::Render(const LevelDefinition& level,
                           const DebugFlags& debugFlags) const {
  drawBackground(level);
  for (const StageLayer& layer : level.layers) {
    if (layer.role == StageLayerRole::PropsFront) {
      continue;
    }
    drawLayer(level, layer);
  }
  if (debugFlags.enabled && debugFlags.showStageGrid) {
    drawGrid(level);
  }
}

void StageRenderer::RenderForeground(const LevelDefinition& level) const {
  for (const StageLayer& layer : level.layers) {
    if (layer.role == StageLayerRole::PropsFront) {
      drawLayer(level, layer);
    }
  }
}

void StageRenderer::drawBackground(const LevelDefinition& level) const {
  drawRect(frostbite2D::Rect(level.collision.levelLeft, 0.0f,
                             worldWidthOf(level), config::kVirtualHeight),
           frostbite2D::Color(0.10f, 0.12f, 0.14f, 1.0f));
}

void StageRenderer::drawGrid(const LevelDefinition& level) const {
  const float worldWidth = worldWidthOf(level);
  for (float x = level.collision.levelLeft; x <= level.collision.levelRight;
       x += 80.0f) {
    drawRect(frostbite2D::Rect(x, 0.0f, 1.0f, config::kVirtualHeight),
             frostbite2D::Color(0.20f, 0.22f, 0.25f, 1.0f));
  }
  for (float y = 0.0f; y <= config::kVirtualHeight; y += 80.0f) {
    drawRect(frostbite2D::Rect(level.collision.levelLeft, y, worldWidth, 1.0f),
             frostbite2D::Color(0.20f, 0.22f, 0.25f, 1.0f));
  }
}

void StageRenderer::drawLayer(const LevelDefinition& level,
                              const StageLayer& layer) const {
  (void)level;
  frostbite2D::Vec2 parallaxOffset;
  if (auto* camera = frostbite2D::Renderer::get().getCamera()) {
    const frostbite2D::Vec2 cameraPos = camera->getPosition();
    parallaxOffset.x = cameraPos.x * (1.0f - layer.parallax);
    parallaxOffset.y = cameraPos.y * (1.0f - layer.parallax);
  }

  for (const StageRect& rect : layer.rects) {
    frostbite2D::Rect bounds(rect.bounds.left() + parallaxOffset.x,
                             rect.bounds.top() + parallaxOffset.y,
                             rect.bounds.width(), rect.bounds.height());
    drawStageRect(rect, bounds);
    if (layer.role == StageLayerRole::LevelVisual) {
      drawRect(frostbite2D::Rect(bounds.left(), bounds.top(), bounds.width(),
                                 4.0f),
               frostbite2D::Color(0.91f, 0.95f, 0.98f, 1.0f));
    }
  }
}

void StageRenderer::drawStageRect(const StageRect& rect,
                                  const frostbite2D::Rect& bounds) const {
  if (!rect.source.hasTexture()) {
    drawRect(bounds, rect.color);
    return;
  }

  frostbite2D::Ptr<frostbite2D::Texture> texture =
      getTexture(rect.source.texturePath);
  if (!texture) {
    drawRect(bounds, rect.color);
    return;
  }

  if (rect.source.useSourceRect) {
    drawTexture(bounds, rect.source.sourceRect, texture, rect.source.tint);
    return;
  }

  drawTexture(bounds,
              frostbite2D::Rect(0.0f, 0.0f,
                                static_cast<float>(texture->getWidth()),
                                static_cast<float>(texture->getHeight())),
              texture, rect.source.tint);
}

frostbite2D::Ptr<frostbite2D::Texture>
StageRenderer::getTexture(const std::string& texturePath) const {
  const auto existing = textureCache_.find(texturePath);
  if (existing != textureCache_.end()) {
    return existing->second;
  }

  frostbite2D::Ptr<frostbite2D::Texture> texture =
      frostbite2D::Texture::loadFromFile(texturePath);
  if (texture) {
    texture->setSampling(frostbite2D::TextureSampling::PixelArt);
  }
  textureCache_[texturePath] = texture;
  return texture;
}

} // namespace ns_game
