import operator
import itertools
from functools import reduce

import packaging.version


def find(pred, items):
    """
    Find the index of the first element in items for which pred returns
    True

    >>> find(lambda x: x > 3, range(100))
    4
    >>> find(lambda x: x < -3, range(100)) is None
    True
    """
    for i, item in enumerate(items):
        if pred(item):
            return i


def rfind(pred, items):
    """
    Find the index of the last element in items for which pred returns
    True. Returns a negative number useful for indexing from the end
    of a list or tuple.

    >>> rfind(lambda x: x > 3, [5,4,3,2,1])
    -4
    """
    return -find(pred, reversed(items)) - 1


class SummableVersion(packaging.version.Version):
    """
    A special version that can be added to another Version.

    >>> SummableVersion('1.1') + packaging.version.Version('2.3')
    <Version('3.4')>
    """

    def __add__(self, other):
        result = SummableVersion('1.0')
        result._version = result._version._replace(
            release=tuple(
                itertools.starmap(
                    operator.add,
                    itertools.zip_longest(
                        self._version.release,
                        other._version.release,
                        fillvalue=0,
                    ),
                ),
            ),
        )
        return SummableVersion(str(result))

    def reset_less_significant(self, significant_version):
        """
        Reset to zero all version info less significant than the
        indicated version.

        >>> ver = SummableVersion('3.1.2')
        >>> ver.reset_less_significant(SummableVersion('0.1'))
        >>> str(ver)
        '3.1'
        """

        def nonzero(x):
            return x != 0

        version_len = len(significant_version._version.release)
        significant_pos = rfind(nonzero, significant_version._version.release)
        significant_pos = version_len + significant_pos + 1
        new_release = self._version.release[:significant_pos] + (0,) * (
            version_len - significant_pos
        )
        self._version = self._version._replace(release=new_release)
        self.__init__(str(self))

    def as_number(self):
        """
        >>> round(SummableVersion('1.9.3').as_number(), 12)
        1.93
        """

        def combine(subver, ver):
            return subver / 10 + ver

        return reduce(combine, reversed(self._version.release))


class VersionManagement:
    """
    Version functions for RepoManager classes
    """

    increment = '0.0.1'

    @staticmethod
    def __versions_from_tags(tags):
        for tag in tags:
            try:
                yield packaging.version.Version(tag)
            except ValueError:
                pass

    @staticmethod
    def __best_version(versions):
        try:
            return max(versions)
        except ValueError:
            pass

    def get_valid_versions(self):
        """
        Return all version tags that can be represented by a Version.
        """
        return self.__versions_from_tags(tag.tag for tag in self.get_repo_tags())