/* ****************************************************************************
*
* updateForward - 
*
* An entity/attribute has been found on some context provider.
* We need to forward the update request to the context provider, indicated in upcrsP->contextProvider
*
* 1. Parse the providing application to extract IP, port and URI-path
* 2. Render the string of the request we want to forward
* 3. Send the request to the providing application (and await the response)
* 4. Parse the response and fill in a binary UpdateContextResponse
* 5. Fill in the response from the redirection into the response of this function
* 6. 'Fix' StatusCode
* 7. Freeing memory
*
*
* FIXME P5: The function 'updateForward' is implemented to pick the format (XML or JSON) based on the
*           count of the Format for all the participating attributes. If we have more attributes 'preferring'
*           XML than JSON, the forward is done in XML, etc. This is all OK.
*           What is not OK is that the Accept HTTP header is set to the same format as the Content-Type HTTP Header.
*           While this is acceptable, it is not great. As the broker understands both XML and JSON, we could send
*           the forward message with an Acceot header of XML/JSON and then at reading the response, instead of 
*           throwing away the HTTP headers, we could read the "Content-Type" and do the parse according the Content-Type.
*/
static void updateForward(ConnectionInfo* ciP, UpdateContextRequest* upcrP, UpdateContextResponse* upcrsP, Format format)
{
  std::string     ip;
  std::string     protocol;
  int             port;
  std::string     prefix;
  std::string     answer;


  //
  // 1. Parse the providing application to extract IP, port and URI-path
  //
  if (parseUrl(upcrP->contextProvider, ip, port, prefix, protocol) == false)
  {
    LM_W(("Bad Input (invalid providing application '%s')", upcrP->contextProvider.c_str()));
    //  Somehow, if we accepted this providing application, it is the brokers fault ...
    //  SccBadRequest should have been returned before, when it was registered!

    upcrsP->errorCode.fill(SccContextElementNotFound, "");
    return;
  }


  //
  // 2. Render the string of the request we want to forward
  //
  Format       outFormat = ciP->outFormat;
  std::string  payload;
  char*        cleanPayload;

  ciP->outFormat  = format;
  payload         = upcrP->render(ciP, UpdateContext, "");
  ciP->outFormat  = outFormat;
  cleanPayload    = (char*) payload.c_str();

  if (format == XML)
  {
    if ((cleanPayload = xmlPayloadClean(payload.c_str(), "<updateContextRequest>")) == NULL)
    {
      LM_E(("Runtime Error (error rendering forward-request)"));
      upcrsP->errorCode.fill(SccContextElementNotFound, "");
      return;
    }
  }


  //
  // 3. Send the request to the Context Provider (and await the reply)
  // FIXME P7: Should Rush be used?
  //
  std::string     out;
  std::string     verb         = "POST";
  std::string     resource     = prefix + "/updateContext";
  std::string     tenant       = ciP->tenant;
  std::string     servicePath  = (ciP->httpHeaders.servicePathReceived == true)? ciP->httpHeaders.servicePath : "";
  std::string     mimeType     = (format == XML)? "application/xml" : "application/json";

  out = httpRequestSend(ip,
                        port,
                        protocol,
                        verb,
                        tenant,
                        servicePath,
                        ciP->httpHeaders.xauthToken,
                        resource,
                        mimeType,
                        cleanPayload,
                        false,
                        true,
                        mimeType);

  if ((out == "error") || (out == ""))
  {
    upcrsP->errorCode.fill(SccContextElementNotFound, "");
    LM_E(("Runtime Error (error forwarding 'Update' to providing application)"));
    return;
  }


  //
  // 4. Parse the response and fill in a binary UpdateContextResponse
  //
  std::string  s;
  std::string  errorMsg;

  if (format == XML)
  {
    cleanPayload = xmlPayloadClean(out.c_str(), "<updateContextResponse>");
  }
  else
  {
    cleanPayload = jsonPayloadClean(out.c_str());
  }

  if ((cleanPayload == NULL) || (cleanPayload[0] == 0))
  {
    //
    // This is really an internal error in the Context Provider
    // It is not in the orion broker though, so 404 is returned
    //
    LM_W(("Other Error (context provider response to UpdateContext is empty)"));
    upcrsP->errorCode.fill(SccContextElementNotFound, "invalid context provider response");
    return;
  }

  //
  // NOTE
  // When coming from a convenience operation, such as GET /v1/contextEntities/EID/attributes/attrName,
  // the verb/method in ciP is GET. However, the parsing function expects a POST, as if it came from a 
  // POST /v1/updateContext. 
  // So, here we change the verb/method for POST.
  //
  ParseData parseData;

  ciP->verb   = POST;
  ciP->method = "POST";

  parseData.upcrs.res.errorCode.fill(SccOk);

  if (format == XML)
  {
    s = xmlTreat(cleanPayload, ciP, &parseData, RtUpdateContextResponse, "updateContextResponse", NULL, &errorMsg);
  }
  else
  {
    s = jsonTreat(cleanPayload, ciP, &parseData, RtUpdateContextResponse, "updateContextResponse", NULL);
  }

  if (s != "OK")
  {
    LM_W(("Internal Error (error parsing reply from prov app: %s)", errorMsg.c_str()));
    upcrsP->errorCode.fill(SccContextElementNotFound, "");
    parseData.upcr.res.release();
    parseData.upcrs.res.release();
    return;
  }


  //
  // 5. Fill in the response from the redirection into the response of this function
  //
  upcrsP->fill(&parseData.upcrs.res);


  //
  // 6. 'Fix' StatusCode
  //
  if (upcrsP->errorCode.code == SccNone)
  {
    upcrsP->errorCode.fill(SccOk);
  }

  if ((upcrsP->contextElementResponseVector.size() == 1) && (upcrsP->contextElementResponseVector[0]->statusCode.code == SccContextElementNotFound))
  {
    upcrsP->errorCode.fill(SccContextElementNotFound);
  }

  
  //
  // 7. Freeing memory
  //
  parseData.upcr.res.release();
  parseData.upcrs.res.release();
}
/* ****************************************************************************
*
* updateForward -
*
* An entity/attribute has been found on some context provider.
* We need to forward the update request to the context provider, indicated in upcrsP->contextProvider
*
* 1. Parse the providing application to extract IP, port and URI-path
* 2. Render the string of the request we want to forward
* 3. Send the request to the providing application (and await the response)
* 4. Parse the response and fill in a binary UpdateContextResponse
* 5. Fill in the response from the redirection into the response of this function
* 6. 'Fix' StatusCode
* 7. Freeing memory
*
*/
static bool updateForward(ConnectionInfo* ciP, UpdateContextRequest* upcrP, UpdateContextResponse* upcrsP)
{
  std::string      ip;
  std::string      protocol;
  int              port;
  std::string      prefix;

  bool asJsonObject = (ciP->uriParam[URI_PARAM_ATTRIBUTE_FORMAT] == "object" && ciP->outMimeType == JSON);

  //
  // 1. Parse the providing application to extract IP, port and URI-path
  //
  if (parseUrl(upcrP->contextProvider, ip, port, prefix, protocol) == false)
  {
    std::string details = std::string("invalid providing application '") + upcrP->contextProvider + "'";

    alarmMgr.badInput(clientIp, details);

    //
    //  Somehow, if we accepted this providing application, it is the brokers fault ...
    //  SccBadRequest should have been returned before, when it was registered!
    //
    upcrsP->errorCode.fill(SccContextElementNotFound, "");
    return false;
  }

  LM_T(LmtForward, ("*** Provider Format: %d", upcrP->providerFormat));

  //
  // 2. Render the string of the request we want to forward
  //
  MimeType     outMimeType = ciP->outMimeType;
  std::string  payload;
  char*        cleanPayload;

  ciP->outMimeType  = JSON;

  std::string     verb;
  std::string     resource;
  std::string     tenant       = ciP->tenant;
  std::string     servicePath  = (ciP->httpHeaders.servicePathReceived == true)? ciP->httpHeaders.servicePath : "";
  std::string     mimeType     = "application/json";
  std::string     out;
  int             r;

  if (upcrP->providerFormat == PfJson)
  {
    TIMED_RENDER(payload = upcrP->toJsonV1(asJsonObject));

    verb     = "POST";
    resource = prefix + "/updateContext";
  }
  else
  {
    TIMED_RENDER(payload = upcrP->toJson());

    verb     = "POST";
    resource = prefix + "/op/update";
#if 0
    // FIXME #3485: this part is not removed by the moment, in the case it may be useful in the
    // context of issue #3485

    std::vector<std::string>  nullFilter;
    Entity*                   eP = upcrP->entityVector[0];

    eP->renderId = false;

    TIMED_RENDER(payload = eP->toJson(NGSI_V2_NORMALIZED, nullFilter, false, nullFilter));

    resource = prefix + "/entities/" + eP->id + "/attrs";
    verb     = "PATCH";

    if (eP->type != "")
    {
      // Add ?type=<TYPE> to 'resource'
      resource += "?type=" + eP->type;
    }
#endif
  }

  ciP->outMimeType  = outMimeType;
  cleanPayload      = (char*) payload.c_str();

  //
  // 3. Send the request to the Context Provider (and await the reply)
  // FIXME P7: Should Rush be used?
  //
  LM_T(LmtCPrForwardRequestPayload, ("forward updateContext request payload: %s", payload.c_str()));

  std::map<std::string, std::string>  noHeaders;
  long long                           statusCode; // not used by the moment

  r = httpRequestSend(fromIp,   // thread variable
                      ip,
                      port,
                      protocol,
                      verb,
                      tenant,
                      servicePath,
                      ciP->httpHeaders.xauthToken,
                      resource,
                      mimeType,
                      cleanPayload,
                      ciP->httpHeaders.correlator,
                      "",
                      false,
                      &out,
                      &statusCode,
                      noHeaders,
                      mimeType);

  if (r != 0)
  {
    upcrsP->errorCode.fill(SccContextElementNotFound, "error forwarding update");
    LM_E(("Runtime Error (error '%s' forwarding 'Update' to providing application)", out.c_str()));
    return false;
  }

  LM_T(LmtCPrForwardResponsePayload, ("forward updateContext response payload: %s", out.c_str()));

  //
  // If NGSIv1 (providerFormat == PfJson):
  //   4. Parse the response and fill in a binary UpdateContextResponse
  //   5. Fill in the response from the redirection into the response of this function
  //   6. 'Fix' StatusCode
  //   7. Free up memory
  //
  // If NGSIv2:
  //   4. Look for "204 No Content" in the response of the forwarded request
  //   5. If found: OK, else, error
  //
  if (upcrP->providerFormat == PfJson)
  {
    LM_T(LmtForward, ("upcrP->providerFormat == PfJson"));
    //
    // 4. Parse the response and fill in a binary UpdateContextResponse
    //
    std::string  s;
    std::string  errorMsg;

    cleanPayload = jsonPayloadClean(out.c_str());

    if ((cleanPayload == NULL) || (cleanPayload[0] == 0))
    {
      //
      // This is really an internal error in the Context Provider
      // It is not in the orion broker though, so 404 is returned
      //
      LM_W(("Other Error (context provider response to UpdateContext is empty)"));
      upcrsP->errorCode.fill(SccContextElementNotFound, "invalid context provider response");
      return false;
    }

    //
    // NOTE
    // When coming from a convenience operation, such as GET /v1/contextEntities/EID/attributes/attrName,
    // the verb/method in ciP is GET. However, the parsing function expects a POST, as if it came from a
    // POST /v1/updateContext.
    // So, here we change the verb/method for POST.
    //
    ParseData parseData;

    ciP->verb   = POST;
    ciP->method = "POST";

    parseData.upcrs.res.errorCode.fill(SccOk);

    LM_T(LmtForward, ("Parsing Response of Forwarded Request: '%s'", cleanPayload));
    s = jsonTreat(cleanPayload, ciP, &parseData, RtUpdateContextResponse, NULL);
    LM_T(LmtForward, ("Parse Result: %s", s.c_str()));

    if (s != "OK")
    {
      LM_W(("Internal Error (error parsing reply from prov app: %s)", errorMsg.c_str()));
      upcrsP->errorCode.fill(SccContextElementNotFound, "");
      parseData.upcr.res.release();
      parseData.upcrs.res.release();
      return false;
    }


    //
    // 5. Fill in the response from the redirection into the response of this function
    //
    upcrsP->fill(&parseData.upcrs.res);


    //
    // 6. 'Fix' StatusCode
    //
    if (upcrsP->errorCode.code == SccNone)
    {
      upcrsP->errorCode.fill(SccOk);
    }

    if ((upcrsP->contextElementResponseVector.size() == 1) && (upcrsP->contextElementResponseVector[0]->statusCode.code == SccContextElementNotFound))
    {
      upcrsP->errorCode.fill(SccContextElementNotFound);
    }

    //
    // 7. Free up memory
    //
    parseData.upcr.res.release();
    parseData.upcrs.res.release();

    return true;
  }
  else  // NGSIv2
  {
    LM_T(LmtForward, ("upcrP->providerFormat == V2. out: '%s'", out.c_str()));
    // NGSIv2 forward - no payload to be received, just a 204 No Content HTTP Header
    if (strstr(out.c_str(), "204 No Content") != NULL)
    {
      LM_T(LmtForward, ("Found '204 No Content'"));
      upcrsP->errorCode.fill(SccNone);
      return true;
    }

    LM_W(("Other Error (unexpected response from context provider: %s)", out.c_str()));
    upcrsP->errorCode.fill(SccReceiverInternalError);
    return false;
  }

  // Can't reach this point - no return-statement needed
}