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

#include "third_party/blink/renderer/platform/scheduler/common/thread_load_tracker.h"

#include <algorithm>

namespace blink {
namespace scheduler {

ThreadLoadTracker::ThreadLoadTracker(base::TimeTicks now,
                                     const Callback& callback,
                                     base::TimeDelta reporting_interval)
    : time_(now),
      thread_state_(ThreadState::kPaused),
      last_state_change_time_(now),
      reporting_interval_(reporting_interval),
      callback_(callback) {
  next_reporting_time_ = now + reporting_interval_;
}

ThreadLoadTracker::~ThreadLoadTracker() = default;

void ThreadLoadTracker::Pause(base::TimeTicks now) {
  Advance(now, TaskState::kIdle);
  thread_state_ = ThreadState::kPaused;

  Reset(now);
}

void ThreadLoadTracker::Resume(base::TimeTicks now) {
  Advance(now, TaskState::kIdle);
  thread_state_ = ThreadState::kActive;

  Reset(now);
}

void ThreadLoadTracker::Reset(base::TimeTicks now) {
  last_state_change_time_ = now;
  next_reporting_time_ = now + reporting_interval_;
  run_time_inside_window_ = base::TimeDelta();
}

void ThreadLoadTracker::RecordTaskTime(base::TimeTicks start_time,
                                       base::TimeTicks end_time) {
  start_time = std::max(last_state_change_time_, start_time);
  end_time = std::max(last_state_change_time_, end_time);

  Advance(start_time, TaskState::kIdle);
  Advance(end_time, TaskState::kTaskRunning);
}

void ThreadLoadTracker::RecordIdle(base::TimeTicks now) {
  Advance(now, TaskState::kIdle);
}

namespace {

// Calculates length of intersection of two time intervals.
base::TimeDelta Intersection(base::TimeTicks left1,
                             base::TimeTicks right1,
                             base::TimeTicks left2,
                             base::TimeTicks right2) {
  DCHECK_LT(left1, right1);
  DCHECK_LT(left2, right2);
  base::TimeTicks left = std::max(left1, left2);
  base::TimeTicks right = std::min(right1, right2);

  if (left <= right)
    return right - left;

  return base::TimeDelta();
}

}  // namespace

void ThreadLoadTracker::Advance(base::TimeTicks now, TaskState task_state) {
  // This function advances |time_| to now and calls |callback_|
  // when appropriate.
  DCHECK_LE(time_, now);

  if (thread_state_ == ThreadState::kPaused) {
    // If the load tracker is paused, bail out early.
    time_ = now;
    return;
  }

  while (time_ < now) {
    // Advance time_ to the earliest of following:
    // a) time to call |callback_|
    // b) requested time to forward (|now|).
    base::TimeTicks next_current_time = std::min(next_reporting_time_, now);

    base::TimeDelta delta = next_current_time - time_;

    // Keep a running total of the time spent running tasks within the window
    // and the total time.
    if (task_state == TaskState::kTaskRunning) {
      run_time_inside_window_ +=
          Intersection(next_reporting_time_ - reporting_interval_,
                       next_reporting_time_, time_, time_ + delta);
    }

    time_ = next_current_time;

    if (time_ == next_reporting_time_) {
      // Call |callback_| if need and update next callback time.
      if (thread_state_ == ThreadState::kActive) {
        callback_.Run(time_, Load());
        DCHECK_EQ(thread_state_, ThreadState::kActive);
      }
      next_reporting_time_ += reporting_interval_;
      run_time_inside_window_ = base::TimeDelta();
    }
  }
}

double ThreadLoadTracker::Load() {
  return run_time_inside_window_ / reporting_interval_;
}

}  // namespace scheduler
}  // namespace blink
