// Copyright (c) 2010 The Chromium Embedded Framework 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 "include/wrapper/cef_xml_object.h"

#include <sstream>

#include "include/base/cef_logging.h"
#include "include/cef_stream.h"

namespace {

class CefXmlObjectLoader {
 public:
  explicit CefXmlObjectLoader(CefRefPtr<CefXmlObject> root_object)
      : root_object_(root_object) {}

  CefXmlObjectLoader(const CefXmlObjectLoader&) = delete;
  CefXmlObjectLoader& operator=(const CefXmlObjectLoader&) = delete;

  bool Load(CefRefPtr<CefStreamReader> stream,
            CefXmlReader::EncodingType encodingType,
            const CefString& URI) {
    CefRefPtr<CefXmlReader> reader(
        CefXmlReader::Create(stream, encodingType, URI));
    if (!reader.get()) {
      return false;
    }

    bool ret = reader->MoveToNextNode();
    if (ret) {
      CefRefPtr<CefXmlObject> cur_object(root_object_), new_object;
      CefXmlObject::ObjectVector queue;
      int cur_depth, value_depth = -1;
      CefXmlReader::NodeType cur_type;
      std::stringstream cur_value;
      bool last_has_ns = false;

      queue.push_back(root_object_);

      do {
        cur_depth = reader->GetDepth();
        if (value_depth >= 0 && cur_depth > value_depth) {
          // The current node has already been parsed as part of a value.
          continue;
        }

        cur_type = reader->GetType();
        if (cur_type == XML_NODE_ELEMENT_START) {
          if (cur_depth == value_depth) {
            // Add to the current value.
            cur_value << std::string(reader->GetOuterXml());
            continue;
          } else if (last_has_ns && reader->GetPrefix().empty()) {
            if (!cur_object->HasChildren()) {
              // Start a new value because the last element has a namespace and
              // this element does not.
              value_depth = cur_depth;
              cur_value << std::string(reader->GetOuterXml());
            } else {
              // Value following a child element is not allowed.
              std::stringstream ss;
              ss << "Value following child element, line "
                 << reader->GetLineNumber();
              load_error_ = ss.str();
              ret = false;
              break;
            }
          } else {
            // Start a new element.
            new_object = new CefXmlObject(reader->GetQualifiedName());
            cur_object->AddChild(new_object);
            last_has_ns = !reader->GetPrefix().empty();

            if (!reader->IsEmptyElement()) {
              // The new element potentially has a value and/or children, so
              // set the current object and add the object to the queue.
              cur_object = new_object;
              queue.push_back(cur_object);
            }

            if (reader->HasAttributes() && reader->MoveToFirstAttribute()) {
              // Read all object attributes.
              do {
                new_object->SetAttributeValue(reader->GetQualifiedName(),
                                              reader->GetValue());
              } while (reader->MoveToNextAttribute());
              reader->MoveToCarryingElement();
            }
          }
        } else if (cur_type == XML_NODE_ELEMENT_END) {
          if (cur_depth == value_depth) {
            // Ending an element that is already in the value.
            continue;
          } else if (cur_depth < value_depth) {
            // Done with parsing the value portion of the current element.
            cur_object->SetValue(cur_value.str());
            cur_value.str("");
            value_depth = -1;
          }

          // Pop the current element from the queue.
          queue.pop_back();

          if (queue.empty() ||
              cur_object->GetName() != reader->GetQualifiedName()) {
            // Open tag without close tag or close tag without open tag should
            // never occur (the parser catches this error).
            DCHECK(false);
            std::stringstream ss;
            ss << "Mismatched end tag for "
               << std::string(cur_object->GetName()) << ", line "
               << reader->GetLineNumber();
            load_error_ = ss.str();
            ret = false;
            break;
          }

          // Set the current object to the previous object in the queue.
          cur_object = queue.back().get();
        } else if (cur_type == XML_NODE_TEXT || cur_type == XML_NODE_CDATA ||
                   cur_type == XML_NODE_ENTITY_REFERENCE) {
          if (cur_depth == value_depth) {
            // Add to the current value.
            cur_value << std::string(reader->GetValue());
          } else if (!cur_object->HasChildren()) {
            // Start a new value.
            value_depth = cur_depth;
            cur_value << std::string(reader->GetValue());
          } else {
            // Value following a child element is not allowed.
            std::stringstream ss;
            ss << "Value following child element, line "
               << reader->GetLineNumber();
            load_error_ = ss.str();
            ret = false;
            break;
          }
        }
      } while (reader->MoveToNextNode());
    }

    if (reader->HasError()) {
      load_error_ = reader->GetError();
      return false;
    }

    return ret;
  }

  CefString GetLoadError() { return load_error_; }

 private:
  CefString load_error_;
  CefRefPtr<CefXmlObject> root_object_;
};

}  // namespace

CefXmlObject::CefXmlObject(const CefString& name) : name_(name) {}

CefXmlObject::~CefXmlObject() = default;

bool CefXmlObject::Load(CefRefPtr<CefStreamReader> stream,
                        CefXmlReader::EncodingType encodingType,
                        const CefString& URI,
                        CefString* loadError) {
  Clear();

  CefXmlObjectLoader loader(this);
  if (!loader.Load(stream, encodingType, URI)) {
    if (loadError) {
      *loadError = loader.GetLoadError();
    }
    return false;
  }
  return true;
}

void CefXmlObject::Set(CefRefPtr<CefXmlObject> object) {
  DCHECK(object.get());

  Clear();

  name_ = object->GetName();
  Append(object, true);
}

void CefXmlObject::Append(CefRefPtr<CefXmlObject> object,
                          bool overwriteAttributes) {
  DCHECK(object.get());

  if (object->HasChildren()) {
    ObjectVector children;
    object->GetChildren(children);
    ObjectVector::const_iterator it = children.begin();
    for (; it != children.end(); ++it) {
      AddChild((*it)->Duplicate());
    }
  }

  if (object->HasAttributes()) {
    AttributeMap attributes;
    object->GetAttributes(attributes);
    AttributeMap::const_iterator it = attributes.begin();
    for (; it != attributes.end(); ++it) {
      if (overwriteAttributes || !HasAttribute(it->first)) {
        SetAttributeValue(it->first, it->second);
      }
    }
  }
}

CefRefPtr<CefXmlObject> CefXmlObject::Duplicate() {
  CefRefPtr<CefXmlObject> new_obj;
  {
    base::AutoLock lock_scope(lock_);
    new_obj = new CefXmlObject(name_);
    new_obj->Append(this, true);
  }
  return new_obj;
}

void CefXmlObject::Clear() {
  ClearChildren();
  ClearAttributes();
}

CefString CefXmlObject::GetName() {
  CefString name;
  {
    base::AutoLock lock_scope(lock_);
    name = name_;
  }
  return name;
}

bool CefXmlObject::SetName(const CefString& name) {
  DCHECK(!name.empty());
  if (name.empty()) {
    return false;
  }

  base::AutoLock lock_scope(lock_);
  name_ = name;
  return true;
}

bool CefXmlObject::HasParent() {
  base::AutoLock lock_scope(lock_);
  return (parent_ != nullptr);
}

CefRefPtr<CefXmlObject> CefXmlObject::GetParent() {
  CefRefPtr<CefXmlObject> parent;
  {
    base::AutoLock lock_scope(lock_);
    parent = parent_;
  }
  return parent;
}

bool CefXmlObject::HasValue() {
  base::AutoLock lock_scope(lock_);
  return !value_.empty();
}

CefString CefXmlObject::GetValue() {
  CefString value;
  {
    base::AutoLock lock_scope(lock_);
    value = value_;
  }
  return value;
}

bool CefXmlObject::SetValue(const CefString& value) {
  base::AutoLock lock_scope(lock_);
  DCHECK(children_.empty());
  if (!children_.empty()) {
    return false;
  }
  value_ = value;
  return true;
}

bool CefXmlObject::HasAttributes() {
  base::AutoLock lock_scope(lock_);
  return !attributes_.empty();
}

size_t CefXmlObject::GetAttributeCount() {
  base::AutoLock lock_scope(lock_);
  return attributes_.size();
}

bool CefXmlObject::HasAttribute(const CefString& name) {
  if (name.empty()) {
    return false;
  }

  base::AutoLock lock_scope(lock_);
  AttributeMap::const_iterator it = attributes_.find(name);
  return (it != attributes_.end());
}

CefString CefXmlObject::GetAttributeValue(const CefString& name) {
  DCHECK(!name.empty());
  CefString value;
  if (!name.empty()) {
    base::AutoLock lock_scope(lock_);
    AttributeMap::const_iterator it = attributes_.find(name);
    if (it != attributes_.end()) {
      value = it->second;
    }
  }
  return value;
}

bool CefXmlObject::SetAttributeValue(const CefString& name,
                                     const CefString& value) {
  DCHECK(!name.empty());
  if (name.empty()) {
    return false;
  }

  base::AutoLock lock_scope(lock_);
  AttributeMap::iterator it = attributes_.find(name);
  if (it != attributes_.end()) {
    it->second = value;
  } else {
    attributes_.insert(std::make_pair(name, value));
  }
  return true;
}

size_t CefXmlObject::GetAttributes(AttributeMap& attributes) {
  base::AutoLock lock_scope(lock_);
  attributes = attributes_;
  return attributes_.size();
}

void CefXmlObject::ClearAttributes() {
  base::AutoLock lock_scope(lock_);
  attributes_.clear();
}

bool CefXmlObject::HasChildren() {
  base::AutoLock lock_scope(lock_);
  return !children_.empty();
}

size_t CefXmlObject::GetChildCount() {
  base::AutoLock lock_scope(lock_);
  return children_.size();
}

bool CefXmlObject::HasChild(CefRefPtr<CefXmlObject> child) {
  DCHECK(child.get());

  base::AutoLock lock_scope(lock_);
  ObjectVector::const_iterator it = children_.begin();
  for (; it != children_.end(); ++it) {
    if ((*it).get() == child.get()) {
      return true;
    }
  }
  return false;
}

bool CefXmlObject::AddChild(CefRefPtr<CefXmlObject> child) {
  DCHECK(child.get());
  if (!child.get()) {
    return false;
  }

  CefRefPtr<CefXmlObject> parent = child->GetParent();
  DCHECK(!parent);
  if (parent) {
    return false;
  }

  base::AutoLock lock_scope(lock_);

  children_.push_back(child);
  child->SetParent(this);
  return true;
}

bool CefXmlObject::RemoveChild(CefRefPtr<CefXmlObject> child) {
  DCHECK(child.get());

  base::AutoLock lock_scope(lock_);
  ObjectVector::iterator it = children_.begin();
  for (; it != children_.end(); ++it) {
    if ((*it).get() == child.get()) {
      children_.erase(it);
      child->SetParent(nullptr);
      return true;
    }
  }
  return false;
}

size_t CefXmlObject::GetChildren(ObjectVector& children) {
  base::AutoLock lock_scope(lock_);
  children = children_;
  return children_.size();
}

void CefXmlObject::ClearChildren() {
  base::AutoLock lock_scope(lock_);
  ObjectVector::iterator it = children_.begin();
  for (; it != children_.end(); ++it) {
    (*it)->SetParent(nullptr);
  }
  children_.clear();
}

CefRefPtr<CefXmlObject> CefXmlObject::FindChild(const CefString& name) {
  DCHECK(!name.empty());
  if (name.empty()) {
    return nullptr;
  }

  base::AutoLock lock_scope(lock_);
  ObjectVector::const_iterator it = children_.begin();
  for (; it != children_.end(); ++it) {
    if ((*it)->GetName() == name) {
      return (*it);
    }
  }
  return nullptr;
}

size_t CefXmlObject::FindChildren(const CefString& name,
                                  ObjectVector& children) {
  DCHECK(!name.empty());
  if (name.empty()) {
    return 0;
  }

  size_t ct = 0;

  base::AutoLock lock_scope(lock_);
  ObjectVector::const_iterator it = children_.begin();
  for (; it != children_.end(); ++it) {
    if ((*it)->GetName() == name) {
      children.push_back(*it);
      ct++;
    }
  }
  return ct;
}

void CefXmlObject::SetParent(CefXmlObject* parent) {
  base::AutoLock lock_scope(lock_);
  if (parent) {
    DCHECK(parent_ == nullptr);
    parent_ = parent;
  } else {
    DCHECK(parent_ != nullptr);
    parent_ = nullptr;
  }
}
