// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/nfc/nfc_ndef_record.h"

#include <map>

#include "base/logging.h"
#include "url/gurl.h"

using base::DictionaryValue;
using base::ListValue;

namespace device {

namespace {

typedef std::map<std::string, base::Value::Type> FieldValueMap;

bool ValidateURI(const DictionaryValue* data) {
  std::string uri;
  if (!data->GetString(NfcNdefRecord::kFieldURI, &uri)) {
    VLOG(1) << "No URI entry in data.";
    return false;
  }
  DCHECK(!uri.empty());

  // Use GURL to check validity.
  GURL url(uri);
  if (!url.is_valid()) {
    LOG(ERROR) << "Invalid URI given: " << uri;
    return false;
  }
  return true;
}

bool CheckFieldsAreValid(
    const FieldValueMap& required_fields,
    const FieldValueMap& optional_fields,
    const DictionaryValue* data) {
  size_t required_count = 0;
  for (DictionaryValue::Iterator iter(*data);
       !iter.IsAtEnd(); iter.Advance()) {
    FieldValueMap::const_iterator field_iter =
        required_fields.find(iter.key());
    if (field_iter == required_fields.end()) {
      // Field wasn't one of the required fields. Check if optional.
      field_iter = optional_fields.find(iter.key());

      if (field_iter == optional_fields.end()) {
        // If the field isn't one of the optional fields either, then it's
        // invalid.
        VLOG(1) << "Tried to populate record with invalid field: "
                << iter.key();
        return false;
      }
    } else {
      required_count++;
    }
    // The field is invalid, if the type of its value is incorrect.
    if (field_iter->second != iter.value().GetType()) {
      VLOG(1) << "Provided value for field \"" << iter.key() << "\" has type "
              << iter.value().GetType() << ", expected: "
              << field_iter->second;
      return false;
    }
    // Make sure that the value is non-empty, if the value is a string.
    std::string string_value;
    if (iter.value().GetAsString(&string_value) && string_value.empty()) {
      VLOG(1) << "Empty value given for field of type string: " << iter.key();
      return false;
    }
  }
  // Check for required fields.
  if (required_count != required_fields.size()) {
    VLOG(1) << "Provided data did not contain all required fields for "
            << "requested NDEF type.";
    return false;
  }
  return true;
}

// Verifies that the contents of |data| conform to the fields of NDEF type
// "Text".
bool HandleTypeText(const DictionaryValue* data) {
  VLOG(1) << "Populating record with type \"Text\".";
  FieldValueMap required_fields;
  required_fields[NfcNdefRecord::kFieldText] = base::Value::TYPE_STRING;
  required_fields[NfcNdefRecord::kFieldEncoding] = base::Value::TYPE_STRING;
  required_fields[NfcNdefRecord::kFieldLanguageCode] = base::Value::TYPE_STRING;
  FieldValueMap optional_fields;
  if (!CheckFieldsAreValid(required_fields, optional_fields, data)) {
    VLOG(1) << "Failed to populate record.";
    return false;
  }

  // Verify that the "Encoding" property has valid values.
  std::string encoding;
  if (!data->GetString(NfcNdefRecord::kFieldEncoding, &encoding)) {
    if (encoding != NfcNdefRecord::kEncodingUtf8 ||
        encoding != NfcNdefRecord::kEncodingUtf16) {
      VLOG(1) << "Invalid \"Encoding\" value:" << encoding;
      return false;
    }
  }
  return true;
}

// Verifies that the contents of |data| conform to the fields of NDEF type
// "SmartPoster".
bool HandleTypeSmartPoster(const DictionaryValue* data) {
  VLOG(1) << "Populating record with type \"SmartPoster\".";
  FieldValueMap required_fields;
  required_fields[NfcNdefRecord::kFieldURI] = base::Value::TYPE_STRING;
  FieldValueMap optional_fields;
  optional_fields[NfcNdefRecord::kFieldAction] = base::Value::TYPE_STRING;
  optional_fields[NfcNdefRecord::kFieldMimeType] = base::Value::TYPE_STRING;
  // base::Value restricts the number types to BOOL, INTEGER, and DOUBLE only.
  // uint32 will automatically get converted to a double. "target size" is
  // really a uint32 but we define it as a double for this reason.
  // (See dbus/values_util.h).
  optional_fields[NfcNdefRecord::kFieldTargetSize] = base::Value::TYPE_DOUBLE;
  optional_fields[NfcNdefRecord::kFieldTitles] = base::Value::TYPE_LIST;
  if (!CheckFieldsAreValid(required_fields, optional_fields, data)) {
    VLOG(1) << "Failed to populate record.";
    return false;
  }
  // Verify that the "titles" field was formatted correctly, if it exists.
  const ListValue* titles = NULL;
  if (data->GetList(NfcNdefRecord::kFieldTitles, &titles)) {
    if (titles->empty()) {
      VLOG(1) << "\"titles\" field of SmartPoster is empty.";
      return false;
    }
    for (ListValue::const_iterator iter = titles->begin();
         iter != titles->end(); ++iter) {
      const DictionaryValue* title_data = NULL;
      if (!(*iter)->GetAsDictionary(&title_data)) {
        VLOG(1) << "\"title\" entry for SmartPoster contains an invalid value "
                << "type";
        return false;
      }
      if (!HandleTypeText(title_data)) {
        VLOG(1) << "Badly formatted \"title\" entry for SmartPoster.";
        return false;
      }
    }
  }
  return ValidateURI(data);
}

// Verifies that the contents of |data| conform to the fields of NDEF type
// "URI".
bool HandleTypeUri(const DictionaryValue* data) {
  VLOG(1) << "Populating record with type \"URI\".";
  FieldValueMap required_fields;
  required_fields[NfcNdefRecord::kFieldURI] = base::Value::TYPE_STRING;
  FieldValueMap optional_fields;
  optional_fields[NfcNdefRecord::kFieldMimeType] = base::Value::TYPE_STRING;
  optional_fields[NfcNdefRecord::kFieldTargetSize] = base::Value::TYPE_DOUBLE;

  // Allow passing TargetSize as an integer, but convert it to a double.
  if (!CheckFieldsAreValid(required_fields, optional_fields, data)) {
    VLOG(1) << "Failed to populate record.";
    return false;
  }
  return ValidateURI(data);
}

}  // namespace

// static
const char NfcNdefRecord::kFieldEncoding[] = "encoding";
// static
const char NfcNdefRecord::kFieldLanguageCode[] = "languageCode";
// static
const char NfcNdefRecord::kFieldText[] = "text";
// static
const char NfcNdefRecord::kFieldURI[] = "uri";
// static
const char NfcNdefRecord::kFieldMimeType[] = "mimeType";
// static
const char NfcNdefRecord::kFieldTargetSize[] = "targetSize";
// static
const char NfcNdefRecord::kFieldTitles[] = "titles";
// static
const char NfcNdefRecord::kFieldAction[] = "action";
// static
const char NfcNdefRecord::kEncodingUtf8[] = "UTF-8";
// static
const char NfcNdefRecord::kEncodingUtf16[] = "UTF-16";
// static
const char NfcNdefRecord::kSmartPosterActionDo[] = "do";
// static
const char NfcNdefRecord::kSmartPosterActionSave[] = "save";
// static
const char NfcNdefRecord::kSmartPosterActionOpen[] = "open";

NfcNdefRecord::NfcNdefRecord() : type_(kTypeUnknown) {
}

NfcNdefRecord::~NfcNdefRecord() {
}

bool NfcNdefRecord::IsPopulated() const {
  return type_ != kTypeUnknown;
}

bool NfcNdefRecord::Populate(Type type, const DictionaryValue* data) {
  if (IsPopulated())
    return false;

  DCHECK(data_.empty());

  // At this time, only "Text", "URI", and "SmartPoster" are supported.
  bool result = false;
  switch (type) {
    case kTypeText:
      result = HandleTypeText(data);
      break;
    case kTypeSmartPoster:
      result = HandleTypeSmartPoster(data);
      break;
    case kTypeURI:
      result = HandleTypeUri(data);
      break;
    default:
      VLOG(1) << "Unsupported NDEF type: " << type;
      break;
  }
  if (!result)
    return false;
  type_ = type;
  data_.MergeDictionary(data);
  return true;
}

NfcNdefMessage::NfcNdefMessage() {
}

NfcNdefMessage::~NfcNdefMessage() {
}

void NfcNdefMessage::AddRecord(NfcNdefRecord* record) {
  records_.push_back(record);
}

bool NfcNdefMessage::RemoveRecord(NfcNdefRecord* record) {
  for (RecordList::iterator iter = records_.begin();
       iter != records_.end(); ++iter) {
    if (*iter == record) {
      records_.erase(iter);
      return true;
    }
  }
  return false;
}

}  // namespace device
