#!/usr/bin/env python
import cmd
import os
import readline
import sys

from astropy.utils import console
from pprint import pprint
from threading import Timer

from peas.sensors import ArduinoSerialMonitor
from peas.weather import AAGCloudSensor
from peas.webcam import Webcam

from pocs.utils.config import load_config
from pocs.utils.logger import get_root_logger
from pocs.utils.database import PanMongo


class PanSensorShell(cmd.Cmd):

    """ A simple command loop for the sensors. """
    intro = 'Welcome to PEAS Shell! Type ? for help'
    prompt = 'PEAS > '
    webcams = None
    environment = None
    weather = None
    active_sensors = dict()
    db = PanMongo()
    _keep_looping = False
    _loop_delay = 60
    _timer = None
    captured_data = list()
    messaging = None

    config = load_config(config_files=['peas'])

##################################################################################################
# Generic Methods
##################################################################################################

    def do_status(self, *arg):
        """ Get the entire system status and print it pretty like! """
        for sensor_name in ['environment', 'weather', 'webcams']:
            if sensor_name in self.active_sensors:
                console.color_print("{:>12s}: ".format(sensor_name.title()), "default", "active", "lightgreen")
            else:
                console.color_print("{:>12s}: ".format(sensor_name.title()), "default", "inactive", "yellow")

    def do_last_reading(self, device):
        """ Gets the last reading from the device. """
        if hasattr(self, device):
            print_info('*' * 80)
            print("{}:".format(device.upper()))

            rec = None
            if device == 'weather':
                rec = self.db.current.find_one({'type': 'weather'})
            elif device == 'environment':
                rec = self.db.current.find_one({'type': 'environment'})

            pprint(rec)
            print_info('*' * 80)

    def do_enable_sensor(self, sensor, delay=None):
        """ Enable the given sensor """
        if delay is None:
            delay = self._loop_delay

        if hasattr(self, sensor) and sensor not in self.active_sensors:
            self.active_sensors[sensor] = {'reader': sensor, 'delay': delay}

    def do_disable_sensor(self, sensor):
        """ Enable the given sensor """
        if hasattr(self, sensor) and sensor in self.active_sensors:
            del self.active_sensors[sensor]

    def do_toggle_debug(self, sensor):
        """ Toggle DEBUG on/off for sensor

        Arguments:
            sensor {str} -- environment, weather, webcams
        """
        # TODO(jamessynge): We currently use a single logger, not one per module or sensor.
        # Figure out whether to keep this code and make it work, or get rid of it.
        import logging
        get_level = {
            logging.DEBUG: logging.INFO,
            logging.INFO: logging.DEBUG,
        }

        if hasattr(self, sensor):
            try:
                log = getattr(self, sensor).logger
                log.setLevel(get_level[log.getEffectiveLevel()])
            except Exception as e:
                print_error("Can't change log level for {}".format(sensor))

##################################################################################################
# Load Methods
##################################################################################################

    def do_load_all(self, *arg):
        self.do_load_weather()
        self.do_load_environment()
        # self.do_load_webcams()

    def do_load_webcams(self, *arg):
        """ Load the webcams """
        print("Loading webcams")

        class WebCams(object):

            def __init__(self, config):

                self.webcams = list()
                self.config = config

                for webcam in self.config:
                    # Create the webcam
                    if os.path.exists(webcam.get('port')):
                        self.webcams.append(Webcam(webcam))

            def capture(self, **kwargs):
                for wc in self.webcams:
                    wc.capture()

        self.webcams = WebCams(self.config.get('webcams', []))

        self.do_enable_sensor('webcams')

    def do_load_environment(self, *arg):
        """ Load the arduino environment sensors """
        print("Loading sensors")
        self.environment = ArduinoSerialMonitor(auto_detect=False)
        self.do_enable_sensor('environment', delay=1)

    def do_load_weather(self, *arg):
        """ Load the weather reader """
        try:
            port = self.config['weather']['aag_cloud']['serial_port']
        except KeyError:
            port = '/dev/ttyUSB0'

        print("Loading AAG Cloud Sensor on {}".format(port))
        self.weather = AAGCloudSensor(serial_address=port, use_mongo=True)
        self.do_enable_sensor('weather')

##################################################################################################
# Relay Methods
##################################################################################################

    def do_toggle_relay(self, *arg):
        """ Toggle a relay

        This will toggle a relay on the on the power board, switching off if on
        and on if off.  Possible relays include:

            * fan
            * camera_box
            * weather
            * mount
            * cam_0
            * cam_0
        """
        relay = arg[0]
        relay_lookup = {
            'fan': {'pin': 6, 'board': 'telemetry_board'},
            'camera_box': {'pin': 7, 'board': 'telemetry_board'},
            'weather': {'pin': 5, 'board': 'telemetry_board'},
            'mount': {'pin': 4, 'board': 'telemetry_board'},
            'cam_0': {'pin': 5, 'board': 'camera_board'},
            'cam_1': {'pin': 6, 'board': 'camera_board'},
        }

        try:
            relay_info = relay_lookup[relay]
            self.environment.serial_readers[relay_info['board']]['reader'].write("{},9".format(relay_info['pin']))
        except Exception as e:
            print_warning("Problem toggling relay {}".format(relay))
            print_warning(e)

    def do_toggle_computer(self, *arg):
        try:
            board = 'telemetry_board'
            pin = 8
            # Special command will toggle off, wait 30 seconds, then toggle on
            self.environment.serial_readers[board]['reader'].write("{},0".format(pin))
        except Exception as e:
            print_warning(e)


##################################################################################################
# Start/Stop Methods
##################################################################################################

    def do_start(self, *arg):
        """ Runs all the `active_sensors`. Blocking loop for now """
        self._keep_looping = True

        print_info("Starting sensors")

        self._loop()

    def do_stop(self, *arg):
        """ Stop the loop and cancel next call """
        print_info("Stopping loop")

        self._keep_looping = False

        if self._timer:
            self._timer.cancel()

    def do_change_delay(self, *arg):
        sensor_name, delay = arg[0].split(' ')
        print_info("Chaning {} to {} second delay".format(sensor_name, delay))
        try:
            self.active_sensors[sensor_name]['delay'] = float(delay)
        except KeyError:
            print_warning("Sensor not active: ".format(sensor_name))


##################################################################################################
# Shell Methods
##################################################################################################

    def do_shell(self, line):
        """ Run a raw shell command. Can also prepend '!'. """
        print("Shell command:", line)

        output = os.popen(line).read()

        print_info("Shell output: ", output)

        self.last_output = output

    def emptyline(self):
        self.do_status()

    def do_exit(self, *arg):
        """ Exits PEAS Shell """
        print("Shutting down")
        self.do_stop()

        print("Please be patient and allow for process to finish. Thanks! Bye!")
        return True

##################################################################################################
# Private Methods
##################################################################################################

    def _capture_data(self, sensor_name):
        if sensor_name in self.active_sensors:
            sensor = getattr(self, sensor_name)
            try:
                sensor.capture(use_mongo=True, send_message=True)
            except Exception as e:
                pass

            self._setup_timer(sensor_name, delay=self.active_sensors[sensor_name]['delay'])

    def _loop(self, *arg):
        for sensor_name in self.active_sensors.keys():
            self._capture_data(sensor_name)

    def _setup_timer(self, sensor_name, delay=None):
        if self._keep_looping and len(self.active_sensors) > 0:

            if not delay:
                delay = self._loop_delay

            self._timer = Timer(delay, self._capture_data, args=(sensor_name,))

            self._timer.start()

##################################################################################################
# Utility Methods
##################################################################################################


def print_info(msg):
    console.color_print(msg, 'lightgreen')


def print_warning(msg):
    console.color_print(msg, 'yellow')


def print_error(msg):
    console.color_print(msg, 'red')


if __name__ == '__main__':
    invoked_script = os.path.basename(sys.argv[0])
    histfile = os.path.expanduser('~/.{}_history'.format(invoked_script))
    histfile_size = 1000
    if os.path.exists(histfile):
        readline.read_history_file(histfile)

    PanSensorShell().cmdloop()

    readline.set_history_length(histfile_size)
    readline.write_history_file(histfile)
