#include "entity.hpp"

#include "util/imgui.hpp"
#include "util/map.hpp"
#include "util/math.hpp"
#include "util/unordered_map.hpp"
#include "util/vector.hpp"

#include <algorithm>
#include <cmath>

#include <glm/glm.hpp>

#include "log.hpp"

#include <imgui.h>

namespace game
{
  constexpr auto CHARACTER_VALUE_ZERO = 0.0f;

  float value_clamp(float value, float minimum, float maximum)
  {
    auto rangeMin = std::min(minimum, maximum);
    auto rangeMax = std::max(minimum, maximum);
    if (!std::isfinite(value)) return rangeMin;
    return std::clamp(value, rangeMin, rangeMax);
  }

  int value_clamp(int value, int minimum, int maximum)
  {
    auto rangeMin = std::min(minimum, maximum);
    auto rangeMax = std::max(minimum, maximum);
    return std::clamp(value, rangeMin, rangeMax);
  }

  Entity::Override::Override(int _id, Entity::Anm2::Type _type, Entity::Override::Mode _mode,
                             Entity::FrameOptional _frame, std::optional<float> _time,
                             Entity::Override::Function _function, float _cycles)
      : id(_id), type(_type), mode(_mode), frame(_frame), time(_time), function(_function), cycles(_cycles)
  {
    frameBase = _frame;
    timeStart = _time;
  }

  Entity::Entity(const Entity&) = default;
  Entity::Entity(Entity&&) noexcept = default;
  Entity& Entity::operator=(const Entity&) = default;
  Entity& Entity::operator=(Entity&&) noexcept = default;

  Entity::Entity(Entity::Anm2 _anm2, glm::vec2 _position, Mode playMode, float startAtTime, int startAnimationIndex)
  {
    anm2_set(_anm2);
    position = _position;
    this->mode = playMode;
    this->startTime = startAtTime;
    if (startAnimationIndex != -1)
      play({.index = startAnimationIndex, .time = startAtTime, .mode = playMode});
    else
      play({.index = defaultAnimationID, .time = startAtTime, .mode = playMode});
    pending_animation_apply();
  }

  void Entity::anm2_set(const resource::xml::Anm2& anm2)
  {
    fps = anm2.fps;
    spritesheets = anm2.spritesheets;
    layers = anm2.layers;
    nulls = anm2.nulls;
    events = anm2.events;
    sounds = anm2.sounds;
    layerMap = anm2.layerMap;
    nullMap = anm2.nullMap;
    eventMap = anm2.eventMap;
    defaultAnimation = anm2.defaultAnimation;
    defaultAnimationID = anm2.defaultAnimationID;
    animations = anm2.animations;
    animationMap = anm2.animationMap;
    animationMapReverse = anm2.animationMapReverse;
    invalidAnimations.clear();
    path = anm2.path;
    isValid = anm2.isValid;
    flags = anm2.flags;
  }

  Entity::Anm2::Animation* Entity::animation_get(int index)
  {
    if (index == -1) index = animationIndex;
    if (animationMapReverse.contains(index)) return &animations[index];
    return nullptr;
  }

  Entity::Anm2::Animation* Entity::animation_get(const std::string& name)
  {
    if (animationMap.contains(name)) return &animations[animationMap[name]];
    return nullptr;
  }

  bool Entity::is_playing(const std::string& name)
  {
    if (name.empty())
      return state == PLAYING;
    else
      return state == PLAYING && animationMap[name] == animationIndex;
  }

  int Entity::animation_index_get(const std::string& name)
  {
    if (animationMap.contains(name)) return animationMap[name];
    return -1;
  }

  Entity::Anm2::Item* Entity::item_get(Entity::Anm2::Type type, int id, int checkAnimationIndex)
  {
    if (checkAnimationIndex == -1) checkAnimationIndex = this->animationIndex;
    if (auto animation = animation_get(checkAnimationIndex))
    {
      switch (type)
      {
        case Entity::Anm2::ROOT:
          return &animation->rootAnimation;
          break;
        case Entity::Anm2::LAYER:
          return util::unordered_map::find(animation->layerAnimations, id);
        case Entity::Anm2::NULL_:
          return util::map::find(animation->nullAnimations, id);
          break;
        case Entity::Anm2::TRIGGER:
          return &animation->triggers;
        default:
          return nullptr;
      }
    }

    return nullptr;
  }

  Entity::Anm2::Frame Entity::frame_generate(Entity::Anm2::Item& item, float frameTime, Entity::Anm2::Type type, int id)
  {
    auto override_handle = [&](Entity::Anm2::Frame& overrideFrame, Entity::Anm2::Type overrideType, int overrideID)
    {
      auto vec2_set = [](glm::vec2& destination, const Entity::Anm2::FrameOptional::Vec2& source)
      {
        if (source.x.has_value()) destination.x = *source.x;
        if (source.y.has_value()) destination.y = *source.y;
      };
      auto vec2_add = [](glm::vec2& destination, const Entity::Anm2::FrameOptional::Vec2& source)
      {
        if (source.x.has_value()) destination.x += *source.x;
        if (source.y.has_value()) destination.y += *source.y;
      };
      auto vec3_set = [](glm::vec3& destination, const Entity::Anm2::FrameOptional::Vec3& source)
      {
        if (source.x.has_value()) destination.x = *source.x;
        if (source.y.has_value()) destination.y = *source.y;
        if (source.z.has_value()) destination.z = *source.z;
      };
      auto vec4_set = [](glm::vec4& destination, const Entity::Anm2::FrameOptional::Vec4& source)
      {
        if (source.x.has_value()) destination.x = *source.x;
        if (source.y.has_value()) destination.y = *source.y;
        if (source.z.has_value()) destination.z = *source.z;
        if (source.w.has_value()) destination.w = *source.w;
      };

      for (auto& override : overrides)
      {
        if (override.type != overrideType) continue;
        if (override.id != overrideID) continue;

        auto& source = override.frame;

        switch (override.mode)
        {
          case Override::SET:
            vec2_set(overrideFrame.position, source.position);
            vec2_set(overrideFrame.pivot, source.pivot);
            vec2_set(overrideFrame.size, source.size);
            vec2_set(overrideFrame.scale, source.scale);
            vec2_set(overrideFrame.crop, source.crop);
            if (source.rotation.has_value()) overrideFrame.rotation = *source.rotation;
            vec4_set(overrideFrame.tint, source.tint);
            vec3_set(overrideFrame.colorOffset, source.colorOffset);
            if (source.isInterpolated.has_value())
            {
              overrideFrame.isInterpolated = *source.isInterpolated;
              overrideFrame.interpolation =
                  *source.isInterpolated ? Entity::Anm2::Frame::LINEAR : Entity::Anm2::Frame::INTERPOLATION_NONE;
            }
            if (source.interpolation.has_value())
            {
              overrideFrame.interpolation = *source.interpolation;
              overrideFrame.isInterpolated = overrideFrame.interpolation != Entity::Anm2::Frame::INTERPOLATION_NONE;
            }
            if (source.isVisible.has_value()) overrideFrame.isVisible = *source.isVisible;
            break;
          case Override::ADD:
            vec2_add(overrideFrame.scale, source.scale);
            break;
          default:
            break;
        }
      }
    };

    return Entity::Anm2::frame_generate(item, frameTime, type, id, override_handle);
  }

  void Entity::animation_start(int index, const AnimationRequest& request)
  {
    if (!util::vector::in_bounds(animations, index)) return;
    if (request.mode != PLAY_FORCE && index == animationIndex) return;

    this->playedEventID = -1;
    this->playedTriggers.clear();

    this->mode = request.mode;
    this->speedMultiplier = request.speedMultiplier;
    this->animationIndex = index;
    this->time = request.time;
    if (request.mode != SET) state = PLAYING;
  }

  void Entity::play(AnimationRequest request)
  {
    if (entityType == CHARACTER && isStageUp) return;
    if (!request.animation.empty()) request.animation += request.appendID;
    pendingAnimation = std::move(request);
  }

  bool Entity::is_animation_request() const { return !pendingAnimation.empty() || !nextAnimation.empty(); }

  void Entity::pending_animation_apply()
  {
    if (pendingAnimation.empty()) return;
    animation_request_apply(pendingAnimation);
    pendingAnimation = AnimationRequest{};
  }

  bool Entity::animation_request_apply(AnimationRequest& request)
  {
    if (request.empty()) return true;

    auto index = request.index;
    if (index == -1)
    {
      if (!animationMap.contains(request.animation))
      {
        if (!invalidAnimations.contains(request.animation))
        {
          logger.error(std::string("Animation \"" + request.animation + "\" does not exist! Unable to play!"));
          invalidAnimations.insert(request.animation);
        }
        return true;
      }
      index = animationMap.at(request.animation);
    }

    auto* currentAnm2Animation = animation_get();
    auto isCurrentLoop = currentAnm2Animation && currentAnm2Animation->isLoop;

    switch (request.transition)
    {
      case Transition::AFTER_CURRENT:
        if (state != STOPPED && !isCurrentLoop)
        {
          nextAnimation = request;
          return true;
        }
        break;
      case Transition::IF_IDLE:
        if (state != STOPPED && !isCurrentLoop) return true;
        break;
      case Transition::NOW:
      default:
        break;
    }

    auto canInterrupt = request.interrupt == Interrupt::ALWAYS || currentAnimation.interrupt != Interrupt::NEVER;
    if (!canInterrupt) return true;

    if (state == STOPPED || index != animationIndex || request.mode == PLAY_FORCE)
    {
      animation_start(index, request);
      currentAnimation = request;
    }

    return true;
  }

  void Entity::animation_update()
  {
    if (state == Entity::STOPPED)
    {
      if (!nextAnimation.empty())
      {
        auto request = nextAnimation;
        request.transition = Transition::NOW;
        nextAnimation = AnimationRequest{};
        animation_request_apply(request);
      }
      currentAnimation = AnimationRequest{};
    }

    if (auto animation = animation_get(); animation && animation->isLoop) currentAnimation = AnimationRequest{};

    auto animation = animation_get();
    if (!animation || animation->frameNum == 1 || mode == SET || state == STOPPED) return;

    playedEventID = -1;

    for (auto& trigger : animation->triggers.frames)
    {
      if (!playedTriggers.contains(trigger.atFrame) && time >= trigger.atFrame)
      {
        if (!trigger.soundIDs.empty())
        {
          auto id = trigger.soundIDs[(int)util::math::random_max((float)trigger.soundIDs.size())];
          if (auto sound = util::map::find(sounds, id)) sound->audio.play();
        }
        playedTriggers.insert((int)trigger.atFrame);
        playedEventID = trigger.eventID;
      }
    }

    auto increment = (fps / UPDATE_RATE) * speedMultiplier;
    time += increment;

    if (time >= animation->frameNum)
    {
      if (animation->isLoop)
        time = 0.0f;
      else
        state = STOPPED;

      playedTriggers.clear();
    }

    for (int i = 0; i < (int)overrides.size();)
    {
      auto& override_ = overrides[i];

      if (override_.function) override_.function(override_);

      if (override_.time.has_value())
      {
        *override_.time -= 1.0f;
        if (*override_.time <= 0.0f)
        {
          overrides.erase(overrides.begin() + i);
          continue;
        }
      }
      i++;
    }
  }

  glm::vec4 Entity::null_frame_rect(int nullID, int checkAnimationIndex, float checkTime)
  {
    constexpr glm::ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};

    if (nullID == -1) return glm::vec4(NAN);
    auto item = item_get(Entity::Anm2::NULL_, nullID, checkAnimationIndex);
    if (!item) return glm::vec4(NAN);

    if (checkAnimationIndex == -1) checkAnimationIndex = animationIndex;
    if (checkTime < 0.0f) checkTime = time;

    auto animation = animation_get(checkAnimationIndex);
    if (!animation) return glm::vec4(NAN);

    auto root = frame_generate(animation->rootAnimation, checkTime, Entity::Anm2::ROOT);

    auto frame = frame_generate(*item, checkTime, Entity::Anm2::NULL_, nullID);
    if (!frame.isVisible) return glm::vec4(NAN);

    auto rootModel = util::math::quad_model_no_size_get(root.position + position, root.pivot,
                                                        util::math::to_unit(root.scale), root.rotation);
    auto frameModel =
        util::math::quad_model_get(frame.scale, frame.position, frame.scale * 0.5f, glm::vec2(1.0f), frame.rotation);
    auto model = rootModel * frameModel;

    float minX = std::numeric_limits<float>::infinity();
    float minY = std::numeric_limits<float>::infinity();
    float maxX = -std::numeric_limits<float>::infinity();
    float maxY = -std::numeric_limits<float>::infinity();

    for (auto& corner : CORNERS)
    {
      glm::vec4 world = model * glm::vec4(corner, 0.0f, 1.0f);
      minX = std::min(minX, world.x);
      minY = std::min(minY, world.y);
      maxX = std::max(maxX, world.x);
      maxY = std::max(maxY, world.y);
    }

    return glm::vec4(minX, minY, maxX - minX, maxY - minY);
  }

  void Entity::render(resource::Shader& textureShader, resource::Shader& rectShader, Canvas& canvas)
  {
    if (!isVisible) return;

    auto animation = animation_get();
    if (!animation) return;

    auto root = frame_generate(animation->rootAnimation, time, Entity::Anm2::ROOT);

    auto rootModel = util::math::quad_model_no_size_get(root.position + position, root.pivot,
                                                        util::math::to_unit(root.scale), root.rotation);

    for (auto& i : animation->layerOrder)
    {
      auto& layerAnimation = animation->layerAnimations[i];
      if (!layerAnimation.isVisible) continue;

      auto layer = util::map::find(layers, i);
      if (!layer) continue;

      auto spritesheet = util::map::find(spritesheets, layer->spritesheetID);
      if (!spritesheet) continue;

      auto frame = frame_generate(layerAnimation, time, Entity::Anm2::LAYER, i);
      if (!frame.isVisible) continue;

      auto model = util::math::quad_model_get(frame.size, frame.position, frame.pivot, util::math::to_unit(frame.scale),
                                              frame.rotation);
      model = rootModel * model;

      auto& texture = spritesheet->texture;
      if (!texture.is_valid()) return;

      auto tint = frame.tint * root.tint;
      auto colorOffset = frame.colorOffset + root.colorOffset;

      auto inset = glm::vec2(0);
      auto uvMin = (frame.crop + inset) / glm::vec2(texture.size);
      auto uvMax = (frame.crop + frame.size - inset) / glm::vec2(texture.size);
      auto uvVertices = util::math::uv_vertices_get(uvMin, uvMax);

      canvas.texture_render(textureShader, texture.id, model, tint, colorOffset, uvVertices.data());
    }

    if (isShowNulls)
    {
      for (int i = 0; i < (int)animation->nullAnimations.size(); i++)
      {
        auto& nullAnimation = animation->nullAnimations[i];
        if (!nullAnimation.isVisible) continue;

        auto frame = frame_generate(nullAnimation, time, Entity::Anm2::NULL_, i);
        if (!frame.isVisible) continue;

        auto model = util::math::quad_model_get(frame.scale, frame.position, frame.scale * 0.5f, glm::vec2(1.0f),
                                                frame.rotation);
        model = rootModel * model;

        canvas.rect_render(rectShader, model);
      }
    }
  }

  glm::vec4 Entity::rect()
  {
    constexpr glm::ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};

    auto animation = animation_get();

    float minX = std::numeric_limits<float>::infinity();
    float minY = std::numeric_limits<float>::infinity();
    float maxX = -std::numeric_limits<float>::infinity();
    float maxY = -std::numeric_limits<float>::infinity();
    bool any = false;

    if (!animation) return glm::vec4(-NAN);

    for (float t = 0.0f; t < (float)animation->frameNum; t += 1.0f)
    {
      glm::mat4 transform(1.0f);

      auto root = frame_generate(animation->rootAnimation, t, Entity::Anm2::ROOT);
      transform *= util::math::quad_model_no_size_get(root.position + position, root.pivot,
                                                      util::math::to_unit(root.scale), root.rotation);

      for (auto& [id, layerAnimation] : animation->layerAnimations)
      {
        if (!layerAnimation.isVisible) continue;

        auto frame = frame_generate(layerAnimation, t, Entity::Anm2::LAYER, id);

        if (frame.size == glm::vec2() || !frame.isVisible) continue;

        auto layerTransform = transform * util::math::quad_model_get(frame.size, frame.position, frame.pivot,
                                                                     util::math::to_unit(frame.scale), frame.rotation);
        for (auto& corner : CORNERS)
        {
          glm::vec4 world = layerTransform * glm::vec4(corner, 0.0f, 1.0f);
          minX = std::min(minX, world.x);
          minY = std::min(minY, world.y);
          maxX = std::max(maxX, world.x);
          maxY = std::max(maxY, world.y);
          any = true;
        }
      }
    }

    if (!any) return glm::vec4(-NAN);
    return {minX, minY, maxX - minX, maxY - minY};
  }

  bool Entity::is_animation_finished()
  {
    if (auto animation = animation_get())
    {
      if (animation->isLoop) return true;
      if (time > animation->frameNum) return true;
    }
    return false;
  }

  void Entity::consume_played_event() { playedEventID = -1; }

  void Entity::update()
  {
    switch (entityType)
    {
      case CHARACTER:
        character_update();
        break;
      case CURSOR:
        if (cursorState == DEFAULT && pendingAnimation.empty())
          pendingAnimation = {.animation = defaultAnimation, .transition = Transition::IF_IDLE};
        pending_animation_apply();
        animation_update();
        cursorState = DEFAULT;
        position = positionOverride.value_or(util::imgui::to_vec2(ImGui::GetMousePos()));
        positionOverride.reset();
        break;
      case AUTOCURSOR:
        if (cursorState == DEFAULT && pendingAnimation.empty())
          pendingAnimation = {.animation = defaultAnimation, .transition = Transition::IF_IDLE};
        pending_animation_apply();
        animation_update();
        cursorState = DEFAULT;
        positionOverride.reset();
        break;
      case ITEM:
        pending_animation_apply();
        animation_update();
        position += velocity;
        *overrides[rotationOverrideID].frame.rotation += angularVelocity;
        break;
      case ACTOR:
      default:
        pending_animation_apply();
        animation_update();
        break;
    }

    if (isRemoveOnAnimationEnd && state == STOPPED) isToBeDeleted = true;
  }

  Entity::Entity(resource::xml::Character& _data, glm::ivec2 _position)
      : Entity(_data.root()->anm2Entry.anm2, _position)
  {
    entityType = CHARACTER;
    data = _data;
    auto* root = data.root();

    auto* saveRoot = data.save.root();
    auto* saveCharacter = saveRoot && saveRoot->type == resource::xml::Schema::Element::SAVE
                              ? data.save.child_get(*saveRoot, resource::xml::Schema::Element::CHARACTER)
                              : nullptr;

    capacity = saveCharacter ? saveCharacter->capacityCalories : (double)root->capacityCalories;
    weight = saveCharacter ? saveCharacter->weightKilograms : (double)root->weightKilograms;
    digestionRate = saveCharacter ? saveCharacter->digestionRate : (double)root->digestionRate;
    eatSpeed = saveCharacter ? saveCharacter->eatSpeedMultiplier : (double)root->eatSpeedMultiplier;

    calories = saveCharacter ? saveCharacter->calories : 0;

    isDigesting = saveCharacter ? saveCharacter->isDigesting : false;
    digestionProgress = saveCharacter ? saveCharacter->digestionProgress : 0;
    digestionTimer = saveCharacter ? saveCharacter->digestionTimer : 0;
    character_values_clamp();

    auto* overridesRoot = data.overrides_root();
    auto* talk = overridesRoot ? data.child_get(*overridesRoot, resource::xml::Schema::Element::TALK) : nullptr;
    auto talkSource = talk ? data.layer_id_get(talk->layerSource) : -1;
    auto talkDestination = talk ? data.layer_id_get(talk->layerDestination) : -1;
    talkOverrideID =
        util::vector::push_index(overrides, Entity::Override(talkDestination, Entity::Anm2::LAYER, Override::SET));
    for (auto& animation : animations)
    {
      if (!animation.layerAnimations.contains(talkSource))
        animationTalkDurations.emplace_back(-1);
      else
        animationTalkDurations.emplace_back(Entity::Anm2::item_length(&animation.layerAnimations.at(talkSource)));
    }

    auto* blink = overridesRoot ? data.child_get(*overridesRoot, resource::xml::Schema::Element::BLINK) : nullptr;
    auto blinkSource = blink ? data.layer_id_get(blink->layerSource) : -1;
    auto blinkDestination = blink ? data.layer_id_get(blink->layerDestination) : -1;
    blinkOverrideID =
        util::vector::push_index(overrides, Entity::Override(blinkDestination, Entity::Anm2::LAYER, Override::SET));
    for (auto& animation : animations)
    {
      if (!animation.layerAnimations.contains(blinkSource))
        animationBlinkDurations.emplace_back(-1);
      else
        animationBlinkDurations.emplace_back(Entity::Anm2::item_length(&animation.layerAnimations.at(blinkSource)));
    }

    auto expandAreas = data.expand_areas_get();
    for (int i = 0; i < (int)expandAreas.size(); i++)
    {
      auto* expandArea = expandAreas[i];
      expandAreaOverrideLayerIDs[i] = util::vector::push_index(
          overrides, Entity::Override(data.layer_id_get(expandArea->layer), Entity::Anm2::LAYER, Override::ADD));
      expandAreaOverrideNullIDs[i] = util::vector::push_index(
          overrides, Entity::Override(data.null_id_get(expandArea->null), Entity::Anm2::NULL_, Override::ADD));
    }

    auto interactAreas = data.interact_areas_get();
    for (int i = 0; i < (int)interactAreas.size(); i++)
    {
      auto* interactArea = interactAreas[i];
      auto layerID = data.layer_id_get(interactArea->layer);
      if (layerID != -1) interactAreaOverrides[i] = Entity::Override(layerID, Entity::Anm2::LAYER, Override::ADD);
    }

    stage = stage_get();
    expand_areas_apply();
  }

  float Entity::weight_get(util::measurement::System system)
  {
    return system == util::measurement::IMPERIAL ? weight * (double)util::measurement::KG_TO_LB : weight;
  }

  int Entity::stage_from_weight_get(float checkWeight) const
  {
    auto stages = data.stages_get();
    if (stages.empty()) return 0;
    if (checkWeight <= data.root()->weightKilograms) return 0;

    for (int i = 0; i < (int)stages.size(); i++)
      if (checkWeight < stages[i]->thresholdKilograms) return i;

    return stage_max_get();
  }

  int Entity::stage_get() const { return stage_from_weight_get(weight); }

  int Entity::stage_max_get() const { return (int)data.stages_get().size(); }

  int Entity::stage_count_get() const { return stage_max_get() + 1; }

  float Entity::stage_threshold_get(int stageIndex, util::measurement::System system) const
  {
    if (stageIndex == -1) stageIndex = this->stage;

    double threshold = data.root()->weightKilograms;

    auto stages = data.stages_get();
    if (!stages.empty())
    {
      if (stageIndex <= 0)
        threshold = data.root()->weightKilograms;
      else if (stageIndex >= stage_max_get())
        threshold = stages.back()->thresholdKilograms;
      else
        threshold = stages[stageIndex - 1]->thresholdKilograms;
    }

    return system == util::measurement::IMPERIAL ? threshold * (double)util::measurement::KG_TO_LB : threshold;
  }

  float Entity::stage_threshold_next_get(util::measurement::System system) const
  {
    return stage_threshold_get(stage + 1, system);
  }

  float Entity::stage_progress_get()
  {
    auto currentStage = stage_get();
    if (currentStage >= stage_max_get()) return 1.0;

    auto currentThreshold = stage_threshold_get(currentStage);
    auto nextThreshold = stage_threshold_get(currentStage + 1);
    if (nextThreshold <= currentThreshold) return 1.0;

    return (weight - currentThreshold) / (nextThreshold - currentThreshold);
  }

  float Entity::digestion_rate_get() { return digestionRate * UPDATE_RATE; }

  float Entity::max_capacity() const { return capacity * data.root()->capacityOverstuffedMultiplier; }
  bool Entity::is_over_capacity() const { return calories > capacity; }
  bool Entity::is_max_capacity() const { return calories >= max_capacity(); }
  float Entity::capacity_percent_get() const
  {
    auto capacityMax = max_capacity();
    return capacityMax > CHARACTER_VALUE_ZERO ? calories / capacityMax : CHARACTER_VALUE_ZERO;
  }

  void Entity::character_values_clamp()
  {
    if (entityType != CHARACTER) return;
    auto* root = data.root();
    if (!root) return;

    weight = value_clamp(weight, root->weightKilograms, root->weightMaxKilograms);
    capacity = value_clamp(capacity, root->capacityMinCalories, root->capacityMaxCalories);
    digestionRate = value_clamp(digestionRate, root->digestionRateMin, root->digestionRateMax);
    eatSpeed = value_clamp(eatSpeed, root->eatSpeedMinMultiplier, root->eatSpeedMaxMultiplier);
    calories = value_clamp(calories, CHARACTER_VALUE_ZERO, std::max(CHARACTER_VALUE_ZERO, max_capacity()));
    digestionProgress = value_clamp(digestionProgress, CHARACTER_VALUE_ZERO, DIGESTION_MAX);
    digestionTimer = value_clamp(digestionTimer, 0, std::max(0, root->digestionTimerMax));
    totalCaloriesConsumed = std::max(CHARACTER_VALUE_ZERO, totalCaloriesConsumed);
    totalFoodItemsEaten = std::max(0, totalFoodItemsEaten);
    stage = value_clamp(stage_get(), 0, stage_max_get());
  }

  std::string Entity::animation_append_id_get() const
  {
    if (stage <= 0) return {};
    auto stageIndex = stage - 1;
    auto stages = data.stages_get();
    if (stageIndex < 0 || stageIndex >= (int)stages.size()) return {};
    return stages[stageIndex]->animationAppendID;
  }

  void Entity::expand_areas_apply()
  {
    auto stageProgress = stage_progress_get();
    auto digestionTimerMax = std::max(1, data.root()->digestionTimerMax);
    auto capacityProgress =
        isDigesting ? capacity_percent_get() * (double)digestionTimer / digestionTimerMax : capacity_percent_get();

    auto expandAreas = data.expand_areas_get();
    for (int i = 0; i < (int)expandAreas.size(); i++)
    {
      auto* expandArea = expandAreas[i];
      auto& overrideLayer = overrides[expandAreaOverrideLayerIDs[i]];
      auto& overrideNull = overrides[expandAreaOverrideNullIDs[i]];

      auto stageScaleAdd = ((double)expandArea->scaleAdd * stageProgress) * 0.5;
      auto capacityScaleAdd = ((double)expandArea->scaleAdd * capacityProgress) * 0.5;

      auto scaleAdd =
          glm::clamp(glm::vec2(), glm::vec2(stageScaleAdd + capacityScaleAdd), glm::vec2(expandArea->scaleAdd));
      overrideLayer.frame.scale = scaleAdd;
      overrideNull.frame.scale = scaleAdd;
    }
  }

  void Entity::character_update()
  {
    character_values_clamp();

    isJustStageUp = false;
    isJustStageFinal = false;
    isJustDigested = false;

    if (state == Entity::STOPPED)
    {
      if (isStageUp)
      {
        if (stage >= (int)data.stages_get().size())
          isJustStageFinal = true;
        else
          isJustStageUp = true;

        isStageUp = false;
      }

      if (nextAnimation.empty() && !isTalking)
        play({.animation = idle_animation_get(), .appendID = animation_append_id_get()});
    }

    pending_animation_apply();
    animation_update();

    if (isDigesting)
    {
      digestionTimer--;

      if (digestionTimer <= 0)
      {
        auto increment = calories * data.root()->caloriesToKilogram;
        auto nextWeight = glm::clamp(weight + increment, data.root()->weightKilograms, data.root()->weightMaxKilograms);

        if (is_over_capacity())
        {
          auto capacityMaxCalorieDifference = (calories - capacity);
          auto overCapacityPercent = capacityMaxCalorieDifference / (max_capacity() - capacity);
          auto capacityIncrement = (double)((overCapacityPercent * data.root()->capacityIfOverStuffedOnDigestBonus) *
                                            capacityMaxCalorieDifference);
          capacity = glm::clamp(capacity + capacityIncrement, (double)data.root()->capacityMinCalories,
                                (double)data.root()->capacityMaxCalories);
        }

        totalCaloriesConsumed += calories;
        calories = 0;

        if (auto nextStage = stage_from_weight_get((double)nextWeight); nextStage > stage_from_weight_get(weight))
        {
          nextAnimation = AnimationRequest{};
          currentAnimation = AnimationRequest{};
          play({.animation = data.animation_get(resource::xml::Schema::Element::STAGE_UP),
                .appendID = animation_append_id_get(),
                .interrupt = Interrupt::NEVER});
          stage = nextStage;
          isStageUp = true;
        }
        else
          isJustDigested = true;

        weight = (double)nextWeight;

        isDigesting = false;
        digestionTimer = data.root()->digestionTimerMax;
        digestionProgress = 0;
      }
    }
    else
    {
      if (calories > 0) digestionProgress += digestionRate;
      if (digestionProgress >= DIGESTION_MAX)
      {
        isDigesting = true;
        digestionTimer = data.root()->digestionTimerMax;
        data.schema.root()->soundDigest.play();
      }
    }

    character_values_clamp();

    if (util::math::random_percent_roll(util::math::to_percent(
            data.root()->gurgleChance * (capacity_percent_get() * data.root()->gurgleChanceFromCapacityMultiplier))))
      data.schema.root()->soundGurgle.play();

    stage = stage_get();
    expand_areas_apply();

    auto& talkOverride = overrides[talkOverrideID];

    if (isTalking)
    {
      auto talk_reset = [&]()
      {
        isTalking = false;
        talkTimer = 0.0;
        talkOverride.frame = FrameOptional();
      };

      auto* talk = data.overrides_root() ? data.child_get(*data.overrides_root(), resource::xml::Schema::Element::TALK)
                                         : nullptr;
      auto id = talk ? data.layer_id_get(talk->layerSource) : -1;
      auto& layerAnimations = animation_get()->layerAnimations;

      if (layerAnimations.contains(id) && animationTalkDurations.at(animationIndex) > -1)
      {
        auto& layerAnimation = layerAnimations.at(id);

        if (!layerAnimation.frames.empty())
        {
          auto frame = frame_generate(layerAnimation, talkTimer, Entity::Anm2::LAYER, id);

          talkOverride.frame.crop = frame.crop;
          talkOverride.frame.size = frame.size;
          talkOverride.frame.pivot = frame.pivot;

          talkTimer += 0.5f;

          if (talkTimer > animationTalkDurations.at(animationIndex)) talkTimer = 0.0;
        }
        else
          talk_reset();
      }
      else
        talk_reset();
    }
    else
      talkOverride.frame = {};

    auto& blinkOverride = overrides[blinkOverrideID];

    if (auto blinkDuration = animationBlinkDurations[animationIndex]; blinkDuration != 1)
    {
      if (util::math::random_percent_roll(data.root()->blinkChance)) isBlinking = true;

      if (isBlinking)
      {
        auto blink_reset = [&]()
        {
          isBlinking = false;
          blinkTimer = 0.0;
          blinkOverride.frame = FrameOptional();
        };

        auto* blink = data.overrides_root()
                          ? data.child_get(*data.overrides_root(), resource::xml::Schema::Element::BLINK)
                          : nullptr;
        auto id = blink ? data.layer_id_get(blink->layerSource) : -1;
        auto& layerAnimations = animation_get()->layerAnimations;

        if (layerAnimations.contains(id))
        {
          auto& layerAnimation = layerAnimations.at(id);

          if (!layerAnimation.frames.empty())
          {
            auto frame = frame_generate(layerAnimation, blinkTimer, Entity::Anm2::LAYER, id);

            blinkOverride.frame.crop = frame.crop;
            blinkOverride.frame.size = frame.size;
            blinkOverride.frame.pivot = frame.pivot;

            blinkTimer += 0.5f;

            if (blinkTimer >= blinkDuration) blink_reset();
          }
          else
            blink_reset();
        }
        else
          blink_reset();
      }
    }
  }

  std::string Entity::idle_animation_get() const
  {
    if (isStageUp) return {};
    auto idle = data.animation_get(resource::xml::Schema::Element::IDLE);
    auto idleFull = data.animation_get(resource::xml::Schema::Element::IDLE_FULL);
    return is_over_capacity() && !idleFull.empty() ? idleFull : idle;
  }

  void Entity::spritesheet_set(SpritesheetType type)
  {
    switch (type)
    {
      case NORMAL:
        if (auto* alternate = data.alternate_spritesheet())
          spritesheets.at(alternate->id).texture = data.root()->anm2Entry.anm2.spritesheets.at(alternate->id).texture;
        break;
      case ALTERNATE:
        if (auto* alternate = data.alternate_spritesheet())
          spritesheets.at(alternate->id).texture = alternate->textureEntry.texture;
        break;
      default:
        break;
    }

    spritesheetType = type;
  }

  Entity::Entity(Entity::Anm2 _anm2, glm::ivec2 _position, int _schemaID, int _durability, int _animationIndex,
                 glm::vec2 _velocity, float _rotation)
  {
    anm2_set(_anm2);
    position = _position;
    schemaID = _schemaID;
    durability = _durability;
    velocity = _velocity;
    entityType = ITEM;
    mode = SET;
    startTime = 0.0f;
    if (_animationIndex != -1)
      play({.index = _animationIndex, .mode = SET});
    else
      play({.index = defaultAnimationID, .mode = SET});
    pending_animation_apply();

    rotationOverrideID = util::vector::push_index(
        overrides, Override(-1, Entity::Anm2::ROOT, Override::SET, Entity::Anm2::FrameOptional{.rotation = _rotation}));
  }

}
