import functools
import logging
import os
import subprocess


def quoted_cmd(args):
    ret = []
    needs_single = '!$(`"\n'
    needs_double = " *&'"
    for arg in args:
        singles = [i for i in needs_single if i in arg]
        doubles = [i for i in needs_double if i in arg]
        if len(singles) + len(doubles) == 0:
            ret.append(arg)
            continue
        if len(singles):
           arg.replace("'", "'\\''")
           ret.append("'%s'" % arg)
        else:
           ret.append('"%s"' % arg)
    return ' '.join(ret)


def runq(*args, **kwargs):
    kwargs.update({
        'stdout': subprocess.DEVNULL,
        'stderr': subprocess.DEVNULL,
        'verbose_on_failure': False,
    })
    return run(*args, **kwargs)


def run(
    args,
    env=None,
    check=True,
    shell=False,
    input=None,
    stderr=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stdin=subprocess.DEVNULL,
    verbose_on_failure=True,
    rcs=None,
    decode=True,
):
    rcs = rcs or []
    if shell:
        if isinstance(args, str):
            pcmd = quoted_cmd(["sh", '-c', args])
        else:
            pcmd = quoted_cmd(["sh", '-c'] + args)
    else:
        pcmd = quoted_cmd(args)

    try:
        logging.debug("Executing: %s", pcmd)
        if input:
            cp = subprocess.run(
                args, env=env, check=check, shell=shell,
                input=input,
                stdout=stdout, stderr=stderr)
        else:
            cp = subprocess.run(
                args, env=env, check=check, shell=shell,
                stdout=stdout, stderr=stderr, stdin=stdin)
        ret = cp
    except subprocess.CalledProcessError as e:
        not_captured = "[Not captured]\n"
        out = not_captured
        err = not_captured
        if stdout is subprocess.PIPE:
            out = e.stdout.decode(errors='replace')
        if stderr is subprocess.PIPE:
            err = e.stderr.decode(errors='replace')
        if e.returncode not in rcs:
            if verbose_on_failure:
                logging.error("Command exited %d: %s", e.returncode, pcmd)
                logging.error(
                    "stdout: %s",
                    out.replace("\n", "\n  "),
                )
                logging.error(
                    "stderr: %s",
                    err.replace("\n", "\n  "),
                )
            raise e
        ret = e
        pass

    if decode:
        return (
            decode_binary(ret.stdout),
            decode_binary(ret.stderr),
        )
    else:
        return (ret.stdout, ret.stderr)

def decode_binary(binary, verbose=True):
    try:
        return binary if binary is None else binary.decode('utf-8')
    except UnicodeDecodeError as e:
        if verbose:
            logging.warning("Failed to decode blob: %s", e)
            logging.warning("blob=%s", binary.decode(errors='replace'))
        return binary.decode('utf-8', errors='replace')

def run_quilt(args, env=None, **kwargs):
    """execute quilt without reading any configuration files

    Params:
    args: A list of string arguments to pass to quilt.
    env: A namespace object to use as the environment. If not specified,
    the calling environment is inherited.
    kwargs: passed directly to run().

    Returns:
    A tuple of stdout, stderr from the quilt process.
    Raises a subprocess.CalledProcessError exception on error.
    """
    cmd = ['quilt', '--quiltrc', '-'] + args
    return run(cmd, env=env, **kwargs)

def run_gbp(args, env=None, **kwargs):
    """execute gbp without reading any configuration files

    Params:
    args: A list of string arguments to pass to gbp.
    env: A namespace object to use as the environment. If not specified,
    the calling environment is inherited.
    kwargs: passed directly to run().

    Returns:
    A tuple of stdout, stderr from the gbp process.
    Raises a subprocess.CalledProcessError exception on error.
    """
    cmd = ['gbp'] + args
    if env:
        env_no_gbp_conf = env.copy()
    else:
        env_no_gbp_conf = os.environ.copy()
    env_no_gbp_conf['GBP_CONF_FILES'] = '/dev/null'

    return run(cmd, env=env_no_gbp_conf, **kwargs)
