#include "XmlParser.h"

#include "StringUtils.h"

#include "XmlDocument.h"
#include "XmlElement.h"
#include "XmlAttribute.h"

#include <iostream>

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

XmlParser::XmlParser()
    : mDocumentState(XmlParser::DocumentState::Await_Prolog),
      mLineState(XmlParser::LineState::Await_Tag_Open),
      mDocument(XmlDocument::Create()),
      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:
        case LS::Await_Tag_Inline_Close:
        {
            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;
        }
    }
    else if(mLineState == LS::Await_Attribute_Name)
    {
        mLineState = LS::Await_Tag_Inline_Close;
    }
    else
    {
        onNonAlphaNumeric(StringUtils::FORWARD_SLASH);
    }
}

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();
        }
        else if (mLineState == LS::Await_Tag_Inline_Close)
        {
            if (!mWorkingElements.empty())
            {
                mWorkingElements.pop();
            }
        }
        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()
{
    return std::move(mDocument);
}
