#pragma once

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <vector>

namespace smartcut::core {

class FrameNavigation {
 public:
  FrameNavigation(std::size_t frame_count, std::vector<std::size_t> keyframes,
                  std::vector<std::int64_t> frame_pts = {}, std::int64_t time_base_num = 1,
                  std::int64_t time_base_den = 1)
      : frame_count_(frame_count), keyframes_(std::move(keyframes)), frame_pts_(std::move(frame_pts)),
        time_base_num_(time_base_num), time_base_den_(time_base_den) {
    keyframes_.erase(std::unique(keyframes_.begin(), keyframes_.end()), keyframes_.end());
    keyframes_.erase(std::remove_if(keyframes_.begin(), keyframes_.end(), [frame_count](std::size_t frame) { return frame >= frame_count; }), keyframes_.end());
  }
  std::size_t current() const { return current_; }
  std::size_t frame_count() const { return frame_count_; }
  bool set_frame(std::size_t frame) { if (frame >= frame_count_) return false; current_ = frame; return true; }
  std::size_t step(std::ptrdiff_t delta) {
    if (frame_count_ == 0) return current_;
    if (delta < 0) {
      const auto magnitude = static_cast<std::size_t>(-(delta + 1)) + 1U;
      current_ = magnitude > current_ ? 0 : current_ - magnitude;
    } else {
      const auto forward = static_cast<std::size_t>(delta);
      current_ = forward >= frame_count_ - current_ ? frame_count_ - 1 : current_ + forward;
    }
    return current_;
  }
  bool step_seconds(std::int64_t seconds) {
    if (frame_pts_.size() != frame_count_ || frame_count_ == 0 || time_base_num_ <= 0 || time_base_den_ <= 0) return false;
    std::int64_t ticks;
    if (seconds > 0 && seconds > std::numeric_limits<std::int64_t>::max() / time_base_den_) ticks = std::numeric_limits<std::int64_t>::max();
    else if (seconds < 0 && seconds < std::numeric_limits<std::int64_t>::min() / time_base_den_) ticks = std::numeric_limits<std::int64_t>::min();
    else ticks = seconds * time_base_den_ / time_base_num_;
    const auto base = frame_pts_[current_];
    const auto target = ticks > 0 && base > std::numeric_limits<std::int64_t>::max() - ticks ? std::numeric_limits<std::int64_t>::max()
        : ticks < 0 && base < std::numeric_limits<std::int64_t>::min() - ticks ? std::numeric_limits<std::int64_t>::min() : base + ticks;
    const auto found = std::lower_bound(frame_pts_.begin(), frame_pts_.end(), target);
    current_ = found == frame_pts_.end() ? frame_count_ - 1 : static_cast<std::size_t>(std::distance(frame_pts_.begin(), found));
    return true;
  }
  std::uint64_t current_time_us() const {
    if (frame_pts_.size() != frame_count_ || frame_count_ == 0 || frame_pts_[current_] < 0 || time_base_num_ <= 0 || time_base_den_ <= 0) return 0;
    const auto multiplier = static_cast<std::uint64_t>(time_base_num_) * 1000000ULL;
    return multiply_divide(static_cast<std::uint64_t>(frame_pts_[current_]), multiplier, static_cast<std::uint64_t>(time_base_den_));
  }
  bool set_time_us(std::uint64_t time_us) {
    if (frame_pts_.size() != frame_count_ || frame_count_ == 0 || time_base_num_ <= 0 || time_base_den_ <= 0) return false;
    const auto denominator = static_cast<std::uint64_t>(time_base_num_) * 1000000ULL;
    const auto target = multiply_divide(time_us, static_cast<std::uint64_t>(time_base_den_), denominator);
    const auto clamped = std::min<std::uint64_t>(target, static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()));
    const auto found = std::lower_bound(frame_pts_.begin(), frame_pts_.end(), static_cast<std::int64_t>(clamped));
    const auto next = found == frame_pts_.end() ? frame_count_ - 1 : static_cast<std::size_t>(std::distance(frame_pts_.begin(), found));
    if (next == current_) return false;
    current_ = next; return true;
  }
  bool next_keyframe() { const auto found = std::upper_bound(keyframes_.begin(), keyframes_.end(), current_); if (found == keyframes_.end()) return false; current_ = *found; return true; }
  bool previous_keyframe() { const auto found = std::lower_bound(keyframes_.begin(), keyframes_.end(), current_); if (found == keyframes_.begin()) return false; current_ = *std::prev(found); return true; }
 private:
  static std::uint64_t multiply_divide(std::uint64_t value, std::uint64_t multiplier, std::uint64_t divisor) {
    if (divisor == 0) return 0;
    if (multiplier != 0 && value > std::numeric_limits<std::uint64_t>::max() / multiplier) return std::numeric_limits<std::uint64_t>::max();
    return value * multiplier / divisor;
  }
  std::size_t frame_count_;
  std::vector<std::size_t> keyframes_;
  std::vector<std::int64_t> frame_pts_;
  std::int64_t time_base_num_{1};
  std::int64_t time_base_den_{1};
  std::size_t current_ = 0;
};

}  // namespace smartcut64::core
