// slang-json-parser.cpp
#include "slang-json-parser.h"

#include "core/slang-string-escape-util.h"
#include "slang-json-diagnostics.h"

/*
https://www.json.org/json-en.html
*/

namespace Slang
{

SlangResult JSONParser::_parseObject()
{
    {
        const SourceLoc loc = m_lexer->peekLoc();
        SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::LBrace));
        m_listener->startObject(loc);
    }

    {
        const SourceLoc loc = m_lexer->peekLoc();
        if (m_lexer->advanceIf(JSONTokenType::RBrace))
        {
            m_listener->endObject(loc);
            return SLANG_OK;
        }
    }

    while (true)
    {
        JSONToken keyToken;
        SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::StringLiteral, keyToken));
        m_listener->addQuotedKey(m_lexer->getLexeme(keyToken), keyToken.loc);

        SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::Colon));

        SLANG_RETURN_ON_FAIL(_parseValue());
        if (m_lexer->advanceIf(JSONTokenType::Comma))
        {
            continue;
        }

        break;
    }

    {
        const SourceLoc loc = m_lexer->peekLoc();
        SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::RBrace));
        m_listener->endObject(loc);
    }
    return SLANG_OK;
}

SlangResult JSONParser::_parseArray()
{
    {
        const SourceLoc loc = m_lexer->peekLoc();
        SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::LBracket));
        m_listener->startArray(loc);
    }

    {
        const SourceLoc loc = m_lexer->peekLoc();
        if (m_lexer->advanceIf(JSONTokenType::RBracket))
        {
            m_listener->endArray(loc);
            return SLANG_OK;
        }
    }

    while (true)
    {
        SLANG_RETURN_ON_FAIL(_parseValue());
        if (m_lexer->advanceIf(JSONTokenType::Comma))
        {
            continue;
        }
        break;
    }

    {
        const SourceLoc loc = m_lexer->peekLoc();
        SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::RBracket));
        m_listener->endArray(loc);
    }
    return SLANG_OK;
}

SlangResult JSONParser::_parseValue()
{
    switch (m_lexer->peekType())
    {
    case JSONTokenType::True:
    case JSONTokenType::False:
    case JSONTokenType::Null:
    case JSONTokenType::IntegerLiteral:
    case JSONTokenType::FloatLiteral:
    case JSONTokenType::StringLiteral:
        {
            const JSONToken& tok = m_lexer->peekToken();
            m_listener->addLexemeValue(tok.type, m_lexer->peekLexeme(), tok.loc);
            m_lexer->advance();
            return SLANG_OK;
        }
    case JSONTokenType::LBracket:
        {
            return _parseArray();
        }
    case JSONTokenType::LBrace:
        {
            return _parseObject();
        }
    default:
        {
            m_sink->diagnose(
                m_lexer->peekLoc(),
                JSONDiagnostics::unexpectedToken,
                getJSONTokenAsText(m_lexer->peekType()));
            return SLANG_FAIL;
        }
    case JSONTokenType::Invalid:
        {
            // It's a lex error, so just fail
            return SLANG_FAIL;
        }
    }
}

SlangResult JSONParser::parse(
    JSONLexer* lexer,
    SourceView* sourceView,
    JSONListener* listener,
    DiagnosticSink* sink)
{
    m_sourceView = sourceView;
    m_lexer = lexer;
    m_listener = listener;
    m_sink = sink;

    SLANG_RETURN_ON_FAIL(_parseValue());

    return m_lexer->expect(JSONTokenType::EndOfFile);
}

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                               JSONWriter

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

Index JSONWriter::_getLineLengthAfterIndent()
{
    if (m_emittedIndent < 0)
    {
        return 0;
    }

    Index lineLength = m_builder.getLength() - m_lineStart;
    return lineLength - m_emittedIndent * m_indentCharCount;
}


void JSONWriter::_emitIndent()
{
    m_builder.appendRepeatedChar(m_indentChar, m_currentIndent * m_indentCharCount);
    m_emittedIndent = m_currentIndent;
    SLANG_ASSERT(m_emittedIndent >= 0);
}

void JSONWriter::_maybeEmitIndent()
{
    if (m_emittedIndent < 0)
    {
        _emitIndent();
    }
}

void JSONWriter::_nextLine()
{
    m_builder << "\n";
    m_lineStart = m_builder.getLength();
    m_lineIndex++;
    m_emittedIndent = -1;
}

void JSONWriter::_maybeNextLine()
{
    // Nothing has been emitted, because nothing has been indented, and we must indent before an
    // emit
    if (m_emittedIndent < 0)
    {
    }
    else
    {
        _nextLine();
    }
}

void JSONWriter::_handleFormat(Location loc)
{
    switch (m_format)
    {
    case IndentationStyle::Allman:
        {
            if (isComma(loc))
            {
                _maybeNextLine();
            }
            else
            {
                if (isBefore(loc))
                {
                    _maybeNextLine();
                    if (isClose(loc))
                    {
                        _dedent();
                    }
                }
                else
                {
                    _maybeNextLine();
                    if (isOpen(loc))
                    {
                        _indent();
                    }
                }
            }
            break;
        }
    case IndentationStyle::KNR:
        {
            if (isComma(loc))
            {
                if (loc == Location::FieldComma ||
                    (m_lineLengthLimit > 0 && _getLineLengthAfterIndent() > m_lineLengthLimit))
                {
                    _maybeNextLine();
                }
            }
            else
            {
                if (isBefore(loc))
                {
                    if (isClose(loc))
                    {
                        _maybeNextLine();
                        _dedent();
                    }
                }
                else
                {
                    _maybeNextLine();
                    if (isOpen(loc))
                    {
                        _indent();
                    }
                }
            }
            break;
        }
    }
}

void JSONWriter::_maybeEmitComma()
{
    if (m_state.m_flags & State::Flag::HasPrevious)
    {
        _maybeEmitIndent();
        m_builder << ", ";
        _handleFormat(Location::Comma);
    }
}

void JSONWriter::_maybeEmitFieldComma()
{
    if (m_state.m_flags & State::Flag::HasPrevious)
    {
        _maybeEmitIndent();
        m_builder << ", ";
        _handleFormat(Location::FieldComma);
    }
}

void JSONWriter::startObject(SourceLoc loc)
{
    SLANG_UNUSED(loc);
    SLANG_ASSERT(m_state.canEmitValue());

    _maybeEmitComma();

    _handleFormat(Location::BeforeOpenObject);
    _maybeEmitIndent();
    m_builder << "{";
    _handleFormat(Location::AfterOpenObject);

    m_state.m_flags |= State::Flag::HasPrevious;
    m_state.m_flags &= State::Flag::HasKey;

    m_stack.add(m_state);

    m_state.m_kind = State::Kind::Object;
    m_state.m_flags = 0;
}

void JSONWriter::endObject(SourceLoc loc)
{
    SLANG_UNUSED(loc);
    SLANG_ASSERT(m_state.m_kind == State::Kind::Object);

    _handleFormat(Location::BeforeCloseObject);
    _maybeEmitIndent();
    m_builder << "}";
    _handleFormat(Location::AfterCloseObject);

    m_state = m_stack.getLast();
    m_stack.removeLast();

    _postValue();
}

void JSONWriter::startArray(SourceLoc loc)
{
    SLANG_UNUSED(loc);
    SLANG_ASSERT(m_state.canEmitValue());

    _maybeEmitComma();

    _handleFormat(Location::BeforeOpenArray);
    _maybeEmitIndent();
    m_builder << "[";
    _handleFormat(Location::AfterOpenArray);

    m_state.m_flags |= State::Flag::HasPrevious;
    m_state.m_flags &= State::Flag::HasKey;

    m_stack.add(m_state);

    m_state.m_kind = State::Kind::Array;
    m_state.m_flags = 0;
}

void JSONWriter::endArray(SourceLoc loc)
{
    SLANG_UNUSED(loc);
    SLANG_ASSERT(m_state.m_kind == State::Kind::Array);

    _handleFormat(Location::BeforeCloseArray);
    _maybeEmitIndent();
    m_builder << "]";
    _handleFormat(Location::AfterCloseArray);

    m_state = m_stack.getLast();
    m_stack.removeLast();

    _postValue();
}

void JSONWriter::addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc)
{
    SLANG_UNUSED(loc);
    SLANG_ASSERT(
        m_state.m_kind == State::Kind::Object && (m_state.m_flags & State::Flag::HasKey) == 0);

    _maybeEmitFieldComma();
    _maybeEmitIndent();

    // Output the key quoted
    StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON);
    StringEscapeUtil::appendQuoted(handler, key, m_builder);

    m_builder << " : ";

    m_state.m_flags |= State::Flag::HasKey;
    // We don't want it to emit a , after the :
    m_state.m_flags &= ~State::Flag::HasPrevious;
}

void JSONWriter::addQuotedKey(const UnownedStringSlice& key, SourceLoc loc)
{
    SLANG_UNUSED(loc);
    SLANG_ASSERT(
        m_state.m_kind == State::Kind::Object && (m_state.m_flags & State::Flag::HasKey) == 0);

    // It should be quoted
    SLANG_ASSERT(key.getLength() >= 2 && key[0] == '"' && key[key.getLength() - 1] == '"');

    _maybeEmitFieldComma();
    _maybeEmitIndent();

    m_builder << key;

    m_builder << " : ";

    m_state.m_flags |= State::Flag::HasKey;
    // We don't want it to emit a , after the :
    m_state.m_flags &= ~State::Flag::HasPrevious;
}

void JSONWriter::_preValue(SourceLoc loc)
{
    SLANG_UNUSED(loc);
    SLANG_ASSERT(m_state.canEmitValue());

    _maybeEmitComma();
    _maybeEmitIndent();
}

void JSONWriter::_postValue()
{
    // We have a previous
    m_state.m_flags |= State::Flag::HasPrevious;
    // We don't have a key
    m_state.m_flags &= ~State::Flag::HasKey;
}


void JSONWriter::addLexemeValue(JSONTokenType type, const UnownedStringSlice& value, SourceLoc loc)
{
    _preValue(loc);

    switch (type)
    {
    case JSONTokenType::IntegerLiteral:
    case JSONTokenType::FloatLiteral:
    case JSONTokenType::StringLiteral:
        {
            m_builder << value;
            break;
        }
    case JSONTokenType::True:
        {
            m_builder << UnownedStringSlice::fromLiteral("true");
            break;
        }
    case JSONTokenType::False:
        {
            m_builder << UnownedStringSlice::fromLiteral("false");
            break;
        }
    case JSONTokenType::Null:
        {
            m_builder << UnownedStringSlice::fromLiteral("null");
            break;
        }
    default:
        {
            SLANG_ASSERT(!"Can only emit values");
        }
    }

    _postValue();
}

void JSONWriter::addIntegerValue(int64_t value, SourceLoc loc)
{
    _preValue(loc);
    m_builder << value;
    _postValue();
}

void JSONWriter::addFloatValue(double value, SourceLoc loc)
{
    _preValue(loc);
    m_builder << value;
    _postValue();
}

void JSONWriter::addBoolValue(bool inValue, SourceLoc loc)
{
    _preValue(loc);
    const UnownedStringSlice slice = inValue ? UnownedStringSlice::fromLiteral("true")
                                             : UnownedStringSlice::fromLiteral("false");
    m_builder << slice;
    _postValue();
}

void JSONWriter::addNullValue(SourceLoc loc)
{
    _preValue(loc);
    m_builder << UnownedStringSlice::fromLiteral("null");
    _postValue();
}

void JSONWriter::addStringValue(const UnownedStringSlice& slice, SourceLoc loc)
{
    _preValue(loc);
    StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON);
    StringEscapeUtil::appendQuoted(handler, slice, m_builder);
    _postValue();
}

} // namespace Slang
