//
// Copyright (c) 2022 Dmitry Arkhipov (grisumbras@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/json
//

#ifndef BOOST_JSON_IMPL_POINTER_IPP
#define BOOST_JSON_IMPL_POINTER_IPP

#include <boost/json/value.hpp>

BOOST_JSON_NS_BEGIN

namespace detail {

class pointer_token
{
public:
    class iterator;

    pointer_token(
        char const* b,
        char const* e) noexcept
        : b_(b)
        , e_(e)
    {
    }

    iterator begin() const noexcept;
    iterator end() const noexcept;

private:
    char const* b_;
    char const* e_;
};

class pointer_token::iterator
{
public:
    explicit iterator(char const* base) noexcept
        : base_(base)
    {
    }

    char operator*() const noexcept
    {
        switch( char c = *base_ )
        {
        case '~':
            c = base_[1];
            if( '0' == c )
                return '~';
            BOOST_ASSERT('1' == c);
            return '/';
        default:
            return c;
        }
    }

    iterator& operator++() noexcept
    {
        if( '~' == *base_ )
            base_ += 2;
        else
            ++base_;
        return *this;
    }

    char const* base() const noexcept
    {
        return base_;
    }

private:
    char const* base_;
};

bool operator==(pointer_token::iterator l, pointer_token::iterator r) noexcept
{
    return l.base() == r.base();
}

bool operator!=(pointer_token::iterator l, pointer_token::iterator r) noexcept
{
    return l.base() != r.base();
}

pointer_token::iterator pointer_token::begin() const noexcept
{
    return iterator(b_);
}

pointer_token::iterator pointer_token::end() const noexcept
{
    return iterator(e_);
}

bool operator==(pointer_token token, string_view sv) noexcept
{
    auto t_b = token.begin();
    auto const t_e = token.end();
    auto s_b = sv.begin();
    auto const s_e = sv.end();
    while( s_b != s_e )
    {
        if( t_e == t_b )
            return false;
        if( *t_b != *s_b )
            return false;
        ++t_b;
        ++s_b;
    }
    return t_b == t_e;
}

bool is_invalid_zero(
    char const* b,
    char const* e) noexcept
{
    // in JSON Pointer only zero index can start character '0'
    if( *b != '0' )
        return false;

    // if an index token starts with '0', then it should not have any more
    // characters: either the string should end, or new token should start
    ++b;
    if( b == e )
        return false;
    return *b != '/';
}

bool is_past_the_end_token(
    char const* b,
    char const* e) noexcept
{
    if( *b != '-' )
        return false;

    ++b;
    if( b == e )
        return true;
    return *b == '/';
}

std::size_t
parse_number_token(
    char const*& b,
    char const* e,
    error_code& ec) noexcept
{
    if( ( b == e )
        || is_invalid_zero(b, e) )
    {
        BOOST_STATIC_CONSTEXPR source_location loc = BOOST_JSON_SOURCE_POS;
        BOOST_JSON_ASSIGN_ERROR_CODE(ec, error::token_not_number, &loc);
        return {};
    }

    if( is_past_the_end_token(b, e) )
    {
        BOOST_STATIC_CONSTEXPR source_location loc = BOOST_JSON_SOURCE_POS;
        BOOST_JSON_ASSIGN_ERROR_CODE(ec, error::past_the_end, &loc);
        return {};
    }

    std::size_t result = 0;
    for( ; b != e; ++b )
    {
        char const c = *b;

        if( '/' == c)
            break;

        unsigned d = c - '0';
        if( d > 9 )
        {
            BOOST_STATIC_CONSTEXPR source_location loc = BOOST_JSON_SOURCE_POS;
            BOOST_JSON_ASSIGN_ERROR_CODE(ec, error::token_not_number, &loc);
            return {};
        }

        std::size_t new_result = result * 10 + d;
        if( new_result < result )
        {
            BOOST_STATIC_CONSTEXPR source_location loc = BOOST_JSON_SOURCE_POS;
            BOOST_JSON_ASSIGN_ERROR_CODE(ec, error::token_overflow, &loc);
            return {};
        }

        result = new_result;

    }
    return result;
}

pointer_token
get_token(
    char const* b,
    char const* e,
    error_code& ec) noexcept
{
    char const* start = b;
    for( ; b < e; ++b )
    {
        char const c = *b;
        if( '/' == c )
            break;

        if( '~' == c )
        {
            if( ++b == e )
            {
                BOOST_STATIC_CONSTEXPR source_location loc
                    = BOOST_JSON_SOURCE_POS;
                BOOST_JSON_ASSIGN_ERROR_CODE(ec, error::invalid_escape, &loc);
                break;
            }

            switch (*b)
            {
            case '0': // fall through
            case '1':
                // valid escape sequence
                continue;
            default: {
                BOOST_STATIC_CONSTEXPR source_location loc
                    = BOOST_JSON_SOURCE_POS;
                BOOST_JSON_ASSIGN_ERROR_CODE(ec, error::invalid_escape, &loc);
                break;
            }
            }
            break;
        }
    }

    return pointer_token(start, b);
}

value*
if_contains_token(object const& obj, pointer_token token)
{
    if( obj.empty() )
        return nullptr;

    auto const it = detail::find_in_object(obj, token).first;
    if( !it )
        return nullptr;

    return &it->value();
}

} // namespace detail

value const&
value::at_pointer(string_view ptr) const
{
    error_code ec;
    auto const found = find_pointer(ptr, ec);
    if( !found )
        detail::throw_system_error(ec, BOOST_JSON_SOURCE_POS);
    return *found;
}

value&
value::at_pointer(string_view ptr)
{
    value const& self = *this;
    return const_cast<value&>(self.at_pointer(ptr));
}

value const*
value::find_pointer(string_view ptr, error_code& ec) const noexcept
{
    ec.clear();

    value const* result = this;
    char const* cur = ptr.data();
    char const* const end = cur + ptr.size();
    while( end != cur )
    {
        if( *cur++ != '/' )
        {
            BOOST_STATIC_CONSTEXPR source_location loc = BOOST_JSON_SOURCE_POS;
            BOOST_JSON_ASSIGN_ERROR_CODE(ec, error::missing_slash, &loc);
            return nullptr;
        }

        if( auto const po = result->if_object() )
        {
            auto const token = detail::get_token(cur, end, ec);
            if( ec )
                return nullptr;

            result = detail::if_contains_token(*po, token);
            cur = token.end().base();
        }
        else if( auto const pa = result->if_array() )
        {
            auto const index = detail::parse_number_token(cur, end, ec);
            if( ec )
                return nullptr;

            result = pa->if_contains(index);
        }
        else
        {
            BOOST_STATIC_CONSTEXPR source_location loc = BOOST_JSON_SOURCE_POS;
            BOOST_JSON_ASSIGN_ERROR_CODE(ec, error::value_is_scalar, &loc);
            return nullptr;
        }

        if( !result )
        {
            BOOST_STATIC_CONSTEXPR source_location loc = BOOST_JSON_SOURCE_POS;
            BOOST_JSON_ASSIGN_ERROR_CODE(ec, error::not_found, &loc);
            return nullptr;
        }
    }

    BOOST_ASSERT(result);
    return result;
}

value*
value::find_pointer(string_view ptr, error_code& ec) noexcept
{
    value const& self = *this;
    return const_cast<value*>(self.find_pointer(ptr, ec));
}

value const*
value::find_pointer(string_view ptr, std::error_code& ec) const noexcept
{
    error_code jec;
    value const* result = find_pointer(ptr, jec);
    ec = jec;
    return result;
}

value*
value::find_pointer(string_view ptr, std::error_code& ec) noexcept
{
    value const& self = *this;
    return const_cast<value*>(self.find_pointer(ptr, ec));
}

BOOST_JSON_NS_END

#endif // BOOST_JSON_IMPL_POINTER_IPP
