// Copyright 2018 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_STAJ_ITERATOR_HPP
#define JSONCONS_STAJ_ITERATOR_HPP

#include <new> // placement new
#include <memory>
#include <string>
#include <stdexcept>
#include <system_error>
#include <ios>
#include <iterator> // std::input_iterator_tag
#include <jsoncons/json_exception.hpp>
#include <jsoncons/staj_cursor.hpp>
#include <jsoncons/basic_json.hpp>
#include <jsoncons/decode_traits.hpp>

namespace jsoncons {

    template <class T, class Json>
    class staj_array_view;

    template<class T, class Json>
    class staj_array_iterator
    {
        using char_type = typename Json::char_type;

        staj_array_view<T, Json>* view_;
        std::exception_ptr eptr_;

    public:
        using value_type = T;
        using difference_type = std::ptrdiff_t;
        using pointer = T*;
        using reference = T&;
        using iterator_category = std::input_iterator_tag;

        staj_array_iterator() noexcept
            : view_(nullptr)
        {
        }

        staj_array_iterator(staj_array_view<T, Json>& view)
            : view_(std::addressof(view))
        {
            if (view_->cursor_->current().event_type() == staj_event_type::begin_array)
            {
                next();
            }
            else
            {
                view_->cursor_ = nullptr;
            }
        }

        staj_array_iterator(staj_array_view<T, Json>& view,
                            std::error_code& ec)
            : view_(std::addressof(view))
        {
            if (view_->cursor_->current().event_type() == staj_event_type::begin_array)
            {
                next(ec);
                if (ec) {view_ = nullptr;}
            }
            else
            {
                view_ = nullptr;
            }
        }

        ~staj_array_iterator() noexcept
        {
        }

        bool has_value() const
        {
            return !eptr_;
        }

        const T& operator*() const
        {
            if (eptr_)
            {
                 std::rethrow_exception(eptr_);
            }
            else
            {
                return *view_->value_;
            }
        }

        const T* operator->() const
        {
            if (eptr_)
            {
                 std::rethrow_exception(eptr_);
            }
            else
            {
                return view_->value_.operator->();
            }
        }

        staj_array_iterator& operator++()
        {
            next();
            return *this;
        }

        staj_array_iterator& increment(std::error_code& ec)
        {
            next(ec);
            if (ec) {view_ = nullptr;}
            return *this;
        }

        staj_array_iterator operator++(int) // postfix increment
        {
            staj_array_iterator temp(*this);
            next();
            return temp;
        }

        friend bool operator==(const staj_array_iterator& a, const staj_array_iterator& b)
        {
            return (!a.view_ && !b.view_)
                || (!a.view_ && b.done())
                || (!b.view_ && a.done());
        }

        friend bool operator!=(const staj_array_iterator& a, const staj_array_iterator& b)
        {
            return !(a == b);
        }

    private:

        bool done() const
        {
            return view_->cursor_->done() || view_->cursor_->current().event_type() == staj_event_type::end_array;
        }

        void next()
        {
            std::error_code ec;
            next(ec);
            if (ec)
            {
                JSONCONS_THROW(ser_error(ec, view_->cursor_->context().line(), view_->cursor_->context().column()));
            }
        }

        void next(std::error_code& ec)
        {
            using char_type = typename Json::char_type;

            if (!done())
            {
                view_->cursor_->next(ec);
                if (ec)
                {
                    return;
                }
                if (!done())
                {
                    eptr_ = std::exception_ptr();
                    JSONCONS_TRY
                    {
                        view_->value_ = decode_traits<T,char_type>::decode(*view_->cursor_, view_->decoder_, ec);
                    }
                    JSONCONS_CATCH(const conv_error&)
                    {
                        eptr_ = std::current_exception();
                    }
                }
            }
        }
    };

    template <class Key,class Json,class T=Json>
    class staj_object_view;

    template <class Key, class T, class Json>
    class staj_object_iterator
    {
        using char_type = typename Json::char_type;

        staj_object_view<Key, T, Json>* view_;
        std::exception_ptr eptr_;
    public:
        using key_type = std::basic_string<char_type>;
        using value_type = std::pair<key_type,T>;
        using difference_type = std::ptrdiff_t;
        using pointer = value_type*;
        using reference = value_type&;
        using iterator_category = std::input_iterator_tag;

    public:

        staj_object_iterator() noexcept
            : view_(nullptr)
        {
        }

        staj_object_iterator(staj_object_view<Key, T, Json>& view)
            : view_(std::addressof(view))
        {
            if (view_->cursor_->current().event_type() == staj_event_type::begin_object)
            {
                next();
            }
            else
            {
                view_ = nullptr;
            }
        }

        staj_object_iterator(staj_object_view<Key, T, Json>& view, 
                             std::error_code& ec)
            : view_(std::addressof(view))
        {
            if (view_->cursor_->current().event_type() == staj_event_type::begin_object)
            {
                next(ec);
                if (ec) {view_ = nullptr;}
            }
            else
            {
                view_ = nullptr;
            }
        }

        ~staj_object_iterator() noexcept
        {
        }

        bool has_value() const
        {
            return !eptr_;
        }

        const value_type& operator*() const
        {
            if (eptr_)
            {
                 std::rethrow_exception(eptr_);
            }
            else
            {
                return *view_->key_value_;
            }
        }

        const value_type* operator->() const
        {
            if (eptr_)
            {
                 std::rethrow_exception(eptr_);
            }
            else
            {
                return view_->key_value_.operator->();
            }
        }

        staj_object_iterator& operator++()
        {
            next();
            return *this;
        }

        staj_object_iterator& increment(std::error_code& ec)
        {
            next(ec);
            if (ec)
            {
                view_ = nullptr;
            }
            return *this;
        }

        staj_object_iterator operator++(int) // postfix increment
        {
            staj_object_iterator temp(*this);
            next();
            return temp;
        }

        friend bool operator==(const staj_object_iterator& a, const staj_object_iterator& b)
        {
            return (!a.view_ && !b.view_)
                   || (!a.view_ && b.done())
                   || (!b.view_ && a.done());
        }

        friend bool operator!=(const staj_object_iterator& a, const staj_object_iterator& b)
        {
            return !(a == b);
        }

    private:

        bool done() const
        {
            return view_->cursor_->done() || view_->cursor_->current().event_type() == staj_event_type::end_object;
        }

        void next()
        {
            std::error_code ec;
            next(ec);
            if (ec)
            {
                JSONCONS_THROW(ser_error(ec, view_->cursor_->context().line(), view_->cursor_->context().column()));
            }
        }

        void next(std::error_code& ec)
        {
            using char_type = typename Json::char_type;

            view_->cursor_->next(ec);
            if (ec)
            {
                return;
            }
            if (!done())
            {
                JSONCONS_ASSERT(view_->cursor_->current().event_type() == staj_event_type::key);
                auto key = view_->cursor_->current(). template get<key_type>();
                view_->cursor_->next(ec);
                if (ec)
                {
                    return;
                }
                if (!done())
                {
                    eptr_ = std::exception_ptr();
                    JSONCONS_TRY
                    {
                        view_->key_value_ = value_type(std::move(key),decode_traits<T,char_type>::decode(*view_->cursor_, view_->decoder_, ec));
                    }
                    JSONCONS_CATCH(const conv_error&)
                    {
                        eptr_ = std::current_exception();
                    }
                }
            }
        }
    };

    // staj_array_view

    template <class T, class Json>
    class staj_array_view
    {
        friend class staj_array_iterator<T, Json>;
    public:
        using char_type = typename Json::char_type;
        using iterator = staj_array_iterator<T, Json>;
    private:
        basic_staj_cursor<char_type>* cursor_;
        json_decoder<Json> decoder_;
        jsoncons::optional<T> value_;
    public:
        staj_array_view(basic_staj_cursor<char_type>& cursor) 
            : cursor_(std::addressof(cursor))
        {
        }

        iterator begin()
        {
            return staj_array_iterator<T, Json>(*this);
        }

        iterator end()
        {
            return staj_array_iterator<T, Json>();
        }
    };

    // staj_object_view

    template <class Key, class T, class Json>
    class staj_object_view
    {
        friend class staj_object_iterator<Key,T,Json>;
    public:
        using char_type = typename Json::char_type;
        using iterator = staj_object_iterator<Key,T,Json>;
        using key_type = std::basic_string<char_type>;
        using value_type = std::pair<key_type,T>;
    private:
        basic_staj_cursor<char_type>* cursor_;
        json_decoder<Json> decoder_;
        jsoncons::optional<value_type> key_value_;
    public:
        staj_object_view(basic_staj_cursor<char_type>& cursor) 
            : cursor_(std::addressof(cursor))
        {
        }

        iterator begin()
        {
            return staj_object_iterator<Key,T,Json>(*this);
        }

        iterator end()
        {
            return staj_object_iterator<Key,T,Json>();
        }
    };

    template <class T, class CharT, class Json=typename std::conditional<type_traits::is_basic_json<T>::value,T,basic_json<CharT>>::type>
    staj_array_view<T, Json> staj_array(basic_staj_cursor<CharT>& cursor)
    {
        return staj_array_view<T, Json>(cursor);
    }

    template <class Key, class T, class CharT, class Json=typename std::conditional<type_traits::is_basic_json<T>::value,T,basic_json<CharT>>::type>
    staj_object_view<Key, T, Json> staj_object(basic_staj_cursor<CharT>& cursor)
    {
        return staj_object_view<Key, T, Json>(cursor);
    }

#if !defined(JSONCONS_NO_DEPRECATED)
    template <class T, class CharT, class Json=typename std::conditional<type_traits::is_basic_json<T>::value,T,basic_json<CharT>>::type>
    JSONCONS_DEPRECATED_MSG("Instead, use staj_array()")
    staj_array_view<T, Json> make_array_iterator(basic_staj_cursor<CharT>& cursor)
    {
        return staj_array_view<T, Json>(cursor);
    }

    template <class T, class CharT, class Json=typename std::conditional<type_traits::is_basic_json<T>::value,T,basic_json<CharT>>::type>
    JSONCONS_DEPRECATED_MSG("Instead, use staj_object()")
    staj_object_view<std::basic_string<CharT>, T, Json> make_object_iterator(basic_staj_cursor<CharT>& cursor)
    {
        return staj_object_view<std::basic_string<CharT>, T, Json>(cursor);
    }
#endif

} // namespace jsoncons

#endif