#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>

using namespace frostbite2D::animation;

namespace frostbite2D {

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);

  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);

    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) {
        auto subAni = MakePtr<Animation>(dir + ani.second.path);
        if (ani.second.layer.size() >= 2) {
          subAni->SetZOrder(ani.second.layer[1]);
        }
        AddChild(subAni);
      }
    }
  }

  FlushFrame(0);
}

void Animation::Update(float deltaTime) {
  if (usable_ && IsVisible()) {
    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;
          if (endCallback_) {
            endCallback_();
          }
          break;
        }
      }
    }
  } else {
    for (auto& sp : spriteFrames_) {
      sp->SetVisible(false);
    }
  }
}

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")) {
  }

  if (flagBuf.count("IMAGE_RATE")) {
    auto rate = std::get<Vec2>(flagBuf.at("IMAGE_RATE"));
    currentFrame_->SetScale(rate);
    currentFrame_->SetPosition(frameInfo.imgPos.x * rate.x, frameInfo.imgPos.y * rate.y);
  }

  if (flagBuf.count("GRAPHIC_EFFECT_LINEARDODGE")) {
    currentFrame_->SetBlendMode(BlendMode::Additive);
  }

  if (flagBuf.count("IMAGE_ROTATE")) {
    auto rotation = std::get<float>(flagBuf.at("IMAGE_ROTATE"));
    currentFrame_->SetRotation(rotation);
  }

  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() {
  return frames_[currentFrameIndex_];
}

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

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 posData = Vec2::lerp(oldData.imgPos, newData.imgPos, interRate);
    currentFrame_->SetPosition(posData);
  }

  {
    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);
    currentFrame_->SetScale(rateData);
  }

  {
    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);
    currentFrame_->SetRotation(angleData);
  }
}

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

} // namespace frostbite2D
