// Copyright 2013 Daniel Parker
// Distributed under the Boost license, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

// See https://github.com/danielaparker/jsoncons for latest version

#ifndef JSONCONS_JSON_ENCODER_HPP
#define JSONCONS_JSON_ENCODER_HPP

#include <array> // std::array
#include <string>
#include <vector>
#include <cmath> // std::isfinite, std::isnan
#include <limits> // std::numeric_limits
#include <memory>
#include <utility> // std::move
#include <jsoncons/config/jsoncons_config.hpp>
#include <jsoncons/json_exception.hpp>
#include <jsoncons/byte_string.hpp>
#include <jsoncons/bignum.hpp>
#include <jsoncons/json_options.hpp>
#include <jsoncons/json_error.hpp>
#include <jsoncons/json_visitor.hpp>
#include <jsoncons/sink.hpp>
#include <jsoncons/detail/write_number.hpp>

namespace jsoncons { namespace detail {
template <class CharT, class Sink>
size_t escape_string(const CharT* s, std::size_t length,
                     bool escape_all_non_ascii, bool escape_solidus,
                     Sink& sink)
{
    std::size_t count = 0;
    const CharT* begin = s;
    const CharT* end = s + length;
    for (const CharT* it = begin; it != end; ++it)
    {
        CharT c = *it;
        switch (c)
        {
            case '\\':
                sink.push_back('\\');
                sink.push_back('\\');
                count += 2;
                break;
            case '"':
                sink.push_back('\\');
                sink.push_back('\"');
                count += 2;
                break;
            case '\b':
                sink.push_back('\\');
                sink.push_back('b');
                count += 2;
                break;
            case '\f':
                sink.push_back('\\');
                sink.push_back('f');
                count += 2;
                break;
            case '\n':
                sink.push_back('\\');
                sink.push_back('n');
                count += 2;
                break;
            case '\r':
                sink.push_back('\\');
                sink.push_back('r');
                count += 2;
                break;
            case '\t':
                sink.push_back('\\');
                sink.push_back('t');
                count += 2;
                break;
            default:
                if (escape_solidus && c == '/')
                {
                    sink.push_back('\\');
                    sink.push_back('/');
                    count += 2;
                }
                else if (is_control_character(c) || escape_all_non_ascii)
                {
                    // convert utf8 to codepoint
                    unicons::sequence_generator<const CharT*> g(it, end, unicons::conv_flags::strict);
                    if (g.done() || g.status() != unicons::conv_errc())
                    {
                        JSONCONS_THROW(ser_error(json_errc::illegal_codepoint));
                    }
                    uint32_t cp = g.get().codepoint();
                    it += (g.get().length() - 1);
                    if (is_non_ascii_codepoint(cp) || is_control_character(c))
                    {
                        if (cp > 0xFFFF)
                        {
                            cp -= 0x10000;
                            uint32_t first = (cp >> 10) + 0xD800;
                            uint32_t second = ((cp & 0x03FF) + 0xDC00);

                            sink.push_back('\\');
                            sink.push_back('u');
                            sink.push_back(to_hex_character(first >> 12 & 0x000F));
                            sink.push_back(to_hex_character(first >> 8 & 0x000F));
                            sink.push_back(to_hex_character(first >> 4 & 0x000F));
                            sink.push_back(to_hex_character(first & 0x000F));
                            sink.push_back('\\');
                            sink.push_back('u');
                            sink.push_back(to_hex_character(second >> 12 & 0x000F));
                            sink.push_back(to_hex_character(second >> 8 & 0x000F));
                            sink.push_back(to_hex_character(second >> 4 & 0x000F));
                            sink.push_back(to_hex_character(second & 0x000F));
                            count += 12;
                        }
                        else
                        {
                            sink.push_back('\\');
                            sink.push_back('u');
                            sink.push_back(to_hex_character(cp >> 12 & 0x000F));
                            sink.push_back(to_hex_character(cp >> 8 & 0x000F));
                            sink.push_back(to_hex_character(cp >> 4 & 0x000F));
                            sink.push_back(to_hex_character(cp & 0x000F));
                            count += 6;
                        }
                    }
                    else
                    {
                        sink.push_back(c);
                        ++count;
                    }
                }
                else
                {
                    sink.push_back(c);
                    ++count;
                }
                break;
        }
    }
    return count;
}

inline
byte_string_chars_format resolve_byte_string_chars_format(byte_string_chars_format format1,
                                                          byte_string_chars_format format2,
                                                          byte_string_chars_format default_format = byte_string_chars_format::base64url)
{
    byte_string_chars_format sink;
    switch (format1)
    {
        case byte_string_chars_format::base16:
        case byte_string_chars_format::base64:
        case byte_string_chars_format::base64url:
            sink = format1;
            break;
        default:
            switch (format2)
            {
                case byte_string_chars_format::base64url:
                case byte_string_chars_format::base64:
                case byte_string_chars_format::base16:
                    sink = format2;
                    break;
                default: // base64url
                {
                    sink = default_format;
                    break;
                }
            }
            break;
    }
    return sink;
}

}}

namespace jsoncons {

template<class CharT,class Sink=jsoncons::stream_sink<CharT>,class Allocator=std::allocator<char>>
class basic_json_encoder final : public basic_json_visitor<CharT>
{
    static const std::array<CharT, 4>& null_k()
    {
        static constexpr std::array<CharT,4> k{'n','u','l','l'};
        return k;
    }
    static const std::array<CharT, 4>& true_k()
    {
        static constexpr std::array<CharT,4> k{'t','r','u','e'};
        return k;
    }
    static const std::array<CharT, 5>& false_k()
    {
        static constexpr std::array<CharT,5> k{'f','a','l','s','e'};
        return k;
    }
public:
    using allocator_type = Allocator;
    using char_type = CharT;
    using typename basic_json_visitor<CharT>::string_view_type;
    using sink_type = Sink;
    using string_type = typename basic_json_encode_options<CharT>::string_type;

private:
    enum class container_type {object, array};

    class encoding_context
    {
        container_type type_;
        std::size_t count_;
        line_split_kind line_splits_;
        bool indent_before_;
        bool new_line_after_;
        std::size_t begin_pos_;
        std::size_t data_pos_;
    public:
        encoding_context(container_type type, line_split_kind split_lines, bool indent_once,
                              std::size_t begin_pos, std::size_t data_pos)
           : type_(type), count_(0), line_splits_(split_lines), indent_before_(indent_once), new_line_after_(false),
             begin_pos_(begin_pos), data_pos_(data_pos)
        {
        }

        encoding_context(const encoding_context&) = default;
        encoding_context& operator=(const encoding_context&) = default;

        void set_position(std::size_t pos)
        {
            data_pos_ = pos;
        }

        std::size_t begin_pos() const
        {
            return begin_pos_;
        }

        std::size_t data_pos() const
        {
            return data_pos_;
        }

        std::size_t count() const
        {
            return count_;
        }

        void increment_count()
        {
            ++count_;
        }

        bool new_line_after() const
        {
            return new_line_after_;
        }

        void new_line_after(bool value) 
        {
            new_line_after_ = value;
        }

        bool is_object() const
        {
            return type_ == container_type::object;
        }

        bool is_array() const
        {
            return type_ == container_type::array;
        }

        bool is_same_line() const
        {
            return line_splits_ == line_split_kind::same_line;
        }

        bool is_new_line() const
        {
            return line_splits_ == line_split_kind::new_line;
        }

        bool is_multi_line() const
        {
            return line_splits_ == line_split_kind::multi_line;
        }

        bool is_indent_once() const
        {
            return count_ == 0 ? indent_before_ : false;
        }

    };
    typedef typename std::allocator_traits<allocator_type>:: template rebind_alloc<encoding_context> encoding_context_allocator_type;

    Sink sink_;
    basic_json_encode_options<CharT> options_;
    jsoncons::detail::write_double fp_;

    std::vector<encoding_context,encoding_context_allocator_type> stack_;
    int indent_amount_;
    std::size_t column_;
    std::basic_string<CharT> colon_str_;
    std::basic_string<CharT> comma_str_;
    std::basic_string<CharT> open_object_brace_str_;
    std::basic_string<CharT> close_object_brace_str_;
    std::basic_string<CharT> open_array_bracket_str_;
    std::basic_string<CharT> close_array_bracket_str_;
    int nesting_depth_;

    // Noncopyable and nonmoveable
    basic_json_encoder(const basic_json_encoder&) = delete;
    basic_json_encoder& operator=(const basic_json_encoder&) = delete;
public:
    basic_json_encoder(Sink&& sink, 
                       const Allocator& alloc = Allocator())
        : basic_json_encoder(std::forward<Sink>(sink), basic_json_encode_options<CharT>(), alloc)
    {
    }

    basic_json_encoder(Sink&& sink, 
                       const basic_json_encode_options<CharT>& options, 
                       const Allocator& alloc = Allocator())
       : sink_(std::forward<Sink>(sink)), 
         options_(options),
         fp_(options.float_format(), options.precision()),
         stack_(alloc),
         indent_amount_(0), 
         column_(0),
         nesting_depth_(0)
    {
        switch (options.spaces_around_colon())
        {
            case spaces_option::space_after:
                colon_str_ = std::basic_string<CharT>({':',' '});
                break;
            case spaces_option::space_before:
                colon_str_ = std::basic_string<CharT>({' ',':'});
                break;
            case spaces_option::space_before_and_after:
                colon_str_ = std::basic_string<CharT>({' ',':',' '});
                break;
            default:
                colon_str_.push_back(':');
                break;
        }
        switch (options.spaces_around_comma())
        {
            case spaces_option::space_after:
                comma_str_ = std::basic_string<CharT>({',',' '});
                break;
            case spaces_option::space_before:
                comma_str_ = std::basic_string<CharT>({' ',','});
                break;
            case spaces_option::space_before_and_after:
                comma_str_ = std::basic_string<CharT>({' ',',',' '});
                break;
            default:
                comma_str_.push_back(',');
                break;
        }
        if (options.pad_inside_object_braces())
        {
            open_object_brace_str_ = std::basic_string<CharT>({'{', ' '});
            close_object_brace_str_ = std::basic_string<CharT>({' ', '}'});
        }
        else
        {
            open_object_brace_str_.push_back('{');
            close_object_brace_str_.push_back('}');
        }
        if (options.pad_inside_array_brackets())
        {
            open_array_bracket_str_ = std::basic_string<CharT>({'[', ' '});
            close_array_bracket_str_ = std::basic_string<CharT>({' ', ']'});
        }
        else
        {
            open_array_bracket_str_.push_back('[');
            close_array_bracket_str_.push_back(']');
        }
    }

    ~basic_json_encoder() noexcept
    {
        JSONCONS_TRY
        {
            sink_.flush();
        }
        JSONCONS_CATCH(...)
        {
        }
    }

private:
    // Implementing methods
    void visit_flush() override
    {
        sink_.flush();
    }

    bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) override
    {
        if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth()))
        {
            ec = json_errc::max_nesting_depth_exceeded;
            return false;
        } 
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.append(comma_str_.data(),comma_str_.length());
            column_ += comma_str_.length();
        }

        if (!stack_.empty()) // object or array
        {
            if (stack_.back().is_object())
            {
                switch (options_.object_object_line_splits())
                {
                    case line_split_kind::same_line:
                        if (column_ >= options_.line_length_limit())
                        {
                            break_line();
                        }
                        break;
                    case line_split_kind::new_line:
                        if (column_ >= options_.line_length_limit())
                        {
                            break_line();
                        }
                        break;
                    default: // multi_line
                        break;
                }
                stack_.emplace_back(container_type::object,options_.object_object_line_splits(), false,
                                    column_, column_+open_object_brace_str_.length());
            }
            else // array
            {
                switch (options_.array_object_line_splits())
                {
                    case line_split_kind::same_line:
                        if (column_ >= options_.line_length_limit())
                        {
                            //stack_.back().new_line_after(true);
                            new_line();
                        }
                        break;
                    case line_split_kind::new_line:
                        stack_.back().new_line_after(true);
                        new_line();
                        break;
                    default: // multi_line
                        stack_.back().new_line_after(true);
                        new_line();
                        break;
                }
                stack_.emplace_back(container_type::object,options_.array_object_line_splits(), false,
                                    column_, column_+open_object_brace_str_.length());
            }
        }
        else 
        {
            stack_.emplace_back(container_type::object, line_split_kind::multi_line, false,
                                column_, column_+open_object_brace_str_.length());
        }
        indent();
        
        sink_.append(open_object_brace_str_.data(), open_object_brace_str_.length());
        column_ += open_object_brace_str_.length();
        return true;
    }

    bool visit_end_object(const ser_context&, std::error_code&) override
    {
        JSONCONS_ASSERT(!stack_.empty());
        --nesting_depth_;

        unindent();
        if (stack_.back().new_line_after())
        {
            new_line();
        }
        stack_.pop_back();
        sink_.append(close_object_brace_str_.data(), close_object_brace_str_.length());
        column_ += close_object_brace_str_.length();

        end_value();
        return true;
    }

    bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) override
    {
        if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth()))
        {
            ec = json_errc::max_nesting_depth_exceeded;
            return false;
        } 
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.append(comma_str_.data(),comma_str_.length());
            column_ += comma_str_.length();
        }
        if (!stack_.empty())
        {
            if (stack_.back().is_object())
            {
                switch (options_.object_array_line_splits())
                {
                    case line_split_kind::same_line:
                        stack_.emplace_back(container_type::array,options_.object_array_line_splits(),false,
                                            column_, column_ + open_array_bracket_str_.length());
                        break;
                    case line_split_kind::new_line:
                    {
                        stack_.emplace_back(container_type::array,options_.object_array_line_splits(),true,
                                            column_, column_+open_array_bracket_str_.length());
                        break;
                    }
                    default: // multi_line
                        stack_.emplace_back(container_type::array,options_.object_array_line_splits(),true,
                                            column_, column_+open_array_bracket_str_.length());
                        break;
                }
            }
            else // array
            {
                switch (options_.array_array_line_splits())
                {
                    case line_split_kind::same_line:
                        if (stack_.back().is_multi_line())
                        {
                            stack_.back().new_line_after(true);
                            new_line();
                        }
                        stack_.emplace_back(container_type::array,options_.array_array_line_splits(), false,
                                            column_, column_+open_array_bracket_str_.length());
                        break;
                    case line_split_kind::new_line:
                        stack_.back().new_line_after(true);
                        new_line();
                        stack_.emplace_back(container_type::array,options_.array_array_line_splits(), false,
                                            column_, column_+open_array_bracket_str_.length());
                        break;
                    default: // multi_line
                        stack_.back().new_line_after(true);
                        new_line();
                        stack_.emplace_back(container_type::array,options_.array_array_line_splits(), false,
                                            column_, column_+open_array_bracket_str_.length());
                        //new_line();
                        break;
                }
            }
        }
        else 
        {
            stack_.emplace_back(container_type::array, line_split_kind::multi_line, false,
                                column_, column_+open_array_bracket_str_.length());
        }
        indent();
        sink_.append(open_array_bracket_str_.data(), open_array_bracket_str_.length());
        column_ += open_array_bracket_str_.length();
        return true;
    }

    bool visit_end_array(const ser_context&, std::error_code&) override
    {
        JSONCONS_ASSERT(!stack_.empty());
        --nesting_depth_;

        unindent();
        if (stack_.back().new_line_after())
        {
            new_line();
        }
        stack_.pop_back();
        sink_.append(close_array_bracket_str_.data(), close_array_bracket_str_.length());
        column_ += close_array_bracket_str_.length();
        end_value();
        return true;
    }

    bool visit_key(const string_view_type& name, const ser_context&, std::error_code&) override
    {
        JSONCONS_ASSERT(!stack_.empty());
        if (stack_.back().count() > 0)
        {
            sink_.append(comma_str_.data(),comma_str_.length());
            column_ += comma_str_.length();
        }

        if (stack_.back().is_multi_line())
        {
            stack_.back().new_line_after(true);
            new_line();
        }
        else if (stack_.back().count() > 0 && column_ >= options_.line_length_limit())
        {
            //stack_.back().new_line_after(true);
            new_line(stack_.back().data_pos());
        }

        if (stack_.back().count() == 0)
        {
            stack_.back().set_position(column_);
        }
        sink_.push_back('\"');
        std::size_t length = jsoncons::detail::escape_string(name.data(), name.length(),options_.escape_all_non_ascii(),options_.escape_solidus(),sink_);
        sink_.push_back('\"');
        sink_.append(colon_str_.data(),colon_str_.length());
        column_ += (length+2+colon_str_.length());
        return true;
    }

    bool visit_null(semantic_tag, const ser_context&, std::error_code&) override
    {
        if (!stack_.empty()) 
        {
            if (stack_.back().is_array())
            {
                begin_scalar_value();
            }
            if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit())
            {
                break_line();
            }
        }

        sink_.append(null_k().data(), null_k().size());
        column_ += null_k().size();

        end_value();
        return true;
    }

    bool visit_string(const string_view_type& sv, semantic_tag tag, const ser_context&, std::error_code&) override
    {
        if (!stack_.empty()) 
        {
            if (stack_.back().is_array())
            {
                begin_scalar_value();
            }
            if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit())
            {
                break_line();
            }
        }

        switch (tag)
        {
            case semantic_tag::bigint:
                write_bigint_value(sv);
                break;
            default:
            {
                sink_.push_back('\"');
                std::size_t length = jsoncons::detail::escape_string(sv.data(), sv.length(),options_.escape_all_non_ascii(),options_.escape_solidus(),sink_);
                sink_.push_back('\"');
                column_ += (length+2);
                break;
            }
        }

        end_value();
        return true;
    }

    bool visit_byte_string(const byte_string_view& b, 
                              semantic_tag tag,
                              const ser_context&,
                              std::error_code&) override
    {
        if (!stack_.empty()) 
        {
            if (stack_.back().is_array())
            {
                begin_scalar_value();
            }
            if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit())
            {
                break_line();
            }
        }

        byte_string_chars_format encoding_hint;
        switch (tag)
        {
            case semantic_tag::base16:
                encoding_hint = byte_string_chars_format::base16;
                break;
            case semantic_tag::base64:
                encoding_hint = byte_string_chars_format::base64;
                break;
            case semantic_tag::base64url:
                encoding_hint = byte_string_chars_format::base64url;
                break;
            default:
                encoding_hint = byte_string_chars_format::none;
                break;
        }

        byte_string_chars_format format = jsoncons::detail::resolve_byte_string_chars_format(options_.byte_string_format(), 
                                                                                             encoding_hint, 
                                                                                             byte_string_chars_format::base64url);
        switch (format)
        {
            case byte_string_chars_format::base16:
            {
                sink_.push_back('\"');
                std::size_t length = encode_base16(b.begin(),b.end(),sink_);
                sink_.push_back('\"');
                column_ += (length + 2);
                break;
            }
            case byte_string_chars_format::base64:
            {
                sink_.push_back('\"');
                std::size_t length = encode_base64(b.begin(), b.end(), sink_);
                sink_.push_back('\"');
                column_ += (length + 2);
                break;
            }
            case byte_string_chars_format::base64url:
            {
                sink_.push_back('\"');
                std::size_t length = encode_base64url(b.begin(),b.end(),sink_);
                sink_.push_back('\"');
                column_ += (length + 2);
                break;
            }
            default:
            {
                JSONCONS_UNREACHABLE();
            }
        }

        end_value();
        return true;
    }

    bool visit_double(double value, 
                         semantic_tag,
                         const ser_context& context,
                         std::error_code& ec) override
    {
        if (!stack_.empty()) 
        {
            if (stack_.back().is_array())
            {
                begin_scalar_value();
            }
            if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit())
            {
                break_line();
            }
        }

        if (!std::isfinite(value))
        {
            if ((std::isnan)(value))
            {
                if (options_.enable_nan_to_num())
                {
                    sink_.append(options_.nan_to_num().data(), options_.nan_to_num().length());
                    column_ += options_.nan_to_num().length();
                }
                else if (options_.enable_nan_to_str())
                {
                    visit_string(options_.nan_to_str(), semantic_tag::none, context, ec);
                }
                else
                {
                    sink_.append(null_k().data(), null_k().size());
                    column_ += null_k().size();
                }
            }
            else if (value == std::numeric_limits<double>::infinity())
            {
                if (options_.enable_inf_to_num())
                {
                    sink_.append(options_.inf_to_num().data(), options_.inf_to_num().length());
                    column_ += options_.inf_to_num().length();
                }
                else if (options_.enable_inf_to_str())
                {
                    visit_string(options_.inf_to_str(), semantic_tag::none, context, ec);
                }
                else
                {
                    sink_.append(null_k().data(), null_k().size());
                    column_ += null_k().size();
                }
            }
            else
            {
                if (options_.enable_neginf_to_num())
                {
                    sink_.append(options_.neginf_to_num().data(), options_.neginf_to_num().length());
                    column_ += options_.neginf_to_num().length();
                }
                else if (options_.enable_neginf_to_str())
                {
                    visit_string(options_.neginf_to_str(), semantic_tag::none, context, ec);
                }
                else
                {
                    sink_.append(null_k().data(), null_k().size());
                    column_ += null_k().size();
                }
            }
        }
        else
        {
            std::size_t length = fp_(value, sink_);
            column_ += length;
        }

        end_value();
        return true;
    }

    bool visit_int64(int64_t value, 
                        semantic_tag,
                        const ser_context&,
                        std::error_code&) override
    {
        if (!stack_.empty()) 
        {
            if (stack_.back().is_array())
            {
                begin_scalar_value();
            }
            if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit())
            {
                break_line();
            }
        }
        std::size_t length = jsoncons::detail::write_integer(value, sink_);
        column_ += length;
        end_value();
        return true;
    }

    bool visit_uint64(uint64_t value, 
                         semantic_tag, 
                         const ser_context&,
                         std::error_code&) override
    {
        if (!stack_.empty()) 
        {
            if (stack_.back().is_array())
            {
                begin_scalar_value();
            }
            if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit())
            {
                break_line();
            }
        }
        std::size_t length = jsoncons::detail::write_integer(value, sink_);
        column_ += length;
        end_value();
        return true;
    }

    bool visit_bool(bool value, semantic_tag, const ser_context&, std::error_code&) override
    {
        if (!stack_.empty()) 
        {
            if (stack_.back().is_array())
            {
                begin_scalar_value();
            }
            if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit())
            {
                break_line();
            }
        }

        if (value)
        {
            sink_.append(true_k().data(), true_k().size());
            column_ += true_k().size();
        }
        else
        {
            sink_.append(false_k().data(), false_k().size());
            column_ += false_k().size();
        }

        end_value();
        return true;
    }

    void begin_scalar_value()
    {
        if (!stack_.empty())
        {
            if (stack_.back().count() > 0)
            {
                sink_.append(comma_str_.data(),comma_str_.length());
                column_ += comma_str_.length();
            }
            if (stack_.back().is_multi_line() || stack_.back().is_indent_once())
            {
                stack_.back().new_line_after(true);
                new_line();
            }
        }
    }

    void write_bigint_value(const string_view_type& sv)
    {
        switch (options_.bigint_format())
        {
            case bigint_chars_format::number:
            {
                sink_.append(sv.data(),sv.size());
                column_ += sv.size();
                break;
            }
            case bigint_chars_format::base64:
            {
                bignum n(sv.data(), sv.length());
                int signum;
                std::vector<uint8_t> v;
                n.dump(signum, v);

                sink_.push_back('\"');
                if (signum == -1)
                {
                    sink_.push_back('~');
                    ++column_;
                }
                std::size_t length = encode_base64(v.begin(), v.end(), sink_);
                sink_.push_back('\"');
                column_ += (length+2);
                break;
            }
            case bigint_chars_format::base64url:
            {
                bignum n(sv.data(), sv.length());
                int signum;
                std::vector<uint8_t> v;
                n.dump(signum, v);

                sink_.push_back('\"');
                if (signum == -1)
                {
                    sink_.push_back('~');
                    ++column_;
                }
                std::size_t length = encode_base64url(v.begin(), v.end(), sink_);
                sink_.push_back('\"');
                column_ += (length+2);
                break;
            }
            default:
            {
                sink_.push_back('\"');
                sink_.append(sv.data(),sv.size());
                sink_.push_back('\"');
                column_ += (sv.size() + 2);
                break;
            }
        }
    }

    void end_value()
    {
        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
    }

    void indent()
    {
        indent_amount_ += static_cast<int>(options_.indent_size());
    }

    void unindent()
    {
        indent_amount_ -= static_cast<int>(options_.indent_size());
    }

    void new_line()
    {
        sink_.append(options_.new_line_chars().data(),options_.new_line_chars().length());
        for (int i = 0; i < indent_amount_; ++i)
        {
            sink_.push_back(' ');
        }
        column_ = indent_amount_;
    }

    void new_line(std::size_t len)
    {
        sink_.append(options_.new_line_chars().data(),options_.new_line_chars().length());
        for (std::size_t i = 0; i < len; ++i)
        {
            sink_.push_back(' ');
        }
        column_ = len;
    }

    void break_line()
    {
        stack_.back().new_line_after(true);
        new_line();
    }
};

template<class CharT,class Sink=jsoncons::stream_sink<CharT>,class Allocator=std::allocator<char>>
class basic_json_compressed_encoder final : public basic_json_visitor<CharT>
{
    static const std::array<CharT, 4>& null_k()
    {
        static constexpr std::array<CharT,4> k{'n','u','l','l'};
        return k;
    }
    static const std::array<CharT, 4>& true_k()
    {
        static constexpr std::array<CharT,4> k{'t','r','u','e'};
        return k;
    }
    static const std::array<CharT, 5>& false_k()
    {
        static constexpr std::array<CharT,5> k{'f','a','l','s','e'};
        return k;
    }
public:
    using allocator_type = Allocator;
    using char_type = CharT;
    using typename basic_json_visitor<CharT>::string_view_type;
    using sink_type = Sink;
    using string_type = typename basic_json_encode_options<CharT>::string_type;

private:
    enum class container_type {object, array};

    class encoding_context
    {
        container_type type_;
        std::size_t count_;
    public:
        encoding_context(container_type type)
           : type_(type), count_(0)
        {
        }

        std::size_t count() const
        {
            return count_;
        }

        void increment_count()
        {
            ++count_;
        }

        bool is_array() const
        {
            return type_ == container_type::array;
        }
    };
    typedef typename std::allocator_traits<allocator_type>:: template rebind_alloc<encoding_context> encoding_context_allocator_type;

    Sink sink_;
    basic_json_encode_options<CharT> options_;
    jsoncons::detail::write_double fp_;
    std::vector<encoding_context,encoding_context_allocator_type> stack_;
    int nesting_depth_;

    // Noncopyable
    basic_json_compressed_encoder(const basic_json_compressed_encoder&) = delete;
    basic_json_compressed_encoder& operator=(const basic_json_compressed_encoder&) = delete;
public:
    basic_json_compressed_encoder(Sink&& sink, 
                                  const Allocator& alloc = Allocator())
        : basic_json_compressed_encoder(std::forward<Sink>(sink), basic_json_encode_options<CharT>(), alloc)
    {
    }

    basic_json_compressed_encoder(Sink&& sink, 
                                  const basic_json_encode_options<CharT>& options, 
                                  const Allocator& alloc = Allocator())
       : sink_(std::forward<Sink>(sink)),
         options_(options),
         fp_(options.float_format(), options.precision()),
         stack_(alloc),
         nesting_depth_(0)          
    {
    }

    basic_json_compressed_encoder(basic_json_compressed_encoder&&) = default;
    basic_json_compressed_encoder& operator=(basic_json_compressed_encoder&&) = default;

    ~basic_json_compressed_encoder() noexcept
    {
        JSONCONS_TRY
        {
            sink_.flush();
        }
        JSONCONS_CATCH(...)
        {
        }
    }


private:
    // Implementing methods
    void visit_flush() override
    {
        sink_.flush();
    }

    bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) override
    {
        if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth()))
        {
            ec = json_errc::max_nesting_depth_exceeded;
            return false;
        } 
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }

        stack_.emplace_back(container_type::object);
        sink_.push_back('{');
        return true;
    }

    bool visit_end_object(const ser_context&, std::error_code&) override
    {
        JSONCONS_ASSERT(!stack_.empty());
        --nesting_depth_;

        stack_.pop_back();
        sink_.push_back('}');

        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
        return true;
    }


    bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) override
    {
        if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth()))
        {
            ec = json_errc::max_nesting_depth_exceeded;
            return false;
        } 
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }
        stack_.emplace_back(container_type::array);
        sink_.push_back('[');
        return true;
    }

    bool visit_end_array(const ser_context&, std::error_code&) override
    {
        JSONCONS_ASSERT(!stack_.empty());
        --nesting_depth_;

        stack_.pop_back();
        sink_.push_back(']');
        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
        return true;
    }

    bool visit_key(const string_view_type& name, const ser_context&, std::error_code&) override
    {
        if (!stack_.empty() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }

        sink_.push_back('\"');
        jsoncons::detail::escape_string(name.data(), name.length(),options_.escape_all_non_ascii(),options_.escape_solidus(),sink_);
        sink_.push_back('\"');
        sink_.push_back(':');
        return true;
    }

    bool visit_null(semantic_tag, const ser_context&, std::error_code&) override
    {
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }

        sink_.append(null_k().data(), null_k().size());

        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
        return true;
    }

    void write_bigint_value(const string_view_type& sv)
    {
        switch (options_.bigint_format())
        {
            case bigint_chars_format::number:
            {
                sink_.append(sv.data(),sv.size());
                break;
            }
            case bigint_chars_format::base64:
            {
                bignum n(sv.data(), sv.length());
                int signum;
                std::vector<uint8_t> v;
                n.dump(signum, v);

                sink_.push_back('\"');
                if (signum == -1)
                {
                    sink_.push_back('~');
                }
                encode_base64(v.begin(), v.end(), sink_);
                sink_.push_back('\"');
                break;
            }
            case bigint_chars_format::base64url:
            {
                bignum n(sv.data(), sv.length());
                int signum;
                std::vector<uint8_t> v;
                n.dump(signum, v);

                sink_.push_back('\"');
                if (signum == -1)
                {
                    sink_.push_back('~');
                }
                encode_base64url(v.begin(), v.end(), sink_);
                sink_.push_back('\"');
                break;
            }
            default:
            {
                sink_.push_back('\"');
                sink_.append(sv.data(),sv.size());
                sink_.push_back('\"');
                break;
            }
        }
    }

    bool visit_string(const string_view_type& sv, semantic_tag tag, const ser_context&, std::error_code&) override
    {
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }

        switch (tag)
        {
            case semantic_tag::bigint:
                write_bigint_value(sv);
                break;
            default:
            {
                sink_.push_back('\"');
                jsoncons::detail::escape_string(sv.data(), sv.length(),options_.escape_all_non_ascii(),options_.escape_solidus(),sink_);
                sink_.push_back('\"');
                break;
            }
        }

        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
        return true;
    }

    bool visit_byte_string(const byte_string_view& b, 
                              semantic_tag tag,
                              const ser_context&,
                              std::error_code&) override
    {
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }

        byte_string_chars_format encoding_hint;
        switch (tag)
        {
            case semantic_tag::base16:
                encoding_hint = byte_string_chars_format::base16;
                break;
            case semantic_tag::base64:
                encoding_hint = byte_string_chars_format::base64;
                break;
            case semantic_tag::base64url:
                encoding_hint = byte_string_chars_format::base64url;
                break;
            default:
                encoding_hint = byte_string_chars_format::none;
                break;
        }

        byte_string_chars_format format = jsoncons::detail::resolve_byte_string_chars_format(options_.byte_string_format(), 
                                                                                   encoding_hint, 
                                                                                   byte_string_chars_format::base64url);
        switch (format)
        {
            case byte_string_chars_format::base16:
            {
                sink_.push_back('\"');
                encode_base16(b.begin(),b.end(),sink_);
                sink_.push_back('\"');
                break;
            }
            case byte_string_chars_format::base64:
            {
                sink_.push_back('\"');
                encode_base64(b.begin(), b.end(), sink_);
                sink_.push_back('\"');
                break;
            }
            case byte_string_chars_format::base64url:
            {
                sink_.push_back('\"');
                encode_base64url(b.begin(),b.end(),sink_);
                sink_.push_back('\"');
                break;
            }
            default:
            {
                JSONCONS_UNREACHABLE();
            }
        }

        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
        return true;
    }

    bool visit_double(double value, 
                         semantic_tag,
                         const ser_context& context,
                         std::error_code& ec) override
    {
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }

        if (JSONCONS_UNLIKELY(!std::isfinite(value)))
        {
            if ((std::isnan)(value))
            {
                if (options_.enable_nan_to_num())
                {
                    sink_.append(options_.nan_to_num().data(), options_.nan_to_num().length());
                }
                else if (options_.enable_nan_to_str())
                {
                    visit_string(options_.nan_to_str(), semantic_tag::none, context, ec);
                }
                else
                {
                    sink_.append(null_k().data(), null_k().size());
                }
            }
            else if (value == std::numeric_limits<double>::infinity())
            {
                if (options_.enable_inf_to_num())
                {
                    sink_.append(options_.inf_to_num().data(), options_.inf_to_num().length());
                }
                else if (options_.enable_inf_to_str())
                {
                    visit_string(options_.inf_to_str(), semantic_tag::none, context, ec);
                }
                else
                {
                    sink_.append(null_k().data(), null_k().size());
                }
            }
            else 
            {
                if (options_.enable_neginf_to_num())
                {
                    sink_.append(options_.neginf_to_num().data(), options_.neginf_to_num().length());
                }
                else if (options_.enable_neginf_to_str())
                {
                    visit_string(options_.neginf_to_str(), semantic_tag::none, context, ec);
                }
                else
                {
                    sink_.append(null_k().data(), null_k().size());
                }
            }
        }
        else
        {
            fp_(value, sink_);
        }

        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
        return true;
    }

    bool visit_int64(int64_t value, 
                        semantic_tag,
                        const ser_context&,
                        std::error_code&) override
    {
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }
        jsoncons::detail::write_integer(value, sink_);
        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
        return true;
    }

    bool visit_uint64(uint64_t value, 
                         semantic_tag, 
                         const ser_context&,
                         std::error_code&) override
    {
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }
        jsoncons::detail::write_integer(value, sink_);
        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
        return true;
    }

    bool visit_bool(bool value, semantic_tag, const ser_context&, std::error_code&) override
    {
        if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0)
        {
            sink_.push_back(',');
        }

        if (value)
        {
            sink_.append(true_k().data(), true_k().size());
        }
        else
        {
            sink_.append(false_k().data(), false_k().size());
        }

        if (!stack_.empty())
        {
            stack_.back().increment_count();
        }
        return true;
    }
};

using json_stream_encoder = basic_json_encoder<char,jsoncons::stream_sink<char>>;
using wjson_stream_encoder = basic_json_encoder<wchar_t,jsoncons::stream_sink<wchar_t>>;
using json_compressed_stream_encoder = basic_json_compressed_encoder<char,jsoncons::stream_sink<char>>;
using wjson_compressed_stream_encoder = basic_json_compressed_encoder<wchar_t,jsoncons::stream_sink<wchar_t>>;

using json_string_encoder = basic_json_encoder<char,jsoncons::string_sink<std::string>>;
using wjson_string_encoder = basic_json_encoder<wchar_t,jsoncons::string_sink<std::wstring>>;
using json_compressed_string_encoder = basic_json_compressed_encoder<char,jsoncons::string_sink<std::string>>;
using wjson_compressed_string_encoder = basic_json_compressed_encoder<wchar_t,jsoncons::string_sink<std::wstring>>;

#if !defined(JSONCONS_NO_DEPRECATED)
template<class CharT,class Sink=jsoncons::stream_sink<CharT>>
using basic_json_serializer = basic_json_encoder<CharT,Sink>; 

template<class CharT,class Sink=jsoncons::stream_sink<CharT>>
using basic_json_compressed_serializer = basic_json_compressed_encoder<CharT,Sink>; 

JSONCONS_DEPRECATED_MSG("Instead, use json_stream_encoder") typedef json_stream_encoder json_encoder;
JSONCONS_DEPRECATED_MSG("Instead, use wjson_stream_encoder") typedef wjson_stream_encoder wjson_encoder;
JSONCONS_DEPRECATED_MSG("Instead, use json_compressed_stream_encoder") typedef json_compressed_stream_encoder json_compressed_encoder;
JSONCONS_DEPRECATED_MSG("Instead, use wjson_compressed_stream_encoder") typedef wjson_compressed_stream_encoder wjson_compressed_encoder;

JSONCONS_DEPRECATED_MSG("Instead, use json_stream_encoder") typedef basic_json_encoder<char,jsoncons::stream_sink<char>> json_serializer;
JSONCONS_DEPRECATED_MSG("Instead, use wjson_stream_encoder") typedef basic_json_encoder<wchar_t,jsoncons::stream_sink<wchar_t>> wjson_serializer;

JSONCONS_DEPRECATED_MSG("Instead, use json_compressed_stream_encoder")  typedef basic_json_compressed_encoder<char,jsoncons::stream_sink<char>> json_compressed_serializer;
JSONCONS_DEPRECATED_MSG("Instead, use wjson_compressed_stream_encoder") typedef basic_json_compressed_encoder<wchar_t,jsoncons::stream_sink<wchar_t>> wjson_compressed_serializer;

JSONCONS_DEPRECATED_MSG("Instead, use json_string_encoder")  typedef basic_json_encoder<char,jsoncons::string_sink<std::string>> json_string_serializer;
JSONCONS_DEPRECATED_MSG("Instead, use wjson_string_encoder") typedef basic_json_encoder<wchar_t,jsoncons::string_sink<std::wstring>> wjson_string_serializer;

JSONCONS_DEPRECATED_MSG("Instead, use json_compressed_string_encoder")  typedef basic_json_compressed_encoder<char,jsoncons::string_sink<std::string>> json_compressed_string_serializer;
JSONCONS_DEPRECATED_MSG("Instead, use wjson_compressed_string_encoder") typedef basic_json_compressed_encoder<wchar_t,jsoncons::string_sink<std::wstring>> wjson_compressed_string_serializer;
#endif

}
#endif
