// SPDX-FileCopyrightText: 2022 James R. Barlow
// SPDX-License-Identifier: MPL-2.0

#include <cctype>
#include <iomanip>
#include <iostream>
#include <sstream>

#include "pikepdf.h"

#include <nanobind/trampoline.h>

#include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QPDFPageObjectHelper.hh>

class TokenFilter : public QPDFObjectHandle::TokenFilter {
public:
    using QPDFObjectHandle::TokenFilter::TokenFilter;
    virtual ~TokenFilter() = default;
    using Token = QPDFTokenizer::Token;

    void handleToken(Token const &token) override
    {
        py::object result = this->handle_token(token);
        if (result.is_none())
            return;
        try {
            if (py::hasattr(result, "__iter__")) {
                for (auto item : result) {
                    const auto returned_token = py::cast<Token>(item);
                    this->writeToken(returned_token);
                }
            } else {
                const auto returned_token = py::cast<Token>(result);
                this->writeToken(returned_token);
            }
        } catch (const py::cast_error &) {
            throw py::type_error("returned object that is not a token");
        }
    }

    virtual py::object handle_token(Token const &token) = 0;
};

class TokenFilterTrampoline : public TokenFilter {
public:
    NB_TRAMPOLINE(TokenFilter, 1);
    using Token = QPDFTokenizer::Token;

    py::object handle_token(Token const &token) override
    {
        NB_OVERRIDE_PURE(handle_token, token);
    }
};

void init_tokenfilter(py::module_ &m)
{
    py::enum_<QPDFTokenizer::token_type_e>(m, "TokenType")
        .value("bad", QPDFTokenizer::token_type_e::tt_bad)
        .value("array_close", QPDFTokenizer::token_type_e::tt_array_close)
        .value("array_open", QPDFTokenizer::token_type_e::tt_array_open)
        .value("brace_close", QPDFTokenizer::token_type_e::tt_brace_close)
        .value("brace_open", QPDFTokenizer::token_type_e::tt_brace_open)
        .value("dict_close", QPDFTokenizer::token_type_e::tt_dict_close)
        .value("dict_open", QPDFTokenizer::token_type_e::tt_dict_open)
        .value("integer", QPDFTokenizer::token_type_e::tt_integer)
        .value("name_", QPDFTokenizer::token_type_e::tt_name)
        .value("real", QPDFTokenizer::token_type_e::tt_real)
        .value("string", QPDFTokenizer::token_type_e::tt_string)
        .value("null", QPDFTokenizer::token_type_e::tt_null)
        .value("bool", QPDFTokenizer::token_type_e::tt_bool)
        .value("word", QPDFTokenizer::token_type_e::tt_word)
        .value("eof", QPDFTokenizer::token_type_e::tt_eof)
        .value("space", QPDFTokenizer::token_type_e::tt_space)
        .value("comment", QPDFTokenizer::token_type_e::tt_comment)
        .value("inline_image", QPDFTokenizer::token_type_e::tt_inline_image);

    py::class_<QPDFTokenizer::Token>(m, "Token", py::type_slots(pikepdf_gc_slots))
        .def("__init__",
            [](QPDFTokenizer::Token *self,
                QPDFTokenizer::token_type_e type,
                py::bytes value) {
                new (self) QPDFTokenizer::Token(type, to_string(value));
            })
        .def_prop_ro("type_", &QPDFTokenizer::Token::getType)
        .def_prop_ro("value", &QPDFTokenizer::Token::getValue)
        .def_prop_ro("raw_value",
            [](const QPDFTokenizer::Token &t) -> py::bytes {
                auto v = t.getRawValue();
                return py::bytes(v.data(), v.size());
            })
        .def_prop_ro("error_msg", &QPDFTokenizer::Token::getErrorMessage)
        .def("__eq__", &QPDFTokenizer::Token::operator==, py::is_operator());

    py::class_<QPDFObjectHandle::TokenFilter> qpdftokenfilter(
        m, "_QPDFTokenFilter", py::type_slots(pikepdf_gc_slots));

    py::class_<TokenFilter, TokenFilterTrampoline>(
        m, "TokenFilter", qpdftokenfilter, py::type_slots(pikepdf_gc_slots))
        .def(py::init<>())
        .def("handle_token", &TokenFilter::handle_token, py::arg("token"));
}
