#include "XmlParser.h"
#include "StringUtils.h"
#include <iostream>

using LS = XmlParser::LineState;
using DS = XmlParser::DocumentState;

XmlParser::XmlParser()
    : mDocument(XmlDocument::Create()),
      mDocumentState(XmlParser::DocumentState::Await_Prolog),
      mLineState(XmlParser::LineState::Await_Tag_Open),
      mWorkingElements()
{

}

void XmlParser::ProcessLine(const std::string& input)
{
    for (std::size_t idx=0; idx<input.size(); idx++)
    {
        switch (input[idx])
        {
            case StringUtils::LEFT_BRACKET:
                OnLeftBracket();
                break;
            case StringUtils::RIGHT_BRACKET:
                OnRightBracket();
                break;
            case StringUtils::QUESTION_MARK:
                OnQuestionMark();
                break;
            case StringUtils::FORWARD_SLASH:
                OnForwardSlash();
                break;
            case StringUtils::EQUALS:
                OnEquals();
                break;
            case StringUtils::DOUBLE_QUOTE:
                OnDoubleQuote();
                break;
            default:
                OnChar(input[idx]);
                break;
        }
    }
}

void XmlParser::OnChar(char c)
{
    if(StringUtils::IsAlphaNumeric(c))
    {
        OnAlphaNumeric(c);
    }
    else if(StringUtils::IsSpace(c))
    {
        OnSpace(c);
    }
    else
    {
        OnNonAlphaNumeric(c);
    }
}

void XmlParser::OnAlphaNumeric(char c)
{
    switch(mLineState)
    {
        case LS::Await_Tag_Open:
            OnTextStart(c);
            break;
        case LS::Await_Tag_Follower:
        case LS::Await_Tag_Name:
        {
            OnTagNameStart(c);
            break;
        }
        case LS::Await_Tag_Name_End:
        {
            mWorkingTagName.push_back(c);
            break;
        }
        case LS::Await_Attribute_Name:
        {
            OnAttributeNameStart(c);
            break;
        }
        case LS::Await_Attribute_Name_End:
        {
            mWorkingAttributeName.push_back(c);
            break;
        }
        case LS::Await_Attribute_Value:
        {
            break;
        }
        case LS::Await_Attribute_Value_End:
        {
            mWorkingAttributeValue.push_back(c);
            break;
        }
        case LS::Await_Text_End:
        {
            mWorkingText.push_back(c);
            break;
        }
        default:
            break;
    }
}

void XmlParser::OnNonAlphaNumeric(char c)
{
    switch(mLineState)
    {
        case LS::Await_Tag_Name_End:
        {
            mWorkingTagName.push_back(c);
            break;
        }
        case LS::Await_Attribute_Name_End:
        {
            mWorkingAttributeName.push_back(c);
            break;
        }
        case LS::Await_Attribute_Value_End:
        {
            mWorkingAttributeValue.push_back(c);
            break;
        }
        case LS::Await_Text_End:
        {
            mWorkingText.push_back(c);
            break;
        }
        default:
            break;
    }
}

void XmlParser::OnSpace(char c)
{
    switch(mLineState)
    {
        case LS::Await_Tag_Name_End:
            OnTagNameEnd();
            break;
        case LS::Await_Attribute_Name_End:
            OnAttributeNameEnd();
            break;
        case LS::Await_Text_End:
            mWorkingText.push_back(c);
            break;
        case LS::Await_Attribute_Value_End:
            mWorkingAttributeValue.push_back(c);
            break;
        default:
            break;
    }
}

void XmlParser::OnLeftBracket()
{
    switch(mLineState)
    {
        case LS::Await_Tag_Open:
        {
            OnTagOpen();
            break;
        }
        case LS::Await_Text_End:
        {
            OnTextEnd();
            OnTagOpen();
            break;
        }
        default:
            OnNonAlphaNumeric(StringUtils::LEFT_BRACKET);
            break;
        }
}

void XmlParser::OnRightBracket()
{
    switch(mLineState)
    {
        case LS::Await_Tag_Name_End:
        case LS::Await_Attribute_Name:
        case LS::Await_Attribute_Name_End:
        case LS::Await_Attribute_Value:
        {
            OnTagClose();
            break;
        }
        default:
            OnNonAlphaNumeric(StringUtils::RIGHT_BRACKET);
            break;
        }
}

void XmlParser::OnQuestionMark()
{
    if(mLineState == LS::Await_Tag_Follower)
    {
        if(mDocumentState == DS::Await_Prolog)
        {
            OnStartProlog();
        }
    }
    else if(mDocumentState != DS::Build_Prolog)
    {
        OnNonAlphaNumeric(StringUtils::QUESTION_MARK);
    }
}

void XmlParser::OnForwardSlash()
{
    if(mLineState == LS::Await_Tag_Follower)
    {
        if(mDocumentState == DS::Await_Element || mDocumentState == DS::Build_Element)
        {
            mDocumentState = DS::Close_Element;
        }
    }
}

void XmlParser::OnEquals()
{
    if(mLineState == LS::Await_Attribute_Name_End)
    {
        OnAttributeNameEnd();
    }
    else
    {
        OnNonAlphaNumeric(StringUtils::EQUALS);
    }
}

void XmlParser::OnDoubleQuote()
{
    if(mLineState == LS::Await_Attribute_Value)
    {
        OnAttributeValueStart();
    }
    else if(mLineState == LS::Await_Attribute_Value_End)
    {
        OnAttributeValueEnd();
    }
}

void XmlParser::OnTagOpen()
{
    mLineState = LS::Await_Tag_Follower;
}

void XmlParser::OnTagClose()
{
    if(mDocumentState == DS::Build_Prolog)
    {
        onFinishProlog();
    }
    else if(mDocumentState == DS::Build_Element)
    {
        if(mLineState == LS::Await_Tag_Name_End)
        {
            OnTagNameEnd();
        }
        onElementTagEnd();
    }
    else if(mDocumentState == DS::Close_Element)
    {
        mDocumentState = DS::Await_Element;
        mLineState = LS::Await_Tag_Open;
        if (!mWorkingElements.empty())
        {
            mWorkingElements.pop();
        }
    }
}

void XmlParser::OnTextStart(char c)
{
    mWorkingText = c;
    mLineState = LS::Await_Text_End;
}

void XmlParser::OnTextEnd()
{
    mWorkingElements.top()->SetText(mWorkingText);
}

void XmlParser::onElementTagEnd()
{
    mDocumentState = DS::Await_Element;
    mLineState = LS::Await_Tag_Open;
}

void XmlParser::OnTagNameStart(char c)
{
    mWorkingTagName = c;
    mLineState = LS::Await_Tag_Name_End;
    if(mDocumentState != DS::Build_Prolog && mDocumentState != DS::Close_Element)
    {
        mDocumentState = DS::Build_Element;
    }
}

void XmlParser::OnTagNameEnd()
{
    if(mDocumentState == DS::Build_Prolog)
    {
        mDocument->GetProlog()->SetTagName(mWorkingTagName);
        mLineState = LS::Await_Attribute_Name;
    }
    else if(mDocumentState == DS::Build_Element)
    {
        auto new_element = XmlElement::Create(mWorkingTagName);
        auto working_element = new_element.get();

        if (!mDocument->GetRoot())
        {
            mDocument->SetRoot(std::move(new_element));
        }
        else
        {
            mWorkingElements.top()->AddChild(std::move(new_element));
        }
        mWorkingElements.push(working_element);
        mLineState = LS::Await_Attribute_Name;
    }
}

void XmlParser::OnAttributeNameStart(char c)
{
    mWorkingAttributeName = c;
    mLineState = LS::Await_Attribute_Name_End;
}

void XmlParser::OnAttributeNameEnd()
{
    auto attribute = XmlAttribute::Create(mWorkingAttributeName);
    if(mDocumentState == DS::Build_Prolog)
    {
        mDocument->GetProlog()->AddAttribute(std::move(attribute));
    }
    else if(mDocumentState == DS::Build_Element)
    {
        mWorkingElements.top()->AddAttribute(std::move(attribute));
    }
    mLineState = LS::Await_Attribute_Value;
}

void XmlParser::OnAttributeValueStart()
{
    mWorkingAttributeValue = "";
    mLineState = LS::Await_Attribute_Value_End;
}

void XmlParser::OnAttributeValueEnd()
{
    if(mDocumentState == DS::Build_Prolog)
    {
        mDocument->GetProlog()->GetAttribute(mWorkingAttributeName)->SetValue(mWorkingAttributeValue);
    }
    else if(mDocumentState == DS::Build_Element)
    {
        mWorkingElements.top()->GetAttribute(mWorkingAttributeName)->SetValue(mWorkingAttributeValue);
    }
    mLineState = LS::Await_Attribute_Name;
}

void XmlParser::OnStartProlog()
{
    mDocumentState = DS::Build_Prolog;
    mLineState = LS::Await_Tag_Name_End;
}

void XmlParser::onFinishProlog()
{
    mDocument->GetProlog()->Update();
    mDocumentState = DS::Await_Element;
    mLineState = LS::Await_Tag_Open;
}

XmlDocumentPtr XmlParser::GetDocument() const
{
    return mDocument;
}
