# taken from: https://github.com/mahmoud/boltons/blob/master/boltons/iterutils.py
# extracted needed functions and removed python 2 support

# Copyright (c) 2013, Mahmoud Hashemi
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#
#    * Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials provided
#      with the distribution.
#
#    * The names of the contributors may not be used to endorse or
#      promote products derived from this software without specific
#      prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import itertools
from typing import Iterable, Iterator, TypeVar

T = TypeVar("T")


def windowed_iter(src: Iterable[T], size: int) -> Iterator[tuple[T, ...]]:
    """Returns tuples with length *size* which represent a sliding
    window over iterable *src*.
    >>> list(windowed_iter(range(7), 3))
    [(0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)]
    If the iterable is too short to make a window of length *size*,
    then no window tuples are returned.
    >>> list(windowed_iter(range(3), 5))
    []
    """
    tees = itertools.tee(src, size)
    try:
        for i, t in enumerate(tees):
            for _ in range(i):
                next(t)
    except StopIteration:
        return zip([])
    return zip(*tees)


def pairwise_iter(src: Iterable[T]) -> Iterator[tuple[T, T]]:
    """Returns tuples of adjacent items from iterable *src*.
    >>> list(pairwise_iter(range(5)))
    [(0, 1), (1, 2), (2, 3), (3, 4)]
    If the iterable is too short to make a window of length 2,
    then no window tuples are returned.
    >>> list(pairwise_iter(range(1)))
    []
    """
    return windowed_iter(src, 2)
