/**
 * @file
 * @version @$Id$
 * @author
 * ui-gxmlcpp: C++ wrapper for Gnome libxml2/libxsl.
 * Copyright (C) Schlund+Partner AG 2002-2004.
 * Licensed under the GNU Lesser General Public License (LGPL). See
 * @ref License, or the file COPYING in distribution.
 */
#ifndef UI_GXML_TREE_HPP
#define UI_GXML_TREE_HPP

// STDC++
#include <string>
#include <cassert>
#include <istream>
#include <vector>

// C++ libraries
#include <ui-gxmlcpp/Exception.hpp>
#include <ui-gxmlcpp/XPathContext.hpp>
#include <ui-gxmlcpp/XPathObject.hpp>
#include <ui-gxmlcpp/Buffer.hpp>
#include <ui-gxmlcpp/Util.hpp>

namespace UI {
namespace GXML {

/** @brief XML tree. */
class Tree: private UI::Util::auto_base<xmlDoc>
{
	friend class XSLTransTree;
	friend class SchemaTree;
	friend class RelaxNGTree;
private:
	/* Create a libxml2 <tt>xmlDoc</tt>.
	 *
	 * @param xmlBuffer Null-terminated C string or buffer of length len.
	 * @param base XML Base (@see RFC 2396).
	 * @param len Len of buffer. If not given, xmlBuffer will be treated as null-terminated c string.
	 * @param options Parse options; To avoid segfaults later, use the
	 *        XML=XML_PARSE_NOBLANKS
	 *        XSL=XML_PARSE_NOBLANKS | XSLT_PARSE_OPTIONS
	 */
	static xmlDoc * create(char const * xml, int len, std::string const & base, int const options);
	static xmlDoc * create(xmlDoc const * const doc);
	static xmlDoc * create(std::string const & file, int const options);

public:
	/** @brief Error codes for exceptions. */
	enum ErrorCode
	{
		Parse_,
		Mem_,
		NoNode_,
		Internal_
	};
	/** @brief Exceptions for this class. */
	typedef CodeException<ErrorCode> Exception;

	/** @brief Structure holding namespaces for xPath expressions. */
	typedef std::map<std::string, std::string> NamespaceMap;

	/** @brief Default encoding. This is used for any dumps if not specified otherwise. */
	static std::string const DefaultEncoding_;
	/** @brief Comma-separated uppercase list of the name and all allowed aliases the default encoding.
	 *
	 * @note "Allowed aliases" as supported by glibc's (2.3.2)
	 * iconv. According to IANA charset registry (2002-09-30), UTF-8 has
	 * no aliases.
	 */
	static std::string const DefaultEncodings_;
	/** @brief Default document base. This is used for from-memory parsing if not specified otherwise. */
	static std::string const DefaultDocbase_;

	/** @brief Compat only. */
	friend class XMLTree;

protected:
	/** @brief Protected default constructor. */
	Tree();

	/** @brief Prefixs and URIs for custom xml namespaces in xPath queries. */
	NamespaceMap xPathNamespaces_;

public:
	/** @brief XPathContext needs access to libxml2 doc ptr. */
	friend class XPathContext;

	/** @name Standard tree constructors.
	 * @{ */
	/** @brief Construct from C string or buffer. */
	Tree(char const * xml, int len=-1, std::string const & base=DefaultDocbase_, int const options=0);

	/** @brief Construct from string. */
	Tree(std::string const & xml, std::string const & base=DefaultDocbase_, int const options=0);

	/** @brief Construct from istream. */
	Tree(std::istream & xml, std::string const & base=DefaultDocbase_, int const options=0);

	/** @brief Helper enum for constructor from file name. */
	enum FileConstructor
	{
		File_
	};
	/** @brief Construct from file name. */
	Tree(FileConstructor const & dummy, std::string const & file, int const options=0);
	/** @brief Construct from another Tree. */
	Tree(Tree const & tree);
	/** @} */

private:
	void dealloc();

public:
	~Tree();

	/** @brief Copy operator. */
	Tree & operator=(Tree const & tree);

	/** @brief Comparison.
	 *
	 * @note This does a string compare on dumps, so it's rather expensive.
	 */
	bool operator==(Tree const & tree);

	/** @name Tree configuration.
	 * @{ */
	/** @brief (Un)configure the tree to use a fix xpath context. */
	Tree & setContext(bool on=true);

	/** @brief Get fixed context.
	 *
	 * @attention XPathContexts are not thread save.
	 */
	XPathContext * getContext() const;
	/** @} */

	/** @todo Learn about namespaces, and why this is still needed (namespace parse option?). */
	Tree & setTopLevelNamespaces();

	/** @brief Set custom xml namespace known in xPath queries. */
	Tree & setXPathNamespace(std::string const & prefix, std::string const & uri);

protected:
	/** @brief En/disable freeing of underlying libxml2 doc_ tree. Use with caution. */
	Tree & setDontFreeDoc(bool on=true);

public:
	class NodeSet;

	/** @brief A XML node. */
	class Node: private UI::Util::auto_base<xmlNode>
	{
		friend class NodeSet;
	public:
		/** @brief Constructor from libxml2 node. */
		Node(xmlNode * const node=0);
		/** @brief Test if this is null ("not a tree node"). */
		bool isNull() const;
		/** @brief Unlink this node (from underlying tree). Memory will be freed, and the node becomes a null node. */
		void unlink();

		/** @name Navigate through the tree.
		 * @{ */
		/** If a node does not exists, the returned node isNull(). */
		Node getParent() const;
		Node getChild() const;
		Node getNext() const;
		Node getPrev() const;
		/** @} */

		/** @brief Get all child nodes of this node. */
		NodeSet getChilds(std::string const & name="") const;

		/** @brief Abstract from libxml2 type. */
		typedef xmlElementType Type;

		/** @brief Get node type. */
		Type getType() const;

		/** @brief Get associated XPath. */
		std::string getXPath() const;

		/** @name Get/set name of node.
		 * @{ */
		/** @note Set will only work for element nodes. */
		char const * getNameC() const;
		std::string getName() const;
		void setName(std::string const & name);
		/** @} */

		/** @name Get/set text content of first child node.
		 * @{ */
		char const * getContentC() const;
		std::string getContent() const;
		void setContent(std::string const & content);
		/** @} */

		/** @brief Returns 0 if not found, else the attribute's content. */
		char const * hasAttribute(std::string const & name) const;
		/** @brief Like above, but always return valid c string (length 0 if not found). */
		char const * getAttributeC(std::string const & name) const;
		/** @brief Like above, but return std::string. */
		std::string getAttribute(std::string const & name) const;
		void setAttribute(std::string const & name, std::string const & content);

		/** @name Xpath evaluation on this node. */
		/** @{ */
		NodeSet getNodeSet(std::string const & xpath) const;
		Node getNode(std::string const & xpath, bool const & doThrow=true) const;
		/** @} */

	private:
		Node copy() const;

	public:
		/** @brief Add a new (element) child node. */
		Node addChild(std::string const & name, std::string const & content="");
		/** @brief Add a (deep) copy of this node. */
		Node addChild(Node const & n);

		/** @brief Add a new (element) sibling node. */
		Node addSiblingAfter(std::string const & name, std::string const & content);
		/** @brief Add a (deep) copy of this node as a sibling */
		Node addSiblingAfter(Node const & n);

		/** @brief Dump shortcut. */
		std::string dump(bool const & format=false, std::string const & encoding=DefaultEncoding_) const;

		/** We cannot declare friend to a nested class ;(. */
		using UI::Util::auto_base<xmlNode>::get;
		using UI::Util::auto_base<xmlNode>::operator->;

	private:
		static xmlNode * const NullNode_;
	};

	/** @brief Vector of nodes with specialised support. */
	class NodeSet: public std::vector<Node>
	{
	public:
		/** @name Construct/add from various input types.
		 *
		 * When "childs" is true, the resp. node's childs will be used.
		 *
		 * @{ */
		NodeSet(XPathObject const & xPathObject);
		NodeSet(Node const & node=Node(0), bool const & childs=false, std::string const & name="");
		NodeSet(NodeSet const & ns, bool const & childs=false, std::string const & name="");
		void add(Node const & node, bool const & childs=false, std::string const & name="");
		void add(NodeSet const & ns, bool const & childs=false, std::string const & name="");
		/** @} */

		/** @brief Unlink all nodes.
		 *
		 * @see Node::unlink().
		 */
		void unlink();
		/** @brief Get a nodeset with all childs of this set. */
		NodeSet getChilds(std::string const & name="") const;
		/** @brief Get nodeset content.
		 *
		 * Concatenates all contents of all nodes. @see Node's getContent().
		 */
		std::string getContent(std::string const & nodeSeparator="") const;

		/** @brief NodeSet serializer. */
		class Dump
		{
		public:
			/** @brief Default constructor, creates an empty dump. */
			Dump();

			/** @brief Append a free text to the dump. */
			int concat(std::string const & text);

			/** @brief Append a node to the dump. */
			int concat(Node const & node,
			            bool const & format=false, std::string const & encoding=DefaultEncoding_);

			/** @brief Construct from node. */
			Dump(Node const & node,
			     bool const & format=false, std::string const & encoding=DefaultEncoding_);

			/** @brief Append a node set to the dump. */
			int concat(NodeSet const & nodeSet,
			            bool const & format=false, std::string const & encoding=DefaultEncoding_);

			/** @brief Construct from node set. */
			Dump(NodeSet const & ns,
			     bool const & format=false, std::string const & encoding=DefaultEncoding_);

			/** @name Get serialized content.
			 * @{ */
			char const * getC() const;
			std::string get() const;
			int getSize() const;
			/** @} */
		private:
			OutputBuffer outbuf_;
		};

		/** @brief Dump shortcut. */
		std::string dump(bool const & format=false, std::string const & encoding=DefaultEncoding_) const;
	};

	/** @brief Serializer for Tree. */
	class Dump
	{
	public:
		/** @brief Constructor. */
		Dump(Tree const & tree, bool const & format=false, std::string const & encoding=DefaultEncoding_);
		~Dump();
		/** @name Get serialized content.
		 * @{ */
		char const * getC() const;
		std::string get() const;
		int getSize() const;
		/** @} */
	private:
		xmlChar * buf_;
		int size_;
	};

	/** @brief Tree dump shortcut. @see Tree::Dump. */
	std::string dump(bool const & format=false, std::string const & encoding=DefaultEncoding_) const;

	/** @brief As dump, but will reparse the tree first.
	 *
	 * This may be useful to get formatting right if you modified the
	 * tree after the initial parsing.
	 */
	std::string formatDump(std::string const & encoding=DefaultEncoding_) const;

	/** @name Get objects from xpaths.
	 * @{ */
	XPathObject getXPathObject(std::string const & xpath) const;
	NodeSet getNodeSet(std::string const & xpath) const;
	Node getNode(std::string const & xpath, bool const & doThrow=true) const;
	/** @brief Shortcut to get the root node. */
	Node getRootNode() const;
	/** @} */

	/** @brief Get original document's encoding. */
	std::string getOrigEncoding() const;

	/** @name Get number of nodes from xpath (or count > 0 for exists).
	 * @{ */
	int getCount(std::string const & xpath) const;
	bool exists(std::string const & xpath, int const times=1) const;
	/** @} */

	/** @name Set/get content of node(s) (i.e., of first child text node).
	 * @{ */
	char const * getContentC(std::string const & xpath) const;
	std::string getContent(std::string const & xpath) const;
	void setContent(std::string const & xpath, std::string const & content);
	/** @} */

	/** @brief Set name of node(s). */
	void setName(std::string const & xpath, std::string const & name);

	/** @name Set/get attributes of nodes.
	 * @{ */
	void setAttribute(std::string const & xpath, std::string const & name, std::string const & content);
	std::string getAttribute(std::string const & xpath, std::string const & name) const;
	/** @} */

	/** @name Add a new (element) child node.
	 * @{ */
	Node addChild(std::string const & xpath, std::string const & name, std::string const & content="");
	Node addChild(std::string const & xpath, Node const & node);
	/** @} */

	/** @brief Remove node(s) from tree. */
	void unlink(std::string const & xpath);

	/** @name Add an XML segment (serialized, or as tree).
	 * @{ */
	void addTree(std::string const & xpath, Tree const & source, std::string const & sourceXPath="/*");
	void addXML(std::string const & xpath, std::string const & source, std::string const & sourceXPath="/*");
	/** @} */

protected:
	using UI::Util::auto_base<xmlDoc>::get;
	using UI::Util::auto_base<xmlDoc>::operator->;

private:
	/** @brief Internal helper to switch between user-given or temporary context. */
	class XPathContextSwitch
	{
	public:
		XPathContextSwitch(Tree const & tree, NamespaceMap const & namespaces);
		~XPathContextSwitch();
		XPathContext & get();
	private:
		XPathContext * treeContext_;
		XPathContext * tempContext_;
	};

	bool dontFreeDoc_;
	XPathContext * context_;
};

}}
#endif
