// Copyright (c) 2012 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 <cmath>
#include <set>
#include <vector>

#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "content/browser/gamepad/data_fetcher.h"
#include "content/browser/gamepad/gamepad_provider.h"
#include "content/browser/gamepad/platform_data_fetcher.h"
#include "content/common/gamepad_hardware_buffer.h"
#include "content/common/gamepad_messages.h"
#include "content/public/browser/browser_thread.h"

namespace content {

GamepadProvider::GamepadProvider(GamepadDataFetcher* fetcher)
    : is_paused_(false),
      devices_changed_(true),
      provided_fetcher_(fetcher),
      ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
  size_t data_size = sizeof(GamepadHardwareBuffer);
  base::SystemMonitor* monitor = base::SystemMonitor::Get();
  if (monitor)
    monitor->AddDevicesChangedObserver(this);
  bool res = gamepad_shared_memory_.CreateAndMapAnonymous(data_size);
  DCHECK(res);
  GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
  memset(hwbuf, 0, sizeof(GamepadHardwareBuffer));

  polling_thread_.reset(new base::Thread("Gamepad polling thread"));
  polling_thread_->StartWithOptions(
      base::Thread::Options(MessageLoop::TYPE_IO, 0));

  MessageLoop* polling_loop = polling_thread_->message_loop();
  polling_loop->PostTask(
      FROM_HERE,
      base::Bind(&GamepadProvider::DoInitializePollingThread, this));
}

GamepadProvider::~GamepadProvider() {
  base::SystemMonitor* monitor = base::SystemMonitor::Get();
  if (monitor)
    monitor->RemoveDevicesChangedObserver(this);

  polling_thread_.reset();
  data_fetcher_.reset();
}

base::SharedMemoryHandle GamepadProvider::GetRendererSharedMemoryHandle(
    base::ProcessHandle process) {
  base::SharedMemoryHandle renderer_handle;
  gamepad_shared_memory_.ShareToProcess(process, &renderer_handle);
  return renderer_handle;
}

void GamepadProvider::Pause() {
  {
    base::AutoLock lock(is_paused_lock_);
    is_paused_ = true;
  }
  if (data_fetcher_.get())
    data_fetcher_->PauseHint(true);
}

void GamepadProvider::Resume() {
  {
    base::AutoLock lock(is_paused_lock_);
    if (!is_paused_)
        return;
    is_paused_ = false;
  }
  if (data_fetcher_.get())
    data_fetcher_->PauseHint(false);

  MessageLoop* polling_loop = polling_thread_->message_loop();
  polling_loop->PostTask(
      FROM_HERE,
      base::Bind(&GamepadProvider::ScheduleDoPoll, this));
}

void GamepadProvider::OnDevicesChanged() {
  base::AutoLock lock(devices_changed_lock_);
  devices_changed_ = true;
}

void GamepadProvider::DoInitializePollingThread() {
  DCHECK(MessageLoop::current() == polling_thread_->message_loop());

  if (!provided_fetcher_.get())
    provided_fetcher_.reset(new GamepadPlatformDataFetcher);

  // Pass ownership of fetcher to provider_.
  data_fetcher_.swap(provided_fetcher_);

  // Start polling.
  ScheduleDoPoll();
}

void GamepadProvider::DoPoll() {
  DCHECK(MessageLoop::current() == polling_thread_->message_loop());
  bool changed;
  GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();

  ANNOTATE_BENIGN_RACE_SIZED(
      &hwbuf->buffer,
      sizeof(WebKit::WebGamepads),
      "Racey reads are discarded");

  {
    base::AutoLock lock(devices_changed_lock_);
    changed = devices_changed_;
    devices_changed_ = false;
  }

  // Acquire the SeqLock. There is only ever one writer to this data.
  // See gamepad_hardware_buffer.h.
  hwbuf->sequence.WriteBegin();
  data_fetcher_->GetGamepadData(&hwbuf->buffer, changed);
  hwbuf->sequence.WriteEnd();

  // Schedule our next interval of polling.
  ScheduleDoPoll();
}

void GamepadProvider::ScheduleDoPoll() {
  DCHECK(MessageLoop::current() == polling_thread_->message_loop());

  {
    base::AutoLock lock(is_paused_lock_);
    if (is_paused_)
      return;
  }

  MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&GamepadProvider::DoPoll, weak_factory_.GetWeakPtr()),
      base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs));
}

GamepadHardwareBuffer* GamepadProvider::SharedMemoryAsHardwareBuffer() {
  void* mem = gamepad_shared_memory_.memory();
  DCHECK(mem);
  return static_cast<GamepadHardwareBuffer*>(mem);
}

}  // namespace content