#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
Sequencer module for Observation Blocks (OBs) in JSON format
'''

# System modules
import importlib
import json
import signal
import sys
import os

# Third-party modules
from datetime import datetime

# Custom modules
from utils import check
from utils.logger import log

class Sequencer():
    '''
    Manage a sequence of JSON Observation Blocks (OBs)
    '''

    def __init__(self, embedded=True):
        '''Constructor'''
        self.ob_file = None
        self.ob = []  # Content of the ob_file
        self.error = []
        self.tpl = None  # The running template
        self.params = {}  # The parameters of the running template
        self.quitting = False
        self.embedded = embedded  # To manage sys.exit if called alone
        signal.signal(signal.SIGINT, self.interrupt)
        self.original_sigint = signal.getsignal(signal.SIGINT)

    def load_script(self, template_script, params={}):
        '''Load a python file where the template is implemented'''

        from pathlib import Path

        def get_module_name(file_path):
            path = Path(file_path)
            module_name = path.stem
            return module_name

        tplmodule = get_module_name(template_script)
        print(tplmodule)

        self.ob = {"template": tplmodule,
                   "params": params
                   }

    def load_file(self, ob_file):
        '''Load a JSON file with an OB'''

        self.ob_file = ob_file

        try:
            with open(self.ob_file, encoding="utf-8") as jsonfile:
                log.info(f"SEQUENCER: Loading the ob from file {self.ob_file}")
                self.error = []
                self.ob = json.load(jsonfile)
        except json.decoder.JSONDecodeError as e:
            msg = "SEQUENCER: Probably malformed json"
            log.error(msg)
            log.error(e)
            self.error.append(msg)
            self.error.append(e)
        except UnicodeDecodeError as e:
            msg = "SEQUENCER: Not a text file"
            log.error(msg)
            log.error(e)
            self.error.append(msg)
            self.error.append(e)
        except Exception as e:
            msg = "SEQUENCER: Not handled exception"
            log.error(msg)
            log.error(e)
            self.error.append(msg)
            self.error.append(e)
            raise SystemExit(e) from e

    def execute(self):
        '''Execute the template(s) contained in the loaded OB'''
        self.quitting = False

        ob = self.ob if isinstance(self.ob, list) else [self.ob]

        log.info(f"There are {len(ob)} templates in the ob")
        log.debug(ob)
        
        now = datetime.utcnow()

        for template in ob:
            if self.quitting:
                break
            log.info("--------------------------------------------")
            try:
                self.error = []

                # Importing the module given its name in the json.
                tplmodule = importlib.import_module(
                    "templates." + template["template"])

                # Every template module has a Template() class.
                self.tpl = tplmodule.Template()

                self.params = template["params"]

                # Run the template given its parameters in the json.
                self.tpl.run(self.params)

            except AttributeError as e:
                msg = "SEQUENCER: Attribute Error"
                log.error(msg)
                log.error(e)
                self.error.append(msg)
                self.error.append(e)

            except KeyError as e:
                msg = f"SEQUENCER: {e} is not defined"
                log.error(msg)
                self.error.append(msg)

        log.info("--------------------------------------------")
        log.debug(f"Total elapsed time: {(datetime.utcnow()-now).total_seconds() :.3f}s")

        
    def interrupt(self, signal, frame):
        '''Intercept a CTRL+C instead of raising a KeyboardInterrupt exception
        in order to be able to operate on the template class, for
        example to modify a pause attribute or call a method.

        '''

        #signal.signal(signal.SIGINT, self.original_sigint)

        try:
            answer = input(
                "(P)ause, (R)esume, (T)ry again paragraph, (N)ext template, (Q)uit:\n")

            if answer.lower().startswith('p'):
                self.pause()
            if answer.lower().startswith('r'):
                self.resume()
            if answer.lower().startswith('n'):
                self.abort()
            if answer.lower().startswith('q'):
                self.quit()
            else:
                pass

        except (KeyboardInterrupt, RuntimeError):
            '''RuntimeError is catched because the
            original_sigint strategy does not work'''

            self.quit()

        except EOFError as e:
            '''On the api/web server, CTRL+C calls the interrupt. Then, to exit the
            api/web sever, a CTRL+D have to be intercepted '''

            msg = f"SEQUENCER: CTRL-D detected! Exiting for real!"
            log.critical(msg)
            os._exit(1)

    def pause(self):
        '''
        Put the pause attribute in the running template to True.
        It will be checked in a template method.
        '''

        if self.tpl:
            msg = f"SEQUENCER: pausing template {self.tpl.name} in ob {self.ob_file}"
            log.warning(msg)

            self.tpl.paused = True

    def resume(self):
        '''
        Restore the pause attribute in the template class to its
        original state. It will be checked in a template method.
        '''

        if self.tpl:
            msg = f"SEQUENCER: resuming template {self.tpl.name} in ob {self.ob_file}"
            log.warning(msg)

            self.tpl.paused = False

    def abort(self):
        '''
        Abort the running template. The sequencer will pass to the
        follwing one, if any.
        '''

        if self.tpl:
            msg = f"SEQUENCER: Aborting template {self.tpl.name} in OB {self.ob_file}."
            log.error(msg)
            self.error.append(msg)
            self.tpl.aborted = True
            self.tpl.abort()

    def quit(self):
        '''Abort the execution of the whole OB'''

        msg = f"SEQUENCER: Quit detected during OB {self.ob_file}."
        log.error(msg)
        self.error.append(msg)

        try:
            msg = f"SEQUENCER: I pass aborting the template, if any is running."
            log.warning(msg)
            self.abort()
        except Exception as e:
            msg = f"SEQUENCER: Error trying to abort {self.ob_file}. Exiting anyway."
            log.error(msg)
            log.error(e)
            self.error.append(msg)
            self.error.append(e)

        if not self.embedded:
            log.info("SEQUENCER: Finish.")
            sys.exit(1)
        else:
            self.quitting = True  # Flask doesn't take sys.exit??


if __name__ == "__main__":

    seq = Sequencer(embedded=False)

    try:
        arg = sys.argv[1]
    except IndexError as e:
        msg = f"No argument provided"
        log.error(msg)
        log.error(e)
        msg = f"""Examples:
                 {sys.argv[0]} defaults/lampsoff.json
                 {sys.argv[0]} --script templates/lampsoff.py {{}}
        """
        log.info(msg)
        sys.exit(1)

    if arg.startswith("--script"):
        try:
            params = sys.argv[3] or '{}'
        except IndexError as e:
            msg = f'Not enough arguments provided: script.py and params'
            log.error(msg)
            log.error(e)
            sys.exit(1)

        script = sys.argv[2]
        seq.load_script(script, json.loads(params))
        seq.execute()
    else:
        filename = arg
        seq.load_file(filename)
        seq.execute()
