"""Facilities for implementing hooks that call shell commands."""
from __future__ import print_function

import logging
import os

from subprocess import Popen, PIPE

from letsencrypt import errors

logger = logging.getLogger(__name__)

def validate_hooks(config):
    """Check hook commands are executable."""
    _validate_hook(config.pre_hook)
    _validate_hook(config.post_hook)
    _validate_hook(config.renew_hook)

def _prog(shell_cmd):
    """Extract the program run by a shell command"""
    cmd = _which(shell_cmd)
    return os.path.basename(cmd) if cmd else None

def _validate_hook(shell_cmd):
    """Check that a command provided as a hook is plausibly executable.

    :raises .errors.HookCommandNotFound: if the command is not found
    """
    cmd = shell_cmd.partition(" ")[0]
    if shell_cmd and not _prog(cmd):
        path = os.environ["PATH"]
        msg = "Unable to find command {0} in the PATH.\n(PATH is {1})".format(
            cmd, path)
        raise errors.HookCommandNotFound(msg)

    return True

def pre_hook(config):
    "Run pre-hook if it's defined and hasn't been run."
    if config.pre_hook and not pre_hook.already:
        logger.info("Running pre-hook command: %s", config.pre_hook)
        _run_hook(config.pre_hook)
        pre_hook.already = True

pre_hook.already = False

def post_hook(config, final=False):
    """Run post hook if defined.

    If the verb is renew, we might have more certs to renew, so we wait until
    we're called with final=True before actually doing anything.
    """
    if config.post_hook:
        if final or config.verb != "renew":
            logger.info("Running post-hook command: %s", config.post_hook)
            _run_hook(config.post_hook)

def renew_hook(config, domains, lineage_path):
    "Run post-renewal hook if defined."
    if config.renew_hook:
        if not config.dry_run:
            os.environ["RENEWED_DOMAINS"] = " ".join(domains)
            os.environ["RENEWED_LINEAGE"] = lineage_path
            _run_hook(config.renew_hook)
        else:
            print("Dry run: skipping renewal hook command: {0}".format(config.renew_hook))

def _run_hook(shell_cmd):
    """Run a hook command.

    :returns: stderr if there was any"""

    cmd = Popen(shell_cmd, shell=True, stdout=PIPE, stderr=PIPE, stdin=PIPE)
    _out, err = cmd.communicate()
    if cmd.returncode != 0:
        logger.error('Hook command "%s" returned error code %d', shell_cmd, cmd.returncode)
    if err:
        logger.error('Error output from %s:\n%s', _prog(shell_cmd), err)

def _which(program):
    """Test if program is in the path."""
    # Borrowed from:
    # https://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
    # XXX May need more porting to handle .exe extensions on Windows
    def _is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, _fname = os.path.split(program)
    if fpath:
        if _is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if _is_exe(exe_file):
                return exe_file

    return None

@@ -334,24 +334,31 @@
 fig.pre_hook
+, %22pre%22
 )%0A    _valid
@@ -378,24 +378,32 @@
 ig.post_hook
+, %22post%22
 )%0A    _valid
@@ -424,24 +424,33 @@
 g.renew_hook
+, %22renew%22
 )%0A%0Adef _prog
@@ -614,32 +614,43 @@
 e_hook(shell_cmd
+, hook_name
 ):%0A    %22%22%22Check 
@@ -925,16 +925,25 @@
 to find 
+%7B2%7D-hook 
 command 
@@ -1004,16 +1004,27 @@
 md, path
+, hook_name
 )%0A      

Make hook errors more helpful