// -----------------------------------------------------------------------------------------------------
// Copyright (c) 2006-2019, Knut Reinert & Freie Universität Berlin
// Copyright (c) 2016-2019, Knut Reinert & MPI für molekulare Genetik
// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
// -----------------------------------------------------------------------------------------------------

/*!\file
 * \author Joshua Kim <joshua.kim AT fu-berlin.de>
 * \brief Provides seqan3::view::interleave.
 */

#pragma once

#include <cmath>

#include <range/v3/view/chunk.hpp>

#include <seqan3/core/type_traits/pre.hpp>
#include <seqan3/core/type_traits/transformation_trait_or.hpp>
#include <seqan3/range/detail/random_access_iterator.hpp>
#include <seqan3/range/view/detail.hpp>
#include <seqan3/range/view/persist.hpp>
#include <seqan3/range/view/view_all.hpp>
#include <seqan3/std/concepts>
#include <seqan3/std/ranges>

namespace seqan3::detail
{

// ============================================================================
//  view_interleave
// ============================================================================

/*!\brief The type returned by seqan3::view::interleave.
 * \tparam urng_t The type of the underlying range, must model std::ranges::RandomAccessRange.
 * \implements std::ranges::View
 * \implements std::ranges::RandomAccessRange
 * \implements std::ranges::SizedRange
 * \ingroup view
 *
 * \details
 *
 * Note that most members of this class are generated by ranges::view_interface which is not yet documented here.
 */
template <std::ranges::RandomAccessRange urng_t, std::ranges::RandomAccessRange inserted_rng_t>
    //!\cond
    requires std::ranges::View<urng_t> && std::ranges::SizedRange<urng_t> &&
             std::ranges::View<inserted_rng_t> && std::ranges::SizedRange<inserted_rng_t> &&
             std::CommonReference<reference_t<urng_t>, reference_t<inserted_rng_t>>
    //!\endcond
class view_interleave : public std::ranges::view_interface<view_interleave<urng_t, inserted_rng_t>>
{
private:
    //!\brief The underlying range.
    urng_t urange;
    //!\brief The step size for the insertion.
    size_t step_size{};
    //!\brief The range to be inserted into urange.
    inserted_rng_t inserted_range;

public:
    /*!\name Associated types
     * \{
     */
    //!\brief The reference_type.
    using reference         = ranges::common_reference_t<reference_t<urng_t>, reference_t<inserted_rng_t>>;
    //!\brief The const_reference type is equal to the reference type.
    using const_reference   = detail::transformation_trait_or_t<
                                ranges::common_reference<reference_t<urng_t const>, reference_t<inserted_rng_t const>>, void>;
    //!\brief The value_type (which equals the reference_type with any references removed).
    using value_type        = value_type_t<urng_t>;
    //!\brief This resolves to range_type::size_type as the underlying range is guaranteed to be Sized.
    using size_type         = size_type_t<urng_t>;
    //!\brief A signed integer type, usually std::ptrdiff_t.
    using difference_type   = difference_type_t<urng_t>;
    //!\brief The iterator type of this view (a random access iterator).
    using iterator          = detail::random_access_iterator<view_interleave>;
    //!\brief The const_iterator type is equal to the iterator type.
    using const_iterator    = detail::random_access_iterator<view_interleave const>;
    //!\}

    /*!\name Constructors, destructor and assignment
     * \{
     */
    constexpr view_interleave()                                        noexcept = default; //!< Defaulted.
    constexpr view_interleave(view_interleave const & rhs)             noexcept = default; //!< Defaulted.
    constexpr view_interleave(view_interleave && rhs)                  noexcept = default; //!< Defaulted.
    constexpr view_interleave & operator=(view_interleave const & rhs) noexcept = default; //!< Defaulted.
    constexpr view_interleave & operator=(view_interleave && rhs)      noexcept = default; //!< Defaulted.
    ~view_interleave()                                                 noexcept = default; //!< Defaulted.

    /*!\brief Construct from a standard RandomAccessRange urange and inserted_range.
     * \param[in] _urange         The underlying range.
     * \param[in] _step_size      The step size for the insertion.
     * \param[in] _inserted_range The range to be inserted.
     */
    view_interleave(urng_t && _urange, size_t const _step_size, inserted_rng_t && _inserted_range) :
        urange{_urange}, step_size{_step_size}, inserted_range{_inserted_range}
    {}

    /*!\brief Construct from a ViewableRange urange and inserted_range by wrapping in a view::all.
     * \tparam    orng_t          A type that is the same as `urng_t` once wrapped in a view
     *                            (e.g. reference to container).
     * \tparam    oirng_t         A type that is the same as `inserted_rng_t` once wrapped in a view
     *                            (e.g. reference to container).
     * \param[in] _urange         The underlying range.
     * \param[in] _step_size      The step size for the insertion.
     * \param[in] _inserted_range The range to be inserted.
     */
    template <typename orng_t, typename oirng_t>
        //!\cond
        requires std::Constructible<urng_t, decltype(view::all(std::declval<orng_t>()))> &&
                 std::Constructible<inserted_rng_t, decltype(view::persist(std::declval<oirng_t>()))>
        //!\endcond
    view_interleave(orng_t && _urange, size_t const _step_size, oirng_t && _inserted_range) :
        view_interleave{view::all(std::forward<orng_t>(_urange)), _step_size, view::persist(std::forward<oirng_t>(_inserted_range))}
    {}
    //!\}

    /*!\name Iterators
     * \{
     */
    /*!\brief Returns an iterator to the first element of the container.
     * \returns Iterator to the first element.
     *
     * If the container is empty, the returned iterator will be equal to end().
     *
     * ### Complexity
     *
     * Constant.
     *
     * ### Exceptions
     *
     * No-throw guarantee.
     */
    iterator begin() noexcept
    {
        return {*this, 0};
    }

    //!\overload
    const_iterator begin() const noexcept
    {
        return {*this, 0};
    }

    //!\overload
    const_iterator cbegin() const noexcept
    {
        return begin();
    }

    /*!\brief Returns an iterator to the element following the last element of the container.
     * \returns Iterator to the first element.
     *
     * This element acts as a placeholder; attempting to dereference it results in undefined behaviour.
     *
     * ### Complexity
     *
     * Constant.
     *
     * ### Exceptions
     *
     * No-throw guarantee.
     */
    iterator end() noexcept
    {
        return {*this, size()};
    }

    //!\overload
    const_iterator end() const noexcept
    {
        return {*this, size()};
    }

    //!\overload
    const_iterator cend() const noexcept
    {
        return end();
    }
    //!\}

    /*!\brief Returns the number of elements in the view.
     * \returns The number of elements in the view.
     *
     * ### Complexity
     *
     * Constant.
     *
     */
    size_type size()
    {
        return std::ranges::size(urange) +
               ((std::floor(std::ranges::size(urange) / step_size) -
                (std::ranges::size(urange) % step_size == 0 ? 1 : 0)) *
                std::ranges::size(inserted_range));
    }

    //!\overload
    size_type size() const
    {
        return std::ranges::size(urange) +
               ((std::floor(std::ranges::size(urange) / step_size) -
                (std::ranges::size(urange) % step_size == 0 ? 1 : 0)) *
                std::ranges::size(inserted_range));
    }

    /*!\brief Return the i-th element.
     * \param i The element to retrieve.
     * \returns Either a writable proxy to the element or a copy (if called in const context).
     *
     * Accessing an element behind the last causes undefined behaviour. In debug mode an assertion checks the size of
     * the container.
     *
     * ### Complexity
     *
     * Constant.
     *
     * ### Exceptions
     *
     * No-throw guarantee.
     */
    reference operator[](size_type const i)
    {
        size_t combined_size = step_size + std::ranges::size(inserted_range);
        assert(i < size());
        if (i % (combined_size) < step_size)
            return urange[i - (std::floor(i/(combined_size)) * std::ranges::size(inserted_range))];
        else
            return inserted_range[(i % (combined_size)) - step_size];
    }

    //!\overload
    const_reference operator[](size_type const i) const
    {
        size_t combined_size = step_size + std::ranges::size(inserted_range);
        assert(i < size());
        if (i % (combined_size) < step_size)
            return urange[i - (std::floor(i/(combined_size)) * std::ranges::size(inserted_range))];
        else
            return inserted_range[(i % (combined_size)) - step_size];
    }
};

//!\brief Template argument type deduction guide for ViewableRange inputs.
//!\relates seqan3::detail::view_interleave
template <std::ranges::RandomAccessRange urng_t, std::ranges::RandomAccessRange inserted_rng_t>
    //!\cond
    requires std::ranges::ViewableRange<urng_t> && std::ranges::SizedRange<urng_t> &&
             std::ranges::SizedRange<inserted_rng_t> &&
             std::CommonReference<reference_t<urng_t>, reference_t<inserted_rng_t>>
    //!\endcond
view_interleave(urng_t &&, size_t, inserted_rng_t &&)
    -> view_interleave<decltype(view::all(std::declval<urng_t>())), decltype(view::persist(std::declval<inserted_rng_t>()))>;

// ============================================================================
//  interleave_fn (adaptor definition)
// ============================================================================

/*!\brief view::interleave's range adaptor object type (non-closure).
 */
struct interleave_fn
{
    //!\brief Store the argument and return a range adaptor closure object.
    template <std::ranges::ForwardRange inserted_rng_t, std::Integral size_type>
    constexpr auto operator()(size_type const size, inserted_rng_t && i) const noexcept
    {
        return detail::adaptor_from_functor{*this, size, std::forward<inserted_rng_t>(i)};
    }

    /*!\brief            Call the view's constructor with the underlying view as argument.
     * \param[in] urange The input range to process. Must model std::ranges::ForwardRange and std::ranges::ViewableRange.
     * \param[in] i      The inserted range to process. Must model std::ranges::ForwardRange.
     * \param[in] size   The step size for insertion into the input range.
     * \returns          A range of with the inserted range interleaved into the underlying range at the specified intervals.
     */
    template <std::ranges::Range urng_t, std::ranges::Range inserted_rng_t, std::Integral size_type>
    constexpr auto operator()(urng_t && urange, size_type const size, inserted_rng_t && i) const noexcept
    {
        static_assert(std::ranges::ForwardRange<urng_t>,
            "The underlying range parameter in view::interleave must model std::ranges::ForwardRange.");
        static_assert(std::ranges::ViewableRange<urng_t>,
            "The underlying range parameter in view::interleave must model std::ranges::ViewableRange.");
        static_assert(std::ranges::ForwardRange<inserted_rng_t>,
            "The range to be inserted by view::interleave must model std::ranges::ForwardRange.");
        if constexpr (std::ranges::RandomAccessRange<urng_t> && std::ranges::SizedRange<urng_t> &&
                      std::ranges::RandomAccessRange<inserted_rng_t> && std::ranges::SizedRange<inserted_rng_t>)
        {
            return detail::view_interleave{std::forward<urng_t>(urange),
                                           static_cast<size_t>(size),
                                           std::forward<inserted_rng_t>(i)};
        }
        else
        {
            return ranges::view::chunk(std::forward<urng_t>(urange), static_cast<size_t>(size))
                 | std::view::join(std::forward<inserted_rng_t>(i));
        }
    }
};

} // namespace seqan3::detail

// ============================================================================
//  view::interleave (adaptor instance definition)
// ============================================================================

namespace seqan3::view
{

/*!\name General purpose views
 * \{
 */

/*!\brief A view that interleaves a given range into another range at regular intervals.
 * \tparam urng_t The type of the range being processed.
 * \tparam inserted_rng_t The type of the range being inserted.
 * \param[in] urange The range being processed.
 * \param[in] inserted_range The range being inserted.
 * \param[in] step_size A value of size_type which indicates the interval to insert the inserted_range.
 * \returns A range with the second range inserted at regular intervals. See below for properties of said range.
 * \ingroup view
 *
 * \details
 *
 * This view can be used to insert one range into another range at regular intervals. It behaves essentially like
 * `| std::view::chunk(step_size) | std::view::join(inserted_range)` except that for input that models
 * std::ranges::RandomAccessRange and std::ranges::SizedRange a more efficient data structure is returned
 * (otherwise it returns exactly the above combination of views).
 *
 * **Header**
 * ```cpp
 *      #include <seqan3/range/view/interleave.hpp>
 * ```
 *
 * ### View properties
 *
 * | range concepts and reference_t  | `urng_t` (underlying range type)      | `rrng_t` (returned range type)  |
 * |---------------------------------|:-------------------------------------:|:-------------------------------:|
 * | std::ranges::InputRange         | *required*                            | *preserved*                     |
 * | std::ranges::ForwardRange       | *required*                            | *preserved*                     |
 * | std::ranges::BidirectionalRange | *required*                            | *preserved*                     |
 * | std::ranges::RandomAccessRange  | *required*                            | *preserved*                     |
 * | std::ranges::ContiguousRange    |                                       | *lost*                          |
 * |                                 |                                       |                                 |
 * | std::ranges::ViewableRange      | *required*                            | *guaranteed*                    |
 * | std::ranges::View               |                                       | *guaranteed*                    |
 * | std::ranges::SizedRange         | *required*                            | *preserved*                     |
 * | std::ranges::CommonRange        |                                       | *preserved*                     |
 * | std::ranges::OutputRange        |                                       | *preserved*                     |
 * | seqan3::ConstIterableRange      |                                       | *preserved*                     |
 * |                                 |                                       |                                 |
 * | seqan3::reference_t             |                                       | seqan3::reference_t<urng_t>     |
 *
 *
 * If above requirements are not met, this adaptor forwards to
 * `| ranges::view::chunk(step_size) | ranges::view::join(inserted_range)`
 * which returns a view with the following properties:
 *
 * | range concepts and reference_t  | `urng_t` (underlying range type)      | `rrng_t` (returned range type)  |
 * |---------------------------------|:-------------------------------------:|:-------------------------------:|
 * | std::ranges::InputRange         | *required*                            | *preserved*                     |
 * | std::ranges::ForwardRange       | *required*                            | *lost*                          |
 * | std::ranges::BidirectionalRange |                                       | *lost*                          |
 * | std::ranges::RandomAccessRange  |                                       | *lost*                          |
 * | std::ranges::ContiguousRange    |                                       | *lost*                          |
 * |                                 |                                       |                                 |
 * | std::ranges::ViewableRange      | *required*                            | *guaranteed*                    |
 * | std::ranges::View               |                                       | *guaranteed*                    |
 * | std::ranges::SizedRange         |                                       | *lost*                          |
 * | std::ranges::CommonRange        |                                       | *lost*                          |
 * | std::ranges::OutputRange        |                                       | *lost*                          |
 * | seqan3::ConstIterableRange      |                                       | *lost*                          |
 * |                                 |                                       |                                 |
 * | seqan3::reference_t             |                                       | seqan3::value_type_t<urng_t>    |
 *
 * * `urng_t` is the type of the range modified by this view (input).
 * * `rrng_type` is the type of the range returned by this view.
 * * for more details, see \ref view.
 *
 * ### Example
 *
 * \include test/snippet/range/view/interleave.cpp
 * \hideinitializer
 */
inline constexpr auto interleave = detail::interleave_fn{};

//!\}

} // namespace seqan3::view
