/*
 * libjingle
 * Copyright 2004--2005, Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products 
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "talk/xmllite/xmlelement.h"

#include <ostream>
#include <sstream>
#include <string>
#include <vector>

#include "talk/base/common.h"
#include "talk/xmllite/qname.h"
#include "talk/xmllite/xmlparser.h"
#include "talk/xmllite/xmlbuilder.h"
#include "talk/xmllite/xmlprinter.h"
#include "talk/xmllite/xmlconstants.h"

namespace buzz {

XmlChild::~XmlChild() {
}

bool XmlText::IsTextImpl() const {
  return true;
}

XmlElement* XmlText::AsElementImpl() const {
  return NULL;
}

XmlText* XmlText::AsTextImpl() const {
  return const_cast<XmlText *>(this);
}

void XmlText::SetText(const std::string& text) {
  text_ = text;
}

void XmlText::AddParsedText(const char* buf, int len) {
  text_.append(buf, len);
}

void XmlText::AddText(const std::string& text) {
  text_ += text;
}

XmlText::~XmlText() {
}

XmlElement::XmlElement(const QName& name) :
    name_(name),
    first_attr_(NULL),
    last_attr_(NULL),
    first_child_(NULL),
    last_child_(NULL),
    cdata_(false) {
}

XmlElement::XmlElement(const XmlElement& elt) :
    XmlChild(),
    name_(elt.name_),
    first_attr_(NULL),
    last_attr_(NULL),
    first_child_(NULL),
    last_child_(NULL),
    cdata_(false) {

  // copy attributes
  XmlAttr* attr;
  XmlAttr ** plast_attr = &first_attr_;
  XmlAttr* newAttr = NULL;
  for (attr = elt.first_attr_; attr; attr = attr->NextAttr()) {
    newAttr = new XmlAttr(*attr);
    *plast_attr = newAttr;
    plast_attr = &(newAttr->next_attr_);
  }
  last_attr_ = newAttr;

  // copy children
  XmlChild* pChild;
  XmlChild ** ppLast = &first_child_;
  XmlChild* newChild = NULL;

  for (pChild = elt.first_child_; pChild; pChild = pChild->NextChild()) {
    if (pChild->IsText()) {
      newChild = new XmlText(*(pChild->AsText()));
    } else {
      newChild = new XmlElement(*(pChild->AsElement()));
    }
    *ppLast = newChild;
    ppLast = &(newChild->next_child_);
  }
  last_child_ = newChild;

  cdata_ = elt.cdata_;
}

XmlElement::XmlElement(const QName& name, bool useDefaultNs) :
  name_(name),
  first_attr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL),
  last_attr_(first_attr_),
  first_child_(NULL),
  last_child_(NULL),
  cdata_(false) {
}

bool XmlElement::IsTextImpl() const {
  return false;
}

XmlElement* XmlElement::AsElementImpl() const {
  return const_cast<XmlElement *>(this);
}

XmlText* XmlElement::AsTextImpl() const {
  return NULL;
}

const std::string XmlElement::BodyText() const {
  if (first_child_ && first_child_->IsText() && last_child_ == first_child_) {
    return first_child_->AsText()->Text();
  }

  return std::string();
}

void XmlElement::SetBodyText(const std::string& text) {
  if (text.empty()) {
    ClearChildren();
  } else if (first_child_ == NULL) {
    AddText(text);
  } else if (first_child_->IsText() && last_child_ == first_child_) {
    first_child_->AsText()->SetText(text);
  } else {
    ClearChildren();
    AddText(text);
  }
}

const QName XmlElement::FirstElementName() const {
  const XmlElement* element = FirstElement();
  if (element == NULL)
    return QName();
  return element->Name();
}

XmlAttr* XmlElement::FirstAttr() {
  return first_attr_;
}

const std::string XmlElement::Attr(const StaticQName& name) const {
  XmlAttr* attr;
  for (attr = first_attr_; attr; attr = attr->next_attr_) {
    if (attr->name_ == name)
      return attr->value_;
  }
  return std::string();
}

const std::string XmlElement::Attr(const QName& name) const {
  XmlAttr* attr;
  for (attr = first_attr_; attr; attr = attr->next_attr_) {
    if (attr->name_ == name)
      return attr->value_;
  }
  return std::string();
}

bool XmlElement::HasAttr(const StaticQName& name) const {
  XmlAttr* attr;
  for (attr = first_attr_; attr; attr = attr->next_attr_) {
    if (attr->name_ == name)
      return true;
  }
  return false;
}

bool XmlElement::HasAttr(const QName& name) const {
  XmlAttr* attr;
  for (attr = first_attr_; attr; attr = attr->next_attr_) {
    if (attr->name_ == name)
      return true;
  }
  return false;
}

void XmlElement::SetAttr(const QName& name, const std::string& value) {
  XmlAttr* attr;
  for (attr = first_attr_; attr; attr = attr->next_attr_) {
    if (attr->name_ == name)
      break;
  }
  if (!attr) {
    attr = new XmlAttr(name, value);
    if (last_attr_)
      last_attr_->next_attr_ = attr;
    else
      first_attr_ = attr;
    last_attr_ = attr;
    return;
  }
  attr->value_ = value;
}

void XmlElement::ClearAttr(const QName& name) {
  XmlAttr* attr;
  XmlAttr* last_attr = NULL;
  for (attr = first_attr_; attr; attr = attr->next_attr_) {
    if (attr->name_ == name)
      break;
    last_attr = attr;
  }
  if (!attr)
    return;
  if (!last_attr)
    first_attr_ = attr->next_attr_;
  else
    last_attr->next_attr_ = attr->next_attr_;
  if (last_attr_ == attr)
    last_attr_ = last_attr;
  delete attr;
}

XmlChild* XmlElement::FirstChild() {
  return first_child_;
}

XmlElement* XmlElement::FirstElement() {
  XmlChild* pChild;
  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
    if (!pChild->IsText())
      return pChild->AsElement();
  }
  return NULL;
}

XmlElement* XmlElement::NextElement() {
  XmlChild* pChild;
  for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
    if (!pChild->IsText())
      return pChild->AsElement();
  }
  return NULL;
}

XmlElement* XmlElement::FirstWithNamespace(const std::string& ns) {
  XmlChild* pChild;
  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
    if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
      return pChild->AsElement();
  }
  return NULL;
}

XmlElement *
XmlElement::NextWithNamespace(const std::string& ns) {
  XmlChild* pChild;
  for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
    if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
      return pChild->AsElement();
  }
  return NULL;
}

XmlElement *
XmlElement::FirstNamed(const QName& name) {
  XmlChild* pChild;
  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
      return pChild->AsElement();
  }
  return NULL;
}

XmlElement *
XmlElement::FirstNamed(const StaticQName& name) {
  XmlChild* pChild;
  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
      return pChild->AsElement();
  }
  return NULL;
}

XmlElement *
XmlElement::NextNamed(const QName& name) {
  XmlChild* pChild;
  for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
      return pChild->AsElement();
  }
  return NULL;
}

XmlElement *
XmlElement::NextNamed(const StaticQName& name) {
  XmlChild* pChild;
  for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
      return pChild->AsElement();
  }
  return NULL;
}

XmlElement* XmlElement::FindOrAddNamedChild(const QName& name) {
  XmlElement* child = FirstNamed(name);
  if (!child) {
    child = new XmlElement(name);
    AddElement(child);
  }

  return child;
}

const std::string XmlElement::TextNamed(const QName& name) const {
  XmlChild* pChild;
  for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
    if (!pChild->IsText() && pChild->AsElement()->Name() == name)
      return pChild->AsElement()->BodyText();
  }
  return std::string();
}

void XmlElement::InsertChildAfter(XmlChild* predecessor, XmlChild* next) {
  if (predecessor == NULL) {
    next->next_child_ = first_child_;
    first_child_ = next;
  }
  else {
    next->next_child_ = predecessor->next_child_;
    predecessor->next_child_ = next;
  }
}

void XmlElement::RemoveChildAfter(XmlChild* predecessor) {
  XmlChild* next;

  if (predecessor == NULL) {
    next = first_child_;
    first_child_ = next->next_child_;
  }
  else {
    next = predecessor->next_child_;
    predecessor->next_child_ = next->next_child_;
  }

  if (last_child_ == next)
    last_child_ = predecessor;

  delete next;
}

void XmlElement::AddAttr(const QName& name, const std::string& value) {
  ASSERT(!HasAttr(name));

  XmlAttr ** pprev = last_attr_ ? &(last_attr_->next_attr_) : &first_attr_;
  last_attr_ = (*pprev = new XmlAttr(name, value));
}

void XmlElement::AddAttr(const QName& name, const std::string& value,
                         int depth) {
  XmlElement* element = this;
  while (depth--) {
    element = element->last_child_->AsElement();
  }
  element->AddAttr(name, value);
}

void XmlElement::AddParsedText(const char* cstr, int len) {
  if (len == 0)
    return;

  if (last_child_ && last_child_->IsText()) {
    last_child_->AsText()->AddParsedText(cstr, len);
    return;
  }
  XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
  last_child_ = *pprev = new XmlText(cstr, len);
}

void XmlElement::AddCDATAText(const char* buf, int len) {
  cdata_ = true;
  AddParsedText(buf, len);
}

void XmlElement::AddText(const std::string& text) {
  if (text == STR_EMPTY)
    return;

  if (last_child_ && last_child_->IsText()) {
    last_child_->AsText()->AddText(text);
    return;
  }
  XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
  last_child_ = *pprev = new XmlText(text);
}

void XmlElement::AddText(const std::string& text, int depth) {
  // note: the first syntax is ambigious for msvc 6
  // XmlElement* pel(this);
  XmlElement* element = this;
  while (depth--) {
    element = element->last_child_->AsElement();
  }
  element->AddText(text);
}

void XmlElement::AddElement(XmlElement *child) {
  if (child == NULL)
    return;

  XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
  *pprev = child;
  last_child_ = child;
  child->next_child_ = NULL;
}

void XmlElement::AddElement(XmlElement *child, int depth) {
  XmlElement* element = this;
  while (depth--) {
    element = element->last_child_->AsElement();
  }
  element->AddElement(child);
}

void XmlElement::ClearNamedChildren(const QName& name) {
  XmlChild* prev_child = NULL;
  XmlChild* next_child;
  XmlChild* child;
  for (child = FirstChild(); child; child = next_child) {
    next_child = child->NextChild();
    if (!child->IsText() && child->AsElement()->Name() == name)
    {
      RemoveChildAfter(prev_child);
      continue;
    }
    prev_child = child;
  }
}

void XmlElement::ClearAttributes() {
  XmlAttr* attr;
  for (attr = first_attr_; attr; ) {
    XmlAttr* to_delete = attr;
    attr = attr->next_attr_;
    delete to_delete;
  }
  first_attr_ = last_attr_ = NULL;
}

void XmlElement::ClearChildren() {
  XmlChild* pchild;
  for (pchild = first_child_; pchild; ) {
    XmlChild* to_delete = pchild;
    pchild = pchild->next_child_;
    delete to_delete;
  }
  first_child_ = last_child_ = NULL;
}

std::string XmlElement::Str() const {
  std::stringstream ss;
  XmlPrinter::PrintXml(&ss, this);
  return ss.str();
}

XmlElement* XmlElement::ForStr(const std::string& str) {
  XmlBuilder builder;
  XmlParser::ParseXml(&builder, str);
  return builder.CreateElement();
}

XmlElement::~XmlElement() {
  XmlAttr* attr;
  for (attr = first_attr_; attr; ) {
    XmlAttr* to_delete = attr;
    attr = attr->next_attr_;
    delete to_delete;
  }

  XmlChild* pchild;
  for (pchild = first_child_; pchild; ) {
    XmlChild* to_delete = pchild;
    pchild = pchild->next_child_;
    delete to_delete;
  }
}

}  // namespace buzz
