#include "query_lexer.h"

QueryLexer::QueryLexer(const std::string &input)
    : input(input)
{
}

Token QueryLexer::next()
{
    while (isSpace(peek())) {
        get();
    }

    switch (peek()) {
    case '\0':
        return Token(Token::Type::eof);
    case '(':
    case ')':
        return single(Token::Type::opcode);
    case ':':
        return single(Token::Type::equal);
    case '=':
        return equal();
    case '<':
        return minor();
    case '>':
        return major();
    case '"':
        return quotedWord();
    default:
        return word();
    }
}

char QueryLexer::peek()
{
    return input[index];
}

char QueryLexer::get()
{
    return input[index++];
}

Token QueryLexer::single(Token::Type type)
{
    return Token(type, input.substr(index++, 1));
}

Token QueryLexer::word()
{
    auto start = index;
    get();
    auto current = peek();
    while (current != '\0' && !isSpace(current) && current != '"' && current != '(' && current != ')' && current != ':' && current != '=' && current != '<' && current != '>') {
        get();
        current = peek();
    }
    return Token(Token::Type::word, input.substr(start, index - start));
}

Token QueryLexer::quotedWord()
{
    auto start = index;
    get();
    auto current = peek();
    while (current != '\0' && current != '"') {
        get();
        current = peek();
    }

    if (current == '"') {
        get();
        return Token(Token::Type::quotedWord, input.substr(start, index - start));
    }

    // This should be a lexical error, but the grammar doesn't support it
    return Token(Token::Type::eof);
}

Token QueryLexer::minor()
{
    auto start = index;
    get();
    auto current = peek();
    if (current == '=') {
        get();
        return Token(Token::Type::minorOrEqual, input.substr(start, index - start));
    }

    return Token(Token::Type::minor, input.substr(start, index - start));
}

Token QueryLexer::major()
{
    auto start = index;
    get();
    auto current = peek();
    if (current == '=') {
        get();
        return Token(Token::Type::majorOrEqual, input.substr(start, index - start));
    }

    return Token(Token::Type::major, input.substr(start, index - start));
}

Token QueryLexer::equal()
{
    auto start = index;
    get();
    auto current = peek();
    if (current == '=') {
        get();
        return Token(Token::Type::exactEqual, input.substr(start, index - start));
    }

    return Token(Token::Type::equal, input.substr(start, index - start));
}

bool QueryLexer::isSpace(char c)
{
    switch (c) {
    case ' ':
    case '\t':
    case '\r':
    case '\n':
        return true;
    default:
        return false;
    }
}