#!/usr/bin/env python

import sys
import logging
import posixpath
import shutil
import tempfile
import time

from datetime import datetime, timedelta
from optparse import OptionParser
from subprocess import Popen, PIPE


DEFAULT_REPEAT = 0
DEFAULT_TIMEOUT = 0
DEFAULT_WAIT = 1

COMMAND_TEMPLATE = "diff %(options)s %(filename1)s %(filename2)s"


class WatcherException(Exception):

    pass


class Watcher(object):

    def __init__(self, command, shell=False, options=[]):
        self.command = command
        self.shell = shell
        self.options = options

        self.directory = tempfile.mkdtemp(prefix="watcher")
        self.filename1 = posixpath.join(self.directory, "filename1")
        self.filename2 = posixpath.join(self.directory, "filename2")

    def __del__(self):
        shutil.rmtree(self.directory)

    @property
    def filename(self):
        if not posixpath.exists(self.filename1):
            return self.filename1
        else:
            return self.filename2

    def watch(self):
        stdout = open(self.filename, "w")
        process = Popen(self.command, shell=self.shell, stdout=stdout, stderr=PIPE)
        status = process.wait()
        stdout.close()

        if status:
            command_string = " ".join(self.command)
            stderr_string = process.stderr.read()
            raise WatcherException, "Command failed '%s':\n%s" % (command_string, stderr_string)

        if posixpath.exists(self.filename2):
            command = COMMAND_TEMPLATE % {
                "options": " ".join(self.options),
                "filename1": self.filename1,
                "filename2": self.filename2}

            process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
            if process.wait():
                return process.stdout.read()


class Sentinel(object):

    def __init__(self, repeat=0, timeout=0):
        self.repeat = repeat
        self.timeout = timeout

        self.repeat_limit = 0
        self.timeout_limit = datetime.now() + timedelta(timeout)

    def check(self):
        if self.repeat and self.repeat_limit >= self.repeat:
            return False

        if self.timeout and datetime.now() >= self.timeout_limit:
            return False

        self.repeat_limit += 1
        return True


def main(args):
    usage = "Usage: %prog [OPTIONS] COMMAND"
    parser = OptionParser(usage=usage)
    parser.add_option("-o", "--option",
        action="append",
        type="string",
        default=[],
        help="Option to pass to the diff command")
    parser.add_option("-q", "--quiet",
        action="store_true",
        help="Do not output messages")
    parser.add_option("-r", "--repeat",
        type="int",
        default=DEFAULT_REPEAT,
        help="Number of times to repeat the command, repeats forever by default")
    parser.add_option("-s", "--shell",
        action="store_true",
        help="Run the command as a shell script")
    parser.add_option("-t", "--timeout",
        type="int",
        default=DEFAULT_TIMEOUT,
        help="Number of seconds to timeout in total, never times out by default")
    parser.add_option("-w", "--wait",
        type="int",
        default=DEFAULT_WAIT,
        help="Number of seconds to wait between commands, default %default")
    (options, args) = parser.parse_args(args)

    if not args:
        parser.error("Must specify a command")

    # Set logging
    format = '%(levelname)-8s %(message)s'
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter(format))
    logger = logging.getLogger()
    logger.addHandler(handler)

    if options.quiet:
        logger.setLevel(logging.CRITICAL)

    # Build watcher and sentinel to watch the command
    watcher = Watcher(args, options.shell, options.option)
    sentinel = Sentinel(options.repeat, options.timeout)

    while True:
        if not sentinel.check():
            logging.error("Watch expired")
            return 1

        # Check if the watcher detected a difference
        try:
            output = watcher.watch()
        except WatcherException, e:
            logging.error(str(e))
            return 2

        if output:
            if not options.quiet:
                print output
            return 0

        if options.wait:
            time.sleep(options.wait)

    # Failed to watch a difference
    return 1

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
