import RPi.GPIO as GPIO
from copreus.baseclasses.adriver import ADriver
from copreus.schema.output import get_schema


class Output(ADriver):
    """Generic driver that sets the given output pin.

    The driver entry in the yaml file consists of:
      * ADriver entries
        * topics_sub: closed - mqtt-translations.closed-true and mqtt-translations.closed-false
      * Output entries
        * pin: gpio pin
        * physical-closed: high/low - mapping between logcial states (closed/open) and physical output
        parameters (low/high)
        * initially-closed: True/False - defines if the output is opened or closed after start of driver

    Example:
    device:
        type: output
        pin: 21
        initially-closed: True
        physical-closed: high
        topics-sub:
            closed: /test/closed
        mqtt-translations:
            closed-true: ON
            closed-false: OFF
    """

    _pin = -1  # gpio pin id
    _gpio_closed = -1  # value to write to gpio pin for closing output (0/1)
    _gpio_opened = -1  # value to write to gpio pin for opening output (0/1)
    _initially_closed = False  # should the output be opened or closed after start

    def __init__(self, config, mqtt_client=None, logger=None):
        ADriver.__init__(self, config, mqtt_client, logger, logger_name=__name__)

        self._mqtt_client.subscribe(self._topics_sub["closed"], self._message_closed)

        self._pin = int(self._config["pin"])
        self._initially_closed = bool(self._config["initially-closed"])
        if str(self._config["physical-closed"].lower()) == "low":
            self._gpio_closed = 0
        elif str(self._config["physical-closed"].lower()) == "high":
            self._gpio_closed = 1
        else:
            self._logger.error("'physical-closed' - expected 'low'/'high' but received '{}'.".
                               format(self._config["physical-closed"].lower()))
            raise ValueError("'physical-closed' - expected 'low'/'high' but received '{}'.".
                             format(self._config["physical-closed"].lower()))
        self._gpio_opened = (self._gpio_closed + 1) % 2

    def _message_closed(self, msg):
        """on_message handler for topic sub 'closed'."""
        self._logger.info("received message '{}' on topic '{}'.".format(msg, self._topics_sub["closed"]))
        if str(msg) == str(self._mqtt_translations["closed-true"]):
            self._logger.info("writing '{}' to pin '{}'.".format(self._gpio_closed, self._pin))
            GPIO.output(self._pin, self._gpio_closed)
        elif str(msg) == str(self._mqtt_translations["closed-false"]):
            self._logger.info("writing '{}' to pin '{}'.".format(self._gpio_opened, self._pin))
            GPIO.output(self._pin, self._gpio_opened)
        else:
            raise ValueError("Output.'{}'.payload expects {} or {}. received '{}' instead.".format(
                msg.topic, self._mqtt_translations["closed-true"], self._mqtt_translations["closed-false"],
                msg))

    def _driver_start(self):
        """ADriver._driver_start"""
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self._pin, GPIO.OUT)
        if self._initially_closed:
            GPIO.output(self._pin, self._gpio_closed)
        else:
            GPIO.output(self._pin, self._gpio_opened)

    def _driver_stop(self):
        """ADriver._driver_stop"""
        GPIO.cleanup(self._pin)

    @classmethod
    def _get_schema(cls):
        return get_schema()


def standalone():
    """Calls the static method Output.standalone()."""
    Output.standalone()


if __name__ == "__main__":
    Output.standalone()
