// 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 "chrome/browser/extensions/api/gcd_private/gcd_private_api.h"

#include <memory>

#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/extensions/api/gcd_private/privet_v3_context_getter.h"
#include "chrome/browser/extensions/api/gcd_private/privet_v3_session.h"
#include "chrome/browser/local_discovery/endpoint_resolver.h"
#include "chrome/browser/local_discovery/service_discovery_shared_client.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "net/url_request/url_request_context_getter.h"

namespace extensions {

namespace gcd_private = api::gcd_private;

namespace {

const char kPrivatAPISetup[] = "/privet/v3/setup/start";
const char kPrivetKeyWifi[] = "wifi";
const char kPrivetKeyPassphrase[] = "passphrase";
const char kPrivetKeySSID[] = "ssid";

base::LazyInstance<BrowserContextKeyedAPIFactory<GcdPrivateAPI> > g_factory =
    LAZY_INSTANCE_INITIALIZER;

}  // namespace

class GcdPrivateSessionHolder;

class GcdPrivateAPIImpl {
 public:
  typedef base::Callback<void(bool success)> SuccessCallback;

  typedef base::Callback<void(int session_id,
                              api::gcd_private::Status status,
                              const base::DictionaryValue& info)>
      CreateSessionCallback;

  typedef base::Callback<void(api::gcd_private::Status status)> SessionCallback;

  typedef base::Callback<void(api::gcd_private::Status status,
                              const base::DictionaryValue& response)>
      MessageResponseCallback;

  explicit GcdPrivateAPIImpl(content::BrowserContext* context);
  virtual ~GcdPrivateAPIImpl();

  static GcdPrivateAPIImpl* Get(content::BrowserContext* context);

  void CreateSession(const std::string& service_name,
                     const CreateSessionCallback& callback);

  void StartPairing(int session_id,
                    api::gcd_private::PairingType pairing_type,
                    const SessionCallback& callback);

  void ConfirmCode(int session_id,
                   const std::string& code,
                   const SessionCallback& callback);

  void SendMessage(int session_id,
                   const std::string& api,
                   const base::DictionaryValue& input,
                   const MessageResponseCallback& callback);

  void RemoveSession(int session_id);
  void RemoveSessionDelayed(int session_id);

  std::unique_ptr<base::ListValue> GetPrefetchedSSIDList();

 private:
  typedef std::map<std::string /* ssid */, std::string /* password */>
      PasswordMap;

  void SendMessageInternal(int session_id,
                           const std::string& api,
                           const base::DictionaryValue& input,
                           const MessageResponseCallback& callback);

  void OnServiceResolved(int session_id,
                         const CreateSessionCallback& callback,
                         const net::IPEndPoint& endpoint);

  scoped_refptr<local_discovery::ServiceDiscoverySharedClient>
      service_discovery_client_;

  struct SessionInfo {
    std::unique_ptr<PrivetV3Session> session;
    std::unique_ptr<local_discovery::EndpointResolver> resolver;
  };

  std::map<int, SessionInfo> sessions_;
  int last_session_id_ = 0;

  content::BrowserContext* const browser_context_;

  scoped_refptr<PrivetV3ContextGetter> context_getter_;

  base::WeakPtrFactory<GcdPrivateAPIImpl> weak_ptr_factory_{this};

  DISALLOW_COPY_AND_ASSIGN(GcdPrivateAPIImpl);
};

GcdPrivateAPIImpl::GcdPrivateAPIImpl(content::BrowserContext* context)
    : browser_context_(context) {
  DCHECK(browser_context_);
}

GcdPrivateAPIImpl::~GcdPrivateAPIImpl() {
}

// static
GcdPrivateAPIImpl* GcdPrivateAPIImpl::Get(content::BrowserContext* context) {
  GcdPrivateAPI* gcd_api =
      BrowserContextKeyedAPIFactory<GcdPrivateAPI>::Get(context);
  return gcd_api ? gcd_api->impl_.get() : NULL;
}

void GcdPrivateAPIImpl::CreateSession(const std::string& service_name,
                                      const CreateSessionCallback& callback) {
  int session_id = last_session_id_++;
  auto& session_data = sessions_[session_id];
  session_data.resolver.reset(new local_discovery::EndpointResolver());
  session_data.resolver->Start(
      service_name, base::Bind(&GcdPrivateAPIImpl::OnServiceResolved,
                               base::Unretained(this), session_id, callback));
}

void GcdPrivateAPIImpl::OnServiceResolved(int session_id,
                                          const CreateSessionCallback& callback,
                                          const net::IPEndPoint& endpoint) {
  if (endpoint.address().empty()) {
    return callback.Run(session_id, gcd_private::STATUS_SERVICERESOLUTIONERROR,
                        base::DictionaryValue());
  }
  auto& session_data = sessions_[session_id];

  if (!context_getter_) {
    context_getter_ = new PrivetV3ContextGetter(
        content::BrowserContext::GetDefaultStoragePartition(browser_context_)->
            GetURLRequestContext()->GetNetworkTaskRunner());
  }

  session_data.session.reset(new PrivetV3Session(
      context_getter_, net::HostPortPair::FromIPEndPoint(endpoint)));
  session_data.session->Init(base::Bind(callback, session_id));
}

void GcdPrivateAPIImpl::StartPairing(int session_id,
                                     api::gcd_private::PairingType pairing_type,
                                     const SessionCallback& callback) {
  auto found = sessions_.find(session_id);

  if (found == sessions_.end())
    return callback.Run(gcd_private::STATUS_UNKNOWNSESSIONERROR);

  found->second.session->StartPairing(pairing_type, callback);
}

void GcdPrivateAPIImpl::ConfirmCode(int session_id,
                                    const std::string& code,
                                    const SessionCallback& callback) {
  auto found = sessions_.find(session_id);

  if (found == sessions_.end())
    return callback.Run(gcd_private::STATUS_UNKNOWNSESSIONERROR);

  found->second.session->ConfirmCode(code, callback);
}

void GcdPrivateAPIImpl::SendMessage(int session_id,
                                    const std::string& api,
                                    const base::DictionaryValue& input,
                                    const MessageResponseCallback& callback) {
  const base::DictionaryValue* input_actual = &input;
  std::unique_ptr<base::DictionaryValue> input_cloned;

  if (api == kPrivatAPISetup) {
    const base::DictionaryValue* wifi = NULL;

    if (input.GetDictionary(kPrivetKeyWifi, &wifi)) {
      std::string ssid;

      if (!wifi->GetString(kPrivetKeySSID, &ssid)) {
        LOG(ERROR) << "Missing " << kPrivetKeySSID;
        return callback.Run(gcd_private::STATUS_SETUPPARSEERROR,
                            base::DictionaryValue());
      }

      if (!wifi->HasKey(kPrivetKeyPassphrase)) {
        LOG(ERROR) << "Password is unknown";
        return callback.Run(gcd_private::STATUS_WIFIPASSWORDERROR,
                            base::DictionaryValue());
      }
    }
  }

  auto found = sessions_.find(session_id);

  if (found == sessions_.end()) {
    return callback.Run(gcd_private::STATUS_UNKNOWNSESSIONERROR,
                        base::DictionaryValue());
  }

  found->second.session->SendMessage(api, *input_actual, callback);
}

void GcdPrivateAPIImpl::RemoveSession(int session_id) {
  sessions_.erase(session_id);
}

void GcdPrivateAPIImpl::RemoveSessionDelayed(int session_id) {
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::Bind(&GcdPrivateAPIImpl::RemoveSession,
                            weak_ptr_factory_.GetWeakPtr(), session_id));
}

std::unique_ptr<base::ListValue> GcdPrivateAPIImpl::GetPrefetchedSSIDList() {
  std::unique_ptr<base::ListValue> retval(new base::ListValue);

  return retval;
}



GcdPrivateAPI::GcdPrivateAPI(content::BrowserContext* context)
    : impl_(new GcdPrivateAPIImpl(context)) {
}

GcdPrivateAPI::~GcdPrivateAPI() {
}

// static
BrowserContextKeyedAPIFactory<GcdPrivateAPI>*
GcdPrivateAPI::GetFactoryInstance() {
  return g_factory.Pointer();
}

GcdPrivateGetDeviceInfoFunction::GcdPrivateGetDeviceInfoFunction() {
}

GcdPrivateGetDeviceInfoFunction::~GcdPrivateGetDeviceInfoFunction() {
}

bool GcdPrivateGetDeviceInfoFunction::RunAsync() {
  std::unique_ptr<gcd_private::CreateSession::Params> params =
      gcd_private::CreateSession::Params::Create(*args_);

  if (!params)
    return false;

  GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile());

  GcdPrivateAPIImpl::CreateSessionCallback callback =
      base::Bind(&GcdPrivateGetDeviceInfoFunction::OnSessionInitialized, this);
  gcd_api->CreateSession(params->service_name, callback);

  return true;
}

void GcdPrivateGetDeviceInfoFunction::OnSessionInitialized(
    int session_id,
    api::gcd_private::Status status,
    const base::DictionaryValue& info) {
  gcd_private::GetDeviceInfo::Results::DeviceInfo device_info;
  device_info.additional_properties.MergeDictionary(&info);

  results_ = gcd_private::GetDeviceInfo::Results::Create(status, device_info);
  SendResponse(true);

  GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile());
  gcd_api->RemoveSessionDelayed(session_id);
}

GcdPrivateCreateSessionFunction::GcdPrivateCreateSessionFunction() {
}

GcdPrivateCreateSessionFunction::~GcdPrivateCreateSessionFunction() {
}

bool GcdPrivateCreateSessionFunction::RunAsync() {
  std::unique_ptr<gcd_private::CreateSession::Params> params =
      gcd_private::CreateSession::Params::Create(*args_);

  if (!params)
    return false;

  GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile());

  GcdPrivateAPIImpl::CreateSessionCallback callback =
      base::Bind(&GcdPrivateCreateSessionFunction::OnSessionInitialized, this);
  gcd_api->CreateSession(params->service_name, callback);

  return true;
}

void GcdPrivateCreateSessionFunction::OnSessionInitialized(
    int session_id,
    api::gcd_private::Status status,
    const base::DictionaryValue& info) {
  std::vector<api::gcd_private::PairingType> pairing_types;

  // TODO(vitalybuka): Remove this parsing and |pairing_types| from callback.
  if (status == gcd_private::STATUS_SUCCESS) {
    const base::ListValue* pairing = nullptr;
    if (info.GetList("authentication.pairing", &pairing)) {
      for (const auto& value : *pairing) {
        std::string pairing_string;
        if (value->GetAsString(&pairing_string)) {
          api::gcd_private::PairingType pairing_type =
              api::gcd_private::ParsePairingType(pairing_string);
          if (pairing_type != api::gcd_private::PAIRING_TYPE_NONE)
            pairing_types.push_back(pairing_type);
        }
      }
    } else {
      status = gcd_private::STATUS_SESSIONERROR;
    }
  }

  results_ = gcd_private::CreateSession::Results::Create(session_id, status,
                                                         pairing_types);
  SendResponse(true);
}

GcdPrivateStartPairingFunction::GcdPrivateStartPairingFunction() {
}

GcdPrivateStartPairingFunction::~GcdPrivateStartPairingFunction() {
}

bool GcdPrivateStartPairingFunction::RunAsync() {
  std::unique_ptr<gcd_private::StartPairing::Params> params =
      gcd_private::StartPairing::Params::Create(*args_);

  if (!params)
    return false;

  GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile());

  gcd_api->StartPairing(
      params->session_id, params->pairing_type,
      base::Bind(&GcdPrivateStartPairingFunction::OnPairingStarted, this));

  return true;
}

void GcdPrivateStartPairingFunction::OnPairingStarted(
    api::gcd_private::Status status) {
  results_ = gcd_private::StartPairing::Results::Create(status);
  SendResponse(true);
}

GcdPrivateConfirmCodeFunction::GcdPrivateConfirmCodeFunction() {
}

GcdPrivateConfirmCodeFunction::~GcdPrivateConfirmCodeFunction() {
}

bool GcdPrivateConfirmCodeFunction::RunAsync() {
  std::unique_ptr<gcd_private::ConfirmCode::Params> params =
      gcd_private::ConfirmCode::Params::Create(*args_);

  if (!params)
    return false;

  GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile());

  gcd_api->ConfirmCode(
      params->session_id, params->code,
      base::Bind(&GcdPrivateConfirmCodeFunction::OnCodeConfirmed, this));

  return true;
}

void GcdPrivateConfirmCodeFunction::OnCodeConfirmed(
    api::gcd_private::Status status) {
  results_ = gcd_private::ConfirmCode::Results::Create(status);
  SendResponse(true);
}

GcdPrivateSendMessageFunction::GcdPrivateSendMessageFunction() {
}

GcdPrivateSendMessageFunction::~GcdPrivateSendMessageFunction() {
}

bool GcdPrivateSendMessageFunction::RunAsync() {
  std::unique_ptr<gcd_private::PassMessage::Params> params =
      gcd_private::PassMessage::Params::Create(*args_);

  if (!params)
    return false;

  GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile());


  gcd_api->SendMessage(
      params->session_id,
      params->api,
      params->input.additional_properties,
      base::Bind(&GcdPrivateSendMessageFunction::OnMessageSentCallback, this));

  return true;
}

void GcdPrivateSendMessageFunction::OnMessageSentCallback(
    api::gcd_private::Status status,
    const base::DictionaryValue& value) {
  gcd_private::PassMessage::Results::Response response;
  response.additional_properties.MergeDictionary(&value);

  results_ = gcd_private::PassMessage::Results::Create(status, response);
  SendResponse(true);
}

GcdPrivateTerminateSessionFunction::GcdPrivateTerminateSessionFunction() {
}

GcdPrivateTerminateSessionFunction::~GcdPrivateTerminateSessionFunction() {
}

bool GcdPrivateTerminateSessionFunction::RunAsync() {
  std::unique_ptr<gcd_private::TerminateSession::Params> params =
      gcd_private::TerminateSession::Params::Create(*args_);

  if (!params)
    return false;

  GcdPrivateAPIImpl* gcd_api = GcdPrivateAPIImpl::Get(GetProfile());

  gcd_api->RemoveSession(params->session_id);

  SendResponse(true);
  return true;
}

}  // namespace extensions
