#include "ui/NineSliceActor.h"

#include <SDL2/SDL.h>
#include <algorithm>
#include <array>

#include <frostbite2D/resource/npk_archive.h>

namespace frostbite2D {

namespace {

constexpr std::array<const char*, 9> kSliceNames = {
    "topLeft", "top",      "topRight", "left", "center",
    "right",   "bottomLeft", "bottom",   "bottomRight"};

struct AxisLayout {
  float start = 0.0f;
  float center = 0.0f;
  float end = 0.0f;
};

AxisLayout resolveAxis(float totalSize, float startSize, float endSize) {
  totalSize = std::max(totalSize, 0.0f);
  startSize = std::max(startSize, 0.0f);
  endSize = std::max(endSize, 0.0f);

  AxisLayout layout;
  float fixedSize = startSize + endSize;
  if (totalSize >= fixedSize) {
    layout.start = startSize;
    layout.center = totalSize - fixedSize;
    layout.end = endSize;
    return layout;
  }

  if (fixedSize <= 0.0f) {
    layout.center = totalSize;
    return layout;
  }

  float scale = totalSize / fixedSize;
  layout.start = startSize * scale;
  layout.end = endSize * scale;
  return layout;
}

} // namespace

NineSliceActor::NineSliceActor() {
  SetName("NineSliceActor");
}

Ptr<NineSliceActor> NineSliceActor::createFromNpk(const std::string& imgPath,
                                                  size_t startIndex) {
  auto actor = MakePtr<NineSliceActor>();
  if (!actor->SetSlices(imgPath, startIndex)) {
    return nullptr;
  }
  return actor;
}

bool NineSliceActor::SetSlices(const std::string& imgPath, size_t startIndex) {
  NpkArchive& npk = NpkArchive::get();
  auto imgOpt = npk.getImg(imgPath);
  if (!imgOpt) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                 "NineSliceActor: img not found: %s", imgPath.c_str());
    return false;
  }

  std::array<Ptr<Sprite>, SliceCount> newSlices;
  std::array<Vec2, SliceCount> newNaturalSizes = {};

  for (size_t i = 0; i < SliceCount; ++i) {
    size_t frameIndex = startIndex + i;
    auto frameOpt = npk.getImageFrame(*imgOpt, frameIndex);
    if (!frameOpt || frameOpt->data.empty()) {
      SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                   "NineSliceActor: frame %u missing for img %s",
                   static_cast<unsigned>(frameIndex), imgPath.c_str());
      return false;
    }

    auto sprite = Sprite::createFromNpk(imgPath, frameIndex);
    if (!sprite || !sprite->GetTexture()) {
      SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                   "NineSliceActor: failed to create sprite for frame %u of %s",
                   static_cast<unsigned>(frameIndex), imgPath.c_str());
      return false;
    }

    sprite->SetName(kSliceNames[i]);
    sprite->SetOffset(0.0f, 0.0f);
    sprite->SetPosition(0.0f, 0.0f);
    newNaturalSizes[i] = sprite->GetSize();
    newSlices[i] = sprite;
  }

  for (auto& sprite : sliceSprites_) {
    if (sprite && sprite->GetParent() == this) {
      RemoveChild(sprite);
    }
  }

  sliceSprites_ = newSlices;
  sliceNaturalSizes_ = newNaturalSizes;
  imgPath_ = imgPath;
  startIndex_ = startIndex;

  Vec2 targetSize = GetSize();
  if (targetSize.x <= 0.0f && targetSize.y <= 0.0f) {
    targetSize = getNaturalSize();
  } else {
    targetSize.x = std::max(targetSize.x, 0.0f);
    targetSize.y = std::max(targetSize.y, 0.0f);
  }

  for (auto& sprite : sliceSprites_) {
    AddChild(sprite);
  }

  Actor::SetSize(targetSize);
  updateLayout();
  return true;
}

void NineSliceActor::SetSize(const Vec2& size) {
  SetSize(size.x, size.y);
}

void NineSliceActor::SetSize(float width, float height) {
  Actor::SetSize(std::max(width, 0.0f), std::max(height, 0.0f));
  updateLayout();
}

Vec2 NineSliceActor::getNaturalSize() const {
  return Vec2(sliceNaturalSizes_[TopLeft].x + sliceNaturalSizes_[Center].x +
                  sliceNaturalSizes_[TopRight].x,
              sliceNaturalSizes_[TopLeft].y + sliceNaturalSizes_[Center].y +
                  sliceNaturalSizes_[BottomLeft].y);
}

void NineSliceActor::updateLayout() {
  if (imgPath_.empty()) {
    return;
  }

  AxisLayout horizontal = resolveAxis(GetSize().x, sliceNaturalSizes_[TopLeft].x,
                                      sliceNaturalSizes_[TopRight].x);
  AxisLayout vertical = resolveAxis(GetSize().y, sliceNaturalSizes_[TopLeft].y,
                                    sliceNaturalSizes_[BottomLeft].y);

  float leftX = 0.0f;
  float centerX = horizontal.start;
  float rightX = horizontal.start + horizontal.center;

  float topY = 0.0f;
  float middleY = vertical.start;
  float bottomY = vertical.start + vertical.center;

  applySliceLayout(sliceSprites_[TopLeft], Vec2(leftX, topY),
                   Vec2(horizontal.start, vertical.start));
  applySliceLayout(sliceSprites_[Top], Vec2(centerX, topY),
                   Vec2(horizontal.center, vertical.start));
  applySliceLayout(sliceSprites_[TopRight], Vec2(rightX, topY),
                   Vec2(horizontal.end, vertical.start));

  applySliceLayout(sliceSprites_[Left], Vec2(leftX, middleY),
                   Vec2(horizontal.start, vertical.center));
  applySliceLayout(sliceSprites_[Center], Vec2(centerX, middleY),
                   Vec2(horizontal.center, vertical.center));
  applySliceLayout(sliceSprites_[Right], Vec2(rightX, middleY),
                   Vec2(horizontal.end, vertical.center));

  applySliceLayout(sliceSprites_[BottomLeft], Vec2(leftX, bottomY),
                   Vec2(horizontal.start, vertical.end));
  applySliceLayout(sliceSprites_[Bottom], Vec2(centerX, bottomY),
                   Vec2(horizontal.center, vertical.end));
  applySliceLayout(sliceSprites_[BottomRight], Vec2(rightX, bottomY),
                   Vec2(horizontal.end, vertical.end));
}

void NineSliceActor::applySliceLayout(Ptr<Sprite> sprite, const Vec2& position,
                                      const Vec2& size) {
  if (!sprite) {
    return;
  }

  sprite->SetPosition(position);

  Vec2 clampedSize(std::max(size.x, 0.0f), std::max(size.y, 0.0f));
  if (clampedSize.x <= 0.0f || clampedSize.y <= 0.0f) {
    sprite->SetSize(0.0f, 0.0f);
    sprite->SetVisible(false);
    return;
  }

  sprite->SetVisible(true);
  sprite->SetSize(clampedSize);
}

} // namespace frostbite2D
