// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_OVERLAY_COMBINATION_CACHE_H_
#define COMPONENTS_VIZ_SERVICE_DISPLAY_OVERLAY_COMBINATION_CACHE_H_

#include <array>
#include <bitset>
#include <map>
#include <memory>
#include <utility>
#include <vector>

#include "base/containers/span.h"
#include "components/viz/common/display/overlay_strategy.h"
#include "components/viz/service/display/overlay_candidate.h"
#include "components/viz/service/display/overlay_proposed_candidate.h"
#include "components/viz/service/viz_service_export.h"
#include "ui/gfx/geometry/rect_f.h"

namespace viz {

// Uniquely identifies OverlayProposedCandidates as it will be stored in the
// OverlayCombinationCache. An overlay with the same strategy and the same
// rounded display_rect are considered identical.
struct OverlayCacheKey {
  explicit OverlayCacheKey(const OverlayProposedCandidate& candidate);

  bool operator<(const OverlayCacheKey& other) const {
    if (other.strategy != strategy) {
      return strategy < other.strategy;
    } else {
      return display_rect < other.display_rect;
    }
  }

  // The candidate's rounded display_rect.
  gfx::Rect display_rect;
  // The strategy that proposed this candidate.
  OverlayStrategy strategy;
};

// A vector of proposed candidates to try and promote to overlays, as determined
// by the OverlayCombinationCache. There is also a flag that indicates that this
// entire combination has been successfully promoted to overlays the last time
// it was tested.
struct VIZ_SERVICE_EXPORT OverlayCombinationToTest {
  OverlayCombinationToTest();
  ~OverlayCombinationToTest();
  OverlayCombinationToTest(OverlayCombinationToTest&& other);
  OverlayCombinationToTest& operator=(OverlayCombinationToTest&& other);

  // The candidates that should be promoted to overlays.
  std::vector<OverlayProposedCandidate> candidates_to_test;
  // True iff this combination of candidates were successfully promoted to
  // overlays the last time they were tested.
  bool previously_succeeded = false;
};

class CombinationIdMapper;

// Attempts to determine the optimal combination of candidates that we should
// attempt to promote to overlays each frame, based on total expected relative
// power gain, and past success/failure of these combinations being promoted.
class VIZ_SERVICE_EXPORT OverlayCombinationCache {
 public:
  // The maximum number of candidates this cache can track.
  static constexpr size_t kMaxTrackedCandidates = 8;
  // The invalid candidate id.
  static constexpr size_t kInvalidCandidateId = kMaxTrackedCandidates;
  // We track all 2^N combinations of the max N tracked overlay candidates.
  static constexpr size_t kMaxTrackedCombinations = 1 << kMaxTrackedCandidates;

  // Represents a set of CandidateIds.
  using CandidateCombination = std::bitset<kMaxTrackedCandidates>;
  // Represents a bit index used in a CandidateCombination.
  // This may also represent `kInvalidCandidateId`.
  // Valid CandidateIds are in the range [0, kMaxTrackedCandidates).
  using CandidateId = size_t;

  OverlayCombinationCache();
  ~OverlayCombinationCache();

  // Given proposed candidates that are sorted by power gain, and the maximum
  // number of overlays you'd like to try and promote, determines the best
  // combination of overlays to try promoting this frame.
  OverlayCombinationToTest GetOverlayCombinationToTest(
      base::span<OverlayProposedCandidate const> sorted_candidates,
      int max_overlays_considered);

  // Records the success/failure of each candidate we attempted to promote to an
  // overlay. This should be called after testing the combination returned by
  // GetOverlayCombinationToTest() each frame.
  // NOTE: The OverlayProposedCandidate.candidate.overlay_handled field should
  // be true iff that candidate was promoted this frame.
  void DeclarePromotedCandidates(
      base::span<OverlayProposedCandidate const> attempted_candidates);

  // Clears all caches. This includes CandidateIds and CombinationResults.
  void ClearCache();

 private:
  friend class OverlayCombinationCacheTest;

  // The result of attempting to promote a CandidateCombination to overlays, or
  // unknown.
  enum class CombinationResult : uint8_t { kUnknown, kPromoted, kFailed };

  // Get the first max_overlays_possible candidates (if there are that many)
  // that represent unique quads. If there are multiple candidates for a single
  // quad, only the first one will be used.
  std::vector<OverlayProposedCandidate> GetConsideredCandidates(
      base::span<OverlayProposedCandidate const> sorted_candidates,
      size_t max_overlays_possible);

  // Gets the CandidateIds for each considered candidate. Reuses cached ids if
  // they exist, or maps new ids. All ids returned will be valid.
  // Any candidates that were considered last frame but are not this frame are
  // considered stale, and will have their CandidateIds and CombinationResults
  // cleared.
  std::vector<CandidateId> GetIds(
      const std::vector<OverlayProposedCandidate>& considered_candidates);

  // Generates all (2^N - 1) possible combinations of considered_candidates
  // (ignoring the empty set), and calculates the sum of their relative power
  // gains. Returns a vector of all of these CandidateCombination and total
  // power gain pairs.
  std::vector<std::pair<CandidateCombination, int>> GetPowerSortedCombinations(
      const std::vector<OverlayProposedCandidate>& considered_candidates,
      const std::vector<CandidateId>& considered_ids);

  // Reset the cached CombinationResult to kUnknown for any combination
  // containing any of the candidates in stale_candidates.
  void RemoveStaleCombinations(const CandidateCombination& stale_candidates);

  // Maps OverlayProposedCandidates to CandidateIds.
  std::unique_ptr<CombinationIdMapper> id_mapper_;
  // The test result, if known, for every possible CandidateCombination.
  std::array<CombinationResult, kMaxTrackedCombinations> combo_results_;
};

// Manages the mapping from OverlayProposedCandidates to CandidateIds ids based
// on their OverlayCacheKey. There are a fixed number of available ids, based on
// the maximum size of a CandidateCombination.
class VIZ_SERVICE_EXPORT CombinationIdMapper {
 public:
  CombinationIdMapper();
  ~CombinationIdMapper();

  // Gets a CandidateId for a given OverlayProposedCandidate.
  // Returns the cached id for this candidate if it exists, otherwise it
  // attempts to map the candidate to new id. If the cache is full (more than
  // kMaxTrackedCandidates in the cache), then kInvalidCandidateId is returned.
  //
  // NOTE: Callers need to check for kInvalidCandidateId before using it to
  // access a CandidateCombination.
  OverlayCombinationCache::CandidateId GetCandidateId(
      const OverlayProposedCandidate& candidate);

  // Returns a CandidateCombination of all the ids currently claimed.
  OverlayCombinationCache::CandidateCombination GetClaimedIds() {
    return claimed_ids_;
  }

  // Remove all ids in stale_candidates from the cache.
  void RemoveStaleIds(
      const OverlayCombinationCache::CandidateCombination& stale_candidates);

  // Clear all cached ids.
  void ClearIds();

 private:
  // Maps up to (kMaxTrackedCandidates) OverlayCacheKeys to unique CandidateIds.
  std::map<OverlayCacheKey, OverlayCombinationCache::CandidateId>
      candidate_ids_;
  // A set containing all ids that have been claimed.
  OverlayCombinationCache::CandidateCombination claimed_ids_;
};

}  // namespace viz

#endif  // COMPONENTS_VIZ_SERVICE_DISPLAY_OVERLAY_COMBINATION_CACHE_H_
