/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#ifndef INCLUDED_ORCUS_THREADED_JSON_PARSER_HPP
#define INCLUDED_ORCUS_THREADED_JSON_PARSER_HPP

#include "json_parser_thread.hpp"
#include "json_parser_base.hpp"
#include "detail/thread.hpp"

#include <algorithm>

namespace orcus {

template<typename _Handler>
class threaded_json_parser
{
public:

    typedef _Handler handler_type;

    /**
     * Constructor.
     *
     * @param p pointer to a string stream containing JSON string.
     * @param n size of the stream.
     * @param hdl handler class instance.
     * @param min_token_size minimum size of the internal token buffer.
     */
    threaded_json_parser(
        const char* p, size_t n, handler_type& hdl, size_t min_token_size);

    /**
     * Constructor.
     *
     * @param p pointer to a string stream containing JSON string.
     * @param n size of the stream.
     * @param hdl handler class instance.
     * @param min_token_size minimum size of the internal token buffer.
     * @param max_token_size maximum size of the internal token buffer.
     */
    threaded_json_parser(
        const char* p, size_t n, handler_type& hdl, size_t min_token_size,
        size_t max_token_size);

    /**
     * Call this method to start parsing.
     */
    void parse();

    /**
     * Get statistics on the parsing session.  Call this only after the
     * parsing has finished.
     *
     * @return structure containing statistics of the parsing session.
     */
    json::parser_stats get_stats() const;

    void swap_string_pool(string_pool& pool);

private:
    void thread_parse();

    void process_tokens(json::parse_tokens_t& tokens);

private:
    json::parser_thread m_parser_thread;
    handler_type& m_handler;
};

template<typename _Handler>
threaded_json_parser<_Handler>::threaded_json_parser(
    const char* p, size_t n, handler_type& hdl, size_t min_token_size) :
    m_parser_thread(p, n, min_token_size), m_handler(hdl) {}

template<typename _Handler>
threaded_json_parser<_Handler>::threaded_json_parser(
    const char* p, size_t n, handler_type& hdl, size_t min_token_size, size_t max_token_size) :
    m_parser_thread(p, n, min_token_size, max_token_size), m_handler(hdl) {}

template<typename _Handler>
void threaded_json_parser<_Handler>::parse()
{
    std::thread t(&threaded_json_parser::thread_parse, this);
    detail::thread::scoped_guard guard(std::move(t));

    json::parse_tokens_t tokens;

    while (m_parser_thread.next_tokens(tokens))
        process_tokens(tokens);

    process_tokens(tokens);
}

template<typename _Handler>
json::parser_stats threaded_json_parser<_Handler>::get_stats() const
{
    return m_parser_thread.get_stats();
}

template<typename _Handler>
void threaded_json_parser<_Handler>::swap_string_pool(string_pool& pool)
{
    m_parser_thread.swap_string_pool(pool);
}

template<typename _Handler>
void threaded_json_parser<_Handler>::thread_parse()
{
    // Start parsing.
    m_parser_thread.start();
}

template<typename _Handler>
void threaded_json_parser<_Handler>::process_tokens(json::parse_tokens_t& tokens)
{
    std::for_each(tokens.begin(), tokens.end(),
        [this](const json::parse_token& t)
        {
            switch (t.type)
            {
                case json::parse_token_t::begin_array:
                    m_handler.begin_array();
                    break;
                case json::parse_token_t::begin_object:
                    m_handler.begin_object();
                    break;
                case json::parse_token_t::begin_parse:
                    m_handler.begin_parse();
                    break;
                case json::parse_token_t::boolean_false:
                    m_handler.boolean_false();
                    break;
                case json::parse_token_t::boolean_true:
                    m_handler.boolean_true();
                    break;
                case json::parse_token_t::end_array:
                    m_handler.end_array();
                    break;
                case json::parse_token_t::end_object:
                    m_handler.end_object();
                    break;
                case json::parse_token_t::end_parse:
                    m_handler.end_parse();
                    break;
                case json::parse_token_t::null:
                    m_handler.null();
                    break;
                case json::parse_token_t::number:
                    m_handler.number(std::get<double>(t.value));
                    break;
                case json::parse_token_t::object_key:
                {
                    auto s = std::get<std::string_view>(t.value);
                    m_handler.object_key(s.data(), s.size(), false);
                    break;
                }
                case json::parse_token_t::string:
                {
                    auto s = std::get<std::string_view>(t.value);
                    m_handler.string(s.data(), s.size(), false);
                    break;
                }
                case json::parse_token_t::parse_error:
                {
                    auto v = std::get<parse_error_value_t>(t.value);
                    throw parse_error(std::string{v.str}, v.offset);
                }
                case json::parse_token_t::unknown:
                default:
                    throw general_error("unknown token type encountered.");
            }
        }
    );
}

}

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
