// Copyright 2014 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 "components/gcm_driver/gcm_driver.h"

#include <algorithm>

#include "base/logging.h"
#include "components/gcm_driver/gcm_app_handler.h"

namespace gcm {

GCMDriver::GCMDriver() {
}

GCMDriver::~GCMDriver() {
}

void GCMDriver::Register(const std::string& app_id,
                         const std::vector<std::string>& sender_ids,
                         const RegisterCallback& callback) {
  DCHECK(!app_id.empty());
  DCHECK(!sender_ids.empty());
  DCHECK(!callback.is_null());

  GCMClient::Result result = EnsureStarted();
  if (result != GCMClient::SUCCESS) {
    callback.Run(std::string(), result);
    return;
  }

  // If previous un/register operation is still in progress, bail out.
  if (IsAsyncOperationPending(app_id)) {
    callback.Run(std::string(), GCMClient::ASYNC_OPERATION_PENDING);
    return;
  }

  // Normalize the sender IDs by making them sorted.
  std::vector<std::string> normalized_sender_ids = sender_ids;
  std::sort(normalized_sender_ids.begin(), normalized_sender_ids.end());

  register_callbacks_[app_id] = callback;

  RegisterImpl(app_id, normalized_sender_ids);
}

void GCMDriver::Unregister(const std::string& app_id,
                           const UnregisterCallback& callback) {
  DCHECK(!app_id.empty());
  DCHECK(!callback.is_null());

  GCMClient::Result result = EnsureStarted();
  if (result != GCMClient::SUCCESS) {
    callback.Run(result);
    return;
  }

  // If previous un/register operation is still in progress, bail out.
  if (IsAsyncOperationPending(app_id)) {
    callback.Run(GCMClient::ASYNC_OPERATION_PENDING);
    return;
  }

  unregister_callbacks_[app_id] = callback;

  UnregisterImpl(app_id);
}

void GCMDriver::Send(const std::string& app_id,
                     const std::string& receiver_id,
                     const GCMClient::OutgoingMessage& message,
                     const SendCallback& callback) {
  DCHECK(!app_id.empty());
  DCHECK(!receiver_id.empty());
  DCHECK(!callback.is_null());

  GCMClient::Result result = EnsureStarted();
  if (result != GCMClient::SUCCESS) {
    callback.Run(std::string(), result);
    return;
  }

  // If the message with send ID is still in progress, bail out.
  std::pair<std::string, std::string> key(app_id, message.id);
  if (send_callbacks_.find(key) != send_callbacks_.end()) {
    callback.Run(message.id, GCMClient::INVALID_PARAMETER);
    return;
  }

  send_callbacks_[key] = callback;

  SendImpl(app_id, receiver_id, message);
}

void GCMDriver::RegisterFinished(const std::string& app_id,
                                 const std::string& registration_id,
                                 GCMClient::Result result) {
  std::map<std::string, RegisterCallback>::iterator callback_iter =
      register_callbacks_.find(app_id);
  if (callback_iter == register_callbacks_.end()) {
    // The callback could have been removed when the app is uninstalled.
    return;
  }

  RegisterCallback callback = callback_iter->second;
  register_callbacks_.erase(callback_iter);
  callback.Run(registration_id, result);
}

void GCMDriver::UnregisterFinished(const std::string& app_id,
                                   GCMClient::Result result) {
  std::map<std::string, UnregisterCallback>::iterator callback_iter =
      unregister_callbacks_.find(app_id);
  if (callback_iter == unregister_callbacks_.end())
    return;

  UnregisterCallback callback = callback_iter->second;
  unregister_callbacks_.erase(callback_iter);
  callback.Run(result);
}

void GCMDriver::SendFinished(const std::string& app_id,
                             const std::string& message_id,
                             GCMClient::Result result) {
  std::map<std::pair<std::string, std::string>, SendCallback>::iterator
      callback_iter = send_callbacks_.find(
          std::pair<std::string, std::string>(app_id, message_id));
  if (callback_iter == send_callbacks_.end()) {
    // The callback could have been removed when the app is uninstalled.
    return;
  }

  SendCallback callback = callback_iter->second;
  send_callbacks_.erase(callback_iter);
  callback.Run(message_id, result);
}

void GCMDriver::Shutdown() {
  for (GCMAppHandlerMap::const_iterator iter = app_handlers_.begin();
       iter != app_handlers_.end(); ++iter) {
    iter->second->ShutdownHandler();
  }
  app_handlers_.clear();
}

void GCMDriver::AddAppHandler(const std::string& app_id,
                              GCMAppHandler* handler) {
  DCHECK(!app_id.empty());
  DCHECK(handler);
  DCHECK_EQ(app_handlers_.count(app_id), 0u);
  app_handlers_[app_id] = handler;
}

void GCMDriver::RemoveAppHandler(const std::string& app_id) {
  DCHECK(!app_id.empty());
  app_handlers_.erase(app_id);
}

GCMAppHandler* GCMDriver::GetAppHandler(const std::string& app_id) {
  // Look for exact match.
  GCMAppHandlerMap::const_iterator iter = app_handlers_.find(app_id);
  if (iter != app_handlers_.end())
    return iter->second;

  // Ask the handlers whether they know how to handle it.
  for (iter = app_handlers_.begin(); iter != app_handlers_.end(); ++iter) {
    if (iter->second->CanHandle(app_id))
      return iter->second;
  }

  return &default_app_handler_;
}

bool GCMDriver::HasRegisterCallback(const std::string& app_id) {
  return register_callbacks_.find(app_id) != register_callbacks_.end();
}

void GCMDriver::ClearCallbacks() {
  register_callbacks_.clear();
  unregister_callbacks_.clear();
  send_callbacks_.clear();
}

bool GCMDriver::IsAsyncOperationPending(const std::string& app_id) const {
  return register_callbacks_.find(app_id) != register_callbacks_.end() ||
         unregister_callbacks_.find(app_id) != unregister_callbacks_.end();
}

}  // namespace gcm