// 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_frame/task_marshaller.h"

#include "base/callback.h"
#include "base/logging.h"

TaskMarshallerThroughMessageQueue::TaskMarshallerThroughMessageQueue()
    : wnd_(NULL),
      msg_(0xFFFF) {
}

TaskMarshallerThroughMessageQueue::~TaskMarshallerThroughMessageQueue() {
  ClearTasks();
}

void TaskMarshallerThroughMessageQueue::PostTask(
    const tracked_objects::Location& from_here, const base::Closure& task) {
  DCHECK(wnd_ != NULL);

  lock_.Acquire();
  bool has_work = !pending_tasks_.empty();
  pending_tasks_.push(task);
  lock_.Release();

  // Don't post message if there is already one.
  if (has_work)
    return;

  if (!::PostMessage(wnd_, msg_, 0, 0)) {
    DVLOG(1) << "Dropping MSG_EXECUTE_TASK message for destroyed window.";
    ClearTasks();
  }
}

void TaskMarshallerThroughMessageQueue::PostDelayedTask(
    const tracked_objects::Location& source,
    const base::Closure& task,
    base::TimeDelta& delay) {
  DCHECK(wnd_);

  base::AutoLock lock(lock_);
  base::PendingTask delayed_task(source, task, base::TimeTicks::Now() + delay,
                                 true);
  base::TimeTicks top_run_time = delayed_tasks_.top().delayed_run_time;
  delayed_tasks_.push(delayed_task);

  // Reschedule the timer if |delayed_task| will be the next delayed task to
  // run.
  if (delayed_task.delayed_run_time < top_run_time) {
    ::SetTimer(wnd_, reinterpret_cast<UINT_PTR>(this),
               static_cast<DWORD>(delay.InMilliseconds()), NULL);
  }
}

BOOL TaskMarshallerThroughMessageQueue::ProcessWindowMessage(HWND hWnd,
                                                             UINT uMsg,
                                                             WPARAM wParam,
                                                             LPARAM lParam,
                                                             LRESULT& lResult,
                                                             DWORD dwMsgMapID) {
  if (hWnd == wnd_ && uMsg == msg_) {
    ExecuteQueuedTasks();
    lResult = 0;
    return TRUE;
  }

  if (hWnd == wnd_ && uMsg == WM_TIMER) {
    ExecuteDelayedTasks();
    lResult = 0;
    return TRUE;
  }

  return FALSE;
}

base::Closure TaskMarshallerThroughMessageQueue::PopTask() {
  base::AutoLock lock(lock_);
  if (pending_tasks_.empty())
    return base::Closure();

  base::Closure task = pending_tasks_.front();
  pending_tasks_.pop();
  return task;
}

void TaskMarshallerThroughMessageQueue::ExecuteQueuedTasks() {
  DCHECK(CalledOnValidThread());
  base::Closure task;
  while (!(task = PopTask()).is_null())
    task.Run();
}

void TaskMarshallerThroughMessageQueue::ExecuteDelayedTasks() {
  DCHECK(CalledOnValidThread());
  ::KillTimer(wnd_, reinterpret_cast<UINT_PTR>(this));
  while (true) {
    lock_.Acquire();

    if (delayed_tasks_.empty()) {
      lock_.Release();
      return;
    }

    base::PendingTask next_task = delayed_tasks_.top();
    base::TimeTicks now = base::TimeTicks::Now();
    base::TimeTicks next_run = next_task.delayed_run_time;
    if (next_run > now) {
      int64 delay = (next_run - now).InMillisecondsRoundedUp();
      ::SetTimer(wnd_, reinterpret_cast<UINT_PTR>(this),
                 static_cast<DWORD>(delay), NULL);
      lock_.Release();
      return;
    }

    delayed_tasks_.pop();
    lock_.Release();

    // Run the task outside the lock.
    next_task.task.Run();
  }
}

void TaskMarshallerThroughMessageQueue::ClearTasks() {
  base::AutoLock lock(lock_);
  DVLOG_IF(1, !pending_tasks_.empty()) << "Destroying "
                                       << pending_tasks_.size()
                                       << " pending tasks.";
  while (!pending_tasks_.empty())
    pending_tasks_.pop();

  while (!delayed_tasks_.empty())
    delayed_tasks_.pop();
}