// -*- mode: C++ -*-
// Copyright 2017 The Procyon Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef PN_FWD_
#define PN_FWD_

#include <pn/procyon.h>
#include <initializer_list>
#include <iterator>
#include <type_traits>

namespace pn {

class value;
class value_ref;
class value_cref;
class data_;
using data = data_;
class data_ref;
class data_view;
class string;
class string_ref;
class string_view;
class array;
class array_ref;
class array_cref;
class key_value;
class key_value_ref;
class key_value_cref;
class map;
class map_ref;
class map_cref;
class input;
class input_view;
class output;
class output_view;

template <typename P>
class byte_iterator;
class rune_iterator;

enum text_mode { binary, text };

using type = pn_type_t;

namespace internal {

template <typename Container>
class iterator {
    friend Container;

    using _is_const = std::is_const<Container>;
    using _val      = typename Container::value_type;
    using _ref      = typename Container::reference;
    using _cref     = typename Container::const_reference;
    using _ptr      = typename Container::pointer;
    using _cptr     = typename Container::const_pointer;

  public:
    using iterator_category = std::random_access_iterator_tag;
    using size_type         = int;
    using difference_type   = std::ptrdiff_t;
    using value_type        = typename std::conditional<_is_const::value, const _val, _val>::type;
    using reference         = typename std::conditional<_is_const::value, _cref, _ref>::type;
    using const_reference   = _cref;
    using pointer           = typename std::conditional<_is_const::value, _cptr, _ptr>::type;
    using const_pointer     = _cptr;

    iterator(const iterator<typename std::remove_const<Container>::type>& it) : _it{it._it} {}
    constexpr explicit iterator(pointer it) : _it{it} {}

    reference       operator*() { return *_it; }
    const_reference operator*() const { return *_it; }
    pointer         operator->() { return _it; }
    const_pointer   operator->() const { return _it; }

    iterator        operator+(difference_type n) const { return iterator{_it + n}; }
    iterator&       operator+=(difference_type n) { return _it += n, *this; }
    iterator        operator-(difference_type n) const { return iterator{_it - n}; }
    iterator&       operator-=(difference_type n) { return _it -= n, *this; }
    difference_type operator-(iterator other) const { return _it - other._it; }

    iterator& operator++() { return ++_it, *this; }
    iterator  operator++(int) { return iterator{_it++}; }
    iterator& operator--() { return --_it, *this; }
    iterator  operator--(int) { return iterator{_it--}; }

    bool operator==(iterator other) const { return _it == other._it; }
    bool operator!=(iterator other) const { return _it != other._it; }
    bool operator<(iterator other) const { return _it < other._it; }
    bool operator<=(iterator other) const { return _it <= other._it; }
    bool operator>(iterator other) const { return _it > other._it; }
    bool operator>=(iterator other) const { return _it >= other._it; }

  private:
    template <typename C>
    friend class iterator;

    pointer _it;
};

template <typename Container>
class reverse_iterator {
    friend Container;

    using _is_const = std::is_const<Container>;
    using _val      = typename Container::value_type;
    using _ref      = typename Container::reference;
    using _cref     = typename Container::const_reference;
    using _ptr      = typename Container::pointer;
    using _cptr     = typename Container::const_pointer;

  public:
    using iterator_category = std::random_access_iterator_tag;
    using size_type         = int;
    using difference_type   = std::ptrdiff_t;
    using value_type        = typename std::conditional<_is_const::value, const _val, _val>::type;
    using reference         = typename std::conditional<_is_const::value, _cref, _ref>::type;
    using const_reference   = _cref;
    using pointer           = typename std::conditional<_is_const::value, _cptr, _ptr>::type;
    using const_pointer     = _cptr;

    reverse_iterator(const reverse_iterator<typename std::remove_const<Container>::type>& it)
            : _it{it._it} {}
    constexpr explicit reverse_iterator(pointer it) : _it{it} {}

    reference       operator*() { return *(_it - 1); }
    const_reference operator*() const { return *(_it - 1); }
    pointer         operator->() { return _it - 1; }
    const_pointer   operator->() const { return _it - 1; }

    reverse_iterator  operator+(difference_type n) const { return reverse_iterator{_it - n}; }
    reverse_iterator& operator+=(difference_type n) { return _it -= n, *this; }
    reverse_iterator  operator-(difference_type n) const { return reverse_iterator{_it + n}; }
    reverse_iterator& operator-=(difference_type n) { return _it += n, *this; }
    difference_type   operator-(reverse_iterator other) const { return other._it - _it; }

    reverse_iterator& operator++() { return --_it, *this; }
    reverse_iterator  operator++(int) { return reverse_iterator{_it--}; }
    reverse_iterator& operator--() { return ++_it, *this; }
    reverse_iterator  operator--(int) { return reverse_iterator{_it++}; }

    bool operator==(reverse_iterator other) const { return other._it == _it; }
    bool operator!=(reverse_iterator other) const { return other._it != _it; }
    bool operator<(reverse_iterator other) const { return other._it < _it; }
    bool operator<=(reverse_iterator other) const { return other._it <= _it; }
    bool operator>(reverse_iterator other) const { return other._it > _it; }
    bool operator>=(reverse_iterator other) const { return other._it >= _it; }

  private:
    template <typename C>
    friend class reverse_iterator;

    pointer _it;
};

template <typename ref>
class ptr {
  public:
    using c_obj_type      = typename ref::c_obj_type;
    using value_type      = typename ref::value_type;
    using reference       = ref;
    using difference_type = std::ptrdiff_t;

    ptr(c_obj_type* x) : _ref{x} {}
    template <typename T>
    ptr(T x) : _ref{x->c_obj()} {}

    const reference& operator*() const { return _ref; }
    const reference* operator->() const { return &_ref; }

    ptr             operator+(difference_type n) const { return ptr{_ref._c_obj + n}; }
    ptr&            operator+=(difference_type n) { return _ref._c_obj += n, *this; }
    ptr             operator-(difference_type n) const { return ptr{_ref._c_obj - n}; }
    ptr&            operator-=(difference_type n) { return _ref._c_obj -= n, *this; }
    difference_type operator-(ptr other) const { return _ref._c_obj - other._ref._c_obj; }

    ptr& operator++() { return ++_ref._c_obj, *this; }
    ptr  operator++(int) { return ptr{_ref._c_obj++}; }
    ptr& operator--() { return --_ref._c_obj, *this; }
    ptr  operator--(int) { return ptr{_ref._c_obj--}; }

    bool operator==(ptr other) const { return _ref._c_obj == other._ref._c_obj; }
    bool operator!=(ptr other) const { return _ref._c_obj != other._ref._c_obj; }
    bool operator<(ptr other) const { return _ref._c_obj < other._ref._c_obj; }
    bool operator<=(ptr other) const { return _ref._c_obj <= other._ref._c_obj; }
    bool operator>(ptr other) const { return _ref._c_obj > other._ref._c_obj; }
    bool operator>=(ptr other) const { return _ref._c_obj >= other._ref._c_obj; }

  private:
    reference _ref;
};

}  // namespace internal

using value_ptr      = internal::ptr<value_ref>;
using value_cptr     = internal::ptr<value_cref>;
using key_value_ptr  = internal::ptr<key_value_ref>;
using key_value_cptr = internal::ptr<key_value_cref>;

}  // namespace pn

#endif  // PN_FWD_
