/* ****************************************************************************
*
* ContextElementResponse::ContextElementResponse -
*
* This constructor builds the CER object based in a BSON object taken from the
* entities collection at DB.
*
* Note that statusCode is not touched by this constructor.
*/
ContextElementResponse::ContextElementResponse(const mongo::BSONObj& entityDoc, const AttributeList& attrL, bool includeEmpty)
{
  prune = false;

  // Entity
  BSONObj id = getField(entityDoc, "_id").embeddedObject();
  contextElement.entityId.fill(getStringField(id, ENT_ENTITY_ID), getStringField(id, ENT_ENTITY_TYPE), "false");
  contextElement.entityId.servicePath = id.hasField(ENT_SERVICE_PATH) ? getStringField(id, ENT_SERVICE_PATH) : "";

  /* Get the location attribute (if it exists) */
  std::string locAttr;
  if (entityDoc.hasElement(ENT_LOCATION))
  {
    locAttr = getStringField(getObjectField(entityDoc, ENT_LOCATION), ENT_LOCATION_ATTRNAME);
  }


  //
  // Attribute vector
  // FIXME P5: constructor for BSONObj could be added to ContextAttributeVector/ContextAttribute classes, to make building more modular
  //
  BSONObj                attrs = getField(entityDoc, ENT_ATTRS).embeddedObject();
  std::set<std::string>  attrNames;

  attrs.getFieldNames(attrNames);
  for (std::set<std::string>::iterator i = attrNames.begin(); i != attrNames.end(); ++i)
  {
    std::string        attrName = *i;
    BSONObj            attr     = getField(attrs, attrName).embeddedObject();
    ContextAttribute*  caP      = NULL;
    ContextAttribute   ca;

    // Name and type
    ca.name           = dbDotDecode(basePart(attrName));
    std::string mdId  = idPart(attrName);
    ca.type           = getStringField(attr, ENT_ATTRS_TYPE);

    // Skip attribute if the attribute is in the list (or attrL is empty)
    if (!includedAttribute(ca, attrL))
    {
      continue;
    }

    /* It could happen (although very rarely) that the value field is missing in the
     * DB for the attribute. The following is a safety check measure to protect against that */
    if (!attr.hasField(ENT_ATTRS_VALUE))
    {
      caP = new ContextAttribute(ca.name, ca.type, "");
    }
    else
    {
      switch(getField(attr, ENT_ATTRS_VALUE).type())
      {
      case String:
        ca.stringValue = getStringField(attr, ENT_ATTRS_VALUE);
        if (!includeEmpty && ca.stringValue.length() == 0)
        {
          continue;
        }
        caP = new ContextAttribute(ca.name, ca.type, ca.stringValue);
        break;

      case NumberDouble:
        ca.numberValue = getField(attr, ENT_ATTRS_VALUE).Number();
        caP = new ContextAttribute(ca.name, ca.type, ca.numberValue);
        break;

      case NumberInt:
        ca.numberValue = (double) getIntField(attr, ENT_ATTRS_VALUE);
        caP = new ContextAttribute(ca.name, ca.type, ca.numberValue);
        break;

      case Bool:
        ca.boolValue = getBoolField(attr, ENT_ATTRS_VALUE);
        caP = new ContextAttribute(ca.name, ca.type, ca.boolValue);
        break;

      case jstNULL:
        caP = new ContextAttribute(ca.name, ca.type, "");
        caP->valueType = orion::ValueTypeNone;
        break;

      case Object:
        caP = new ContextAttribute(ca.name, ca.type, "");
        caP->compoundValueP = new orion::CompoundValueNode(orion::ValueTypeObject);
        compoundObjectResponse(caP->compoundValueP, getField(attr, ENT_ATTRS_VALUE));
        break;

      case Array:
        caP = new ContextAttribute(ca.name, ca.type, "");
        caP->compoundValueP = new orion::CompoundValueNode(orion::ValueTypeVector);
        compoundVectorResponse(caP->compoundValueP, getField(attr, ENT_ATTRS_VALUE));
        break;

      default:
        LM_E(("Runtime Error (unknown attribute value type in DB: %d)", getField(attr, ENT_ATTRS_VALUE).type()));
      }
    }

    /* Setting ID (if found) */
    if (mdId != "")
    {
      Metadata* md = new Metadata(NGSI_MD_ID, "string", mdId);
      caP->metadataVector.push_back(md);
    }

    /* Setting location metatda (if found) */
    if (locAttr == ca.name)
    {
      Metadata* md = new Metadata(NGSI_MD_LOCATION, "string", LOCATION_WGS84);
      caP->metadataVector.push_back(md);
    }

    /* Setting custom metadata (if any) */
    if (attr.hasField(ENT_ATTRS_MD))
    {
      std::vector<BSONElement> metadataV = getField(attr, ENT_ATTRS_MD).Array();

      for (unsigned int ix = 0; ix < metadataV.size(); ++ix)
      {
        Metadata* md = new Metadata(metadataV[ix].embeddedObject());
        caP->metadataVector.push_back(md);
      }
    }

    contextElement.contextAttributeVector.push_back(caP);
  }
}
/* ****************************************************************************
*
* ContextElementResponse::ContextElementResponse -
*
* This constructor builds the CER object based in a BSON object taken from the
* entities collection at DB.
*
* Note that statusCode is not touched by this constructor.
*/
ContextElementResponse::ContextElementResponse
(
  const mongo::BSONObj&  entityDoc,
  const AttributeList&   attrL,
  bool                   includeEmpty,
  const std::string&     apiVersion
)
{
  prune = false;

  // Entity
  BSONObj id = getFieldF(entityDoc, "_id").embeddedObject();

  std::string entityId   = getStringFieldF(id, ENT_ENTITY_ID);
  std::string entityType = id.hasField(ENT_ENTITY_TYPE) ? getStringFieldF(id, ENT_ENTITY_TYPE) : "";

  contextElement.entityId.fill(entityId, entityType, "false");
  contextElement.entityId.servicePath = id.hasField(ENT_SERVICE_PATH) ? getStringFieldF(id, ENT_SERVICE_PATH) : "";

  /* Get the location attribute (if it exists) */
  std::string locAttr;
  if (entityDoc.hasElement(ENT_LOCATION))
  {
    locAttr = getStringFieldF(getObjectFieldF(entityDoc, ENT_LOCATION), ENT_LOCATION_ATTRNAME);
  }


  //
  // Attribute vector
  // FIXME P5: constructor for BSONObj could be added to ContextAttributeVector/ContextAttribute classes, to make building more modular
  //
  BSONObj                attrs = getObjectFieldF(entityDoc, ENT_ATTRS);
  std::set<std::string>  attrNames;

  attrs.getFieldNames(attrNames);
  for (std::set<std::string>::iterator i = attrNames.begin(); i != attrNames.end(); ++i)
  {
    std::string        attrName = *i;
    BSONObj            attr     = getObjectFieldF(attrs, attrName);
    ContextAttribute*  caP      = NULL;
    ContextAttribute   ca;

    // Name and type
    ca.name           = dbDotDecode(basePart(attrName));
    std::string mdId  = idPart(attrName);
    ca.type           = getStringFieldF(attr, ENT_ATTRS_TYPE);

    // Skip attribute if the attribute is in the list (or attrL is empty or includes "*")
    if (!includedAttribute(ca, attrL))
    {
      continue;
    }

    /* It could happen (although very rarely) that the value field is missing in the
     * DB for the attribute. The following is a safety check measure to protect against that */
    if (!attr.hasField(ENT_ATTRS_VALUE))
    {
      caP = new ContextAttribute(ca.name, ca.type, "");
    }
    else
    {
      switch(getFieldF(attr, ENT_ATTRS_VALUE).type())
      {
      case String:
        ca.stringValue = getStringFieldF(attr, ENT_ATTRS_VALUE);
        if (!includeEmpty && ca.stringValue.length() == 0)
        {
          continue;
        }
        caP = new ContextAttribute(ca.name, ca.type, ca.stringValue);
        break;

      case NumberDouble:
        ca.numberValue = getNumberFieldF(attr, ENT_ATTRS_VALUE);
        caP = new ContextAttribute(ca.name, ca.type, ca.numberValue);
        break;

      case NumberInt:
        ca.numberValue = (double) getIntFieldF(attr, ENT_ATTRS_VALUE);
        caP = new ContextAttribute(ca.name, ca.type, ca.numberValue);
        break;

      case Bool:
        ca.boolValue = getBoolFieldF(attr, ENT_ATTRS_VALUE);
        caP = new ContextAttribute(ca.name, ca.type, ca.boolValue);
        break;

      case jstNULL:
        caP = new ContextAttribute(ca.name, ca.type, "");
        caP->valueType = orion::ValueTypeNone;
        break;

      case Object:
        caP = new ContextAttribute(ca.name, ca.type, "");
        caP->compoundValueP = new orion::CompoundValueNode(orion::ValueTypeObject);
        caP->valueType = orion::ValueTypeObject;
        compoundObjectResponse(caP->compoundValueP, getFieldF(attr, ENT_ATTRS_VALUE));
        break;

      case Array:
        caP = new ContextAttribute(ca.name, ca.type, "");
        caP->compoundValueP = new orion::CompoundValueNode(orion::ValueTypeVector);
        // FIXME P7: next line is counterintuitive. If the object is a vector, why
        // we need to use ValueTypeObject here? Because otherwise Metadata::toJson()
        // method doesn't work. A littely crazy... it should be fixed.
        caP->valueType = orion::ValueTypeObject;
        compoundVectorResponse(caP->compoundValueP, getFieldF(attr, ENT_ATTRS_VALUE));
        break;

      default:
        LM_E(("Runtime Error (unknown attribute value type in DB: %d)", getFieldF(attr, ENT_ATTRS_VALUE).type()));
      }
    }

    /* Setting ID (if found) */
    if (mdId != "")
    {
      Metadata* md = new Metadata(NGSI_MD_ID, "string", mdId);
      caP->metadataVector.push_back(md);
    }

    if (apiVersion == "v1")
    {
      /* Setting location metadata (if found) */
      if ((locAttr == ca.name) && (ca.type != GEO_POINT))
      {
        /* Note that if attribute type is geo:point then the user is using the "new way"
         * of locating entities in NGSIv1, thus location metadata is not rendered */
        Metadata* md = new Metadata(NGSI_MD_LOCATION, "string", LOCATION_WGS84);
        caP->metadataVector.push_back(md);
      }
    }

    /* Setting custom metadata (if any) */
    if (attr.hasField(ENT_ATTRS_MD))
    {
      BSONObj                mds = getObjectFieldF(attr, ENT_ATTRS_MD);
      std::set<std::string>  mdsSet;

      mds.getFieldNames(mdsSet);
      for (std::set<std::string>::iterator i = mdsSet.begin(); i != mdsSet.end(); ++i)
      {
        std::string currentMd = *i;
        Metadata*   md = new Metadata(dbDotDecode(currentMd), getObjectFieldF(mds, currentMd));
        caP->metadataVector.push_back(md);
      }
    }

    /* Set creDate and modDate at attribute level */
    if (attr.hasField(ENT_ATTRS_CREATION_DATE))
    {
      caP->creDate = (double) getIntOrLongFieldAsLongF(attr, ENT_ATTRS_CREATION_DATE);
    }

    if (attr.hasField(ENT_ATTRS_MODIFICATION_DATE))
    {
      caP->modDate = (double) getIntOrLongFieldAsLongF(attr, ENT_ATTRS_MODIFICATION_DATE);
    }

    contextElement.contextAttributeVector.push_back(caP);
  }

  /* Set creDate and modDate at entity level */
  if (entityDoc.hasField(ENT_CREATION_DATE))
  {
    contextElement.entityId.creDate = (double) getIntOrLongFieldAsLongF(entityDoc, ENT_CREATION_DATE);
  }

  if (entityDoc.hasField(ENT_MODIFICATION_DATE))
  {
    contextElement.entityId.modDate = (double) getIntOrLongFieldAsLongF(entityDoc, ENT_MODIFICATION_DATE);
  }
}