#! /usr/bin/env python
__author__ = "Ben Raanan"
__copyright__ = "Copyright 2021, MBARI"
__credits__ = ["MBARI"]
__license__ = "GPL-3.0"
__maintainer__ = "Ben Raanan"
__email__ = "byraanan at mbari.org"
__doc__ = '''

Defines the LRAUV backseat helper service supervisor class.

@author: __author__
@license: __license__
'''
import os
import subprocess
from time import time

import supervisor.Logger

logger, log_path = supervisor.Logger.configure_logger(name='backseat')

from LCM.HandlerBase import LcmHandlerBase
from LCM.Publisher import LcmPublisher
from LCM.DataRequester import LcmDataRequester
from config.AppConfig import read_config


class Supervisor(LcmHandlerBase):


    def __init__(self, lcm_instance, config):

        logger.info("Creating Supervisor.")

        self.run, self.power_down = True, False
        self.cfg = config
        self.hostname = os.uname()[1]

        # Configure the logger, and kick-off lcm-logger
        logger.info("Log folder created at: {0}.".format(log_path))
        self.log_path = log_path  # path assigned by the Logger
        self.start_lcm_logger(channel_regex=self.cfg['lcm_log_channel_regex'])

        self.request_data = LcmDataRequester(lcm_instance, self.cfg['lcm_data_request_channel'])
        self.pub_syslog = LcmPublisher(lcm_instance)
        self.pub_heartbeat = LcmPublisher(lcm_instance)
        self.lcm_logger = None
        self.last_heartbeat = 0.0

        self.publish_syslog(priority='IMPORTANT', msg=self.hostname + ': running backseat application.')


    def start_lcm_logger(self, channel_regex='.*', log_name='lcmlog', cmd=None):
        """
        Execute lcm-logger as a child process in the background. Log LCM messages on channels
        matching the channel regex.

        cmd takes format:
            lcm-logger -c <channel_regex> --increment --split-mb=25 --quiet ./logs/logfolder/<log_name>

        example:
            lcm-logger -c '(tethys_slate|bsd_command).*' --increment --split-mb=25 --quiet ./logs/20190929T135720/lcmlog

        see lcm-logger man page for more details on args.

        :param channel_regex: optional, regex channel string to pass to lcm_subscribe
        :param log_name: optional, lcm logfile name. e.g., lcmlog will take form of: lcmlog.00, lcmlog.01,...
        :param cmd: optional, specify command string
        :return: update lcm_logger class member
        """
        if not cmd:
            cmd = "lcm-logger -c '{0}' --increment --split-mb=25 --quiet ".format(channel_regex)
            cmd += log_path + "/" + log_name

        logger.info("Starting lcm-log with cmd: {0}".format(cmd))
        self.lcm_logger = subprocess.Popen("exec " + cmd, stdout=subprocess.PIPE, shell=True)


    def kill_lcm_logger(self):
        """
        Kill the lcm-logger process.

        :return: modifies lcm_logger class member
        """
        if self.lcm_logger:
            logger.info("Terminating lcm-logger.")
            self.lcm_logger.kill()


    def sync_logs(self):
        cmd = "rsync -az {0} {1}".format(self.log_path, self.cfg['rsync_target'])
        logger.info("Syncing logs with cmd: {0}".format(cmd))
        rsync = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
        rsync.wait(timeout=30.0)


    def strobe_heartbeat(self, heart_rate_sec=1.0):
        """
        Publish BackSeatDriver heartbeat to LRAUV if heartbeat is due.

        :param heart_rate_sec:
        :return: void
        """
        now = time()
        if self.cfg['publish_heartbeat'] and (now - self.last_heartbeat) > heart_rate_sec:
            self.publish_heartbeat()
            self.last_heartbeat = now



    def request_slate(self, slate_vars, request=True):
        """
        Add LRAUV Slate variable names to the list of requested data.

        :param slate_vars: list of strings containing var names.
        :param request: bool, set to True to request, False to stop LCM data stream.
        :return: void
        """
        for var in slate_vars:
            if request:
                logger.info("Adding data request for: {}".format(var))
                self.request_data.add_request(var)
            else:
                logger.info("Removing data request for: {}".format(var))
                self.request_data.remove_request(var)


    def publish_slate_request(self):
        """
        Publish Slate request messages to LRAUV.

        :return: void
        """
        self.request_data.request(self.cfg['lrauv_data_timout'])


    def publish_syslog(self, priority="INFO", msg="", channel_name=""):
        """
        Publish syslog messages to LRAUV.

        LRAUV logging priorities are:
            CRITICAL:
                * aborts the mission if the backseat component is configured as mission critical.
                * recycles power on the backseat.
                * message is sent to shore.
            FAULT:
                * recycles power on the backseat.
                * message is sent to shore.
            IMPORTANT:
                * message is sent to shore.
            ERROR:
            INFO:
            DEBUG:

        :param priority: syslog entry logging priority.
        :param msg: syslog entry text.
        :param channel_name: LCM channel name (string)
        :return: publishes LCM message
        """
        if not channel_name:
            channel_name = self.cfg['lcm_syslog_channel']
        self.pub_syslog.add_variable(name=priority, val=msg)
        self.pub_syslog.publish(channel_name)
        self.pub_syslog.clear_msg()


    def publish_heartbeat(self, channel_name=""):
        """
        Publish backseat heartbeat to LRAUV.

        :param channel_name: LCM channel name (string)
        :return: publishes LCM message
        """
        if not channel_name:
            channel_name = self.cfg['lcm_heartbeat_channel']
        logger.debug("Publishing heartbeat.")
        self.pub_heartbeat.add_variable(name=self.hostname, val=True)
        self.pub_heartbeat.publish(channel_name)


    def lrauv_command_handler(self, channel, data):
        """
        Handle LCM messages sent from the LRAUV on the 'bsd_command' LCM channel.

        TODO: add option to exe any command on Linux shell.
             example:
             - start stop services
             - load new configuration files
             - report system status back to MVA

        :param channel: LCM channel name string
        :param data: coded LCM packet
        :return: updates the run class member
        """
        logger.debug("Handling LCM msg on {0}.".format(channel))
        msg = self.decode(data)

        if msg:
            cmd = self.get_variable("bsd_command", msg)
            try:
                if cmd.data[0] == 'shutdown':
                    logger.info("Got command: {0}.".format(cmd.data[0]))
                    self.run, self.power_down = False, True
                elif cmd.data[0] == 'quit':
                    logger.info("Got command: {0}.".format(cmd.data[0]))
                    self.run, self.power_down = False, False
            except AttributeError:
                logger.info("Failed to unpack cmd.".format(cmd.data), exc_info=True)


    def shutdown(self):
        """
        Powers off the host.

        :return: void
        """
        self.kill_lcm_logger()

        cmd = self.cfg['shutdown_cmd']

        if self.power_down and self.cfg['designated_host'] in self.hostname:
            logger.info("Powering down with command: {0}.".format(cmd))
            os.system(cmd)
