#include <frostbite2D/animation/animation.h>
#include <frostbite2D/2d/sprite.h>
#include <frostbite2D/resource/npk_archive.h>
#include <frostbite2D/resource/pvf_archive.h>
#include <frostbite2D/types/type_math.h>
#include <SDL2/SDL.h>
#include <algorithm>
#include <cmath>

using namespace frostbite2D::animation;

namespace frostbite2D {
namespace {

struct BoundsAccumulator {
  bool valid = false;
  float minX = 0.0f;
  float minY = 0.0f;
  float maxX = 0.0f;
  float maxY = 0.0f;

  void includePoint(const Vec2& point) {
    if (!valid) {
      minX = maxX = point.x;
      minY = maxY = point.y;
      valid = true;
      return;
    }

    minX = std::min(minX, point.x);
    minY = std::min(minY, point.y);
    maxX = std::max(maxX, point.x);
    maxY = std::max(maxY, point.y);
  }

  void includeRect(const Rect& rect) {
    if (rect.empty()) {
      return;
    }

    includePoint(Vec2(rect.left(), rect.top()));
    includePoint(Vec2(rect.right(), rect.top()));
    includePoint(Vec2(rect.left(), rect.bottom()));
    includePoint(Vec2(rect.right(), rect.bottom()));
  }

  Rect toRect() const {
    if (!valid) {
      return Rect();
    }
    return Rect(minX, minY, maxX - minX, maxY - minY);
  }
};

Vec2 resolveFramePosition(const AniFrame& frameInfo, const Vec2& imageRate) {
  return Vec2(frameInfo.imgPos.x * imageRate.x, frameInfo.imgPos.y * imageRate.y);
}

float resolveFrameRotation(const AniFrame& frameInfo) {
  if (frameInfo.flag.count("IMAGE_ROTATE")) {
    return std::get<float>(frameInfo.flag.at("IMAGE_ROTATE"));
  }
  return 0.0f;
}

Vec2 resolveImageRate(const AniFrame& frameInfo) {
  if (frameInfo.flag.count("IMAGE_RATE")) {
    return std::get<Vec2>(frameInfo.flag.at("IMAGE_RATE"));
  }
  return Vec2::One();
}

BlendMode resolveBlendMode(const AniFrame& frameInfo) {
  if (frameInfo.flag.count("GRAPHIC_EFFECT_LINEARDODGE")) {
    return BlendMode::Additive;
  }
  return BlendMode::Normal;
}

Rect transformRectBounds(const Rect& rect, const Transform2D& transform) {
  BoundsAccumulator bounds;
  bounds.includePoint(transform.transformPoint(Vec2(rect.left(), rect.top())));
  bounds.includePoint(transform.transformPoint(Vec2(rect.right(), rect.top())));
  bounds.includePoint(transform.transformPoint(Vec2(rect.left(), rect.bottom())));
  bounds.includePoint(transform.transformPoint(Vec2(rect.right(), rect.bottom())));
  return bounds.toRect();
}

bool computeAnimationFrameBounds(const Animation& animation,
                                 int frameIndex,
                                 int direction,
                                 Rect& outBounds) {
  if (frameIndex < 0 ||
      frameIndex >= static_cast<int>(animation.frames_.size()) ||
      frameIndex >= static_cast<int>(animation.spriteFrames_.size()) ||
      frameIndex >= static_cast<int>(animation.spriteFrameOffsets_.size())) {
    return false;
  }

  const AniFrame& frameInfo = animation.frames_[frameIndex];
  const auto& sprite = animation.spriteFrames_[frameIndex];
  if (!sprite) {
    return false;
  }

  Vec2 spriteSize = sprite->GetSize();
  if (spriteSize.x <= 0.0f || spriteSize.y <= 0.0f) {
    return false;
  }

  Vec2 imageRate = resolveImageRate(frameInfo);
  float rotation = resolveFrameRotation(frameInfo);
  Vec2 framePos = resolveFramePosition(frameInfo, imageRate);
  Vec2 offset = animation.spriteFrameOffsets_[frameIndex];
  float mirroredRotation = rotation;
  if (direction < 0) {
    float visualWidth = spriteSize.x * std::abs(imageRate.x);
    framePos.x = -framePos.x;
    offset.x = -offset.x - visualWidth;
    mirroredRotation = -rotation;
  }

  Transform2D transform =
      Transform2D::translation(offset) *
      Transform2D::translation(framePos) *
      Transform2D::rotation(mirroredRotation) *
      Transform2D::scaling(imageRate.x, imageRate.y);
  outBounds = transformRectBounds(Rect(0.0f, 0.0f, spriteSize.x, spriteSize.y),
                                  transform);
  return !outBounds.empty();
}

void combineHash(uint64& seed, uint64 value) {
  constexpr uint64 kOffset = 0x9e3779b97f4a7c15ULL;
  seed ^= value + kOffset + (seed << 6) + (seed >> 2);
}

} // namespace

Animation::Animation() {
}

Animation::Animation(const std::string& aniPath) {
  Init(aniPath);
}

Animation::Animation(const std::string& aniPath,
                               std::function<std::string(std::string, ReplaceData)> additionalOptions,
                               ReplaceData data)
  : additionalOptions_(additionalOptions)
  , additionalOptionsData_(data) {
  Init(aniPath);
}

Animation::~Animation() {
}

void Animation::Init(const std::string& aniPath) {
  auto info = animation::parseAniFromPvf(aniPath);
  if (!info) {
    SDL_Log("Failed to load animation: %s", aniPath.c_str());
    usable_ = false;
    return;
  }

  aniPath_ = aniPath;
  animationFlag_ = info->flag;
  frames_ = std::move(info->frames);
  spriteFrames_.clear();
  spriteFrameOffsets_.clear();

  if (animationFlag_.count("LOOP")) {
    isLooping_ = true;
  } else {
    isLooping_ = false;
  }

  for (size_t i = 0; i < frames_.size(); i++) {
    AniFrame& frameObj = frames_[i];
    Ptr<Sprite> spriteObj = nullptr;

    if (!frameObj.imgPath.empty()) {
      if (additionalOptions_) {
        frameObj.imgPath = additionalOptions_(frameObj.imgPath, additionalOptionsData_);
      }

      auto sprite = Sprite::createFromNpk(frameObj.imgPath, frameObj.imgIndex);
      if (sprite) {
        sprite->SetPosition(frameObj.imgPos);
        sprite->SetVisible(false);
        spriteObj = sprite;
      }
    }

    if (!spriteObj) {
      spriteObj = MakePtr<Sprite>();
      spriteObj->SetVisible(false);
    }

    spriteFrames_.push_back(spriteObj);
    spriteFrameOffsets_.push_back(spriteObj->GetOffset());

    auto spriteSize = spriteObj->GetSize();
    if (maxSize_.x < spriteSize.x) maxSize_.x = spriteSize.x;
    if (maxSize_.y < spriteSize.y) maxSize_.y = spriteSize.y;

    AddChild(spriteObj);
  }

  if (currentFrameTime_ == 0.0f && !spriteFrames_.empty()) {
    SetSize(spriteFrames_[0]->GetSize());
  }

  totalFrameCount_ = static_cast<int>(frames_.size());

  auto alsPath = aniPath + ".als";
  if (PvfArchive::get().hasFile(alsPath)) {
    auto alsInfo = animation::parseAlsFromPvf(alsPath);
    if (alsInfo && !alsInfo->aniList.empty()) {
      size_t lastSlash = aniPath.find_last_of('/');
      std::string dir = (lastSlash != std::string::npos) ? aniPath.substr(0, lastSlash + 1) : "";

      for (auto& ani : alsInfo->aniList) {
        std::string subAniPath = dir + ani.second.path;
        auto subAni = MakePtr<Animation>(subAniPath);
        if (!subAni || !subAni->IsUsable()) {
          SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                      "Animation: failed to load ALS child animation %s",
                      subAniPath.c_str());
          continue;
        }
        if (ani.second.layer.size() >= 2) {
          subAni->SetZOrder(ani.second.layer[1]);
        }
        subAni->SetDirection(direction_);
        AddChild(subAni);
      }
    }
  }

  FlushFrame(0);
}

void Animation::OnUpdate(float deltaTime) {
  if (!IsVisible()) {
    for (auto& sp : spriteFrames_) {
      sp->SetVisible(false);
    }
    return;
  }

  if (!usable_) {
    if (currentFrame_) {
      currentFrame_->SetVisible(true);
    }
    return;
  }

  float dtMs = deltaTime * 1000.0f;
  currentFrameTime_ += dtMs;

  while (currentFrameTime_ >= nextFrameDelay_) {
    currentFrameTime_ -= nextFrameDelay_;

    if (currentFrameIndex_ < (totalFrameCount_ - 1)) {
      FlushFrame(currentFrameIndex_ + 1);
    } else {
      if (isLooping_) {
        FlushFrame(0);
      } else {
        usable_ = false;
        currentFrameTime_ = 0.0f;
        if (currentFrame_) {
          currentFrame_->SetVisible(true);
        }
        if (endCallback_) {
          endCallback_();
        }
        break;
      }
    }
  }
}

void Animation::OnAdded(Actor* parent) {
  FlushFrame(0);
}

void Animation::SetVisible(bool visible) {
  if (visible) {
    if (currentFrame_) {
      currentFrame_->SetVisible(true);
    }
  }
  Actor::SetVisible(visible);
}

void Animation::FlushFrame(int index) {
  if (currentFrame_) {
    currentFrame_->SetVisible(false);
  }

  currentFrameIndex_ = index;
  currentFrame_ = spriteFrames_[currentFrameIndex_];
  currentFrame_->SetVisible(true);

  AniFrame& frameInfo = frames_[currentFrameIndex_];
  nextFrameDelay_ = static_cast<float>(frameInfo.delay);

  auto& flagBuf = frameInfo.flag;

  if (flagBuf.count("SET_FLAG")) {
    if (changeFrameCallback_) {
      auto flagValue = std::get<int>(flagBuf.at("SET_FLAG"));
      changeFrameCallback_(flagValue);
    }
  }

  if (flagBuf.count("PLAY_SOUND")) {
  }

  Vec2 imageRate = resolveImageRate(frameInfo);
  float rotation = resolveFrameRotation(frameInfo);
  BlendMode blendMode = resolveBlendMode(frameInfo);
  Vec2 framePos = resolveFramePosition(frameInfo, imageRate);
  ApplyFramePresentation(framePos, imageRate, rotation, blendMode);

  if (flagBuf.count("INTERPOLATION")) {
    if (interpolationData_.empty()) {
      interpolationData_.push_back(frames_[currentFrameIndex_]);
      if (currentFrameIndex_ + 1 < static_cast<int>(frames_.size())) {
        interpolationData_.push_back(frames_[currentFrameIndex_ + 1]);
      }
    }
  } else {
    if (!interpolationData_.empty()) {
      interpolationData_.clear();
    }
  }

  SetSize(currentFrame_->GetSize());
}

void Animation::Reset() {
  usable_ = true;
  currentFrameTime_ = 0.0f;
  FlushFrame(0);
}

AniFrame Animation::GetCurrentFrameInfo() const {
  return frames_[currentFrameIndex_];
}

void Animation::SetFrameIndex(int index) {
  FlushFrame(index);
  currentFrameTime_ = 0.0f;
}

void Animation::SetDirection(int direction) {
  direction_ = direction >= 0 ? 1 : -1;

  if (currentFrame_ && currentFrameIndex_ >= 0 &&
      currentFrameIndex_ < static_cast<int>(frames_.size())) {
    const AniFrame& frameInfo = frames_[currentFrameIndex_];
    Vec2 imageRate = resolveImageRate(frameInfo);
    float rotation = resolveFrameRotation(frameInfo);
    BlendMode blendMode = resolveBlendMode(frameInfo);
    Vec2 framePos = resolveFramePosition(frameInfo, imageRate);
    ApplyFramePresentation(framePos, imageRate, rotation, blendMode);
  }

  for (const auto& child : GetChildren()) {
    auto* subAnimation = dynamic_cast<Animation*>(child.Get());
    if (subAnimation) {
      subAnimation->SetDirection(direction_);
    }
  }
}

void Animation::InterpolationLogic() {
  if (interpolationData_.empty()) {
    return;
  }

  float interRate = math::lerp(0.0f, 1.0f, currentFrameTime_ / nextFrameDelay_);

  AniFrame& oldData = interpolationData_[0];
  AniFrame& newData = interpolationData_[1];

  {
    std::vector<int> oldRgbaData = {255, 255, 255, 250};
    std::vector<int> newRgbaData = {255, 255, 255, 250};

    if (oldData.flag.count("RGBA")) {
      oldRgbaData = std::get<std::vector<int>>(oldData.flag.at("RGBA"));
    }
    if (newData.flag.count("RGBA")) {
      newRgbaData = std::get<std::vector<int>>(newData.flag.at("RGBA"));
    }

    std::vector<int> rgbaData = {
        static_cast<int>(oldRgbaData[0] + (newRgbaData[0] - oldRgbaData[0]) * interRate),
        static_cast<int>(oldRgbaData[1] + (newRgbaData[1] - oldRgbaData[1]) * interRate),
        static_cast<int>(oldRgbaData[2] + (newRgbaData[2] - oldRgbaData[2]) * interRate),
        static_cast<int>(oldRgbaData[3] + (newRgbaData[3] - oldRgbaData[3]) * interRate)};

    currentFrame_->SetColor(
        static_cast<float>(rgbaData[0]) / 255.0f,
        static_cast<float>(rgbaData[1]) / 255.0f,
        static_cast<float>(rgbaData[2]) / 255.0f,
        static_cast<float>(rgbaData[3]) / 255.0f);
  }

  {
    Vec2 oldRateData = Vec2::One();
    Vec2 newRateData = Vec2::One();

    if (oldData.flag.count("IMAGE_RATE")) {
      oldRateData = std::get<Vec2>(oldData.flag.at("IMAGE_RATE"));
    }
    if (newData.flag.count("IMAGE_RATE")) {
      newRateData = std::get<Vec2>(newData.flag.at("IMAGE_RATE"));
    }

    Vec2 rateData = Vec2::lerp(oldRateData, newRateData, interRate);
    float oldAngleData = 0.0f;
    float newAngleData = 0.0f;

    if (oldData.flag.count("IMAGE_ROTATE")) {
      oldAngleData = std::get<float>(oldData.flag.at("IMAGE_ROTATE"));
    }
    if (newData.flag.count("IMAGE_ROTATE")) {
      newAngleData = std::get<float>(newData.flag.at("IMAGE_ROTATE"));
    }

    float angleData = math::lerp(oldAngleData, newAngleData, interRate);
    Vec2 posData = Vec2::lerp(oldData.imgPos, newData.imgPos, interRate);
    Vec2 framePos(posData.x * rateData.x, posData.y * rateData.y);
    ApplyFramePresentation(framePos, rateData, angleData, currentFrame_->GetBlendMode());
  }
}

Vec2 Animation::GetMaxSize() const {
  return maxSize_;
}

bool Animation::GetStaticLocalBounds(Rect& outBounds) const {
  BoundsAccumulator bounds;
  Rect directionalBounds;
  if (GetStaticLocalBounds(1, directionalBounds)) {
    bounds.includeRect(directionalBounds);
  }
  if (GetStaticLocalBounds(-1, directionalBounds)) {
    bounds.includeRect(directionalBounds);
  }

  outBounds = bounds.toRect();
  return bounds.valid;
}

bool Animation::GetStaticLocalBounds(int direction, Rect& outBounds) const {
  BoundsAccumulator bounds;

  for (int i = 0; i < static_cast<int>(frames_.size()); ++i) {
    Rect frameBounds;
    if (computeAnimationFrameBounds(*this, i, direction, frameBounds)) {
      bounds.includeRect(frameBounds);
    }
  }

  for (const auto& child : GetChildren()) {
    auto* subAnimation = dynamic_cast<Animation*>(child.Get());
    if (!subAnimation) {
      continue;
    }

    Rect childBounds;
    if (!subAnimation->GetStaticLocalBounds(direction, childBounds)) {
      continue;
    }

    bounds.includeRect(transformRectBounds(childBounds, subAnimation->GetLocalTransform()));
  }

  outBounds = bounds.toRect();
  return bounds.valid;
}

uint64 Animation::GetRenderSignature() const {
  uint64 signature = 1469598103934665603ULL;
  combineHash(signature, static_cast<uint64>(usable_));
  combineHash(signature, static_cast<uint64>(direction_ >= 0 ? 1 : 0));
  combineHash(signature, static_cast<uint64>(std::max(currentFrameIndex_, 0)));
  combineHash(signature, static_cast<uint64>(std::max(totalFrameCount_, 0)));

  for (const auto& child : GetChildren()) {
    auto* subAnimation = dynamic_cast<Animation*>(child.Get());
    if (!subAnimation) {
      continue;
    }
    combineHash(signature, subAnimation->GetRenderSignature());
  }

  return signature;
}

void Animation::ApplyFramePresentation(const Vec2& framePos,
                                       const Vec2& imageRate,
                                       float rotation,
                                       BlendMode blendMode) {
  if (!currentFrame_ || currentFrameIndex_ < 0 ||
      currentFrameIndex_ >= static_cast<int>(spriteFrameOffsets_.size())) {
    return;
  }

  currentFrame_->SetScale(imageRate);
  currentFrame_->SetBlendMode(blendMode);
  currentFrame_->SetFlippedX(direction_ < 0);

  Vec2 offset = spriteFrameOffsets_[currentFrameIndex_];
  Vec2 position = framePos;
  float mirroredRotation = rotation;
  if (direction_ < 0) {
    // 朝左时要同时镜像帧位置和素材锚点偏移，避免多层时装各自翻转后错位。
    float visualWidth = currentFrame_->GetSize().x * std::abs(imageRate.x);
    position.x = -position.x;
    offset.x = -offset.x - visualWidth;
    mirroredRotation = -rotation;
  }

  currentFrame_->SetOffset(offset);
  currentFrame_->SetPosition(position);
  currentFrame_->SetRotation(mirroredRotation);
}

} // namespace frostbite2D
