/* ****************************************************************************
*
* httpRequestSend - 
*
* RETURN VALUES
*   httpRequestSend returns 0 on success and a negative number on failure:
*     -1: Invalid port
*     -2: Invalid IP
*     -3: Invalid verb
*     -4: Invalid resource
*     -5: No Content-Type BUT content present
*     -6: Content-Type present but there is no content
*     -7: Total outgoing message size is too big
*     -8: Unable to initialize libcurl
*     -9: Error making HTTP request
*
*   [ error codes -1 to -7 comes from httpRequestSendWithCurl ]
*/
int httpRequestSend
(
   const std::string&     _ip,
   unsigned short         port,
   const std::string&     protocol,
   const std::string&     verb,
   const std::string&     tenant,
   const std::string&     servicePath,
   const std::string&     xauthToken,
   const std::string&     resource,
   const std::string&     orig_content_type,
   const std::string&     content,
   const std::string&     fiwareCorrelation,
   const std::string&     ngisv2AttrFormat,
   bool                   useRush,
   bool                   waitForResponse,
   std::string*           outP,
   const std::string&     acceptFormat,
   long                   timeoutInMilliseconds
)
{
  struct curl_context  cc;
  int                  response;

  get_curl_context(_ip, &cc);
  if (cc.curl == NULL)
  {
    release_curl_context(&cc);
    LM_E(("Runtime Error (could not init libcurl)"));
    lmTransactionEnd();

    *outP = "error";
    return -8;
  }

  response = httpRequestSendWithCurl(cc.curl,
                                     _ip,
                                     port,
                                     protocol,
                                     verb,
                                     tenant,
                                     servicePath,
                                     xauthToken,
                                     resource,
                                     orig_content_type,
                                     content,
                                     fiwareCorrelation,
                                     ngisv2AttrFormat,
                                     useRush,
                                     waitForResponse,
                                     outP,
                                     acceptFormat,
                                     timeoutInMilliseconds);
  release_curl_context(&cc);
  return response;
}
/* ****************************************************************************
*
* httpRequestSend -
*/
std::string httpRequestSend
(
    const std::string&     _ip,
    unsigned short         port,
    const std::string&     protocol,
    const std::string&     verb,
    const std::string&     tenant,
    const std::string&     servicePath,
    const std::string&     xauthToken,
    const std::string&     resource,
    const std::string&     content_type,
    const std::string&     content,
    bool                   useRush,
    bool                   waitForResponse,
    const std::string&     acceptFormat,
    long                   timeoutInMilliseconds
)
{
    char                       portAsString[16];
    static unsigned long long  callNo             = 0;
    std::string                result;
    std::string                ip                 = _ip;
    struct curl_slist*         headers            = NULL;
    MemoryStruct*              httpResponse       = NULL;
    CURLcode                   res;
    int                        outgoingMsgSize       = 0;
    CURL*                      curl;
    struct curl_context        cc;

    ++callNo;

    if (timeoutInMilliseconds == -1)
    {
        timeoutInMilliseconds = defaultTimeout;
    }

    LM_TRANSACTION_START("to", ip.c_str(), port, resource.c_str());

    // Preconditions check
    if (port == 0)
    {
        LM_E(("Runtime Error (port is ZERO)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if (ip.empty())
    {
        LM_E(("Runtime Error (ip is empty)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if (verb.empty())
    {
        LM_E(("Runtime Error (verb is empty)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if (resource.empty())
    {
        LM_E(("Runtime Error (resource is empty)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if ((content_type.empty()) && (!content.empty()))
    {
        LM_E(("Runtime Error (Content-Type is empty but there is actual content)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if ((!content_type.empty()) && (content.empty()))
    {
        LM_E(("Runtime Error (Content-Type non-empty but there is no content)"));
        LM_TRANSACTION_END();
        return "error";
    }

    get_curl_context(ip, &cc);
    if ((curl = cc.curl) == NULL)
    {
        LM_E(("Runtime Error (could not init libcurl)"));
        LM_TRANSACTION_END();
        release_curl_context(&cc);
        return "error";
    }

    // Allocate to hold HTTP response
    httpResponse = new MemoryStruct;
    httpResponse->memory = (char*) malloc(1); // will grow as needed
    httpResponse->size = 0; // no data at this point

    //
    // Rush
    // Every call to httpRequestSend specifies whether RUSH should be used or not.
    // But, this depends also on how the broker was started, so here the 'useRush'
    // parameter is cancelled in case the broker was started without rush.
    //
    // If rush is to be used, the IP/port is stored in rushHeaderIP/rushHeaderPort and
    // after that, the host and port of rush is set as ip/port for the message, that is
    // now sent to rush instead of to its final destination.
    // Also, a few HTTP headers for rush must be setup.
    //
    if ((rushPort == 0) || (rushHost == ""))
        useRush = false;

    if (useRush)
    {
        char         rushHeaderPortAsString[16];
        uint16_t     rushHeaderPort     = 0;
        std::string  rushHeaderIP       = "";
        std::string  headerRushHttp     = "";

        rushHeaderIP   = ip;
        rushHeaderPort = port;
        ip             = rushHost;
        port           = rushPort;

        snprintf(rushHeaderPortAsString, sizeof(rushHeaderPortAsString), "%d", rushHeaderPort);
        headerRushHttp = "X-relayer-host: " + rushHeaderIP + ":" + rushHeaderPortAsString;
        LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerRushHttp.c_str()));
        headers = curl_slist_append(headers, headerRushHttp.c_str());
        outgoingMsgSize += headerRushHttp.size();

        if (protocol == "https:")
        {
            headerRushHttp = "X-relayer-protocol: https";
            LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerRushHttp.c_str()));
            headers = curl_slist_append(headers, headerRushHttp.c_str());
            outgoingMsgSize += headerRushHttp.size();
        }
    }

    snprintf(portAsString, sizeof(portAsString), "%d", port);

    // ----- User Agent
    char cvBuf[CURL_VERSION_MAX_LENGTH];
    char headerUserAgent[HTTP_HEADER_USER_AGENT_MAX_LENGTH];

    snprintf(headerUserAgent, sizeof(headerUserAgent), "User-Agent: orion/%s libcurl/%s", versionGet(), curlVersionGet(cvBuf, sizeof(cvBuf)));
    LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerUserAgent));
    headers = curl_slist_append(headers, headerUserAgent);
    outgoingMsgSize += strlen(headerUserAgent) + 1;

    // ----- Host
    char headerHost[HTTP_HEADER_HOST_MAX_LENGTH];

    snprintf(headerHost, sizeof(headerHost), "Host: %s:%d", ip.c_str(), (int) port);
    LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerHost));
    headers = curl_slist_append(headers, headerHost);
    outgoingMsgSize += strlen(headerHost) + 1;

    // ----- Tenant
    if (tenant != "")
    {
        headers = curl_slist_append(headers, ("fiware-service: " + tenant).c_str());
        outgoingMsgSize += tenant.size() + 16; // "fiware-service: "
    }

    // ----- Service-Path
    if (servicePath != "")
    {
        headers = curl_slist_append(headers, ("Fiware-ServicePath: " + servicePath).c_str());
        outgoingMsgSize += servicePath.size() + strlen("Fiware-ServicePath: ");
    }

    // ----- X-Auth-Token
    if (xauthToken != "")
    {
        headers = curl_slist_append(headers, ("X-Auth-Token: " + xauthToken).c_str());
        outgoingMsgSize += xauthToken.size() + strlen("X-Auth-Token: ");
    }

    // ----- Accept
    std::string acceptedFormats = "application/xml, application/json";
    if (acceptFormat != "")
    {
        acceptedFormats = acceptFormat;
    }

    std::string acceptString = "Accept: " + acceptedFormats;
    headers = curl_slist_append(headers, acceptString.c_str());
    outgoingMsgSize += acceptString.size();

    // ----- Expect
    headers = curl_slist_append(headers, "Expect: ");
    outgoingMsgSize += 8; // from "Expect: "

    // ----- Content-length
    std::stringstream contentLengthStringStream;
    contentLengthStringStream << content.size();
    std::string headerContentLength = "Content-length: " + contentLengthStringStream.str();
    LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerContentLength.c_str()));
    headers = curl_slist_append(headers, headerContentLength.c_str());
    outgoingMsgSize += contentLengthStringStream.str().size() + 16; // from "Content-length: "
    outgoingMsgSize += content.size();

    // ----- Content-type
    headers = curl_slist_append(headers, ("Content-type: " + content_type).c_str());
    outgoingMsgSize += content_type.size() + 14; // from "Content-type: "


    // Check if total outgoing message size is too big
    if (outgoingMsgSize > MAX_DYN_MSG_SIZE)
    {
        LM_E(("Runtime Error (HTTP request to send is too large: %d bytes)", outgoingMsgSize));

        // Cleanup curl environment
        curl_slist_free_all(headers);
        release_curl_context(&cc);

        free(httpResponse->memory);
        delete httpResponse;

        LM_TRANSACTION_END();
        return "error";
    }

    // Contents
    const char* payload = content.c_str();
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (u_int8_t*) payload);

    // Set up URL
    std::string url;
    if (isIPv6(ip))
        url = "[" + ip + "]";
    else
        url = ip;
    url = url + ":" + portAsString + (resource.at(0) == '/'? "" : "/") + resource;

    // Prepare CURL handle with obtained options
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb.c_str()); // Set HTTP verb
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // Allow redirection (?)
    curl_easy_setopt(curl, CURLOPT_HEADER, 1); // Activate include the header in the body output
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // Put headers in place
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeMemoryCallback); // Send data here
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*) httpResponse); // Custom data for response handling

    //
    // There is a known problem in libcurl (see http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame)
    // which is solved using CURLOPT_NOSIGNAL. If we update some day from libcurl 7.19 (the one that comes with CentOS 6.x) to a newer version
    // (there are some reports about the bug is no longer in libcurl 7.32), using CURLOPT_NOSIGNAL could be not necessary and this be removed).
    // See issue #1016 for more details.
    //
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);

    //
    // Timeout
    //
    // The parameter timeoutInMilliseconds holds the timeout time in milliseconds.
    // If the timeout time requested is 0, then no timeuot is used.
    //
    if (timeoutInMilliseconds != 0)
    {
        curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeoutInMilliseconds);
    }


    // Synchronous HTTP request
    LM_T(LmtClientOutputPayload, ("Sending message %lu to HTTP server: sending message of %d bytes to HTTP server", callNo, outgoingMsgSize));
    res = curl_easy_perform(curl);

    if (res != CURLE_OK)
    {
        //
        // NOTE: This log line is used by the functional tests in cases/880_timeout_for_forward_and_notifications/
        //       So, this line should not be removed/altered, at least not without also modifying the functests.
        //
        LM_W(("Notification failure for %s:%s (curl_easy_perform failed: %s)", ip.c_str(), portAsString, curl_easy_strerror(res)));
        result = "";
    }
    else
    {
        // The Response is here
        LM_I(("Notification Successfully Sent to %s", url.c_str()));
        result.assign(httpResponse->memory, httpResponse->size);
    }

    // Cleanup curl environment
    curl_slist_free_all(headers);
    release_curl_context(&cc);

    free(httpResponse->memory);
    delete httpResponse;

    LM_TRANSACTION_END();

    return result;
}