/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/trace_processor/util/clock_synchronizer.h"

#include <algorithm>
#include <cinttypes>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/murmur_hash.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/public/compiler.h"

namespace perfetto::trace_processor {

// --- ClockId ---

std::string ClockId::ToString() const {
  if (seq_id == 0 && trace_file_id == 0)
    return std::to_string(clock_id);
  return std::to_string(clock_id) + "(seq=" + std::to_string(seq_id) +
         ",tf=" + std::to_string(trace_file_id) + ")";
}

// --- ClockSynchronizerListener ---

ClockSynchronizerListener::~ClockSynchronizerListener() = default;

// --- ClockSynchronizer ---

ClockSynchronizer::ClockSynchronizer(
    TraceTimeState* trace_time_state,
    std::unique_ptr<ClockSynchronizerListener> clock_event_listener)
    : trace_time_state_(trace_time_state),
      clock_event_listener_(std::move(clock_event_listener)) {}

base::StatusOr<uint32_t> ClockSynchronizer::AddSnapshot(
    const std::vector<ClockTimestamp>& clock_timestamps) {
  const auto snapshot_id = cur_snapshot_id_++;

  // Clear the cache
  cache_.fill({});

  // Compute the fingerprint of the snapshot by hashing all clock ids. This is
  // used by the clock pathfinding logic.
  base::MurmurHashCombiner hasher;
  for (const auto& clock_ts : clock_timestamps)
    hasher.Combine(clock_ts.clock.id);
  const auto snapshot_hash = static_cast<SnapshotHash>(hasher.digest());

  // Add a new entry in each clock's snapshot vector.
  for (const auto& clock_ts : clock_timestamps) {
    ClockId clock_id = clock_ts.clock.id;
    ClockDomain& domain = clocks_[clock_id];

    if (domain.snapshots.empty()) {
      if (clock_ts.clock.is_incremental &&
          !IsConvertedSequenceClock(clock_id)) {
        clock_event_listener_->OnInvalidClockSnapshot();
        return base::ErrStatus(
            "Clock sync error: the global clock with id=%s"
            " cannot use incremental encoding; this is only "
            "supported for sequence-scoped clocks.",
            clock_id.ToString().c_str());
      }
      domain.unit_multiplier_ns = clock_ts.clock.unit_multiplier_ns;
      domain.is_incremental = clock_ts.clock.is_incremental;
    } else if (PERFETTO_UNLIKELY(domain.unit_multiplier_ns !=
                                     clock_ts.clock.unit_multiplier_ns ||
                                 domain.is_incremental !=
                                     clock_ts.clock.is_incremental)) {
      clock_event_listener_->OnInvalidClockSnapshot();
      return base::ErrStatus(
          "Clock sync error: the clock domain with id=%s"
          " (unit=%" PRId64
          ", incremental=%d), was previously registered with "
          "different properties (unit=%" PRId64 ", incremental=%d).",
          clock_id.ToString().c_str(), clock_ts.clock.unit_multiplier_ns,
          clock_ts.clock.is_incremental, domain.unit_multiplier_ns,
          domain.is_incremental);
    }
    if (PERFETTO_UNLIKELY(clock_id == trace_time_state_->clock_id &&
                          domain.unit_multiplier_ns != 1)) {
      clock_event_listener_->OnInvalidClockSnapshot();
      return base::ErrStatus(
          "Clock sync error: the trace clock (id=%s"
          ") must always use nanoseconds as unit multiplier.",
          clock_id.ToString().c_str());
    }
    const int64_t timestamp_ns = clock_ts.timestamp * domain.unit_multiplier_ns;
    domain.last_timestamp_ns = timestamp_ns;

    ClockSnapshots& vect = domain.snapshots[snapshot_hash];
    if (!vect.snapshot_ids.empty() &&
        PERFETTO_UNLIKELY(vect.snapshot_ids.back() == snapshot_id)) {
      clock_event_listener_->OnInvalidClockSnapshot();
      return base::ErrStatus(
          "Clock sync error: duplicate clock domain with id=%s"
          " at snapshot %" PRIu32 ".",
          clock_id.ToString().c_str(), snapshot_id);
    }

    // Clock ids in the range [64, 128) are sequence-scoped and must be
    // translated to global ids via SequenceToGlobalClock() before calling
    // this function.
    PERFETTO_DCHECK(!clock_id.IsSequenceClock() || clock_id.seq_id != 0);

    // Snapshot IDs must be always monotonic.
    PERFETTO_DCHECK(vect.snapshot_ids.empty() ||
                    vect.snapshot_ids.back() < snapshot_id);

    if (!vect.timestamps_ns.empty() &&
        timestamp_ns < vect.timestamps_ns.back()) {
      // Clock is not monotonic.

      if (clock_id == trace_time_state_->clock_id) {
        clock_event_listener_->OnInvalidClockSnapshot();
        return base::ErrStatus(
            "Clock sync error: the trace clock (id=%s"
            ") is not monotonic at snapshot %" PRIu32 ". %" PRId64
            " not >= %" PRId64 ".",
            clock_id.ToString().c_str(), snapshot_id, timestamp_ns,
            vect.timestamps_ns.back());
      }

      PERFETTO_DLOG("Detected non-monotonic clock with ID %s",
                    clock_id.ToString().c_str());

      // For the other clocks the best thing we can do is mark it as
      // non-monotonic and refuse to use it as a source clock in the
      // resolution graph. We can still use it as a target clock, but not
      // viceversa. The concrete example is the CLOCK_REALTIME going 1h
      // backwards during daylight saving. We can still answer the question
      // "what was the REALTIME timestamp when BOOTTIME was X?" but we can't
      // answer the opposite question because there can be two valid
      // BOOTTIME(s) for the same REALTIME instant because of the 1:many
      // relationship.
      non_monotonic_clocks_.insert(clock_id);

      // Erase all edges from the graph that start from this clock (but keep
      // the ones that end on this clock).
      PERFETTO_CHECK(clock_id.trace_file_id <
                     std::numeric_limits<uint32_t>::max());
      auto begin = graph_.lower_bound(ClockGraphEdge{clock_id, ClockId{}, 0});
      ClockId upper = {clock_id.clock_id, clock_id.seq_id,
                       clock_id.trace_file_id + 1};
      auto end = graph_.lower_bound(ClockGraphEdge{upper, ClockId{}, 0});
      graph_.erase(begin, end);
    }
    vect.snapshot_ids.emplace_back(snapshot_id);
    vect.timestamps_ns.emplace_back(timestamp_ns);
  }
  // Create graph edges for all the possible tuples of clocks in this
  // snapshot. If the snapshot contains clock a, b, c, d create edges [ab, ac,
  // ad, bc, bd, cd] and the symmetrical ones [ba, ca, da, bc, db, dc]. This
  // is to store the information: Clock A is syncable to Clock B via the
  // snapshots of type (hash).
  // Clocks that were previously marked as non-monotonic won't be added as
  // valid sources.
  for (auto it1 = clock_timestamps.begin(); it1 != clock_timestamps.end();
       ++it1) {
    auto it2 = it1;
    ++it2;
    for (; it2 != clock_timestamps.end(); ++it2) {
      if (!non_monotonic_clocks_.count(it1->clock.id))
        graph_.emplace(it1->clock.id, it2->clock.id, snapshot_hash);

      if (!non_monotonic_clocks_.count(it2->clock.id))
        graph_.emplace(it2->clock.id, it1->clock.id, snapshot_hash);
    }
  }

  return snapshot_id;
}

std::optional<int64_t> ClockSynchronizer::ConvertSlowpath(
    ClockId src_clock_id,
    int64_t src_timestamp,
    std::optional<int64_t> src_ts_ns,
    ClockId target_clock_id,
    std::optional<size_t> byte_offset,
    bool suppress_errors) {
  PERFETTO_DCHECK(!src_clock_id.IsSequenceClock() || src_clock_id.seq_id != 0);
  PERFETTO_DCHECK(!target_clock_id.IsSequenceClock() ||
                  target_clock_id.seq_id != 0);
  clock_event_listener_->OnClockSyncCacheMiss();

  ClockPath path = FindPath(src_clock_id, target_clock_id);
  if (!path.valid()) {
    if (!suppress_errors) {
      // Determine which clock(s) are unknown and record error
      ClockSyncErrorType error;
      if (clocks_.find(src_clock_id) == clocks_.end()) {
        error = ClockSyncErrorType::kUnknownSourceClock;
      } else if (clocks_.find(target_clock_id) == clocks_.end()) {
        error = ClockSyncErrorType::kUnknownTargetClock;
      } else {
        error = ClockSyncErrorType::kNoPath;
      }
      clock_event_listener_->RecordConversionError(
          error, src_clock_id, target_clock_id, src_timestamp, byte_offset);
    }
    return std::nullopt;
  }

  // Iterate trough the path found and translate timestamps onto the new clock
  // domain on each step, until the target domain is reached.
  ClockDomain* src_domain = GetClock(src_clock_id);
  int64_t ns = src_ts_ns ? *src_ts_ns : src_domain->ToNs(src_timestamp);

  // These will track the overall translation and valid range for the whole
  // path.
  int64_t total_translation_ns = 0;
  int64_t path_min_ts_ns = std::numeric_limits<int64_t>::min();
  int64_t path_max_ts_ns = std::numeric_limits<int64_t>::max();

  for (uint32_t i = 0; i < path.len; ++i) {
    const ClockGraphEdge edge = path.at(i);
    ClockDomain* cur_clock = GetClock(std::get<0>(edge));
    ClockDomain* next_clock = GetClock(std::get<1>(edge));
    const SnapshotHash hash = std::get<2>(edge);

    // Find the closest timestamp within the snapshots of the source clock.
    const ClockSnapshots& cur_snap = cur_clock->GetSnapshot(hash);
    const auto& ts_vec = cur_snap.timestamps_ns;
    auto it = std::upper_bound(ts_vec.begin(), ts_vec.end(), ns);
    if (it != ts_vec.begin())
      --it;

    // Now lookup the snapshot id that matches the closest timestamp.
    size_t index = static_cast<size_t>(std::distance(ts_vec.begin(), it));
    PERFETTO_DCHECK(index < ts_vec.size());
    PERFETTO_DCHECK(cur_snap.snapshot_ids.size() == ts_vec.size());
    uint32_t snapshot_id = cur_snap.snapshot_ids[index];

    // And use that to retrieve the corresponding time in the next clock
    // domain. The snapshot id must exist in the target clock domain. If it
    // doesn't either the hash logic or the pathfinding logic are bugged. This
    // can also happen if the checks in AddSnapshot fail and we skip part of
    // the snapshot.
    const ClockSnapshots& next_snap = next_clock->GetSnapshot(hash);

    // Using std::lower_bound because snapshot_ids is sorted, so we can do
    // a binary search. std::find would do a linear scan.
    auto next_it = std::lower_bound(next_snap.snapshot_ids.begin(),
                                    next_snap.snapshot_ids.end(), snapshot_id);
    if (next_it == next_snap.snapshot_ids.end() || *next_it != snapshot_id) {
      PERFETTO_DFATAL("Snapshot does not exist in clock domain.");
      continue;
    }
    size_t next_index = static_cast<size_t>(
        std::distance(next_snap.snapshot_ids.begin(), next_it));
    PERFETTO_DCHECK(next_index < next_snap.snapshot_ids.size());
    int64_t next_timestamp_ns = next_snap.timestamps_ns[next_index];

    // The translated timestamp is the relative delta of the source timestamp
    // from the closest snapshot found (ns - *it), plus the timestamp in
    // the new clock domain for the same snapshot id.
    const int64_t hop_translation_ns = next_timestamp_ns - *it;
    ns += hop_translation_ns;

    // Now, calculate the valid range for this specific hop and intersect it
    // with the accumulated valid range for the whole path.
    // The range for this hop needs to be translated back to the source
    // clock's coordinate system.
    const int64_t kInt64Min = std::numeric_limits<int64_t>::min();
    const int64_t kInt64Max = std::numeric_limits<int64_t>::max();

    int64_t hop_min_ts_ns = (it == ts_vec.begin()) ? kInt64Min : *it;
    auto ubound = it + 1;
    int64_t hop_max_ts_ns = (ubound == ts_vec.end()) ? kInt64Max : *ubound;

    // Translate the hop's valid range back to the original source clock's
    // domain. `total_translation_ns` is the translation from the *start* of
    // the path to the *start* of the current hop.
    int64_t hop_min_in_src_domain_ns =
        (hop_min_ts_ns == kInt64Min) ? kInt64Min
                                     : hop_min_ts_ns - total_translation_ns;
    int64_t hop_max_in_src_domain_ns =
        (hop_max_ts_ns == kInt64Max) ? kInt64Max
                                     : hop_max_ts_ns - total_translation_ns;

    // Intersect with the path's current valid range.
    path_min_ts_ns = std::max(path_min_ts_ns, hop_min_in_src_domain_ns);
    path_max_ts_ns = std::min(path_max_ts_ns, hop_max_in_src_domain_ns);

    // Accumulate the translation.
    total_translation_ns += hop_translation_ns;

    // The last clock in the path must be the target clock.
    PERFETTO_DCHECK(i < path.len - 1 || std::get<1>(edge) == target_clock_id);
  }

  // After the loop, we have the final converted timestamp `ns`, and the
  // total translation and valid range for the entire path.
  // We can now cache this result.
  CachedClockPath cache_entry{};
  cache_entry.src = src_clock_id;
  cache_entry.target = target_clock_id;
  cache_entry.src_domain = src_domain;
  cache_entry.min_ts_ns = path_min_ts_ns;
  cache_entry.max_ts_ns = path_max_ts_ns;
  cache_entry.translation_ns = total_translation_ns;
  cache_[rnd_() % cache_.size()] = cache_entry;

  return ns;
}

ClockSynchronizer::ClockPath ClockSynchronizer::FindPath(ClockId src,
                                                         ClockId target) {
  PERFETTO_CHECK(src != target);

  // If we've never heard of the clock before there is no hope:
  if (clocks_.find(target) == clocks_.end()) {
    return ClockPath();
  }
  if (clocks_.find(src) == clocks_.end()) {
    return ClockPath();
  }

  // This is a classic breadth-first search. Each node in the queue holds also
  // the full path to reach that node.
  // We assume the graph is acyclic, if it isn't the ClockPath::kMaxLen will
  // stop the search anyways.
  queue_find_path_cache_.clear();
  queue_find_path_cache_.emplace_back(src);

  while (!queue_find_path_cache_.empty()) {
    ClockPath cur_path = queue_find_path_cache_.front();
    queue_find_path_cache_.pop_front();

    const ClockId cur_clock_id = cur_path.last;
    if (cur_path.len >= ClockPath::kMaxLen)
      continue;

    // Expore all the adjacent clocks.
    // The lower_bound() below returns an iterator to the first edge that
    // starts on |cur_clock_id|. The edges are sorted by (src, target, hash).
    for (auto it =
             graph_.lower_bound(ClockGraphEdge(cur_clock_id, ClockId{}, 0));
         it != graph_.end() && std::get<0>(*it) == cur_clock_id; ++it) {
      ClockId next_clock_id = std::get<1>(*it);
      SnapshotHash hash = std::get<2>(*it);
      if (next_clock_id == target)
        return ClockPath(cur_path, next_clock_id, hash);
      queue_find_path_cache_.emplace_back(
          ClockPath(cur_path, next_clock_id, hash));
    }
  }
  return ClockPath();  // invalid path.
}

ClockSynchronizer::ClockDomain* ClockSynchronizer::GetClock(ClockId clock_id) {
  auto it = clocks_.find(clock_id);
  PERFETTO_DCHECK(it != clocks_.end());
  return &it->second;
}

}  // namespace perfetto::trace_processor
