#include "xmpmeta/xml/deserializer_impl.h"

#include <algorithm>

#include "base/integral_types.h"
#include "android-base/logging.h"
#include "strings/numbers.h"
#include "xmpmeta/base64.h"
#include "xmpmeta/xml/const.h"
#include "xmpmeta/xml/search.h"
#include "xmpmeta/xml/utils.h"
#include "xmpmeta/xmp_parser.h"

namespace dynamic_depth {
namespace xmpmeta {
namespace xml {
namespace {

// Converts a string to a boolean value if bool_str is one of "false" or "true",
// regardless of letter casing.
bool BoolStringToBool(const string& bool_str, bool* value) {
  string bool_str_lower = bool_str;
  std::transform(bool_str_lower.begin(), bool_str_lower.end(),
                 bool_str_lower.begin(), ::tolower);
  if (bool_str_lower == "true") {
    *value = true;
    return true;
  }
  if (bool_str_lower == "false") {
    *value = false;
    return true;
  }
  return false;
}

// Search for an rdf:Seq node, if it hasn't already been set.
// parent_name is the name of the rdf:Seq node's parent.
xmlNodePtr FindSeqNode(const xmlNodePtr node, const string& prefix,
                       const string& parent_name) {
  xmlNodePtr parent_node =
      DepthFirstSearch(node, prefix.data(), parent_name.data());
  if (parent_node == nullptr) {
    LOG(WARNING) << "Node " << parent_name << " not found";
    return nullptr;
  }
  return GetFirstSeqElement(parent_node);
}

// Extracts the specified string attribute.
bool GetStringProperty(const xmlNodePtr node, const string& prefix,
                       const string& property, string* value) {
  const xmlDocPtr doc = node->doc;
  for (const _xmlAttr* attribute = node->properties; attribute != nullptr;
       attribute = attribute->next) {
    // If prefix is not empty, then the attribute's namespace must not be null.
    if (((attribute->ns && !prefix.empty() &&
          strcmp(FromXmlChar(attribute->ns->prefix), prefix.data()) == 0) ||
         prefix.empty()) &&
        strcmp(FromXmlChar(attribute->name), property.data()) == 0) {
      xmlChar* attribute_string =
          xmlNodeListGetString(doc, attribute->children, 1);
      *value = FromXmlChar(attribute_string);
      xmlFree(attribute_string);
      return true;
    }
  }
  return false;
}

// Reads the contents of a node.
// E.g. <prefix:node_name>Contents Here</prefix:node_name>
bool ReadNodeContent(const xmlNodePtr node, const string& prefix,
                     const string& node_name, string* value) {
  auto* element = DepthFirstSearch(node, prefix.data(), node_name.data());
  if (element == nullptr) {
    return false;
  }
  if (!prefix.empty() &&
      (element->ns == nullptr || element->ns->prefix == nullptr ||
       strcmp(FromXmlChar(element->ns->prefix), prefix.data()) != 0)) {
    return false;
  }
  xmlChar* node_content = xmlNodeGetContent(element);
  *value = FromXmlChar(node_content);
  free(node_content);
  return true;
}

// Reads the string value of a property from the given XML node.
bool ReadStringProperty(const xmlNodePtr node, const string& prefix,
                        const string& property, string* value) {
  if (node == nullptr) {
    return false;
  }
  if (property.empty()) {
    LOG(ERROR) << "Property not given";
    return false;
  }

  // Try parsing in the format <Node ... Prefix:Property="Value"/>
  bool success = GetStringProperty(node, prefix, property, value);
  if (!success) {
    // Try parsing in the format <Prefix:Property>Value</Prefix:Property>
    success = ReadNodeContent(node, prefix, property, value);
  }
  return success;
}

// Same as ReadStringProperty, but applies base-64 decoding to the output.
bool ReadBase64Property(const xmlNodePtr node, const string& prefix,
                        const string& property, string* value) {
  string base64_data;
  if (!ReadStringProperty(node, prefix, property, &base64_data)) {
    return false;
  }
  return DecodeBase64(base64_data, value);
}

}  // namespace

DeserializerImpl::DeserializerImpl(const xmlNodePtr node)
    : node_(node), list_node_(nullptr) {}

// Public methods.
std::unique_ptr<Deserializer> DeserializerImpl::CreateDeserializer(
    const string& prefix, const string& child_name) const {
  if (child_name.empty()) {
    LOG(ERROR) << "Child name is empty";
    return nullptr;
  }
  xmlNodePtr child_node =
      DepthFirstSearch(node_, prefix.data(), child_name.data());
  if (child_node == nullptr) {
    return nullptr;
  }
  return std::unique_ptr<Deserializer>(
      new DeserializerImpl(child_node));  // NOLINT
}

std::unique_ptr<Deserializer>
DeserializerImpl::CreateDeserializerFromListElementAt(const string& prefix,
                                                      const string& list_name,
                                                      int index) const {
  if (index < 0) {
    LOG(ERROR) << "Index must be greater than or equal to zero";
    return nullptr;
  }

  if (list_name.empty()) {
    LOG(ERROR) << "Parent name cannot be empty";
    return nullptr;
  }
  // Search for an rdf:Seq node, if the name of list_node_ doesn't match
  // the given parent name.
  // Ensures thread safety.
  const xmlNodePtr list_node = [&] {
    std::lock_guard<std::mutex> lock(mtx_);
    if (list_node_ == nullptr ||
        string(FromXmlChar(list_node_->name)) != list_name) {
      list_node_ = DepthFirstSearch(node_, prefix.data(), list_name.data());
    }
    return list_node_;
  }();
  if (list_node == nullptr) {
    return nullptr;
  }

  xmlNodePtr seq_node = GetFirstSeqElement(list_node);
  if (seq_node == nullptr) {
    LOG(ERROR) << "No rdf:Seq node found on " << list_name;
    return nullptr;
  }
  xmlNodePtr li_node = GetElementAt(seq_node, index);
  if (li_node == nullptr) {
    return nullptr;
  }
  // Return a new Deserializer with the current rdf:li node and the current
  // node name.
  return std::unique_ptr<Deserializer>(
      new DeserializerImpl(li_node));  // NOLINT
}

bool DeserializerImpl::ParseBase64(const string& prefix, const string& name,
                                   string* value) const {
  return ReadBase64Property(node_, prefix, name, value);
}

bool DeserializerImpl::ParseIntArrayBase64(const string& prefix,
                                           const string& name,
                                           std::vector<int>* values) const {
  string base64_data;
  if (!ReadStringProperty(node_, prefix, name, &base64_data)) {
    return false;
  }
  return DecodeIntArrayBase64(base64_data, values);
}

bool DeserializerImpl::ParseFloatArrayBase64(const string& prefix,
                                             const string& name,
                                             std::vector<float>* values) const {
  string base64_data;
  if (!ReadStringProperty(node_, prefix, name, &base64_data)) {
    return false;
  }
  return DecodeFloatArrayBase64(base64_data, values);
}

bool DeserializerImpl::ParseDoubleArrayBase64(
    const string& prefix, const string& name,
    std::vector<double>* values) const {
  string base64_data;
  if (!ReadStringProperty(node_, prefix, name, &base64_data)) {
    return false;
  }
  return DecodeDoubleArrayBase64(base64_data, values);
}

bool DeserializerImpl::ParseBoolean(const string& prefix, const string& name,
                                    bool* value) const {
  string string_value;
  if (!ReadStringProperty(node_, prefix, name, &string_value)) {
    return false;
  }
  return BoolStringToBool(string_value, value);
}

bool DeserializerImpl::ParseDouble(const string& prefix, const string& name,
                                   double* value) const {
  string string_value;
  if (!ReadStringProperty(node_, prefix, name, &string_value)) {
    return false;
  }
  *value = std::stod(string_value);
  return true;
}

bool DeserializerImpl::ParseInt(const string& prefix, const string& name,
                                int* value) const {
  string string_value;
  if (!ReadStringProperty(node_, prefix, name, &string_value)) {
    return false;
  }
  *value = std::stoi(string_value);  // NOLINT
  return true;
}

bool DeserializerImpl::ParseFloat(const string& prefix, const string& name,
                                  float* value) const {
  string string_value;
  if (!ReadStringProperty(node_, prefix, name, &string_value)) {
    return false;
  }
  *value = std::stof(string_value);
  return true;
}

bool DeserializerImpl::ParseLong(const string& prefix, const string& name,
                                 int64* value) const {
  string string_value;
  if (!ReadStringProperty(node_, prefix, name, &string_value)) {
    return false;
  }
  *value = std::stol(string_value);
  return true;
}

bool DeserializerImpl::ParseString(const string& prefix, const string& name,
                                   string* value) const {
  return ReadStringProperty(node_, prefix, name, value);
}

bool DeserializerImpl::ParseIntArray(const string& prefix,
                                     const string& list_name,
                                     std::vector<int>* values) const {
  xmlNodePtr seq_node = FindSeqNode(node_, prefix, list_name);
  if (seq_node == nullptr) {
    return false;
  }
  values->clear();
  int i = 0;
  for (xmlNodePtr li_node = GetElementAt(seq_node, 0); li_node != nullptr;
       li_node = GetElementAt(seq_node, ++i)) {
    string value = GetLiNodeContent(li_node);
    for (int i = 0; i < value.size(); ++i) {
      if (!isdigit(value[i])) {
        LOG(ERROR) << "Could not parse rdf:li node value to an integer";
        return false;
      }
    }
    int int_value = std::atoi(value.c_str());  // NOLINT
    values->push_back(int_value);
  }

  return true;
}

bool DeserializerImpl::ParseDoubleArray(const string& prefix,
                                        const string& list_name,
                                        std::vector<double>* values) const {
  xmlNodePtr seq_node = FindSeqNode(node_, prefix, list_name);
  if (seq_node == nullptr) {
    return false;
  }
  values->clear();
  int i = 0;
  for (xmlNodePtr li_node = GetElementAt(seq_node, 0); li_node != nullptr;
       li_node = GetElementAt(seq_node, ++i)) {
    double double_value;
    if (!dynamic_depth::strings::safe_strtod(GetLiNodeContent(li_node),
                                             &double_value)) {
      LOG(ERROR) << "Could not parse rdf:li node value to a double";
      return false;
    }
    values->push_back(double_value);
  }

  return true;
}

}  // namespace xml
}  // namespace xmpmeta
}  // namespace dynamic_depth