// Copyright 2019 The MediaPipe Authors.
//
// 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 "mediapipe/framework/deps/monotonic_clock.h"

#include "absl/base/macros.h"
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
#include "mediapipe/framework/port/logging.h"

namespace mediapipe {

// This state, which contains the "guts" of MonotonicClockImpl, is separate
// from the class instance so that it can be shared to implement a
// SynchronizedMonotonicClock.  (The per-instance state of MonotonicClock is
// just for frills like the correction metrics and callback.)  It lives in this
// private namespace so that test code can use it without exposing it to the
// world.
struct MonotonicClock::State {
  // The clock whose time is being corrected.
  Clock* raw_clock;
  absl::Mutex lock;
  // The largest time ever returned by Now().
  absl::Time max_time ABSL_GUARDED_BY(lock);
  explicit State(Clock* clock)
      : raw_clock(clock), max_time(absl::UnixEpoch()) {}
};

using State = MonotonicClock::State;

class MonotonicClockImpl : public MonotonicClock {
 public:
  // By default, MonotonicClockImpl owns the state_.  ReleaseState(), below,
  // can be used to prevent the MCI destructor from deleting a shared state_.
  explicit MonotonicClockImpl(State* state)
      : state_(state),
        state_owned_(true),
        last_raw_time_(absl::UnixEpoch()),
        correction_count_(0),
        max_correction_(absl::ZeroDuration()) {}

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

  virtual ~MonotonicClockImpl() {
    if (state_owned_) delete state_;
  }

  // Absolve this object of responsibility for state_.
  void ReleaseState() {
    CHECK(state_owned_);
    state_owned_ = false;
  }

  //
  // The Clock interface (see util/time/clock.h).
  //

  // The logic in TimeNow() is based on GFS_NowMS().
  virtual absl::Time TimeNow() {
    // These variables save some state from the critical section below.
    absl::Time raw_time;
    absl::Time local_max_time;
    absl::Time local_last_raw_time;

    // As there are several early exits from this function, use absl::MutexLock.
    {
      absl::MutexLock m(&state_->lock);

      // Check consistency of internal data with state_.
      CHECK_LE(last_raw_time_, state_->max_time)
          << "non-monotonic behavior: last_raw_time_=" << last_raw_time_
          << ", max_time=" << state_->max_time;

      raw_time = state_->raw_clock->TimeNow();

      // Normal case: time is advancing.  Update state and return the raw time.
      if (raw_time >= state_->max_time) {
        last_raw_time_ = raw_time;
        state_->max_time = raw_time;
        return raw_time;
      }

      // Exceptional case: Raw time is within a window of a previous backward
      // jump.  We do not run any callbacks or update metrics here since we
      // already did that when the backward jump was detected.
      if (raw_time >= last_raw_time_) {
        last_raw_time_ = raw_time;
        return state_->max_time;
      }

      // Exceptional case: Raw time jumped backward.  Remainder of function
      // handles this case.
      //
      // First, update correction metrics.
      ++correction_count_;
      absl::Duration delta = state_->max_time - raw_time;
      CHECK_LT(absl::ZeroDuration(), delta);
      if (delta > max_correction_) {
        max_correction_ = delta;
      }

      // Copy state into local vars before updating last_raw_time_ and leaving
      // the critical section.
      local_max_time = state_->max_time;
      local_last_raw_time = last_raw_time_;
      last_raw_time_ = raw_time;
    }  // absl::MutexLock

    // Return the saved maximum time.
    return local_max_time;
  }

  // The strategy of Sleep and SleepUntil is K.I.S.S.: set an alarm on the
  // raw_clock for the desired wakeup_time, and then snooze the alarm if we wake
  // up too soon.  This guarantees that the caller won't wake up too soon (which
  // would require us to advance monotonic time simply by the act of waking up),
  // however the caller may sleep for much longer (in monotonic time) if
  // monotonic time jumps far into the future.  Whether or not this happens
  // depends on the behavior of the raw clock.
  virtual void Sleep(absl::Duration d) {
    absl::Time wakeup_time = TimeNow() + d;
    SleepUntil(wakeup_time);
  }

  virtual void SleepUntil(absl::Time wakeup_time) {
    while (TimeNow() < wakeup_time) {
      state_->raw_clock->SleepUntil(wakeup_time);
    }
  }

  //
  // End of Clock interface.
  //

 private:
  // Get metrics about time corrections.
  virtual void GetCorrectionMetrics(int* correction_count,
                                    double* max_correction) {
    absl::MutexLock l(&state_->lock);
    if (correction_count != nullptr) *correction_count = correction_count_;
    if (max_correction != nullptr)
      *max_correction = absl::FDivDuration(max_correction_, absl::Seconds(1));
  }

  // Reset values returned by GetCorrectionMetrics().
  virtual void ResetCorrectionMetrics() {
    absl::MutexLock l(&state_->lock);
    correction_count_ = 0;
    max_correction_ = absl::ZeroDuration();
  }

  // The guts of the monotonic clock.  Caution: this may point to a static
  // object.
  State* state_;
  // If true, this object owns state_ and is responsible for deallocating it.
  bool state_owned_;

  // last_raw_time_ remembers the last value obtained from raw_clock_.
  // It prevents spurious calls to ReportCorrection when time moves
  // forward by a smaller amount than a prior backward jump.
  absl::Time last_raw_time_ ABSL_GUARDED_BY(state_->lock);

  // Variables that keep track of time corrections made by this instance of
  // MonotonicClock.  (All such metrics are instance-local for reasons
  // described earlier.)
  int correction_count_ ABSL_GUARDED_BY(state_->lock);
  absl::Duration max_correction_ ABSL_GUARDED_BY(state_->lock);
};

// Factory methods.
MonotonicClock* MonotonicClock::CreateMonotonicClock(Clock* clock) {
  State* state = new State(clock);
  // MonotonicClockImpl takes ownership of state.
  return new MonotonicClockImpl(state);
}

namespace {
State* GlobalSyncState() {
  static State* sync_state = new State(Clock::RealClock());
  return sync_state;
}
}  // namespace

// The reason that SynchronizedMonotonicClock is not implemented as a singleton
// is so that different code bases can handle clock corrections their own way.
MonotonicClock* MonotonicClock::CreateSynchronizedMonotonicClock() {
  MonotonicClockImpl* clock = new MonotonicClockImpl(GlobalSyncState());
  // Release ownership of sync_state.
  clock->ReleaseState();
  return clock;
}

// Test access methods.
void MonotonicClockAccess::SynchronizedMonotonicClockReset() {
  LOG(INFO) << "Resetting SynchronizedMonotonicClock";
  State* sync_state = GlobalSyncState();
  absl::MutexLock m(&sync_state->lock);
  sync_state->max_time = absl::UnixEpoch();
}

State* MonotonicClockAccess::CreateMonotonicClockState(Clock* raw_clock) {
  return new State(raw_clock);
}

void MonotonicClockAccess::DeleteMonotonicClockState(State* state) {
  delete state;
}

MonotonicClock* MonotonicClockAccess::CreateMonotonicClock(State* state) {
  MonotonicClockImpl* clock = new MonotonicClockImpl(state);
  // Release ownership of sync_state.
  clock->ReleaseState();
  return clock;
}

}  // namespace mediapipe
