/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2010-2018 Danny Robson <danny@nerdcruft.net>
 */

#pragma once

namespace cruft::iterator {
    ///////////////////////////////////////////////////////////////////////////
    /// Adapts a supplied iterator by dereferencing any operations that deal
    /// with the underlying value_type of the supplied iterator.
    template <typename IteratorT>
    struct dereference_adapter {
        //---------------------------------------------------------------------
        using difference_type = typename std::iterator_traits<IteratorT>::difference_type;
        using iterator_category = typename std::iterator_traits<IteratorT>::iterator_category;

        using value_type = std::remove_reference_t<
            decltype(
                *std::declval<
                    typename std::iterator_traits<IteratorT>::value_type
                > ()
            )
        >;

        using pointer = value_type*;
        using reference = value_type&;


        //---------------------------------------------------------------------
        dereference_adapter (IteratorT _cursor)
           : m_cursor (_cursor)
        { ; }


        //---------------------------------------------------------------------
        // Trivial conditional operations
        decltype(auto)
        operator== (dereference_adapter const &rhs) const
        {
            return m_cursor == rhs.m_cursor;
        }


        decltype(auto)
        operator!= (dereference_adapter const &rhs) const
        {
            return !(*this == rhs);
        }


        decltype(auto)
        operator<= (dereference_adapter const &rhs) const
        {
            return m_cursor <= rhs.m_cursor;
        }


        //---------------------------------------------------------------------
        // Iterator movement operations
        dereference_adapter&
        operator++ ()
        {
            ++m_cursor;
            return *this;
        }


        difference_type
        operator- (dereference_adapter const &rhs) const
        {
            return m_cursor - rhs.m_cursor;
        }


        auto
        operator+ (difference_type offset) const
        {
            return dereference_adapter { m_cursor + offset };
        }


        //---------------------------------------------------------------------
        // value_type operations
        decltype(auto)
        operator= (value_type const &rhs)
        {
            return **m_cursor = rhs;
        }

        decltype(auto) operator*  () { return **m_cursor; }
        decltype(auto) operator-> () { return **m_cursor; }

        decltype(auto) operator*  () const { return **m_cursor; }
        decltype(auto) operator-> () const { return **m_cursor; }


    private:
        IteratorT m_cursor;
    };
}