"""The command line interface implementation"""
import os
import sys

import prysk
from prysk._process import execute

__all__ = ["runcli"]


def _prompt(question, answers, auto=None):
    """Write a prompt to stdout and ask for answer in stdin.

    answers should be a string, with each character a single
    answer. An uppercase letter is considered the default answer.

    If an invalid answer is given, this asks again until it gets a
    valid one.

    If auto is set, the question is answered automatically with the
    specified value.
    """
    default = [c for c in answers if c.isupper()]
    while True:
        sys.stdout.write(f"{question} [{answers}] ")
        sys.stdout.flush()
        if auto is not None:
            sys.stdout.write(auto + "\n")
            sys.stdout.flush()
            return auto

        answer = sys.stdin.readline().strip().lower()
        if not answer and default:
            return default[0]
        elif answer and answer in answers.lower():
            return answer


def _log(msg=None, verbosemsg=None, verbose=False):
    """Write msg to standard out and flush.

    If verbose is True, write verbosemsg instead.
    """
    if verbose:
        msg = verbosemsg
    if msg:
        if isinstance(msg, bytes):
            sys.stdout.buffer.write(msg)
        else:
            sys.stdout.write(msg)
        sys.stdout.flush()


def _patch(cmd, diff):
    """Run echo [lines from diff] | cmd -p0"""
    _, retcode = execute([cmd, "-p0"], stdin=b"".join(diff))
    return retcode == 0


def runcli(tests, quiet=False, verbose=False, patchcmd=None, answer=None):
    """Run tests with command line interface input/output.

    tests should be a sequence of 2-tuples containing the following:

        (test path, test function)

    This function yields a new sequence where each test function is wrapped
    with a function that handles CLI input/output.

    If quiet is True, diffs aren't printed. If verbose is True,
    filenames and status information are printed.

    If patchcmd is set, a prompt is written to stdout asking if
    changed output should be merged back into the original test. The
    answer is read from stdin. If 'y', the test is patched using patch
    based on the changed output.
    """
    total, skipped, failed = [0], [0], [0]

    for path, test in tests:

        def testwrapper():
            """Test function that adds CLI output"""
            total[0] += 1
            _log(None, path + b": ", verbose)

            refout, postout, diff = test()
            if refout is None:
                skipped[0] += 1
                _log("s", "empty\n", verbose)
                return refout, postout, diff

            abspath = os.path.abspath(path)
            errpath = abspath + b".err"

            if postout is None:
                skipped[0] += 1
                _log("s", "skipped\n", verbose)
            elif not diff:
                _log(".", "passed\n", verbose)
                if os.path.exists(errpath):
                    os.remove(errpath)
            else:
                failed[0] += 1
                _log("!", "failed\n", verbose)
                if not quiet:
                    _log("\n", None, verbose)

                with open(errpath, "wb") as errfile:
                    for line in postout:
                        errfile.write(line)

                if not quiet:
                    origdiff = diff
                    diff = []
                    for line in origdiff:
                        sys.stdout.buffer.write(line)
                        diff.append(line)

                    if patchcmd and _prompt("Accept this change?", "yN", answer) == "y":
                        if _patch(patchcmd, diff):
                            _log(None, path + b": merged output\n", verbose)
                            os.remove(errpath)
                        else:
                            _log(path + b": merge failed\n")

            return refout, postout, diff

        yield path, testwrapper

    if total[0] > 0:
        _log("\n", None, verbose)
        _log(f"# Ran {total[0]} tests, {skipped[0]} skipped, {failed[0]} failed.\n")


def main():
    try:
        sys.exit(prysk.main(sys.argv[1:]))
    except (BrokenPipeError, KeyboardInterrupt):
        pass


if __name__ == "__main__":
    main()
