/***************************************************************************
* Copyright (c) 2016, Wolf Vollprecht, Johan Mabille and Sylvain Corlay    *
*                                                                          *
* Distributed under the terms of the BSD 3-Clause License.                 *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#ifndef XSIMD_ITERATOR_HPP
#define XSIMD_ITERATOR_HPP

namespace xsimd
{
    template <class B>
    class batch_proxy;

    template <class B>
    struct simd_batch_traits<batch_proxy<B>>
        : simd_batch_traits<B>
    {
    };

    template <class X>
    struct simd_batch_inner_types<batch_proxy<X>>
    {
        using batch_reference = X;
        using const_batch_reference = X;
    };

    /**
     * Aligned proxy that iterators can dereference to
     */
    template <class B>
    class batch_proxy : public simd_base<batch_proxy<B>>
    {
    public:

        using self_type = batch_proxy<B>;
        using base_type = simd_type<self_type>;
        using batch_reference = typename base_type::batch_reference;
        using const_batch_reference = typename base_type::const_batch_reference;
        using batch_type = B;
        using value_type = typename B::value_type;
        using pointer = value_type*;

        batch_proxy(pointer ptr);

        batch_reference get();
        const_batch_reference get() const;

        self_type& set(const batch_type& rhs);
        self_type& set(const self_type& rhs);

        operator batch_type() const;

        batch_proxy& operator=(const batch_type& rhs);

    private:

        value_type* m_ptr;
    };

    template <class X>
    struct is_proxy : std::false_type
    {
    };

    template <class X>
    struct is_proxy<batch_proxy<X>> : std::true_type
    {
    };

    template <class X>
    std::ostream& operator<<(std::ostream& os, const batch_proxy<X>& bp)
    {
        return os << bp.get();
    }

    template <class B>
    class aligned_iterator
    {
    public:

        using self_type = aligned_iterator<B>;
        using batch_type = B;
        using value_type = typename B::value_type;
        static constexpr std::size_t batch_size = B::size;

        using proxy_type = batch_proxy<B>;
        using pointer = value_type*;
        using reference = proxy_type;

        aligned_iterator(pointer memory);

        reference operator*();

        void operator++(int);
        aligned_iterator& operator++();

        bool equal(const aligned_iterator& rhs) const;

    private:

        pointer m_cur_pointer;
    };

    /******************************
     * batch proxy implementation *
     *****************************/

    template <class B>
    inline batch_proxy<B>::batch_proxy(pointer ptr)
        : m_ptr(ptr)
    {
    }

    template <class B>
    inline auto batch_proxy<B>::get() -> batch_reference
    {
        return batch_type(*this);
    }

    template <class B>
    inline auto batch_proxy<B>::get() const -> const_batch_reference
    {
        return batch_type(*this);
    }

    template <class B>
    inline auto batch_proxy<B>::set(const batch_type& rhs) -> self_type&
    {
        xsimd::store_aligned(m_ptr, rhs);
        return *this;
    }

    template <class B>
    inline auto batch_proxy<B>::set(const self_type& rhs) -> self_type&
    {
        xsimd::store_aligned(m_ptr, rhs.get());
        return *this;
    }

    template <class B>
    inline batch_proxy<B>::operator batch_type() const
    {
        batch_type m_reg;
        m_reg.load_aligned(m_ptr);
        return m_reg;
    }

    template <class B>
    inline auto batch_proxy<B>::operator=(const batch_type& rhs) -> batch_proxy&
    {
        xsimd::store_aligned(m_ptr, rhs);
        return *this;
    }

    /***********************************
     * aligned iterator implementation *
     ***********************************/

    template <class B>
    inline aligned_iterator<B>::aligned_iterator(pointer memory)
        : m_cur_pointer(memory)
    {
    }

    template <class B>
    inline auto aligned_iterator<B>::operator*() -> reference
    {
        return m_cur_pointer;
    }

    template <class B>
    inline aligned_iterator<B>& aligned_iterator<B>::operator++()
    {
        m_cur_pointer += batch_size;
        return *this;
    }

    template <class B>
    inline void aligned_iterator<B>::operator++(int)
    {
        m_cur_pointer += batch_size;
    }

    template <class B>
    inline bool aligned_iterator<B>::equal(const aligned_iterator& rhs) const
    {
        return m_cur_pointer == rhs.m_cur_pointer;
    }

    template <class B>
    inline bool operator==(const aligned_iterator<B>& lhs, const aligned_iterator<B>& rhs)
    {
        return lhs.equal(rhs);
    }

    template <class B>
    inline bool operator!=(const aligned_iterator<B>& lhs, const aligned_iterator<B>& rhs)
    {
        return !lhs.equal(rhs);
    }
}

#endif
