#!/usr/bin/env python3

import signal
import subprocess
import sys
from flowgraph_builder import FlowgraphBuilder

from concurrent import futures

import grpc
import radio_config_pb2
import transmitter_pb2
import observation_pb2
import flowgraph_config_pb2
import flowgraph_dispatcher_service_pb2_grpc
import message_valid_pb2


GRPC_SERVER_PORT = '50051'


class FlowgraphDispatcher(flowgraph_dispatcher_service_pb2_grpc.FlowgraphDispatcherServiceServicer):
    """
    Execute gRPC server and ZMQ subscribers for handling PHASMA observations.
    """

    def __init__(self):
        self.radio_config = radio_config_pb2.RadioConfig()
        self.transmitter = transmitter_pb2.Transmitter()
        self.flowgraph_config = flowgraph_config_pb2.FlowgraphConfig()
        self.tx_rx = observation_pb2.Observation.RX 

        self.radio_config_is_set = False
        self.transmitter_is_set = False
        self.flowgraph_config_is_set = False
        # Set flowgraph to False before starting any to avoid error if user
        # tries to stop it
        self.flowgraph = False 
        
        self.grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
        self.grpc_server.add_insecure_port("[::]:" + GRPC_SERVER_PORT)

    @property
    def radio_config_is_set(self):
        """Property indicating whether the radio config param is set"""
        return self._radio_config_is_set

    @radio_config_is_set.setter
    def radio_config_is_set(self, radio_config_is_set):
        self._radio_config_is_set = radio_config_is_set

    @property
    def transmitter_is_set(self):
        """Property indicating whether the transmitter is set"""
        return self._transmitter_is_set

    @transmitter_is_set.setter
    def transmitter_is_set(self, transmitter_is_set):
        self._transmitter_is_set = transmitter_is_set

    def SetRadioConfigService(self, request, context):
        self.radio_config.CopyFrom(request)
        if self.flowgraph and self.flowgraph.running:
            try:
                self.__run_flowgraph()
            except Exception as error:
                return message_valid_pb2.MessageValid(
                    valid=False, error_msg=str(error))
        return message_valid_pb2.MessageValid(valid=True)

    def SetFlowgraphConfigService(self, request, context):
        self.flowgraph_config.CopyFrom(request)
        if self.flowgraph and self.flowgraph.running:
            try:
                self.__run_flowgraph()
            except Exception as error:
                return message_valid_pb2.MessageValid(
                    valid=False, error_msg=str(error))
        return message_valid_pb2.MessageValid(valid=True)

    def SetTransmitterService (self, request, context):
        self.transmitter.CopyFrom(request)
        if self.flowgraph and self.flowgraph.running:
            try:
                self.__run_flowgraph()
            except Exception as error:
                return message_valid_pb2.MessageValid(
                    valid=False, error_msg=str(error))
        return message_valid_pb2.MessageValid(valid=True)

    def StartObservationService(self, request, context):
        if (request.HasField("radio_config")):
            self.radio_config.CopyFrom(request.radio_config)
            self.radio_config_is_set = True
        if (request.HasField("transmitter")):
            self.transmitter.CopyFrom(request.transmitter)
            self.transmitter_is_set = True
        if (request.HasField("flowgraph_config")):
            self.flowgraph_config.CopyFrom(request.flowgraph_config)
            self.flowgraph_config_is_set = True
        
        if (self.transmitter_is_set and self.radio_config_is_set):
            if ((not request.HasField("tx_rx")) or 
                (request.tx_rx == observation_pb2.Observation.RX)):
                try:
                    self.__run_flowgraph()
                    print("Starting RX Flowgraph")
                except Exception as error:
                    return message_valid_pb2.MessageValid(
                            valid=False, error_msg=str(error))
            else:
                print("Starting TX Flowgraph")
        else:
            return message_valid_pb2.MessageValid(
                    valid=False,
                    error_msg="Transmitter and/or radio configuration are not set!\
                         Cannot start Flowgraph")

        return message_valid_pb2.MessageValid(valid=True)

    def StopObservationService(self, request, context):
        self.__terminate_flowgraph()
        return message_valid_pb2.MessageValid(valid=True)

    def SoapyCustomSdrCommandService(self, request, context):
        args = ["./flowgraph_dispatcher/zmq_subs/zmq_soapy_command_push.py", request.command, request.value]
        command_subprocess = subprocess.Popen(args)
        command_subprocess.wait()
        return message_valid_pb2.MessageValid(ack=True)

    def GetRadioConfigService(self, request, context):
        return self.radio_config

    def GetFlowgraphConfigService(self, request, context):
        return self.flowgraph_config

    def GetTransmitterService(self, request, context):
        return self.transmitter

    def GetObservationConfigService(self, request, context):
        observation = observation_pb2.Observation()
        observation.radio_config.CopyFrom(self.radio_config)
        observation.flowgraph_config.CopyFrom(self.flowgraph_config)
        observation.transmitter.CopyFrom(self.transmitter)
        return observation

    def __run_flowgraph(self):
        """
        Starts the Flowgraph

        Call the flowgraph_builder member run function to start the flowgraph
        On runtime error print the error and exit.
        """
        # Stop previous flowgraph if active
        if (self.flowgraph and self.flowgraph.running):
            self.__terminate_flowgraph()
        try:
            self.flowgraph = FlowgraphBuilder(self.radio_config,
                                              self.transmitter,
                                              self.flowgraph_config,
                                              True)
        except Exception as error:
            raise Exception(
                "\033[31mError creating flowgraph: {}\nAborting Observation!\033[0m"
                .format(error))
        
        try:
            self.flowgraph.run()
        except RuntimeError as error:
            self.__terminate_flowgraph()
            raise RuntimeError(
                "\033[31mError starting flowgraph: {}\nAborting Observation!\033[0m"
                .format(error))

    def __terminate_flowgraph(self):
        """
        Terminates the flowgraph
        """
        if self.flowgraph and self.flowgraph.running:
            print("Stoping Observation")
            self.flowgraph.stop()    
        self.flowgraph = None

    def signal_handler(self, sig, frame):
        """
        Singal handler for SIGINT

        If SIGINT is received try to stop the flowgraph and then the
        ZMQ subscriber script
        """
        sys.exit()


def main():
    # Instantiate flowgraph dispatcher 
    flowgraph_d = FlowgraphDispatcher()
 
    # Add signal callbacks
    signal.signal(signal.SIGINT, flowgraph_d.signal_handler)
    signal.signal(signal.SIGTERM, flowgraph_d.signal_handler)
        
    (flowgraph_dispatcher_service_pb2_grpc
        .add_FlowgraphDispatcherServiceServicer_to_server(flowgraph_d,
                                                          flowgraph_d.grpc_server)
    )
    flowgraph_d.grpc_server.start()
    print("Server started")
    flowgraph_d.grpc_server.wait_for_termination()


if __name__ == '__main__':
    main()
