# -*- coding: utf-8 -*-
from __future__ import absolute_import

from abc import ABCMeta, abstractmethod

import inject
import paho.mqtt.client as mqtt
import rospy
import copy
import re

from .util import lookup_object, extract_values, populate_instance


def create_bridge(factory, msg_type, topic_from, topic_to, **kwargs):
    u""" bridge generator function

    :param (str|class) factory: Bridge class
    :param (str|class) msg_type: ROS message type
    :param str topic_from: incoming topic path
    :param str topic_to: outgoing topic path
    :param (float|None) frequency: publish frequency
    :return Bridge: bridge object
    """
    if isinstance(factory, basestring):
        factory = lookup_object(factory)
    if not issubclass(factory, Bridge):
        raise ValueError("factory should be Bridge subclass")
    if isinstance(msg_type, basestring):
        msg_type = lookup_object(msg_type)
    if not issubclass(msg_type, rospy.Message):
        raise TypeError(
            "msg_type should be rospy.Message instance or its string"
            "reprensentation")
    return factory(
        topic_from=topic_from, topic_to=topic_to, msg_type=msg_type, **kwargs)


class Bridge(object):
    u""" Bridge base class

    :param mqtt.Client _mqtt_client: MQTT client
    :param _serialize: message serialize callable
    :param _deserialize: message deserialize callable
    """
    __metaclass__ = ABCMeta

    _mqtt_client = inject.attr(mqtt.Client)
    _serialize = inject.attr('serializer')
    _deserialize = inject.attr('deserializer')
    _extract_private_path = inject.attr('mqtt_private_path_extractor')


class RosToMqttBridge(Bridge):
    u""" Bridge from ROS topic to MQTT

    :param str topic_from: incoming ROS topic path
    :param str topic_to: outgoing MQTT topic path
    :param class msg_type: subclass of ROS Message
    :param (float|None) frequency: publish frequency
    """

    def __init__(self, topic_from, topic_to, msg_type, frequency=None):
        self._topic_from = topic_from
        self._topic_to = self._extract_private_path(topic_to)
        self._last_published = rospy.get_time()
        self._interval = 0 if frequency is None else 1.0 / frequency
        rospy.Subscriber(topic_from, msg_type, self._callback_ros)

    def _callback_ros(self, msg):
        rospy.logdebug("ROS received from {}".format(self._topic_from))
        now = rospy.get_time()
        if now - self._last_published > self._interval:
            self._publish(msg)
            self._last_published = now

    def _publish(self, msg):
        payload = bytearray(self._serialize(extract_values(msg)))
        self._mqtt_client.publish(topic=self._topic_to, payload=payload, qos=2)


class AlternativeFlightPlanRosToMqttBridge(Bridge):
    u""" Bridge from ROS topic to MQTT

    :param str topic_from: incoming ROS topic path
    :param str topic_to: outgoing MQTT topic path
    :param class msg_type: subclass of ROS Message
    :param (float|None) frequency: publish frequency
    """

    def __init__(self, topic_from, topic_to, msg_type, frequency=None):
        self._topic_from = topic_from
        self._topic_to = self._extract_private_path(topic_to)
        self._last_published = rospy.get_time()
        self._interval = 0 if frequency is None else 1.0 / frequency
        rospy.Subscriber(topic_from, msg_type, self._callback_ros)

    def _callback_ros(self, msg):
        rospy.logdebug("ROS received from {}".format(self._topic_from))
        now = rospy.get_time()
        if now - self._last_published > self._interval:
            self._publish(msg)
            self._last_published = now

    def _publish(self, msg):
        msg_dict = extract_values(msg)
        new_msg_dict = copy.deepcopy(msg_dict)
        
        payload = bytearray(self._custom_json_dumps(new_msg_dict))
        aux_topic_to = self._topic_to + str(msg.icao)
        self._mqtt_client.publish(topic=aux_topic_to, payload=payload, qos=2)

    def _custom_json_dumps(self, msg_dict):
        json_converted_msg = "{ 'icao': '" + str(msg_dict['icao']) + "',\n'flight_plan_id': '" + msg_dict['flight_plan_id'] + "',\n'new_flight_plan': ["
        flight_plan_size = len(msg_dict['new_flight_plan'])
        for index in range(flight_plan_size):
            json_converted_msg += "[" + str(msg_dict['new_flight_plan'][index]['waypoint_elements'][0]) + ","
            json_converted_msg += str(msg_dict['new_flight_plan'][index]['waypoint_elements'][1]) + ","
            json_converted_msg += str(msg_dict['new_flight_plan'][index]['waypoint_elements'][2]) + ","
            json_converted_msg += str(int(msg_dict['new_flight_plan'][index]['waypoint_elements'][3])) + "]"
            
            if index < (flight_plan_size-1):
                json_converted_msg += ",\n"
            else:
                json_converted_msg += "]\n"            
        json_converted_msg += "}\n"
        return json_converted_msg

class UTMAlertRosToMqttBridge(Bridge):
    u""" Bridge from ROS topic to MQTT

    :param str topic_from: incoming ROS topic path
    :param str topic_to: outgoing MQTT topic path
    :param class msg_type: subclass of ROS Message
    :param (float|None) frequency: publish frequency
    """

    def __init__(self, topic_from, topic_to, msg_type, frequency=None):
        self._topic_from = topic_from
        self._topic_to = self._extract_private_path(topic_to)
        self._last_published = rospy.get_time()
        self._interval = 0 if frequency is None else 1.0 / frequency
        rospy.Subscriber(topic_from, msg_type, self._callback_ros)

    def _callback_ros(self, msg):
        rospy.logdebug("ROS received from {}".format(self._topic_from))
        now = rospy.get_time()
        if now - self._last_published > self._interval:
            self._publish(msg)
            self._last_published = now

    def _publish(self, msg):
        msg_dict = extract_values(msg)
        new_msg_dict = copy.deepcopy(msg_dict)
        payload = bytearray(self._custom_json_dumps(new_msg_dict))
        self._mqtt_client.publish(topic=self._topic_to, payload=payload, qos=2)

    def _custom_json_dumps(self, msg_dict):
        json_converted_msg = "{\n'icao': '" + str(msg_dict['icao']) + "',\n'alert_title': '" + msg_dict['alert_title'] + "',\n"
        json_converted_msg += "'alert_id': '" + msg_dict['alert_id'] + "',\n" + "'alert_message': '" + msg_dict['alert_message'] + "',\n"
        json_converted_msg += "'alert_wp': [" + str(msg_dict['alert_wp']['waypoint_elements'][0]) + "," + str(msg_dict['alert_wp']['waypoint_elements'][1]) + ","
        json_converted_msg += str(msg_dict['alert_wp']['waypoint_elements'][2]) + "," + str(msg_dict['alert_wp']['waypoint_elements'][3]) + "]\n"
        
        json_converted_msg += "}\n"
        return json_converted_msg

class RPAStateInfoMqttToRosBridge(Bridge):
    u""" Bridge from MQTT to ROS topic

    :param str topic_from: incoming MQTT topic path
    :param str topic_to: outgoing ROS topic path
    :param class msg_type: subclass of ROS Message
    :param (float|None) frequency: publish frequency
    :param int queue_size: ROS publisher's queue size
    """

    def __init__(self, topic_from, topic_to, msg_type, frequency=None,
                 queue_size=10):
        self._topic_from = self._extract_private_path(topic_from)
        self._topic_to = topic_to
        self._msg_type = msg_type
        self._queue_size = queue_size
        self._last_published = rospy.get_time()
        self._interval = None if frequency is None else 1.0 / frequency

        self._mqtt_client.subscribe(topic_from, qos=2)
        self._mqtt_client.message_callback_add(topic_from, self._callback_mqtt)
        self._publisher = rospy.Publisher(
            self._topic_to, self._msg_type, queue_size=self._queue_size)

    def _callback_mqtt(self, client, userdata, mqtt_msg):
        u""" callback from MQTT

        :param mqtt.Client client: MQTT client used in connection
        :param userdata: user defined data
        :param mqtt.MQTTMessage mqtt_msg: MQTT message
        """
        rospy.logdebug("MQTT received from {}".format(mqtt_msg.topic))
        now = rospy.get_time()

        if self._interval is None or now - self._last_published >= self._interval:
            try:
                ros_msgs = self._create_ros_messages(mqtt_msg)
                for ros_msg in ros_msgs:
                    self._publisher.publish(ros_msg)
                self._last_published = now
            except Exception as e:
                rospy.logerr(e)

    def _create_ros_messages(self, mqtt_msg):
        u""" create ROS message from MQTT payload

        :param mqtt.Message mqtt_msg: MQTT Message
        :return rospy.Message: ROS Message
        """
        # Divide message payload in individual JSON messages
        result = re.findall(r"\{[^}]*?\}", mqtt_msg.payload)
        num_concatenated_msgs = len(result)

        populated_instances = []

        for i in range(num_concatenated_msgs):
            msg_dict = self._deserialize(result[i])
            populated_instances.append(populate_instance(msg_dict, self._msg_type()))

        return populated_instances

class MqttToRosBridge(Bridge):
    u""" Bridge from MQTT to ROS topic

    :param str topic_from: incoming MQTT topic path
    :param str topic_to: outgoing ROS topic path
    :param class msg_type: subclass of ROS Message
    :param (float|None) frequency: publish frequency
    :param int queue_size: ROS publisher's queue size
    """

    def __init__(self, topic_from, topic_to, msg_type, frequency=None,
                 queue_size=10):
        self._topic_from = self._extract_private_path(topic_from)
        self._topic_to = topic_to
        self._msg_type = msg_type
        self._queue_size = queue_size
        self._last_published = rospy.get_time()
        self._interval = None if frequency is None else 1.0 / frequency

        self._mqtt_client.subscribe(topic_from, qos=2)
        self._mqtt_client.message_callback_add(topic_from, self._callback_mqtt)
        self._publisher = rospy.Publisher(
            self._topic_to, self._msg_type, queue_size=self._queue_size)

    def _callback_mqtt(self, client, userdata, mqtt_msg):
        u""" callback from MQTT

        :param mqtt.Client client: MQTT client used in connection
        :param userdata: user defined data
        :param mqtt.MQTTMessage mqtt_msg: MQTT message
        """
        rospy.logdebug("MQTT received from {}".format(mqtt_msg.topic))
        now = rospy.get_time()

        if self._interval is None or now - self._last_published >= self._interval:
            try:
                ros_msg = self._create_ros_message(mqtt_msg)
                self._publisher.publish(ros_msg)
                self._last_published = now
            except Exception as e:
                rospy.logerr(e)

    def _create_ros_message(self, mqtt_msg):
        u""" create ROS message from MQTT payload

        :param mqtt.Message mqtt_msg: MQTT Message
        :return rospy.Message: ROS Message
        """
        msg_dict = self._deserialize(mqtt_msg.payload)
        return populate_instance(msg_dict, self._msg_type())


__all__ = ['register_bridge_factory', 'create_bridge', 'Bridge',
           'RosToMqttBridge', 'MqttToRosBridge', 'RPAStateInfoMqttToRosBridge', 
           'RPAStateInfoMqttToRosBridge', 'AlternativeFlightPlanRosToMqttBridge']
