#! /usr/bin/python

import argparse
import importlib
import sys
from datetime import datetime

from templateLoader import TemplateLoader
from util.shinsLogger import logger
from util import functions as ufun
from util import exception


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

    def __init__(self, embedded=True):
        '''Constructor'''
        self.ob = []
        self.ob_file = None
        self.error = []
        self.tpl = None # The running template
        self.quitting = False
        self.embedded = embedded  # To manage sys.exit if called alone


    def execute(self):
        """Execute the ob XML file."""
        self.quitting = False

        try:
            logger.info("--------------------------------------------------------------------------")
            logger.info("| {:^70s} |".format("OB"))
            logger.info("| {:^70s} |".format(self.ob_file))
            logger.info("| {:^70s} |".format("STARTED"))
            logger.info("--------------------------------------------------------------------------")
            ob_xml = ufun.parse(self.ob_file)
            NTpls = len(ob_xml)
            ob_time_start=str(datetime.now())[:-4]
        except:
            logger.error("File {} not found or error in the XML".format(self.ob_file))
            return

        #################
        # Template loop #
        #################

        logger.info("Found {} template(s) in OB".format(NTpls))

        for i in range(NTpls):

            #################################
            # Check if a quit was requested #
            #################################

            if self.quitting:
                break

            ##############################################
            # Load the n-th template to check its params #
            ##############################################

            logger.info('======================')
            logger.info("=   Template {}/{}   =".format(i, str(NTpls-1)))
            logger.info('======================')

            tpl_loader = TemplateLoader(ob_xml[i])

            logger.debug("Loading the OB")
            tpl_loader.load()
            logger.debug("Loading the OB... DONE")

            logger.info('----------------------')
            logger.info('Tpl {} : {}'.format(i, tpl_loader.TPLName))
            logger.info('TPLID : {}'.format(tpl_loader.TPLID))
            logger.info('----------------------')

            ###########################
            # Instanciate and call it #
            ###########################

            moduleToUse = importlib.import_module("tpl."+tpl_loader.Script)
            classToUse = getattr(moduleToUse, tpl_loader.Script)
            self.tpl = classToUse()
            self.tpl.aborted = False

            # Info for fits header
            context = {
                "TPL": tpl_loader.TPLName,
                "TPLSCRIPT": tpl_loader.Script,
                "TPLID": tpl_loader.TPLID,
                "TPLTIME":ob_time_start,
                "OB": self.ob_file,
            }


            # DPR Keywords
            dpr_type = "DPR_TYPE"
            try:
                context.update({dpr_type: tpl_loader.dpr_type})
            except AttributeError as e:
                logger.warning("SEQUENCER: No DPR TYPE in this template")

            status = self.tpl.main(ins_s=tpl_loader.ins_sup,
                                   det_s=tpl_loader.det_sup,
                                   tel_s=tpl_loader.tel_sup,
                                   rtc_s=tpl_loader.rtc_sup,
                                   info_s=tpl_loader.info_sup,
                                   **context)

            logger.debug("SEQUENCER: {} is returning this: {}".format(__name__, status))
            status = status[0] if type(status) == tuple else status

            if status == 1: # 1 == failure # uvars.EXIT_FAILURE:
                errMsg = "SEQUENCER: Template {} ({}-th template in OB) failed execution".format(tpl_loader.TPLID, i)
                logger.error(errMsg)

            else:
                logger.debug("Template {:s} execution success".format(tpl_loader.TPLID))

        ##########################
        # Reset OB Context in OS #
        ##########################
        status = self.tpl.resetOSContext(ob_context=context)

        ##########
        # Finish #
        ##########

        logger.info("--------------------------------------------------------------------------")
        logger.info("| {:^70s} |".format("OB"))
        logger.info("| {:^70s} |".format(self.ob_file))
        logger.info("| {:^70s} |".format("FINISHED"))
        logger.info("--------------------------------------------------------------------------")


    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.

        '''

        try:
            answer = raw_input("(P)ause, (R)esume, (Q)uit:\n")

            if answer.lower().startswith('p'):
                self.pause()
            if answer.lower().startswith('r'):
                self.resume()
            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 web server, CTRL+C calls the interrupt. Then, to exit the
            web sever, a CTRL+D have to be intercepted '''

            msg = "SEQUENCER: CTRL-D detected! Exiting for real {} in ob {}".format(self.tpl.name, self.ob_file)
            logger.critical(msg)
            sys.exit(1)
            return

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

        if self.tpl:
            logger.warning("SEQUENCER: pause called on template {} in ob {}".format(self.tpl.name, self.ob_file))
            logger.warning("SEQUENCER: Template will be paused once it will reach the pause control")
            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:
            logger.warning("SEQUENCER: resume called on template {} in ob {}".format(self.tpl.name, self.ob_file))
            self.tpl.paused = False

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

        self.quitting = True
        
        if self.tpl:
            msg = "SEQUENCER: aborting template {} in ob {}".format(self.tpl.name, self.ob_file)
            logger.error(msg)
            self.error.append(msg)
            self.tpl.aborted = True

            if hasattr(self.tpl, 'gen_base'):
                self.tpl.gen_base.aborted = True                
            
            self.tpl.abort()
        else:
            msg = "SEQUENCER: no template to abort"
            logger.error(msg)
            self.error.append(msg)
                        
        if not self.embedded:
            sys.exit(1)
        else:
            self.quitting = True  # Flask doesn't take sys.exit??




#####################
# To call as script #
#####################

def parser():
    """Parser for the sequencer arguments."""

    parser = argparse.ArgumentParser(description='''Sequencer for the SHARK-NIR instrument. It executes Observation Blocks (OBs)
        in the form of XML files calling one or more templates''')

    obname = ['-o', '--ob']
    kwobname = {'type':str, 'help':'OB XML file name'}
    guiname = ['-g', '--gui']

    parser.add_argument(*obname, **kwobname)
    parser.add_argument(*guiname, action='store_true')
    args = parser.parse_args()

    return(args)


def main(args):

    # Have to import signal here due to Flask bug
    import signal
    
    seq = Sequencer(embedded=False)

    signal.signal(signal.SIGINT, seq.interrupt)

    logger.info('{}: begin'.format(__name__))

    if args.ob:

        seq.ob_file = args.ob
        status = seq.execute()
        sys.exit(status)

    else:
        logger.error("See sequencer.py --help for usage")

    logger.info('{}: end'.format(__name__))



if __name__ == "__main__":
    args = parser()
    main(args)
