#!/usr/bin/python
# -*- coding: utf8 -*-
"""
The MIT License (MIT)

Copyright (c) 2014 SDElements

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import sys
import os
import logging
import listen
import subprocess
import tempfile


class Example(object):
    def __init__(self, verbose=True):

        # Create a logger for Example project
        console_handler = logging.StreamHandler()
        console_handler.setLevel(logging.DEBUG if verbose else logging.INFO)
        console_handler.setFormatter(logging.Formatter('%(levelname)s - %(message)s'))

        self.log = logging.getLogger('example')
        self.log.setLevel(logging.DEBUG)
        self.log.addHandler(console_handler)

        # A Popen object for any curretly running process
        self.external_running_process = None

        # A flag to indicate if a user aborted a step
        self.user_aborted = False

        # Create a signal handler
        self.sig_hand = listen.SignalHandler(self.log)

        # Create a temporary file
        self.tempfile = tempfile.mkstemp()
        self.log.info("Creating temporary file {0}".format(self.tempfile[1]))

    def run_tests(self):
        """
        Runs test suite
        """
        self.log.info("Running tests... Pass SIGINFO (ctrl-t) to skip")

        # Register signal handler to cancel tests on SIGINFO (ctrl-t on Mac)
        kill_event = self.sig_hand.reg_on_status(self.kill_external)

        # Run tests that can be skipped by passing a SIGINFO to this process
        self.run('bash', '-c', 'sleep 10')

        # Unregister signal handlers used to skip tests
        self.sig_hand.del_status_event(kill_event)

    def run_tasks(self):
        # Trigger an asynchronous external action, wait until if sends back
        # A signal or timeout after 1 hour
        subprocess.Popen(['bash', 'external_process.bash'])

        if self.sig_hand.pause(3600):
            self.log.error("Timeout while waiting for external process")
            raise
        else:
            self.log.info("Got signal callback from external process")

        # Trigger a synchronous external action, wait for exit status to be
        # returned
        return self.run('bash', 'external_process.bash')

    def rollback(self):
        """
        Roll back to the original state.
        """
        self.log.warning("Rolling back")
        try:
            os.remove(self.tempfile[1])
            self.log.info("Removing temporary file: {0}".format(self.tempfile[1]))
        except:
            self.log.exception("Failed to rollback")

    def run(self, command, *args):
        """
        Run a command on the local system.
        Raises an exception on non-zero exit status, except when aborted
        using the kill method.
        Returns the output generated by the command.
        """
        self.log.debug("Running '%s'", ' '.join([command] + list(args)))
        self.external_running_process = subprocess.Popen([command] + list(args),
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.STDOUT)
        # Capture results so they can be returned, but also pass them to logger
        # so they can stored and/or output to user.
        result = ''
        while True:
            line = self.external_running_process.stdout.readline()
            if not line:
                break
            line = line.strip()
            result += line
            self.log.debug('> ' + line)
        self.external_running_process.wait()
        if self.external_running_process.returncode != 0 and self.user_aborted is False:
            raise subprocess.CalledProcessError(self.external_running_process.returncode, command)
        self.user_aborted = False
        return result

    def kill_external(self):
        """
        Kills the currently running external process if one is running
        """
        self.user_aborted = True
        return self.external_running_process.kill()

    def main(self):
        """
        Do all the important work
        """
        try:
            # Register rollback and cleanup aborted installs
            self.sig_hand.reg_on_abort(self.rollback)
            # Run the tests
            self.run_tests()
            # Do the work that needs to be done
            self.run_tasks()
        except:
            self.log.exception("Error running main")
            self.rollback()
            raise

example = Example()
example.main()
