"""
Helpful generators and other tools
"""

import fnmatch
import re

import salt.utils.files


def split(orig, sep=None):
    """
    Generator function for iterating through large strings, particularly useful
    as a replacement for str.splitlines().

    See http://stackoverflow.com/a/3865367
    """
    exp = re.compile(r"\s+" if sep is None else re.escape(sep))
    pos = 0
    length = len(orig)
    while True:
        match = exp.search(orig, pos)
        if not match:
            if pos < length or sep is not None:
                val = orig[pos:]
                if val:
                    # Only yield a value if the slice was not an empty string,
                    # because if it is then we've reached the end. This keeps
                    # us from yielding an extra blank value at the end.
                    yield val
            break
        if pos < match.start() or sep is not None:
            yield orig[pos : match.start()]
        pos = match.end()


def read_file(fh_, chunk_size=1048576):
    """
    Generator that reads chunk_size bytes at a time from a file/filehandle and
    yields it.
    """
    try:
        if chunk_size != int(chunk_size):
            raise ValueError
    except ValueError:
        raise ValueError("chunk_size must be an integer")
    try:
        while True:
            try:
                chunk = fh_.read(chunk_size)
            except AttributeError:
                # Open the file and re-attempt the read
                fh_ = salt.utils.files.fopen(fh_, "rb")  # pylint: disable=W8470
                chunk = fh_.read(chunk_size)
            if not chunk:
                break
            yield chunk
    finally:
        try:
            fh_.close()
        except AttributeError:
            pass


def fnmatch_multiple(candidates, pattern):
    """
    Convenience function which runs fnmatch.fnmatch() on each element of passed
    iterable. The first matching candidate is returned, or None if there is no
    matching candidate.
    """
    # Make sure that candidates is iterable to avoid a TypeError when we try to
    # iterate over its items.
    try:
        candidates_iter = iter(candidates)
    except TypeError:
        return None

    for candidate in candidates_iter:
        try:
            if fnmatch.fnmatch(candidate, pattern):
                return candidate
        except TypeError:
            pass
    return None
