#include <cassert>
#include <cstddef>
#include <cstring>
#include <utility>
#include <drogon/orm/ArrayParser.h>
#include <drogon/orm/Exception.h>
using namespace drogon::orm;
namespace
{
const char *scan_single_quoted_string(const char begin[])
{
    const char *here = begin;
    assert(*here == '\'');
    for (here++; *here; here++)
    {
        switch (*here)
        {
            case '\'':
                here++;
                if (*here != '\'')
                    return here;
                break;
            case '\\':
                here++;
                if (!*here)
                    throw ArgumentError("SQL string ends in escape: " +
                                        std::string(begin));
                break;
        }
    }
    throw ArgumentError("Null byte in SQL string: " + std::string(begin));
}
std::string parse_single_quoted_string(const char begin[], const char end[])
{
    assert(begin + 1 < end);
    assert(*begin == '\'');
    assert(*(end - 1) == '\'');
    std::string output;
    output.reserve(std::size_t(end - begin - 2));
    for (const char *here = begin + 1; here < end - 1; here++)
    {
        auto c = *here;
        if (c == '\'' || c == '\\')
            here++;
        output.push_back(c);
    }
    return output;
}
const char *scan_double_quoted_string(const char begin[])
{
    const char *here = begin;
    assert(*here == '"');
    for (here++; *here; here++)
    {
        switch (*here)
        {
            case '\\':
                here++;
                if (!*here)
                    throw ArgumentError("SQL string ends in escape: " +
                                        std::string(begin));
                break;
            case '"':
                return here + 1;
        }
    }
    throw ArgumentError("Null byte in SQL string: " + std::string(begin));
}
std::string parse_double_quoted_string(const char begin[], const char end[])
{
    assert(begin + 1 < end);
    assert(*begin == '"');
    assert(*(end - 1) == '"');
    std::string output;
    output.reserve(std::size_t(end - begin - 2));
    for (const char *here = begin + 1; here < end - 1; here++)
    {
        auto c = *here;
        if (c == '\\')
            here++;
        output.push_back(c);
    }
    return output;
}
const char *scan_unquoted_string(const char begin[])
{
    assert(*begin != '\'');
    assert(*begin != '"');
    const char *p;
    for (p = begin; *p != ',' && *p != ';' && *p != '}'; p++)
        ;
    return p;
}
std::string parse_unquoted_string(const char begin[], const char end[])
{
    return std::string(begin, end);
}
}  
ArrayParser::ArrayParser(const char input[]) : pos_(input)
{
}
std::pair<ArrayParser::juncture, std::string> ArrayParser::getNext()
{
    ArrayParser::juncture found;
    std::string value;
    const char *end;
    if (pos_ == nullptr)
    {
        found = juncture::done;
        end = nullptr;
    }
    else
        switch (*pos_)
        {
            case '\0':
                found = juncture::done;
                end = pos_;
                break;
            case '{':
                found = juncture::row_start;
                end = pos_ + 1;
                break;
            case '}':
                found = juncture::row_end;
                end = pos_ + 1;
                break;
            case '\'':
                found = juncture::string_value;
                end = scan_single_quoted_string(pos_);
                value = parse_single_quoted_string(pos_, end);
                break;
            case '"':
                found = juncture::string_value;
                end = scan_double_quoted_string(pos_);
                value = parse_double_quoted_string(pos_, end);
                break;
            default:
                end = scan_unquoted_string(pos_);
                value = parse_unquoted_string(pos_, end);
                if (value == "NULL")
                {
                    value.clear();
                    found = juncture::null_value;
                }
                else
                {
                    found = juncture::string_value;
                }
                break;
        }
    if (end != nullptr && (*end == ',' || *end == ';'))
        end++;
    pos_ = end;
    return std::make_pair(found, value);
}