import functools
from itertools import chain
from subprocess import getstatusoutput
from typing import Any, Callable, List, Iterable

__all__: List[str] = [
    "lru_cache",
    "run",
    "iter_parse_delimited_values",
]
lru_cache: Callable[..., Any] = functools.lru_cache
# For backwards compatibility:
cerberus: Any
try:
    from daves_dev_tools import cerberus
except ImportError:
    cerberus = None


def _iter_parse_delimited_value(value: str, delimiter: str) -> Iterable[str]:
    return value.split(delimiter)


def iter_parse_delimited_values(
    values: Iterable[str], delimiter: str = ","
) -> Iterable[str]:
    """
    This function iterates over input values which have been provided as a
    list or iterable and/or a single string of character-delimited values.
    A typical use-case is parsing multi-value command-line arguments.
    """
    if isinstance(values, str):
        values = (values,)

    def iter_parse_delimited_value_(value: str) -> Iterable[str]:
        return _iter_parse_delimited_value(value, delimiter=delimiter)

    return chain(*map(iter_parse_delimited_value_, values))


def run(command: str, echo: bool = True) -> str:
    """
    This function runs a shell command, raises an error if a non-zero
    exit code is returned, and echo's both the command and output *if*
    the `echo` parameter is `True`.

    Parameters:

    - command (str): A shell command
    - echo (bool) = True: If `True`, the command and the output from the
      command will be printed to stdout
    """
    if echo:
        print(command)
    status: int
    output: str
    status, output = getstatusoutput(command)
    # Create an error if a non-zero exit status is encountered
    if status:
        raise OSError(output if echo else f"$ {command}\n{output}")
    else:
        output = output.strip()
        if output and echo:
            print(output)
    return output
