#include "singledocparser.h"
#include "collectionstack.h"
#include "directives.h"
#include "yaml-cpp-pm/eventhandler.h"
#include "yaml-cpp-pm/exceptions.h"
#include "scanner.h"
#include "tag.h"
#include "token.h"
#include <sstream>
#include <cstdio>
#include <algorithm>

namespace YAML_PM
{
	SingleDocParser::SingleDocParser(Scanner& scanner, const Directives& directives): m_scanner(scanner), m_directives(directives), m_pCollectionStack(new CollectionStack), m_curAnchor(0)
	{
	}

	SingleDocParser::~SingleDocParser()
	{
	}

	// HandleDocument
	// . Handles the next document
	// . Throws a ParserException on error.
	void SingleDocParser::HandleDocument(EventHandler& eventHandler)
	{
		assert(!m_scanner.empty()); // guaranteed that there are tokens
		assert(!m_curAnchor);

		eventHandler.OnDocumentStart(m_scanner.peek().mark);
		
		// eat doc start
		if(m_scanner.peek().type == Token::DOC_START)
			m_scanner.pop();
		
		// recurse!
		HandleNode(eventHandler);
		
		eventHandler.OnDocumentEnd();
		
		// and finally eat any doc ends we see
		while(!m_scanner.empty() && m_scanner.peek().type == Token::DOC_END)
			m_scanner.pop();
	}
	
	void SingleDocParser::HandleNode(EventHandler& eventHandler)
	{
		// an empty node *is* a possibility
		if(m_scanner.empty()) {
			eventHandler.OnNull(Mark::null(), NullAnchor);
			return;
		}
		
		// save location
		Mark mark = m_scanner.peek().mark;
		
		// special case: a value node by itself must be a map, with no header
		if(m_scanner.peek().type == Token::VALUE) {
			eventHandler.OnMapStart(mark, "", NullAnchor);
			HandleMap(eventHandler);
			eventHandler.OnMapEnd();
			return;
		}
		
		// special case: an alias node
		if(m_scanner.peek().type == Token::ALIAS) {
			eventHandler.OnAlias(mark, LookupAnchor(mark, m_scanner.peek().value));
			m_scanner.pop();
			return;
		}
		
		std::string tag;
		anchor_t anchor;
		ParseProperties(tag, anchor);
		
		const Token& token = m_scanner.peek();
		
		// add non-specific tags
		if(tag.empty())
			tag = (token.type == Token::NON_PLAIN_SCALAR ? "!" : "?");
		
		// now split based on what kind of node we should be
		switch(token.type) {
			case Token::PLAIN_SCALAR:
			case Token::NON_PLAIN_SCALAR:
				eventHandler.OnScalar(mark, tag, anchor, token.value);
				m_scanner.pop();
				return;
			case Token::FLOW_SEQ_START:
			case Token::BLOCK_SEQ_START:
				eventHandler.OnSequenceStart(mark, tag, anchor);
				HandleSequence(eventHandler);
				eventHandler.OnSequenceEnd();
				return;
			case Token::FLOW_MAP_START:
			case Token::BLOCK_MAP_START:
				eventHandler.OnMapStart(mark, tag, anchor);
				HandleMap(eventHandler);
				eventHandler.OnMapEnd();
				return;
			case Token::KEY:
				// compact maps can only go in a flow sequence
				if(m_pCollectionStack->GetCurCollectionType() == CollectionType::FlowSeq) {
					eventHandler.OnMapStart(mark, tag, anchor);
					HandleMap(eventHandler);
					eventHandler.OnMapEnd();
					return;
				}
				break;
			default:
				break;
		}
		
		if(tag == "?")
			eventHandler.OnNull(mark, anchor);
		else
			eventHandler.OnScalar(mark, tag, anchor, "");
	}
	
	void SingleDocParser::HandleSequence(EventHandler& eventHandler)
	{
		// split based on start token
		switch(m_scanner.peek().type) {
			case Token::BLOCK_SEQ_START: HandleBlockSequence(eventHandler); break;
			case Token::FLOW_SEQ_START: HandleFlowSequence(eventHandler); break;
			default: break;
		}
	}
	
	void SingleDocParser::HandleBlockSequence(EventHandler& eventHandler)
	{
		// eat start token
		m_scanner.pop();
		m_pCollectionStack->PushCollectionType(CollectionType::BlockSeq);
		
		while(1) {
			if(m_scanner.empty())
				throw ParserException(Mark::null(), ErrorMsg::END_OF_SEQ);
			
			Token token = m_scanner.peek();
			if(token.type != Token::BLOCK_ENTRY && token.type != Token::BLOCK_SEQ_END)
				throw ParserException(token.mark, ErrorMsg::END_OF_SEQ);
			
			m_scanner.pop();
			if(token.type == Token::BLOCK_SEQ_END)
				break;
			
			// check for null
			if(!m_scanner.empty()) {
				const Token& token = m_scanner.peek();
				if(token.type == Token::BLOCK_ENTRY || token.type == Token::BLOCK_SEQ_END) {
					eventHandler.OnNull(token.mark, NullAnchor);
					continue;
				}
			}
			
			HandleNode(eventHandler);
		}
		
		m_pCollectionStack->PopCollectionType(CollectionType::BlockSeq);
	}
	
	void SingleDocParser::HandleFlowSequence(EventHandler& eventHandler)
	{
		// eat start token
		m_scanner.pop();
		m_pCollectionStack->PushCollectionType(CollectionType::FlowSeq);
		
		while(1) {
			if(m_scanner.empty())
				throw ParserException(Mark::null(), ErrorMsg::END_OF_SEQ_FLOW);
			
			// first check for end
			if(m_scanner.peek().type == Token::FLOW_SEQ_END) {
				m_scanner.pop();
				break;
			}
			
			// then read the node
			HandleNode(eventHandler);
			
			// now eat the separator (or could be a sequence end, which we ignore - but if it's neither, then it's a bad node)
			Token& token = m_scanner.peek();
			if(token.type == Token::FLOW_ENTRY)
				m_scanner.pop();
			else if(token.type != Token::FLOW_SEQ_END)
				throw ParserException(token.mark, ErrorMsg::END_OF_SEQ_FLOW);
		}
		
		m_pCollectionStack->PopCollectionType(CollectionType::FlowSeq);
	}
	
	void SingleDocParser::HandleMap(EventHandler& eventHandler)
	{
		// split based on start token
		switch(m_scanner.peek().type) {
			case Token::BLOCK_MAP_START: HandleBlockMap(eventHandler); break;
			case Token::FLOW_MAP_START: HandleFlowMap(eventHandler); break;
			case Token::KEY: HandleCompactMap(eventHandler); break;
			case Token::VALUE: HandleCompactMapWithNoKey(eventHandler); break;
			default: break;
		}
	}
	
	void SingleDocParser::HandleBlockMap(EventHandler& eventHandler)
	{
		// eat start token
		m_scanner.pop();
		m_pCollectionStack->PushCollectionType(CollectionType::BlockMap);
		
		while(1) {
			if(m_scanner.empty())
				throw ParserException(Mark::null(), ErrorMsg::END_OF_MAP);
			
			Token token = m_scanner.peek();
			if(token.type != Token::KEY && token.type != Token::VALUE && token.type != Token::BLOCK_MAP_END)
				throw ParserException(token.mark, ErrorMsg::END_OF_MAP);
			
			if(token.type == Token::BLOCK_MAP_END) {
				m_scanner.pop();
				break;
			}
			
			// grab key (if non-null)
			if(token.type == Token::KEY) {
				m_scanner.pop();
				HandleNode(eventHandler);
			} else {
				eventHandler.OnNull(token.mark, NullAnchor);
			}
			
			// now grab value (optional)
			if(!m_scanner.empty() && m_scanner.peek().type == Token::VALUE) {
				m_scanner.pop();
				HandleNode(eventHandler);
			} else {
				eventHandler.OnNull(token.mark, NullAnchor);
			}
		}
		
		m_pCollectionStack->PopCollectionType(CollectionType::BlockMap);
	}
	
	void SingleDocParser::HandleFlowMap(EventHandler& eventHandler)
	{
		// eat start token
		m_scanner.pop();
		m_pCollectionStack->PushCollectionType(CollectionType::FlowMap);
		
		while(1) {
			if(m_scanner.empty())
				throw ParserException(Mark::null(), ErrorMsg::END_OF_MAP_FLOW);
			
			Token& token = m_scanner.peek();
			// first check for end
			if(token.type == Token::FLOW_MAP_END) {
				m_scanner.pop();
				break;
			}
			
			// grab key (if non-null)
			if(token.type == Token::KEY) {
				m_scanner.pop();
				HandleNode(eventHandler);
			} else {
				eventHandler.OnNull(token.mark, NullAnchor);
			}
			
			// now grab value (optional)
			if(!m_scanner.empty() && m_scanner.peek().type == Token::VALUE) {
				m_scanner.pop();
				HandleNode(eventHandler);
			} else {
				eventHandler.OnNull(token.mark, NullAnchor);
			}
			
			// now eat the separator (or could be a map end, which we ignore - but if it's neither, then it's a bad node)
			Token& nextToken = m_scanner.peek();
			if(nextToken.type == Token::FLOW_ENTRY)
				m_scanner.pop();
			else if(nextToken.type != Token::FLOW_MAP_END)
				throw ParserException(nextToken.mark, ErrorMsg::END_OF_MAP_FLOW);
		}
		
		m_pCollectionStack->PopCollectionType(CollectionType::FlowMap);
	}
	
	// . Single "key: value" pair in a flow sequence
	void SingleDocParser::HandleCompactMap(EventHandler& eventHandler)
	{
		m_pCollectionStack->PushCollectionType(CollectionType::CompactMap);
		
		// grab key
		Mark mark = m_scanner.peek().mark;
		m_scanner.pop();
		HandleNode(eventHandler);
		
		// now grab value (optional)
		if(!m_scanner.empty() && m_scanner.peek().type == Token::VALUE) {
			m_scanner.pop();
			HandleNode(eventHandler);
		} else {
			eventHandler.OnNull(mark, NullAnchor);
		}
		
		m_pCollectionStack->PopCollectionType(CollectionType::CompactMap);
	}
	
	// . Single ": value" pair in a flow sequence
	void SingleDocParser::HandleCompactMapWithNoKey(EventHandler& eventHandler)
	{
		m_pCollectionStack->PushCollectionType(CollectionType::CompactMap);
		
		// null key
		eventHandler.OnNull(m_scanner.peek().mark, NullAnchor);
		
		// grab value
		m_scanner.pop();
		HandleNode(eventHandler);
		
		m_pCollectionStack->PopCollectionType(CollectionType::CompactMap);
	}
	
	// ParseProperties
	// . Grabs any tag or anchor tokens and deals with them.
	void SingleDocParser::ParseProperties(std::string& tag, anchor_t& anchor)
	{
		tag.clear();
		anchor = NullAnchor;
		
		while(1) {
			if(m_scanner.empty())
				return;
			
			switch(m_scanner.peek().type) {
				case Token::TAG: ParseTag(tag); break;
				case Token::ANCHOR: ParseAnchor(anchor); break;
				default: return;
			}
		}
	}
	
	void SingleDocParser::ParseTag(std::string& tag)
	{
		Token& token = m_scanner.peek();
		if(!tag.empty())
			throw ParserException(token.mark, ErrorMsg::MULTIPLE_TAGS);
		
		Tag tagInfo(token);
		tag = tagInfo.Translate(m_directives);
		m_scanner.pop();
	}
	
	void SingleDocParser::ParseAnchor(anchor_t& anchor)
	{
		Token& token = m_scanner.peek();
		if(anchor)
			throw ParserException(token.mark, ErrorMsg::MULTIPLE_ANCHORS);
		
		anchor = RegisterAnchor(token.value);
		m_scanner.pop();
	}
	
	anchor_t SingleDocParser::RegisterAnchor(const std::string& name)
	{
		if(name.empty())
			return NullAnchor;
		
		return m_anchors[name] = ++m_curAnchor;
	}
	
	anchor_t SingleDocParser::LookupAnchor(const Mark& mark, const std::string& name) const
	{
		Anchors::const_iterator it = m_anchors.find(name);
		if(it == m_anchors.end())
			throw ParserException(mark, ErrorMsg::UNKNOWN_ANCHOR);
		
		return it->second;
	}
}
