// Copyright 2014 The Chromium OS 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 <brillo/http/curl_api.h>

#include <base/logging.h>

namespace brillo {
namespace http {

namespace {

static_assert(CURLOPTTYPE_LONG == 0 &&
              CURLOPTTYPE_OBJECTPOINT == 10000 &&
              CURLOPTTYPE_FUNCTIONPOINT == 20000 &&
              CURLOPTTYPE_OFF_T == 30000,
              "CURL option types are expected to be multiples of 10000");

inline bool VerifyOptionType(CURLoption option, int expected_type) {
  int option_type = (static_cast<int>(option) / 10000) * 10000;
  return (option_type == expected_type);
}

}  // anonymous namespace

CurlApi::CurlApi() {
  curl_global_init(CURL_GLOBAL_ALL);
}

CurlApi::~CurlApi() {
  curl_global_cleanup();
}

CURL* CurlApi::EasyInit() {
  return curl_easy_init();
}

void CurlApi::EasyCleanup(CURL* curl) {
  curl_easy_cleanup(curl);
}

CURLcode CurlApi::EasySetOptInt(CURL* curl, CURLoption option, int value) {
  CHECK(VerifyOptionType(option, CURLOPTTYPE_LONG))
      << "Only options that expect a LONG data type must be specified here";
  // CURL actually uses "long" type, so have to make sure we feed it what it
  // expects.
  // NOLINTNEXTLINE(runtime/int)
  return curl_easy_setopt(curl, option, static_cast<long>(value));
}

CURLcode CurlApi::EasySetOptStr(CURL* curl,
                                CURLoption option,
                                const std::string& value) {
  CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT))
      << "Only options that expect a STRING data type must be specified here";
  return curl_easy_setopt(curl, option, value.c_str());
}

CURLcode CurlApi::EasySetOptPtr(CURL* curl, CURLoption option, void* value) {
  CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT))
      << "Only options that expect a pointer data type must be specified here";
  return curl_easy_setopt(curl, option, value);
}

CURLcode CurlApi::EasySetOptCallback(CURL* curl,
                                     CURLoption option,
                                     intptr_t address) {
  CHECK(VerifyOptionType(option, CURLOPTTYPE_FUNCTIONPOINT))
      << "Only options that expect a function pointers must be specified here";
  return curl_easy_setopt(curl, option, address);
}

CURLcode CurlApi::EasySetOptOffT(CURL* curl,
                                 CURLoption option,
                                 curl_off_t value) {
  CHECK(VerifyOptionType(option, CURLOPTTYPE_OFF_T))
      << "Only options that expect a large data size must be specified here";
  return curl_easy_setopt(curl, option, value);
}

CURLcode CurlApi::EasyPerform(CURL* curl) {
  return curl_easy_perform(curl);
}

CURLcode CurlApi::EasyGetInfoInt(CURL* curl, CURLINFO info, int* value) const {
  CHECK_EQ(CURLINFO_LONG, info & CURLINFO_TYPEMASK) << "Wrong option type";
  long data = 0;  // NOLINT(runtime/int) - curl expects a long here.
  CURLcode code = curl_easy_getinfo(curl, info, &data);
  if (code == CURLE_OK)
    *value = static_cast<int>(data);
  return code;
}

CURLcode CurlApi::EasyGetInfoDbl(CURL* curl,
                                 CURLINFO info,
                                 double* value) const {
  CHECK_EQ(CURLINFO_DOUBLE, info & CURLINFO_TYPEMASK) << "Wrong option type";
  return curl_easy_getinfo(curl, info, value);
}

CURLcode CurlApi::EasyGetInfoStr(CURL* curl,
                                 CURLINFO info,
                                 std::string* value) const {
  CHECK_EQ(CURLINFO_STRING, info & CURLINFO_TYPEMASK) << "Wrong option type";
  char* data = nullptr;
  CURLcode code = curl_easy_getinfo(curl, info, &data);
  if (code == CURLE_OK)
    *value = data;
  return code;
}

CURLcode CurlApi::EasyGetInfoPtr(CURL* curl,
                                 CURLINFO info,
                                 void** value) const {
  // CURL uses "string" type for generic pointer info. Go figure.
  CHECK_EQ(CURLINFO_STRING, info & CURLINFO_TYPEMASK) << "Wrong option type";
  return curl_easy_getinfo(curl, info, value);
}

std::string CurlApi::EasyStrError(CURLcode code) const {
  return curl_easy_strerror(code);
}

CURLM* CurlApi::MultiInit() {
  return curl_multi_init();
}

CURLMcode CurlApi::MultiCleanup(CURLM* multi_handle) {
  return curl_multi_cleanup(multi_handle);
}

CURLMsg* CurlApi::MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) {
  return curl_multi_info_read(multi_handle, msgs_in_queue);
}

CURLMcode CurlApi::MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) {
  return curl_multi_add_handle(multi_handle, curl_handle);
}

CURLMcode CurlApi::MultiRemoveHandle(CURLM* multi_handle, CURL* curl_handle) {
  return curl_multi_remove_handle(multi_handle, curl_handle);
}

CURLMcode CurlApi::MultiSetSocketCallback(CURLM* multi_handle,
                                          curl_socket_callback socket_callback,
                                          void* userp) {
  CURLMcode code =
      curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, socket_callback);
  if (code != CURLM_OK)
    return code;
  return curl_multi_setopt(multi_handle, CURLMOPT_SOCKETDATA, userp);
}

CURLMcode CurlApi::MultiSetTimerCallback(
    CURLM* multi_handle,
    curl_multi_timer_callback timer_callback,
    void* userp) {
  CURLMcode code =
      curl_multi_setopt(multi_handle, CURLMOPT_TIMERFUNCTION, timer_callback);
  if (code != CURLM_OK)
    return code;
  return curl_multi_setopt(multi_handle, CURLMOPT_TIMERDATA, userp);
}

CURLMcode CurlApi::MultiAssign(CURLM* multi_handle,
                               curl_socket_t sockfd,
                               void* sockp) {
  return curl_multi_assign(multi_handle, sockfd, sockp);
}

CURLMcode CurlApi::MultiSocketAction(CURLM* multi_handle,
                                     curl_socket_t s,
                                     int ev_bitmask,
                                     int* running_handles) {
  return curl_multi_socket_action(multi_handle, s, ev_bitmask, running_handles);
}

std::string CurlApi::MultiStrError(CURLMcode code) const {
  return curl_multi_strerror(code);
}

CURLMcode CurlApi::MultiPerform(CURLM* multi_handle, int* running_handles) {
  return curl_multi_perform(multi_handle, running_handles);
}

CURLMcode CurlApi::MultiWait(CURLM* multi_handle,
                             curl_waitfd extra_fds[],
                             unsigned int extra_nfds,
                             int timeout_ms,
                             int* numfds) {
  return curl_multi_wait(multi_handle, extra_fds, extra_nfds, timeout_ms,
                         numfds);
}

}  // namespace http
}  // namespace brillo