
#include <orcus/stream.hpp>
#include <orcus/threaded_json_parser.hpp>
#include <orcus/string_pool.hpp>

#include <vector>
#include <iostream>
#include <stdio.h>
#include <string>
#include <sys/time.h>

#define SIMULATE_PROCESSING_OVERHEAD 0

using namespace std;
using namespace orcus;

namespace {

class stack_printer
{
public:
    explicit stack_printer(const char* msg) :
        m_msg(msg)
    {
        fprintf(stdout, "%s: --begin\n", m_msg.c_str());
        m_start_time = get_time();
    }

    ~stack_printer()
    {
        double end_time = get_time();
        fprintf(stdout, "%s: --end (duration: %g sec)\n", m_msg.c_str(), (end_time-m_start_time));
    }

private:
    double get_time() const
    {
        timeval tv;
        gettimeofday(&tv, NULL);
        return tv.tv_sec + tv.tv_usec / 1000000.0;
    }

    ::std::string m_msg;
    double m_start_time;
};

}

class handler
{
    string_pool m_pool;
    json::parse_tokens_t m_tokens;
    std::vector<double> m_results;

    void do_work()
    {
#if SIMULATE_PROCESSING_OVERHEAD
        double f = m_results.empty() ? 0.0 : m_results.back();

        for (size_t i = 0; i < 1000; ++i)
            f += 0.1;

        m_results.push_back(f);
#endif
    }

public:
    void begin_parse()
    {
        m_tokens.emplace_back(json::parse_token_t::begin_parse);
        do_work();
    }

    void end_parse()
    {
        m_tokens.emplace_back(json::parse_token_t::end_parse);
        do_work();
    }

    void begin_array()
    {
        m_tokens.emplace_back(json::parse_token_t::begin_array);
        do_work();
    }

    void end_array()
    {
        m_tokens.emplace_back(json::parse_token_t::end_array);
        do_work();
    }

    void begin_object()
    {
        m_tokens.emplace_back(json::parse_token_t::begin_object);
        do_work();
    }

    void object_key(const char* p, size_t len, bool transient)
    {
        std::string_view s{p, len};
        if (transient)
            s = m_pool.intern(s).first;

        m_tokens.emplace_back(json::parse_token_t::object_key, s);
        do_work();
    }

    void end_object()
    {
        m_tokens.emplace_back(json::parse_token_t::end_object);
        do_work();
    }

    void boolean_true()
    {
        m_tokens.emplace_back(json::parse_token_t::boolean_true);
        do_work();
    }

    void boolean_false()
    {
        m_tokens.emplace_back(json::parse_token_t::boolean_false);
        do_work();
    }

    void null()
    {
        m_tokens.emplace_back(json::parse_token_t::null);
        do_work();
    }

    void string(const char* p, size_t len, bool transient)
    {
        std::string_view s{p, len};
        if (transient)
            s = m_pool.intern(s).first;

        m_tokens.emplace_back(json::parse_token_t::string, s);
        do_work();
    }

    void number(double val)
    {
        m_tokens.emplace_back(val);
        do_work();
    }

    size_t token_size() const
    {
        return m_tokens.size();
    }

    double work_value() const
    {
        return m_results.back();
    }
};

int main(int argc, char** argv) try
{
    if (argc < 2)
        return EXIT_FAILURE;

    const char* filepath = argv[1];
    orcus::file_content content(filepath);

    size_t min_token_size = 0;
    size_t max_token_size = 0;

    if (argc >= 3)
    {
        const char* p = argv[2];
        min_token_size = strtol(p, nullptr, 10);
    }

    if (argc >= 4)
    {
        const char* p = argv[3];
        max_token_size = strtol(p, nullptr, 10);
    }

    cout << "file: " << filepath << endl;
    cout << "min token size: " << min_token_size << endl;
    cout << "max token size: " << max_token_size << endl;

    handler hdl;
    orcus::json::parser_stats stats;
    {
        stack_printer __stack_printer__("parsing");
        orcus::threaded_json_parser<handler> parser(content.data(), content.size(), hdl, min_token_size, max_token_size);
        parser.parse();

        stats = parser.get_stats();
    }

    cout << "final token buffer size threshold: " << stats.token_buffer_size_threshold << endl;

    cout << "parsed token count: " << hdl.token_size() << endl;
#if SIMULATE_PROCESSING_OVERHEAD
    cout << "work value: " << hdl.work_value() << endl;
#endif

    return EXIT_SUCCESS;
}
catch (const std::exception& e)
{
    cerr << e.what() << endl;
    return EXIT_FAILURE;
}
