// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/policy/logging_work_scheduler.h"

#include <algorithm>

#include "base/bind.h"
#include "base/message_loop.h"
#include "content/public/browser/browser_thread.h"

using content::BrowserThread;

namespace policy {

// Objects of this class are used in the priority-queue of future tasks
// in EventLogger.
class EventLogger::Task {
 public:
  Task();
  Task(int64 trigger_time,
       int64 secondary_key,
       linked_ptr<base::Closure> callback);
  ~Task();

  // Returns true if |this| should be executed before |rhs|.
  // Used for sorting by the priority queue.
  bool operator< (const Task& rhs) const;

  int64 trigger_time() const;

  // Returns a copy of the callback object of this task, and resets the
  // original callback object. (LoggingTaskScheduler owns a linked_ptr to
  // its task's callback objects and it only allows firing new tasks if the
  // previous task's callback object has been reset.)
  base::Closure GetAndResetCallback();

 private:
  // The virtual time when this task will trigger.
  // Smaller times win.
  int64 trigger_time_;
  // Used for sorting tasks that have the same trigger_time.
  // Bigger keys win.
  int64 secondary_key_;

  linked_ptr<base::Closure> callback_;
};

EventLogger::Task::Task() {
}

EventLogger::Task::Task(int64 trigger_time,
                        int64 secondary_key,
                        linked_ptr<base::Closure> callback)
    : trigger_time_(trigger_time),
      secondary_key_(secondary_key),
      callback_(callback) {
}

EventLogger::Task::~Task() {
}

bool EventLogger::Task::operator< (const Task& rhs) const {
  if (trigger_time_ == rhs.trigger_time_) {
    return secondary_key_ < rhs.secondary_key_;
  } else {
    return trigger_time_ > rhs.trigger_time_;
  }
}

int64 EventLogger::Task::trigger_time() const {
  return trigger_time_;
}

base::Closure EventLogger::Task::GetAndResetCallback() {
  // Make a copy.
  base::Closure callback = *(callback_.get());
  // Reset the original callback, which is shared with LoggingTaskScheduler.
  callback_->Reset();
  return callback;
}

EventLogger::EventLogger()
    : step_pending_(false),
      ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
      current_time_(0),
      task_counter_(0) {
}

EventLogger::~EventLogger() {
}

void EventLogger::PostDelayedWork(
    linked_ptr<base::Closure> callback,
    int64 delay) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  int64 trigger_time = current_time_ + delay;
  task_counter_++;
  scheduled_tasks_.push(Task(trigger_time, task_counter_, callback));
  if (!step_pending_) {
    step_pending_ = true;
    MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&EventLogger::Step, weak_ptr_factory_.GetWeakPtr()));
  }
}

void EventLogger::RegisterEvent() {
  events_.push_back(current_time_);
}

void EventLogger::Swap(std::vector<int64>* events) {
  std::swap(events_, *events);
}

// static
// Counts the number of elements in a sorted integer vector that are
// >= |start| but < |start| + |length|.
int EventLogger::CountEvents(const std::vector<int64>& events,
                             int64 start, int64 length) {
  // The first interesting event.
  std::vector<int64>::const_iterator begin_it =
      std::lower_bound(events.begin(), events.end(), start);
  // The event after the last interesting event.
  std::vector<int64>::const_iterator end_it =
      std::lower_bound(begin_it, events.end(), start + length);
  return end_it - begin_it;
}

void EventLogger::Step() {
  DCHECK(step_pending_);
  step_pending_ = false;
  if (scheduled_tasks_.empty())
    return;
  // Take the next scheduled task from the queue.
  Task next = scheduled_tasks_.top();
  scheduled_tasks_.pop();
  base::Closure callback = next.GetAndResetCallback();
  int64 trigger_time = next.trigger_time();
  DCHECK(trigger_time >= current_time_);
  // Execute the next scheduled task if it was not cancelled.
  if (!callback.is_null()) {
    current_time_ = trigger_time;
    callback.Run();  // Note: new items may be added to scheduled_tasks_ here.
  }
  // Trigger calling this method if there are remaining tasks.
  if (!step_pending_ && !scheduled_tasks_.empty()) {
    step_pending_ = true;
    MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&EventLogger::Step, weak_ptr_factory_.GetWeakPtr()));
  }
}

DummyWorkScheduler::DummyWorkScheduler() {
}

DummyWorkScheduler::~DummyWorkScheduler() {
  CancelDelayedWork();
}

void DummyWorkScheduler::PostDelayedWork(
    const base::Closure& callback, int64 delay) {
  DelayedWorkScheduler::PostDelayedWork(callback, 0);
}

LoggingWorkScheduler::LoggingWorkScheduler(EventLogger* logger)
    : logger_(logger) {
}

LoggingWorkScheduler::~LoggingWorkScheduler() {
  if (callback_.get())
    callback_->Reset();
}

void LoggingWorkScheduler::PostDelayedWork(
    const base::Closure& callback, int64 delay) {
  DCHECK(!callback_.get() || callback_->is_null());
  callback_.reset(new base::Closure(callback));
  logger_->PostDelayedWork(callback_, delay);
}

void LoggingWorkScheduler::CancelDelayedWork() {
  if (!callback_.get()) return;
  callback_->Reset();  // Erase the callback to the delayed work.
  callback_.reset(NULL);  // Erase the pointer to the callback.
}

}  // namespace policy