/* ****************************************************************************
*
* mongoUnsubscribeContextAvailability - 
*/
HttpStatusCode mongoUnsubscribeContextAvailability
(
  UnsubscribeContextAvailabilityRequest*   requestP,
  UnsubscribeContextAvailabilityResponse*  responseP,
  const std::string&                       tenant
)
{
  bool        reqSemTaken;
  std::string err;

  reqSemTake(__FUNCTION__, "ngsi9 unsubscribe request", SemWriteOp, &reqSemTaken);

  LM_T(LmtMongo, ("Unsubscribe Context Availability"));

  /* No matter if success or failure, the subscriptionId in the response is always the one
   * in the request */
  responseP->subscriptionId = requestP->subscriptionId;

  /* Look for document */
  BSONObj sub;
  OID     id;

  if (!safeGetSubId(requestP->subscriptionId, &id, &(responseP->statusCode)))
  {
    reqSemGive(__FUNCTION__, "ngsi9 unsubscribe request (safeGetSubId fail)", reqSemTaken);
    if (responseP->statusCode.code == SccContextElementNotFound)
    {
      // FIXME: doubt: invalid OID format? Or, subscription not found?
      std::string details = std::string("invalid OID format: '") + requestP->subscriptionId.get() + "'";
      alarmMgr.badInput(clientIp, details);
    }
    else // SccReceiverInternalError
    {
      LM_E(("Runtime Error (exception getting OID: %s)", responseP->statusCode.details.c_str()));
    }
    return SccOk;
  }

  if (!collectionFindOne(getSubscribeContextAvailabilityCollectionName(tenant), BSON("_id" << id), &sub, &err))
  {
    reqSemGive(__FUNCTION__, "ngsi9 unsubscribe request (mongo db exception)", reqSemTaken);
    responseP->statusCode.fill(SccReceiverInternalError, err);
    return SccOk;
  }
  alarmMgr.dbErrorReset();

  if (sub.isEmpty())
  {
    responseP->statusCode.fill(SccContextElementNotFound);
    reqSemGive(__FUNCTION__, "ngsi9 unsubscribe request (no subscriptions)", reqSemTaken);
    return SccOk;
  }

  /* Remove document in MongoDB */
  // FIXME: I would prefer to do the find and remove in a single operation. Is the some similar
  // to findAndModify for this?  
  if (!collectionRemove(getSubscribeContextAvailabilityCollectionName(tenant), BSON("_id" << OID(requestP->subscriptionId.get())), &err))
  {
    reqSemGive(__FUNCTION__, "ngsi9 unsubscribe request (mongo db exception)", reqSemTaken);
    responseP->statusCode.fill(SccReceiverInternalError, err);
    return SccOk;
  }

  reqSemGive(__FUNCTION__, "ngsi9 unsubscribe request", reqSemTaken);
  responseP->statusCode.fill(SccOk);
  return SccOk;
}
/* ****************************************************************************
*
* mongoUnsubscribeContext - 
*/
HttpStatusCode mongoUnsubscribeContext(UnsubscribeContextRequest* requestP, UnsubscribeContextResponse* responseP, const std::string& tenant)
{
    bool         reqSemTaken;
    std::string  err;

    reqSemTake(__FUNCTION__, "ngsi10 unsubscribe request", SemWriteOp, &reqSemTaken);

    LM_T(LmtMongo, ("Unsubscribe Context"));

    /* No matter if success or failure, the subscriptionId in the response is always the one
     * in the request */
    responseP->subscriptionId = requestP->subscriptionId;

    if (responseP->subscriptionId.get() == "")
    {
        reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (no subscriptions found)", reqSemTaken);
        responseP->statusCode.fill(SccContextElementNotFound);
        alarmMgr.badInput(clientIp, "no subscriptionId");
        return SccOk;
    }

    /* Look for document */
    BSONObj sub;
    OID     id;

    if (!safeGetSubId(requestP->subscriptionId, &id, &(responseP->statusCode)))
    {
      reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (safeGetSubId fail)", reqSemTaken);
      if (responseP->statusCode.code == SccContextElementNotFound)
      {
        // FIXME: Doubt - invalid OID format?  Or, just a subscription that was not found?
        std::string details = std::string("invalid OID format: '") + requestP->subscriptionId.get() + "'";
        alarmMgr.badInput(clientIp, details);
      }
      else // SccReceiverInternalError
      {
        LM_E(("Runtime Error (exception getting OID: %s)", responseP->statusCode.details.c_str()));
      }
      return SccOk;
    }

    if (!collectionFindOne(getSubscribeContextCollectionName(tenant), BSON("_id" << id), &sub, &err))
    {
      reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo db exception)", reqSemTaken);
      responseP->statusCode.fill(SccReceiverInternalError, err);
      return SccOk;
    }

    if (sub.isEmpty())
    {
       reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (no subscriptions found)", reqSemTaken);
       responseP->statusCode.fill(SccContextElementNotFound, std::string("subscriptionId: /") + requestP->subscriptionId.get() + "/");
       return SccOk;
    }

    /* Remove document in MongoDB */
    // FIXME: I would prefer to do the find and remove in a single operation. Is there something similar
    // to findAndModify for this?    
    if (!collectionRemove(getSubscribeContextCollectionName(tenant), BSON("_id" << OID(requestP->subscriptionId.get())), &err))
    {
      reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo db exception)", reqSemTaken);
      responseP->statusCode.fill(SccReceiverInternalError, err);
      return SccOk;
    }

    /* Destroy any previous ONTIMEINTERVAL thread */
    getNotifier()->destroyOntimeIntervalThreads(requestP->subscriptionId.get());


    //
    // Removing subscription from mongo subscription cache
    //
    LM_T(LmtSubCache, ("removing subscription '%s' (tenant '%s') from mongo subscription cache", requestP->subscriptionId.get().c_str(), tenant.c_str()));

    cacheSemTake(__FUNCTION__, "Removing subscription from cache");

    CachedSubscription* cSubP = subCacheItemLookup(tenant.c_str(), requestP->subscriptionId.get().c_str());

    if (cSubP != NULL)
    {
      subCacheItemRemove(cSubP);
    }

    cacheSemGive(__FUNCTION__, "Removing subscription from cache");

    reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request", reqSemTaken);

    responseP->statusCode.fill(SccOk);
    return SccOk;
}
/* ****************************************************************************
*
* mongoUnsubscribeContext - 
*/
HttpStatusCode mongoUnsubscribeContext(UnsubscribeContextRequest* requestP, UnsubscribeContextResponse* responseP, const std::string& tenant)
{
    bool         reqSemTaken;
    std::string  err;

    reqSemTake(__FUNCTION__, "ngsi10 unsubscribe request", SemWriteOp, &reqSemTaken);

    LM_T(LmtMongo, ("Unsubscribe Context"));

    /* No matter if success or failure, the subscriptionId in the response is always the one
     * in the request */
    responseP->subscriptionId = requestP->subscriptionId;

    if (responseP->subscriptionId.get() == "")
    {
        responseP->statusCode.fill(SccContextElementNotFound);
        LM_W(("Bad Input (no subscriptionId)"));
        return SccOk;
    }

    /* Look for document */
    BSONObj sub;
    OID     id;
    try
    {
      id = OID(requestP->subscriptionId.get());
    }
    catch (const AssertionException &e)
    {
      reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo assertion exception)", reqSemTaken);
      //
      // This happens when OID format is wrong
      // FIXME: this checking should be done at parsing stage, without progressing to
      // mongoBackend. For the moment we can live this here, but we should remove in the future
      // (old issue #95)
      //
      responseP->statusCode.fill(SccContextElementNotFound);
      LM_W(("Bad Input (invalid OID format)"));
      return SccOk;
    }

    if (!collectionFindOne(getSubscribeContextCollectionName(tenant), BSON("_id" << id), &sub, &err))
    {
      reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo db exception)", reqSemTaken);
      responseP->statusCode.fill(SccReceiverInternalError, err);
      return SccOk;
    }

    if (sub.isEmpty())
    {
       reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (no subscriptions found)", reqSemTaken);

       responseP->statusCode.fill(SccContextElementNotFound, std::string("subscriptionId: /") + requestP->subscriptionId.get() + "/");
       return SccOk;
    }

    /* Remove document in MongoDB */
    // FIXME: I would prefer to do the find and remove in a single operation. Is there something similar
    // to findAndModify for this?    
    if (!collectionRemove(getSubscribeContextCollectionName(tenant), BSON("_id" << OID(requestP->subscriptionId.get())), &err))
    {
      reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request (mongo db exception)", reqSemTaken);
      responseP->statusCode.fill(SccReceiverInternalError, err);
      return SccOk;
    }

    /* Destroy any previous ONTIMEINTERVAL thread */
    getNotifier()->destroyOntimeIntervalThreads(requestP->subscriptionId.get());


    // FIXME P7: mongoSubCache stuff could be avoided if subscription is not patterned

    //
    // Removing subscription from mongo subscription cache
    //
    LM_T(LmtMongoSubCache, ("removing subscription '%s' (tenant '%s') from mongo subscription cache", requestP->subscriptionId.get().c_str(), tenant.c_str()));

    cacheSemTake(__FUNCTION__, "Removing subscription from cache");

    CachedSubscription* cSubP = mongoSubCacheItemLookup(tenant.c_str(), requestP->subscriptionId.get().c_str());

    if (cSubP != NULL)  // Will only enter here if wildcard subscription
    {
      mongoSubCacheItemRemove(cSubP);
    }

    cacheSemGive(__FUNCTION__, "Removing subscription from cache");

    reqSemGive(__FUNCTION__, "ngsi10 unsubscribe request", reqSemTaken);

    responseP->statusCode.fill(SccOk);
    return SccOk;
}