/*
 * Copyright (c) 2022, Alliance for Open Media. All rights reserved
 *
 * This source code is subject to the terms of the BSD 2 Clause License and
 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
 * was not distributed with this source code in the LICENSE file, you can
 * obtain it at www.aomedia.org/license/software. If the Alliance for Open
 * Media Patent License 1.0 was not distributed with this source code in the
 * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
 */

#ifndef AOM_AV1_QMODE_RC_REFERENCE_MANAGER_H_
#define AOM_AV1_QMODE_RC_REFERENCE_MANAGER_H_

#include <deque>
#include <iostream>
#include <vector>

#include "av1/qmode_rc/ratectrl_qmode_interface.h"

namespace aom {

enum class RefUpdateType { kForward, kBackward, kLast, kNone };

class RefFrameManager {
 public:
  explicit RefFrameManager(int ref_frame_table_size, int max_ref_frames)
      : ref_frame_table_(ref_frame_table_size),
        max_ref_frames_(max_ref_frames) {
    // forward_max_size_ define max number of arf frames that can exists at
    // the same time. In the other words, it's the max size of forward_stack_.
    // TODO(angiebird): Figure out if this number is optimal.
    forward_max_size_ = ref_frame_table_size - 2;
    cur_global_order_idx_ = 0;
    Reset();
  }
  ~RefFrameManager() = default;

  RefFrameManager(const RefFrameManager &) = delete;
  RefFrameManager &operator=(const RefFrameManager &) = delete;

  friend std::ostream &operator<<(std::ostream &os,
                                  const RefFrameManager &rfm) {
    os << "=" << std::endl;
    os << "forward: ";
    for (const auto &ref_idx : rfm.forward_stack_) {
      os << rfm.ref_frame_table_[ref_idx].order_idx << " ";
    }
    os << std::endl;
    os << "backward: ";
    for (const auto &ref_idx : rfm.backward_queue_) {
      os << rfm.ref_frame_table_[ref_idx].order_idx << " ";
    }
    os << std::endl;
    os << "last: ";
    for (const auto &ref_idx : rfm.last_queue_) {
      os << rfm.ref_frame_table_[ref_idx].order_idx << " ";
    }
    os << std::endl;
    return os;
  }

  void Reset();
  int AllocateRefIdx();
  int GetRefFrameCountByType(RefUpdateType ref_update_type) const;
  int GetRefFrameCount() const;
  std::vector<ReferenceFrame> GetRefFrameListByPriority() const;
  int GetRefFrameIdxByPriority(RefUpdateType ref_update_type,
                               int priority_idx) const;
  GopFrame GetRefFrameByPriority(RefUpdateType ref_update_type,
                                 int priority_idx) const;
  GopFrame GetRefFrameByIndex(int ref_idx) const;
  void UpdateOrder(int global_order_idx);
  int ColocatedRefIdx(int global_order_idx);
  int ForwardMaxSize() const { return forward_max_size_; }
  int MaxRefFrame() const { return max_ref_frames_; }
  int CurGlobalOrderIdx() const { return cur_global_order_idx_; }
  void UpdateRefFrameTable(GopFrame *gop_frame);
  ReferenceFrame GetPrimaryRefFrame(const GopFrame &gop_frame) const;

 private:
  int forward_max_size_;
  int cur_global_order_idx_;
  RefFrameTable ref_frame_table_;
  int max_ref_frames_;
  bool allow_two_fwd_frames_;
  std::deque<int> free_ref_idx_list_;
  std::vector<int> forward_stack_;
  std::deque<int> backward_queue_;
  std::deque<int> last_queue_;
};

}  // namespace aom

#endif  // AOM_AV1_QMODE_RC_REFERENCE_MANAGER_H_
