#ifndef INCLUDED_UTIL_CONFIG_H
#define INCLUDED_UTIL_CONFIG_H

#include "Util/GenericValue.h"
#include <map>
#include <memory>
#include <iterator>
#include <exception>

namespace Util
{
  namespace Config
  {
    class Node
    {
    private:
      typedef std::shared_ptr<Node> ptr_t;
      typedef std::shared_ptr<const Node> const_ptr_t;
      const std::string m_key;                // this cannot be changed (maps depend on it)
      std::shared_ptr<GenericValue> m_value;  // null pointer marks this node as an invalid, empty node
      ptr_t m_next_sibling;
      ptr_t m_first_child;
      ptr_t m_last_child;
      std::map<std::string, ptr_t> m_children;
      mutable std::map<std::string, Node> m_missing_nodes;  // missing nodes from failed queries (must also be empty)
      bool m_missing = false;

      void Destroy()
      {
        m_value.reset();
        m_next_sibling.reset();
        m_first_child.reset();
        m_last_child.reset();
        m_children.clear();
      }

      void CheckEmptyOrMissing() const;
      const Node &MissingNode(const std::string &key) const;
      void AddChild(Node &parent, ptr_t &node);
      void DeepCopy(const Node &that);
      void Swap(Node &rhs);
      Node(); // prohibit accidental/unintentional creation of empty nodes

    public:
      class const_iterator;

      class iterator: public std::iterator<std::forward_iterator_tag, Node>
      {
      private:
        ptr_t m_node;
        friend class const_iterator;    
      public:
        inline iterator(ptr_t node = ptr_t())
          : m_node(node)
        {}
        inline iterator(const iterator &it)
          : m_node(it.m_node)
        {}
        inline iterator operator++()
        {
          // Prefix increment
          m_node = m_node->m_next_sibling;
          return *this;
        }
        inline iterator operator++(int)
        {
          // Postfix increment
          iterator current(*this);
          m_node = m_node->m_next_sibling;
          return *this;
        }
        inline Node &operator*() const
        {
          return *m_node;
        }
        inline ptr_t operator->() const
        {
          return m_node;
        }
        inline bool operator==(const iterator &rhs) const
        {
          return m_node == rhs.m_node;
        }
        inline bool operator!=(const iterator &rhs) const
        {
          return m_node != rhs.m_node;
        }
      };

      class const_iterator: public std::iterator<std::forward_iterator_tag, const Node>
      {
      private:
        const_ptr_t m_node;
      public:
        inline const_iterator()
		  : m_node(const_ptr_t())
        {}
        inline const_iterator(const_ptr_t node)
          : m_node(node)
        {}
        inline const_iterator(ptr_t node)
          : m_node(std::const_pointer_cast<const Node>(node))
        {}
        inline const_iterator(const const_iterator &it)
          : m_node(it.m_node)
        {}
        inline const_iterator(const Node::iterator &it)
          : m_node(it.m_node)
        {}
        inline const_iterator operator++()
        {
          // Prefix increment
          m_node = m_node->m_next_sibling;
          return *this;
        }
        inline const_iterator operator++(int)
        {
          // Postfix increment
          //iterator current(*this);	//unreferenced local variable
          m_node = m_node->m_next_sibling;
          return *this;
        }
        inline const Node &operator*() const
        {
          return *m_node;
        }
        inline const_ptr_t operator->() const
        {
          return m_node;
        }
        inline bool operator==(const const_iterator &rhs) const
        {
          return m_node == rhs.m_node;
        }
        inline bool operator!=(const const_iterator &rhs) const
        {
          return m_node != rhs.m_node;
        }
      };

      inline iterator begin()
      {
        return iterator(m_first_child);
      }

      inline iterator end()
      {
        return iterator();
      }
      
      inline const_iterator begin() const
      {
        return iterator(m_first_child);
      }

      inline const_iterator end() const
      {
        return iterator();
      }

      const inline std::string &Key() const
      {
        return m_key;
      }

      inline std::shared_ptr<GenericValue> GetValue() const
      {
        return m_value;
      }

      inline void SetValue(const std::shared_ptr<GenericValue> &value)
      {
        m_value = value;
      }

      template <typename T>
      const T &Value() const
      {
        CheckEmptyOrMissing();
        return m_value->Value<T>();
      }

      template <typename T>
      T ValueAs() const
      {
        CheckEmptyOrMissing();
        return m_value->ValueAs<T>();
      }

      template <typename T>
      T ValueAsDefault(const T &default_value) const
      {
        if (Empty())
          return default_value;
        return m_value->ValueAs<T>();
      }

      // Set value of this node
      template <typename T>
      inline void SetValue(const T &value)
      {
        if (!m_missing)
        {
          if (!Empty() && m_value->Is<T>())
            m_value->Set(value);
          else
            m_value = std::make_shared<ValueInstance<T>>(value);
        }
        else
          throw std::range_error(Util::Format() << "Node \"" << m_key << "\" does not exist");
      }

      // char* is a troublesome case because we want to convert it to std::string
      inline void SetValue(const char *value)
      {
        SetValue<std::string>(value);
      }

      // Add a child node. Multiple nodes of the same key may be added but only
      // when specified as leaves. For example, adding "foo/bar" twice will 
      // result in one "foo" with two "bar" children.
      template <typename T>
      Node &Add(const std::string &path, const T &value)
      {
        std::vector<std::string> keys = Util::Format(path).Split('/');
        Node *parent = this;
        ptr_t node;
        for (size_t i = 0; i < keys.size(); i++)
        {
          bool leaf = i == keys.size() - 1;
          auto it = parent->m_children.find(keys[i]);
          if (leaf || it == parent->m_children.end())
          {
            // Create node at this level
            node = std::make_shared<Node>(keys[i]);
            // The leaf node gets the value
            if (leaf)
              node->SetValue(value);
            // Attach node to parent and move down to next nesting level: last
            // created node is new parent
            AddChild(*parent, node);
            parent = node.get();
          }
          else
          {
            // Descend deeper...
            parent = it->second.get();
          }
        }
        return *node;
      }

      Node &Add(const std::string &path)
      {
        return Add(path, std::string());
      }

      // Set the value of the matching child node if it exists, else add new
      template <typename T>
      void Set(const std::string &key, const T &value)
      {
        Node *node = TryGet(key);
        if (node)
          node->SetValue(value);
        else
          Add(key, value);
      }

      // True if value is empty (does not exist)
      inline bool Empty() const
      {
        return !m_value;
      }
      
      // True if value exists (is not empty)
      inline bool Exists() const
      {
        return !Empty();
      }

      // True if no keys under this node
      inline bool IsLeaf() const
      {
        return m_children.empty();
      }

      // True if this node has keys
      inline bool HasChildren() const
      {
        return !IsLeaf();
      }

      // Always succeeds -- failed lookups permanently create an empty node.
      // Use with caution. Intended for hard-coded lookups.
      const Node &operator[](const std::string &path) const;
        
      // These throw if the node is missing
      Node &Get(const std::string &path);
      const Node &Get(const std::string &path) const;
        
      // This returns nullptr if node is missing
      Node *TryGet(const std::string &path);
      const Node *TryGet(const std::string &path) const;

      void Serialize(std::ostream *os, size_t indent_level = 0) const;
      std::string ToString(size_t indent_level = 0) const;
      Node &operator=(const Node &rhs);
      Node &operator=(Node &&rhs);
      Node(const std::string &key);
      Node(const std::string &key, const std::string &value);
      Node(const Node &that);
      Node(Node &&that);
      ~Node();
    };
  } // Config
} // Util

#endif // INCLUDED_UTIL_CONFIG_H