/* ****************************************************************************
*
* mongoQueryContext - 
*
* NOTE
*   If the in/out-parameter countP is non-NULL then the number of matching entities
*   must be returned in *countP.
*
*   This replaces the 'uriParams[URI_PARAM_PAGINATION_DETAILS]' way of passing this information.
*   The old method was one-way, using the new method 
*/
HttpStatusCode mongoQueryContext
(
  QueryContextRequest*                 requestP,
  QueryContextResponse*                responseP,
  const std::string&                   tenant,
  const std::vector<std::string>&      servicePathV,
  std::map<std::string, std::string>&  uriParams,
  long long*                           countP
)
{
    int         offset         = atoi(uriParams[URI_PARAM_PAGINATION_OFFSET].c_str());
    int         limit          = atoi(uriParams[URI_PARAM_PAGINATION_LIMIT].c_str());
    std::string detailsString  = uriParams[URI_PARAM_PAGINATION_DETAILS];
    bool        details        = (strcasecmp("on", detailsString.c_str()) == 0)? true : false;

    LM_T(LmtMongo, ("QueryContext Request"));    
    LM_T(LmtPagination, ("Offset: %d, Limit: %d, Details: %s", offset, limit, (details == true)? "true" : "false"));

    /* FIXME: restriction not supported for the moment */
    if (!requestP->restriction.attributeExpression.isEmpty())
    {
      LM_W(("Bad Input (restriction found, but restrictions are not supported by mongo backend)"));
    }

    std::string err;
    bool        ok;
    bool        limitReached = false;
    bool        reqSemTaken;

    ContextElementResponseVector rawCerV;    
    reqSemTake(__FUNCTION__, "ngsi10 query request", SemReadOp, &reqSemTaken);
    ok = entitiesQuery(requestP->entityIdVector,
                       requestP->attributeList,
                       requestP->restriction,
                       &rawCerV,
                       &err,
                       true,
                       tenant,
                       servicePathV,
                       offset,
                       limit,
                       &limitReached,
                       countP);

    if (!ok)
    {
        responseP->errorCode.fill(SccReceiverInternalError, err);
        rawCerV.release();
        reqSemGive(__FUNCTION__, "ngsi10 query request", reqSemTaken);
        return SccOk;
    }

    ContextRegistrationResponseVector crrV;

    /* In the case of empty response, if only generic processing is needed */
    if (rawCerV.size() == 0)
    {
      if (registrationsQuery(requestP->entityIdVector, requestP->attributeList, &crrV, &err, tenant, servicePathV, 0, 0, false))
      {
        if (crrV.size() > 0)
        {
          processGenericEntities(requestP->entityIdVector, rawCerV, crrV, limitReached);
        }
      }
      else
      {
        /* Different from errors in DB at entitiesQuery(), DB fails at registrationsQuery() are not considered "critical" */
        LM_E(("Database Error (%s)", err.c_str()));
      }
      crrV.release();
    }

    /* First CPr lookup (in the case some CER is not found): looking in E-A registrations */
    if (someContextElementNotFound(rawCerV))
    {
      if (registrationsQuery(requestP->entityIdVector, requestP->attributeList, &crrV, &err, tenant, servicePathV, 0, 0, false))
      {
        if (crrV.size() > 0)
        {
          fillContextProviders(rawCerV, crrV);
          processGenericEntities(requestP->entityIdVector, rawCerV, crrV, limitReached);
        }
      }
      else
      {
        /* Different from errors in DB at entitiesQuery(), DB fails at registrationsQuery() are not considered "critical" */
        LM_E(("Database Error (%s)", err.c_str()));
      }
      crrV.release();
    }

    /* Second CPr lookup (in the case some element stills not being found): looking in E-<null> registrations */
    AttributeList attrNullList;
    if (someContextElementNotFound(rawCerV))
    {
      if (registrationsQuery(requestP->entityIdVector, attrNullList, &crrV, &err, tenant, servicePathV, 0, 0, false))
      {
        if (crrV.size() > 0)
        {
          fillContextProviders(rawCerV, crrV);
        }
      }
      else
      {
        /* Different from errors in DB at entitiesQuery(), DB fails at registrationsQuery() are not considered "critical" */
        LM_E(("Database Error (%s)", err.c_str()));
      }
      crrV.release();
    }

    /* Special case: request with <null> attributes. In that case, entitiesQuery() may have captured some local attribute, but
     * the list need to be completed. Note that in the case of having this request someContextElementNotFound() is always false
     * so we efficient not invoking registrationQuery() too much times */
    if (requestP->attributeList.size() == 0)
    {
      if (registrationsQuery(requestP->entityIdVector, requestP->attributeList, &crrV, &err, tenant, servicePathV, 0, 0, false))
      {
        if (crrV.size() > 0)
        {
          addContextProviders(rawCerV, crrV, limitReached);
        }
      }
      else
      {
        /* Different from fails in DB at entitiesQuery(), DB fails at registrationsQuery() are not considered "critical" */
        LM_E(("Database Error (%s)", err.c_str()));
      }
      crrV.release();
    }

    /* Prune "not found" CERs */
    pruneContextElements(rawCerV, &responseP->contextElementResponseVector);

    /* Pagination stuff */
    if (responseP->contextElementResponseVector.size() == 0)
    {

      // If the query has an empty response, we have to fill in the status code part in the response.
      //
      // However, if the response was empty due to a too high pagination offset,
      // and if the user has asked for 'details' (as URI parameter, then the response should include information about
      // the number of hits without pagination.
      //

      if ((countP != NULL) && (*countP > 0) && (offset >= *countP))
      {
        char details[256];

        snprintf(details, sizeof(details), "Number of matching entities: %lld. Offset is %d", *countP, offset);
        responseP->errorCode.fill(SccContextElementNotFound, details);
      }
      else
      {
        responseP->errorCode.fill(SccContextElementNotFound);
      }
    }
    else if (countP != NULL)
    {
      //
      // If all was OK, but the details URI param was set to 'on', then the responses error code details
      // 'must' contain the total count of hits.
      //

      char details[64];

      snprintf(details, sizeof(details), "Count: %lld", *countP);
      responseP->errorCode.fill(SccOk, details);
    }

    rawCerV.release();
    
    reqSemGive(__FUNCTION__, "ngsi10 query request", reqSemTaken);
    return SccOk;
}