/*
 * client.cpp
 *
 *  Created on: 2021年8月16日
 *      Author: pengzc
 */

#include "http_client.h"
#include <string.h>
#include <algorithm>
#include <signal.h>
#include <unistd.h>
#include <fstream>
#include "utils/Log.h"
#include "manager/ConfigManager.h"
#include "curl/easy.h"
#include "curl/curl.h"

namespace {
class CURLGlobal {
public:
  CURLGlobal() {
    CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
    if (res != CURLE_OK) {
      LOGE("CURL curl_global_init failed");
    }

    //忽略SIGPIPE
    struct sigaction sa;
    sa.sa_handler = SIG_IGN;
    sa.sa_flags = 0;
    if (sigemptyset(&sa.sa_mask) == -1 ||
        sigaction(SIGPIPE, &sa, 0) == -1) {
      LOGE("failed ignore SIGPIPE");
    }
  }

  ~ CURLGlobal() {
    curl_global_cleanup();
  }
};

} /* anonymous namespace */

namespace base {

HttpClient::HttpClient() {
  static CURLGlobal global;
  curl_ = curl_easy_init();
  if (curl_ == NULL) {
    LOGE("curl_easy_init failed");
  }

  timeout_ = 0;
  connection_timeout_ = 0;

  ssl_verify_ = true;
  aborted_ = false;

  low_speed_limit_.bytes = 0;
  low_speed_limit_.seconds = 0;

  debug_ = false;

  ca_info_file_path_ = CONFIGMANAGER->getResFilePath("cacert.pem");

}

HttpClient::~HttpClient() {
  if (curl_) {
    curl_easy_cleanup(curl_);
  }
}

HttpResponse HttpClient::Do(const HttpRequest& request) {
  return Do(request, "", NULL);
}

HttpResponse HttpClient::Do(const HttpRequest& request, ProgressCallback progress) {
  return Do(request, "", progress);
}

HttpResponse HttpClient::Do(const HttpRequest& request, const std::string& save_path,
    ProgressCallback progress) {
  Mutex::Autolock lock(do_mutex_);

  if (curl_ == NULL) {
    return HttpResponse();
  }
  curl_easy_reset(curl_);

  aborted_ = false;
  HttpResponse response;
  std::string url = request.url_;
  std::string header_string;
  CURLcode res = CURLE_OK;

  /** set query URL */
  curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());

#if 0
  if (request.method_.compare("download") != 0) {
      /** set callback function */
      curl_easy_setopt(this->curl_, CURLOPT_WRITEFUNCTION,
                       Helpers::write_callback);
      /** set data object to pass to callback function */
      curl_easy_setopt(this->curl_, CURLOPT_WRITEDATA, &ret);
  } else
#endif

  /** set data callback */
  curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION,
      curl_response_write_callback);
  /** set data object to pass to callback function */
  curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response);

  if (request.method_.compare("GET") == 0) {

  } else if (request.method_.compare("POST") == 0) {
    /** Now specify we want to POST data */
    curl_easy_setopt(this->curl_, CURLOPT_POST, 1L);

    if (!request.body_.empty()) {
      /** set post fields */
      curl_easy_setopt(this->curl_, CURLOPT_POSTFIELDS,
          request.body_.c_str());
      curl_easy_setopt(this->curl_, CURLOPT_POSTFIELDSIZE,
          request.body_.size());
    }

    if (!request.multiparts_.empty()) {
      struct curl_httppost *formpost = NULL;
      struct curl_httppost *lastptr  = NULL;
      for (auto it = request.multiparts_.begin(); it != request.multiparts_.end(); ++it) {
        curl_formadd(&formpost, &lastptr,
            CURLFORM_PTRNAME, it->first.c_str(),
            CURLFORM_PTRCONTENTS, it->second.data(),
            CURLFORM_CONTENTSLENGTH, it->second.length(),
            CURLFORM_END);
      }

      /** Now specify we want to POST data */
      curl_easy_setopt(this->curl_, CURLOPT_HTTPPOST, formpost);
    }
  }


#if 0
  /** set the header callback function */
  curl_easy_setopt(this->curl_, CURLOPT_HEADERFUNCTION, header_callback);
  /** callback object for headers */
  curl_easy_setopt(this->curl_, CURLOPT_HEADERDATA, &response);
#endif

  /** set http headers */
  curl_slist* header_list = NULL;
  for (HttpRequest::HeaderFields::const_iterator it = request.header.begin();
      it != request.header.end(); ++it) {
    header_string = it->first;
    header_string += ": ";
    header_string += it->second;
    curl_slist* temp = curl_slist_append(header_list, header_string.c_str());
    if (temp == NULL) {
      LOGE("curl_slist_append failed");
      curl_slist_free_all(header_list);
      header_list = NULL;
      break;
    }
    header_list = temp;
  }
  if (header_list != NULL) {
    curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, header_list);
  }

  progress_callback_ = progress;
  curl_easy_setopt(curl_, CURLOPT_XFERINFOFUNCTION,
      HttpClient::curl_progress_callback);
  curl_easy_setopt(curl_, CURLOPT_XFERINFODATA, this);
  curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, 0);

  if ((low_speed_limit_.bytes > 0) && (low_speed_limit_.seconds > 0)) {
    curl_easy_setopt(curl_, CURLOPT_LOW_SPEED_LIMIT, low_speed_limit_.bytes);
    curl_easy_setopt(curl_, CURLOPT_LOW_SPEED_TIME, low_speed_limit_.seconds);
  }

#if 0
  // set basic auth if configured
  if (this->basicAuth.username.length() > 0) {
    std::string authString = std::string(this->basicAuth.username + ":" +
                                         this->basicAuth.password);
    curl_easy_setopt(this->curl_, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
    curl_easy_setopt(this->curl_, CURLOPT_USERPWD, authString.c_str());
  }
#endif

#if 0
  /** set user agent */
  curl_easy_setopt(this->curl_, CURLOPT_USERAGENT,
                   this->GetUserAgent().c_str());
#endif

  // set timeout
  if (connection_timeout_ > 0) {
    curl_easy_setopt(this->curl_, CURLOPT_CONNECTTIMEOUT_MS,
        connection_timeout_);
    // dont want to get a sig alarm on timeout
    curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L);
  }
  if (timeout_ > 0) {
    curl_easy_setopt(this->curl_, CURLOPT_TIMEOUT_MS, timeout_);
    // dont want to get a sig alarm on timeout
    curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L);
  }

  //允许重定向
  curl_easy_setopt(this->curl_, CURLOPT_FOLLOWLOCATION, 1L);
  curl_easy_setopt(this->curl_, CURLOPT_MAXREDIRS, static_cast<int64_t>(9));

  //多线程下使用超时禁用SIGNAL
  curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L);

  //指定DNS解析规范为IPV4
  curl_easy_setopt(curl_, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);

    //指定DNS
  curl_easy_setopt(curl_, CURLOPT_DNS_SERVERS,
      "1.2.4.8,210.2.4.8,114.114.114.114,119.29.29.29");

  // if provided, supply CA path
  if (!this->ca_info_file_path_.empty()) {
    curl_easy_setopt(curl_, CURLOPT_CAINFO, ca_info_file_path_.c_str());
  }

  if (!ssl_verify_) {
    curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYHOST, 0L);
  }

  if (debug_) {
    curl_easy_setopt(curl_, CURLOPT_DEBUGFUNCTION, debug_trace);
    /* the DEBUGFUNCTION has no effect until we enable VERBOSE */
    curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
  }

  if (!save_path.empty()) {
    response.dst_file_ = fopen(save_path.c_str(), "wb");
    if (response.dst_file_ == NULL) {
      LOGE("open %s failed, %s", save_path.c_str(), strerror(errno));
      response.error_code_ = errno;
      response.error_message_ = strerror(errno);
      return response;
    }
  }

#if 0
  // set cert file path
  if (!this->certPath.empty()) {
    curl_easy_setopt(this->curl_, CURLOPT_SSLCERT,
                     this->certPath.c_str());
  }

  // set cert type
  if (!this->certType.empty()) {
    curl_easy_setopt(this->curl_, CURLOPT_SSLCERTTYPE,
                     this->certType.c_str());
  }
  // set key file path
  if (!this->keyPath.empty()) {
    curl_easy_setopt(this->curl_, CURLOPT_SSLKEY,
                     this->keyPath.c_str());
  }
  // set key password
  if (!this->keyPassword.empty()) {
    curl_easy_setopt(this->curl_, CURLOPT_KEYPASSWD,
                     this->keyPassword.c_str());
  }

  // set web proxy address
  if (!this->uriProxy.empty()) {
    curl_easy_setopt(this->curl_, CURLOPT_PROXY,
                     uriProxy.c_str());
    curl_easy_setopt(this->curl_, CURLOPT_HTTPPROXYTUNNEL,
                     1L);
  }
#endif

  char error_buffer[CURL_ERROR_SIZE] = { 0 };
  curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buffer);

  res = curl_easy_perform(curl_);
  if (res != CURLE_OK) {
    response.error_message_ = error_buffer;
    response.error_code_ = res;
  } else {
    int64_t http_code = 0;
    curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code);
    response.status_code_ = static_cast<int>(http_code);
  }

  curl_easy_getinfo(curl_, CURLINFO_TOTAL_TIME,
                    &response.total_time);
  curl_easy_getinfo(this->curl_, CURLINFO_NAMELOOKUP_TIME,
                    &response.name_lookup_time);
  curl_easy_getinfo(this->curl_, CURLINFO_CONNECT_TIME,
                    &response.connect_time);
  curl_easy_getinfo(this->curl_, CURLINFO_APPCONNECT_TIME,
                    &response.app_connect_time);
  curl_easy_getinfo(this->curl_, CURLINFO_PRETRANSFER_TIME,
                    &response.pre_transfer_time);
  curl_easy_getinfo(this->curl_, CURLINFO_STARTTRANSFER_TIME,
                    &response.start_transfer_time);
  curl_easy_getinfo(this->curl_, CURLINFO_REDIRECT_TIME,
                    &response.redirect_time);
  curl_easy_getinfo(this->curl_, CURLINFO_REDIRECT_COUNT,
                    &response.redirect_count);

  if (response.dst_file_ != NULL) {
    fflush(response.dst_file_);
    fclose(response.dst_file_);
    sync();
    response.dst_file_ = NULL;
  }

  // free header list
  curl_slist_free_all(header_list);
  return response;
}

/**
 * @brief set custom Certificate Authority (CA) path
 *
 * @param caInfoFilePath - The path to a file holding the certificates used to
 * verify the peer with. See CURLOPT_CAINFO
 *
 */
void
HttpClient::SetCAInfoFilePath(const std::string& caInfoFilePath) {
  this->ca_info_file_path_ = caInfoFilePath;
}

#if 0
/**
 * @brief set username and password for basic auth
 *
 * @param username
 * @param password
 *
 */
void
HttpClient::SetBasicAuth(const std::string& username,
                                     const std::string& password) {
  this->basicAuth.username = username;
  this->basicAuth.password = password;
}

#endif

/**
 * @brief set certificate path
 *
 * @param path to certificate file
 *
 */
//void
//HttpClient::SetCertPath(const std::string& cert) {
//  this->certPath = cert;
//}

/**
 * @brief set certificate type
 *
 * @param certificate type (e.g. "PEM" or "DER")
 *
 */
//void
//HttpClient::SetCertType(const std::string& certType) {
//  this->certType = certType;
//}

/**
 * @brief set key path
 *
 * @param path to key file
 *
 */
//void
//HttpClient::SetKeyPath(const std::string& keyPath) {
//  this->keyPath = keyPath;
//}

/**
 * @brief set key password
 *
 * @param key password
 *
 */
//void
//HttpClient::SetKeyPassword(const std::string& keyPassword) {
//  this->keyPassword = keyPassword;
//}

/**
 * @brief set HTTP proxy address and port
 *
 * @param proxy address with port number
 *
 */
//void
//HttpClient::SetProxy(const std::string& uriProxy) {
//  std::string uriProxyUpper = uriProxy;
//  // check if the provided address is prefixed with "http"
//  std::transform(uriProxyUpper.begin(), uriProxyUpper.end(),
//    uriProxyUpper.begin(), ::toupper);
//
//  if ((uriProxy.length() > 0) && (uriProxyUpper.compare(0, 4, "HTTP") != 0)) {
//    this->uriProxy = "http://" + uriProxy;
//  } else {
//    this->uriProxy = uriProxy;
//  }
//}

/**
 * @brief helper function to get called from the actual request methods to
 * prepare the curlHandle for transfer with generic options, perform the
 * request and record some stats from the last request and then reset the
 * handle with curl_easy_reset to its default state. This will keep things
 * like connections and session ID intact but makes sure you can change
 * parameters on the object for another request.
 *
 * @param uri URI to query
 * @param ret Reference to the Response struct that should be filled
 *
 * @return 0 on success and 1 on error
 */
//Response
//HttpClient::performCurlRequest(const std::string& uri, const std::string method_type) {}

/**
 * @brief HTTP GET method
 *
 * @param url to query
 *
 * @return response struct
 */
//Response
//HttpClient::get(const std::string& url) {
//  return this->performCurlRequest(url);
//}
/**
 * @brief HTTP POST method
 *
 * @param url to query
 * @param data HTTP POST body
 *
 * @return response struct
 */
//Response
//HttpClient::post(const std::string& url,
//                             const std::string& data) {
//  /** Now specify we want to POST data */
//  curl_easy_setopt(this->curlHandle, CURLOPT_POST, 1L);
//  /** set post fields */
//  curl_easy_setopt(this->curlHandle, CURLOPT_POSTFIELDS, data.c_str());
//  curl_easy_setopt(this->curlHandle, CURLOPT_POSTFIELDSIZE, data.size());
//
//  return this->performCurlRequest(url);
//}

//Response
//HttpClient::post(const std::string& url,
//    const std::map<std::string, std::string>& form_data) {
//
//  struct curl_httppost *formpost = 0;
//  struct curl_httppost *lastptr  = 0;
//  for (auto it = form_data.begin(); it != form_data.end(); ++it) {
//    curl_formadd(&formpost, &lastptr,
//        CURLFORM_PTRNAME, it->first.c_str(),
//        CURLFORM_PTRCONTENTS, it->second.data(),
//        CURLFORM_CONTENTSLENGTH, it->second.length(),
//        CURLFORM_END);
//  }
//
//  /** Now specify we want to POST data */
//  curl_easy_setopt(this->curlHandle, CURLOPT_HTTPPOST, formpost);
//
//  Response response = this->performCurlRequest(url);
//  curl_formfree(formpost);
//  return response;
//}

HttpClient& HttpClient::SetConnectionTimeout(int ms) {
  connection_timeout_ = ms;
  return *this;
}

HttpClient& HttpClient::SetTimeout(int ms) {
  timeout_ = ms;
  return *this;
}


// trim from start
static inline std::string &ltrim(std::string &s) {  // NOLINT
  s.erase(s.begin(), std::find_if(s.begin(), s.end(),
        std::not1(std::ptr_fun<int, int>(std::isspace))));
  return s;
}

// trim from end
static inline std::string &rtrim(std::string &s) { // NOLINT
  s.erase(std::find_if(s.rbegin(), s.rend(),
        std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
  return s;
}

// trim from both ends
static inline std::string &trim(std::string &s) {  // NOLINT
  return ltrim(rtrim(s));
}

size_t HttpClient::curl_response_write_callback(void* data, size_t size,
    size_t nmemb, void* userdata) {
  HttpResponse* response = (HttpResponse*) userdata;
  if (response->dst_file_ == NULL) {
    response->body_.append(reinterpret_cast<char*>(data), size * nmemb);
    return size * nmemb;
  }
  return fwrite(reinterpret_cast<char*>(data), 1, size * nmemb,
      response->dst_file_);
}

size_t HttpClient::header_callback(void *data, size_t size,
                                            size_t nmemb, void *userdata) {

  HttpResponse* r = (HttpResponse*)userdata;
  std::string header(reinterpret_cast<char*>(data), size*nmemb);

  //LOGD("Header Callback  [%s]", trim(header).c_str());

  size_t seperator = header.find_first_of(':');
  if ( std::string::npos == seperator ) {
    // roll with non seperated headers...
    trim(header);
    if (0 == header.length()) {
      return (size * nmemb);  // blank line;
    }
    r->header_[header] = "present";
  } else {
    std::string key = header.substr(0, seperator);
    trim(key);
    std::string value = header.substr(seperator + 1);
    trim(value);

    auto it = r->header_.find(key);
    if (it == r->header_.end()) {
      //LOGD("It's new Head Field %s", key.c_str());
      r->header_[key] = value;
    } else {
      //LOGD("Duplicate Head Field %s", key.c_str());
    }
    //LOGD("headers size %d", r->header_.size());
//    LOGD("HHHHHHHHHHHHHead%p %s %s", r,  key.c_str(), value.c_str());
  }
  return (size * nmemb);
}

void HttpClient::SetSSLVerify(bool verify) {
  ssl_verify_ = verify;
}

void HttpClient::Abort() {
  aborted_ = true;
  Mutex::Autolock lock(do_mutex_);
}

int HttpClient::curl_progress_callback(void* data, curl_off_t dltotal,
    curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
  HttpClient* client = (HttpClient*)data;

  if (client->aborted_) {
    return -1;
  }
  if (client->progress_callback_ != NULL) {
    return client->progress_callback_(dltotal, dlnow, ultotal, ulnow);
  }
  return 0;
}

static void dump(const char *text, unsigned char *ptr, size_t size) {
  LOGD("%s, %10.10ld bytes (0x%8.8lx)\n",
          text, (long)size, (long)size);
}

int HttpClient::debug_trace(CURL *handle, curl_infotype type, char *data, ::size_t size,
    void *userp) {
  const char *text;
   (void)handle; /* prevent compiler warning */
   (void)userp;

   switch (type) {
   case CURLINFO_TEXT:
     LOGD("== Info: %s", data);
   default: /* in case a new one is introduced to shock us */
     return 0;

   case CURLINFO_HEADER_OUT:
     text = "=> Send header";
     break;
   case CURLINFO_DATA_OUT:
     text = "=> Send data";
     break;
   case CURLINFO_SSL_DATA_OUT:
     text = "=> Send SSL data";
     break;
   case CURLINFO_HEADER_IN:
     text = "<= Recv header";
     break;
   case CURLINFO_DATA_IN:
     text = "<= Recv data";
     break;
   case CURLINFO_SSL_DATA_IN:
     text = "<= Recv SSL data";
     break;
   }

   dump(text, (unsigned char *)data, size);
   return 0;
}

HttpClient& HttpClient::SetLowSpeedLimit(int bytes, int seconds) {
  low_speed_limit_.bytes = bytes;
  low_speed_limit_.seconds = seconds;
  return *this;
}

void HttpClient::SetDebug(bool debug) {
  debug_ = debug;
}

} /* namespace base */
