#include "emitterstate.h"
#include "yaml-cpp/exceptions.h"
#include <limits>

namespace YAML
{
	EmitterState::EmitterState(): m_isGood(true), m_curIndent(0), m_requiresSoftSeparation(false), m_requiresHardSeparation(false)
	{
		// start up
		m_stateStack.push(ES_WAITING_FOR_DOC);
		
		// set default global manipulators
		m_charset.set(EmitNonAscii);
		m_strFmt.set(Auto);
		m_boolFmt.set(TrueFalseBool);
		m_boolLengthFmt.set(LongBool);
		m_boolCaseFmt.set(LowerCase);
		m_intFmt.set(Dec);
		m_indent.set(2);
		m_preCommentIndent.set(2);
		m_postCommentIndent.set(1);
		m_seqFmt.set(Block);
		m_mapFmt.set(Block);
		m_mapKeyFmt.set(Auto);
        m_floatPrecision.set(6);
        m_doublePrecision.set(15);
	}
	
	EmitterState::~EmitterState()
	{
	}

	// SetLocalValue
	// . We blindly tries to set all possible formatters to this value
	// . Only the ones that make sense will be accepted
	void EmitterState::SetLocalValue(EMITTER_MANIP value)
	{
		SetOutputCharset(value, LOCAL);
		SetStringFormat(value, LOCAL);
		SetBoolFormat(value, LOCAL);
		SetBoolCaseFormat(value, LOCAL);
		SetBoolLengthFormat(value, LOCAL);
		SetIntFormat(value, LOCAL);
		SetFlowType(GT_SEQ, value, LOCAL);
		SetFlowType(GT_MAP, value, LOCAL);
		SetMapKeyFormat(value, LOCAL);
	}
	
	void EmitterState::BeginGroup(GROUP_TYPE type)
	{
		unsigned lastIndent = (m_groups.empty() ? 0 : m_groups.top().indent);
		m_curIndent += lastIndent;
		
		std::auto_ptr<Group> pGroup(new Group(type));
		
		// transfer settings (which last until this group is done)
		pGroup->modifiedSettings = m_modifiedSettings;

		// set up group
		pGroup->flow = GetFlowType(type);
		pGroup->indent = GetIndent();
		pGroup->usingLongKey = (GetMapKeyFormat() == LongKey ? true : false);

		m_groups.push(pGroup);
	}
	
	void EmitterState::EndGroup(GROUP_TYPE type)
	{
		if(m_groups.empty())
			return SetError(ErrorMsg::UNMATCHED_GROUP_TAG);
		
		// get rid of the current group
		{
			std::auto_ptr<Group> pFinishedGroup = m_groups.pop();
			if(pFinishedGroup->type != type)
				return SetError(ErrorMsg::UNMATCHED_GROUP_TAG);
		}

		// reset old settings
		unsigned lastIndent = (m_groups.empty() ? 0 : m_groups.top().indent);
		assert(m_curIndent >= lastIndent);
		m_curIndent -= lastIndent;
		
		// some global settings that we changed may have been overridden
		// by a local setting we just popped, so we need to restore them
		m_globalModifiedSettings.restore();
	}
		
	GROUP_TYPE EmitterState::GetCurGroupType() const
	{
		if(m_groups.empty())
			return GT_NONE;
		
		return m_groups.top().type;
	}
	
	FLOW_TYPE EmitterState::GetCurGroupFlowType() const
	{
		if(m_groups.empty())
			return FT_NONE;
		
		return (m_groups.top().flow == Flow ? FT_FLOW : FT_BLOCK);
	}
	
	bool EmitterState::CurrentlyInLongKey()
	{
		if(m_groups.empty())
			return false;
		return m_groups.top().usingLongKey;
	}
	
	void EmitterState::StartLongKey()
	{
		if(!m_groups.empty())
			m_groups.top().usingLongKey = true;
	}
	
	void EmitterState::StartSimpleKey()
	{
		if(!m_groups.empty())
			m_groups.top().usingLongKey = false;
	}

	void EmitterState::ClearModifiedSettings()
	{
		m_modifiedSettings.clear();
	}

	bool EmitterState::SetOutputCharset(EMITTER_MANIP value, FMT_SCOPE scope)
	{
		switch(value) {
			case EmitNonAscii:
			case EscapeNonAscii:
				_Set(m_charset, value, scope);
				return true;
			default:
				return false;
		}
	}
	
	bool EmitterState::SetStringFormat(EMITTER_MANIP value, FMT_SCOPE scope)
	{
		switch(value) {
			case Auto:
			case SingleQuoted:
			case DoubleQuoted:
			case Literal:
				_Set(m_strFmt, value, scope);
				return true;
			default:
				return false;
		}
	}
	
	bool EmitterState::SetBoolFormat(EMITTER_MANIP value, FMT_SCOPE scope)
	{
		switch(value) {
			case OnOffBool:
			case TrueFalseBool:
			case YesNoBool:
				_Set(m_boolFmt, value, scope);
				return true;
			default:
				return false;
		}
	}

	bool EmitterState::SetBoolLengthFormat(EMITTER_MANIP value, FMT_SCOPE scope)
	{
		switch(value) {
			case LongBool:
			case ShortBool:
				_Set(m_boolLengthFmt, value, scope);
				return true;
			default:
				return false;
		}
	}

	bool EmitterState::SetBoolCaseFormat(EMITTER_MANIP value, FMT_SCOPE scope)
	{
		switch(value) {
			case UpperCase:
			case LowerCase:
			case CamelCase:
				_Set(m_boolCaseFmt, value, scope);
				return true;
			default:
				return false;
		}
	}

	bool EmitterState::SetIntFormat(EMITTER_MANIP value, FMT_SCOPE scope)
	{
		switch(value) {
			case Dec:
			case Hex:
			case Oct:
				_Set(m_intFmt, value, scope);
				return true;
			default:
				return false;
		}
	}

	bool EmitterState::SetIndent(unsigned value, FMT_SCOPE scope)
	{
		if(value == 0)
			return false;
		
		_Set(m_indent, value, scope);
		return true;
	}

	bool EmitterState::SetPreCommentIndent(unsigned value, FMT_SCOPE scope)
	{
		if(value == 0)
			return false;
		
		_Set(m_preCommentIndent, value, scope);
		return true;
	}
	
	bool EmitterState::SetPostCommentIndent(unsigned value, FMT_SCOPE scope)
	{
		if(value == 0)
			return false;
		
		_Set(m_postCommentIndent, value, scope);
		return true;
	}

	bool EmitterState::SetFlowType(GROUP_TYPE groupType, EMITTER_MANIP value, FMT_SCOPE scope)
	{
		switch(value) {
			case Block:
			case Flow:
				_Set(groupType == GT_SEQ ? m_seqFmt : m_mapFmt, value, scope);
				return true;
			default:
				return false;
		}
	}

	EMITTER_MANIP EmitterState::GetFlowType(GROUP_TYPE groupType) const
	{
		// force flow style if we're currently in a flow
		FLOW_TYPE flowType = GetCurGroupFlowType();
		if(flowType == FT_FLOW)
			return Flow;
		
		// otherwise, go with what's asked of use
		return (groupType == GT_SEQ ? m_seqFmt.get() : m_mapFmt.get());
	}
	
	bool EmitterState::SetMapKeyFormat(EMITTER_MANIP value, FMT_SCOPE scope)
	{
		switch(value) {
			case Auto:
			case LongKey:
				_Set(m_mapKeyFmt, value, scope);
				return true;
			default:
				return false;
		}
	}

    bool EmitterState::SetFloatPrecision(int value, FMT_SCOPE scope)
    {
        if(value < 0 || value > std::numeric_limits<float>::digits10)
            return false;
        _Set(m_floatPrecision, value, scope);
        return true;
    }
    
    bool EmitterState::SetDoublePrecision(int value, FMT_SCOPE scope)
    {
        if(value < 0 || value > std::numeric_limits<double>::digits10)
            return false;
        _Set(m_doublePrecision, value, scope);
        return true;
    }
}