#include "yaml-cpp/node/detail/node_data.h"
#include "yaml-cpp/node/detail/memory.h"
#include "yaml-cpp/node/detail/node.h"
#include "yaml-cpp/exceptions.h"
#include <sstream>

namespace YAML
{
	namespace detail
	{
		std::string node_data::empty_scalar;

		node_data::node_data(): m_isDefined(false), m_type(NodeType::Null), m_seqSize(0)
		{
		}

		void node_data::mark_defined()
		{
			if(m_type == NodeType::Undefined)
				m_type = NodeType::Null;
			m_isDefined = true;
		}

		void node_data::set_type(NodeType::value type)
		{
			if(type == NodeType::Undefined) {
				m_type = type;
				m_isDefined = false;
				return;
			}


			m_isDefined = true;
			if(type == m_type)
				return;

			m_type = type;

			switch(m_type) {
				case NodeType::Null:
					break;
				case NodeType::Scalar:
					m_scalar.clear();
					break;
				case NodeType::Sequence:
					reset_sequence();
					break;
				case NodeType::Map:
					reset_map();
					break;
				case NodeType::Undefined:
					assert(false);
					break;
			}
		}

		void node_data::set_tag(const std::string& tag)
		{
			m_tag = tag;
		}

		void node_data::set_null()
		{
			m_isDefined = true;
			m_type = NodeType::Null;
		}

		void node_data::set_scalar(const std::string& scalar)
		{
			m_isDefined = true;
			m_type = NodeType::Scalar;
			m_scalar = scalar;
		}

		// size/iterator
		std::size_t node_data::size() const
		{
			if(!m_isDefined)
				return 0;

			switch(m_type) {
				case NodeType::Sequence: compute_seq_size(); return m_seqSize;
				case NodeType::Map: compute_map_size(); return m_map.size() - m_undefinedPairs.size();
				default:
					return 0;
			}
			return 0;
		}

		void node_data::compute_seq_size() const
		{
			while(m_seqSize < m_sequence.size() && m_sequence[m_seqSize]->is_defined())
				m_seqSize++;
		}

		void node_data::compute_map_size() const
		{
			kv_pairs::iterator it = m_undefinedPairs.begin();
			while(it != m_undefinedPairs.end()) {
				kv_pairs::iterator jt = boost::next(it);
				if(it->first->is_defined() && it->second->is_defined())
					m_undefinedPairs.erase(it);
				it = jt;
			}
		}

		const_node_iterator node_data::begin() const
		{
			if(!m_isDefined)
				return const_node_iterator();

			switch(m_type) {
				case NodeType::Sequence: return const_node_iterator(m_sequence.begin());
				case NodeType::Map: return const_node_iterator(m_map.begin(), m_map.end());
				default: return const_node_iterator();
			}
		}

		node_iterator node_data::begin()
		{
			if(!m_isDefined)
				return node_iterator();

			switch(m_type) {
				case NodeType::Sequence: return node_iterator(m_sequence.begin());
				case NodeType::Map: return node_iterator(m_map.begin(), m_map.end());
				default: return node_iterator();
			}
		}

		const_node_iterator node_data::end() const
		{
			if(!m_isDefined)
				return const_node_iterator();

			switch(m_type) {
				case NodeType::Sequence: return const_node_iterator(m_sequence.end());
				case NodeType::Map: return const_node_iterator(m_map.end(), m_map.end());
				default: return const_node_iterator();
			}
		}

		node_iterator node_data::end()
		{
			if(!m_isDefined)
				return node_iterator();

			switch(m_type) {
				case NodeType::Sequence: return node_iterator(m_sequence.end());
				case NodeType::Map: return node_iterator(m_map.end(), m_map.end());
				default: return node_iterator();
			}
		}

		// sequence
		void node_data::push_back(node& node, shared_memory_holder /* pMemory */)
		{
			if(m_type == NodeType::Undefined || m_type == NodeType::Null) {
				m_type = NodeType::Sequence;
				reset_sequence();
			}

			if(m_type != NodeType::Sequence)
                throw BadPushback();

			m_sequence.push_back(&node);
		}

		void node_data::insert(node& key, node& value, shared_memory_holder pMemory)
		{
			switch(m_type) {
				case NodeType::Map:
					break;
				case NodeType::Undefined:
				case NodeType::Null:
				case NodeType::Sequence:
					convert_to_map(pMemory);
					break;
				case NodeType::Scalar:
                    throw BadSubscript();
			}

			insert_map_pair(key, value);
		}

		// indexing
		node& node_data::get(node& key, shared_memory_holder pMemory) const
		{
			if(m_type != NodeType::Map)
				return pMemory->create_node(Mark::null_mark());

			for(node_map::const_iterator it=m_map.begin();it!=m_map.end();++it) {
				if(it->first->is(key))
					return *it->second;
			}

			return pMemory->create_node(Mark::null_mark());
		}

		node& node_data::get(node& key, shared_memory_holder pMemory)
		{
			switch(m_type) {
				case NodeType::Map:
					break;
				case NodeType::Undefined:
				case NodeType::Null:
				case NodeType::Sequence:
					convert_to_map(pMemory);
					break;
				case NodeType::Scalar:
                    throw BadSubscript();
			}

			for(node_map::const_iterator it=m_map.begin();it!=m_map.end();++it) {
				if(it->first->is(key))
					return *it->second;
			}

			node& value = pMemory->create_node(Mark::null_mark());
			insert_map_pair(key, value);
			return value;
		}

		bool node_data::remove(node& key, shared_memory_holder /* pMemory */)
		{
			if(m_type != NodeType::Map)
				return false;

			for(node_map::iterator it=m_map.begin();it!=m_map.end();++it) {
				if(it->first->is(key)) {
					m_map.erase(it);
					return true;
				}
			}

			return false;
		}

		void node_data::reset_sequence()
		{
			m_sequence.clear();
			m_seqSize = 0;
		}

		void node_data::reset_map()
		{
			m_map.clear();
			m_undefinedPairs.clear();
		}

		void node_data::insert_map_pair(node& key, node& value)
		{
			m_map[&key] = &value;
			if(!key.is_defined() || !value.is_defined())
				m_undefinedPairs.push_back(kv_pair(&key, &value));
		}

		void node_data::convert_to_map(shared_memory_holder pMemory)
		{
			switch(m_type) {
				case NodeType::Undefined:
				case NodeType::Null:
					reset_map();
					m_type = NodeType::Map;
					break;
				case NodeType::Sequence:
					convert_sequence_to_map(pMemory);
					break;
				case NodeType::Map:
					break;
				case NodeType::Scalar:
					assert(false);
					break;
			}
		}

		void node_data::convert_sequence_to_map(shared_memory_holder pMemory)
		{
			assert(m_type == NodeType::Sequence);

			reset_map();
			for(std::size_t i=0;i<m_sequence.size();i++) {
				std::stringstream stream;
				stream << i;

				node& key = pMemory->create_node(m_sequence[i]->mark());
				key.set_scalar(stream.str());
				insert_map_pair(key, *m_sequence[i]);
			}

			reset_sequence();
			m_type = NodeType::Map;
		}
	}
}
