#######################################################
# 
# orchestrator.py
# Python implementation of the Class orchestrator
# Generated by Enterprise Architect
# Created on:      21-May-2020 12:24:48 PM
# Original author: Natha Paquette
# 
#######################################################
import threading
import time
import traceback

from FreeTAKServer.controllers.ActiveThreadsController import ActiveThreadsController
from FreeTAKServer.controllers.AsciiController import AsciiController
from FreeTAKServer.controllers.ClientInformationController import ClientInformationController
from FreeTAKServer.controllers.DatabaseControllers.DatabaseController import DatabaseController
from FreeTAKServer.controllers.MainSocketController import MainSocketController
from FreeTAKServer.controllers.ReceiveConnections import ReceiveConnections
from FreeTAKServer.controllers.ReceiveConnectionsProcessController import ReceiveConnectionsProcessController
from FreeTAKServer.controllers.SendClientData import SendClientData
from FreeTAKServer.controllers.SendDataController import SendDataController
from FreeTAKServer.controllers.SpecificCoTControllers.SendDisconnectController import SendDisconnectController
from FreeTAKServer.controllers.XMLCoTController import XMLCoTController
from FreeTAKServer.controllers.SendDataController import SendDataController
from FreeTAKServer.controllers.AsciiController import AsciiController
from FreeTAKServer.controllers.configuration.LoggingConstants import LoggingConstants
from FreeTAKServer.controllers.configuration.DataPackageServerConstants import DataPackageServerConstants as DPConst
from FreeTAKServer.model.RawCoT import RawCoT
from FreeTAKServer.controllers.SpecificCoTControllers.SendDisconnectController import SendDisconnectController
from FreeTAKServer.controllers.configuration.OrchestratorConstants import OrchestratorConstants
from FreeTAKServer.controllers.serializers.SqlAlchemyObjectController import SqlAlchemyObjectController
from FreeTAKServer.controllers.serializers.xml_serializer import XmlSerializer
from FreeTAKServer.model.Enumerations.connectionTypes import ConnectionTypes
from FreeTAKServer.model.FTSModel.Event import Event
from FreeTAKServer.model.RawCoT import RawCoT
from FreeTAKServer.model.SSLConnection import SSLConnection
from FreeTAKServer.model.SpecificCoT.Presence import Presence
from FreeTAKServer.model.TCPConnection import TCPConnection
from FreeTAKServer.model.User import User
from FreeTAKServer.model.ClientInformation import ClientInformation

ascii = AsciiController().ascii
from logging.handlers import RotatingFileHandler
import logging
import multiprocessing
import importlib
import socket
import sys

loggingConstants = LoggingConstants()

from FreeTAKServer.controllers.ClientReceptionHandler import ClientReceptionHandler


class Orchestrator:
    """ this class is the main class responsible for handling the CoT listener for XML
    """
    # TODO: fix repeat attempts to add user
    def __init__(self):
        self.connection_received = 0
        log_format = logging.Formatter(loggingConstants.LOGFORMAT)
        self.logger = logging.getLogger(loggingConstants.LOGNAME)
        self.logger.setLevel(logging.DEBUG)
        self.logger.addHandler(self.newHandler(loggingConstants.DEBUGLOG, logging.DEBUG, log_format))
        self.logger.addHandler(self.newHandler(loggingConstants.ERRORLOG, logging.ERROR, log_format))
        self.logger.addHandler(self.newHandler(loggingConstants.INFOLOG, logging.INFO, log_format))
        self.logger.addHandler(logging.StreamHandler(sys.stdout))
        self.sent_message_count = 0
        self.received_message_count = 0
        self.messages_to_core_count = 0
        self.messages_from_core_count = 0

        # create necessary queues
        # ex client information queue
        # {
        #   "client_id": socket
        # }
        self.clientInformationQueue = {}
        # this contains a list of all pipes which are transmitting CoT from clients
        self.pipeList = []
        # Internal Pipe used for CoT generated by the server itself
        self.internalCoTArray = []
        self.ClientReceptionHandlerEventPipe = ''
        # health check events
        self.healthCheckEventArray = []
        # instantiate controllers
        self.ActiveThreadsController = ActiveThreadsController()
        self.ClientInformationController = ClientInformationController()
        self.ReceiveConnections = ReceiveConnections()
        self.ReceiveConnectionsProcessController = ReceiveConnectionsProcessController()
        self.MainSocketController = MainSocketController()
        self.XMLCoTController = XMLCoTController()
        self.SendClientData = SendClientData()
        self.KillSwitch = 0
        self.openSockets = 0
        self.openSocketsArray = []

    def clear_user_table(self):
        """ clear all users from table

        """
        self.dbController.remove_user()
        print('user table cleared')

    def clientdatapipe_status_check(self) -> bool:
        """ this method checks that the clientdatapipe is not full and wont block

        Returns: bool representing whether or not the data pipe is functioning
        """
        if self.clientDataPipe.full():  # if queue is full return False as queue isnt working
            return False
        else:  # otherwise return true
            return True

    def remove_service_user(self, client_information):
        """ this method generates the presence object from the
        client_information parameter and sends it as a remove message
        to the client data pipe

        Args:
            client_information:

        Returns:
        """
        try:
            if self.clientdatapipe_status_check():
                if self.ssl:
                    connection_type = ConnectionTypes.SSL
                elif self.ssl is False:
                    connection_type = ConnectionTypes.TCP

                self.clientDataPipe.put(['remove', client_information, self.openSockets, connection_type])
                self.logger.debug("client removal has been sent through queue " + str(client_information))
            else:
                self.logger.critical("client data pipe is Full !")
        except Exception as e:
            self.logger.error("exception has been thrown removing client data from queue "+str(e))
            raise e

    def update_client_information(self, client_information: ClientInformation):
        """ this method generates the presence object from the
        client_information parameter and sends it as an update message
        to the client data pipe

        :param client_information: this is the information of the client to be updated

        Returns:

        """
        try:
            if self.clientdatapipe_status_check():
                presence_object = Presence()
                presence_object.modelObject = client_information.modelObject
                presence_object.xmlString = client_information.xmlString.decode()
                presence_object.clientInformation = client_information.modelObject
                self.clientDataPipe.put(['update', presence_object, self.openSockets, None])
                self.logger.debug("client update has been sent through queue " + str(client_information))
                self.get_client_information()
            else:
                self.logger.critical("client data pipe is Full !")
        except Exception as e:
            self.logger.error("exception has been thrown updating client data in queue "+str(e))
            raise e

    def add_service_user(self, client_information: ClientInformation):
        """ this method generates the presence and connection objects from the
        client_information parameter and sends it to

        :param client_information: this is the information of the client to be added
        :return:
        """
        try:
            if self.clientdatapipe_status_check():
                presence_object = Presence()
                presence_object.modelObject = client_information.modelObject
                presence_object.xmlString = client_information.idData
                presence_object.clientInformation = client_information.modelObject
                if self.ssl:
                    connection_object = SSLConnection()
                    # TODO: add certificate name derived from socket
                    connection_object.certificate_name = None
                elif self.ssl is False:
                    connection_object = TCPConnection()
                connection_object.sock = None
                connection_object.user_id = client_information.modelObject.uid
                self.clientDataPipe.put(['add', presence_object, self.openSockets, connection_object])
                self.logger.debug("client addition has been sent through queue " + str(client_information))
            else:
                self.logger.critical("client data pipe is Full !")
        except Exception as e:
            self.logger.error("exception has been thrown adding client data from queue " + str(e))
            raise e

    def get_client_information(self):
        """ this method gets client information from the client information pipe and returns it as a dict merged
        with the current client information list and updates the self variable

        :return:
        """
        try:
            if self.clientdatapipe_status_check():
                import copy
                if self.ssl is True:
                    conn_type = ConnectionTypes.SSL
                elif self.ssl is False:
                    conn_type = ConnectionTypes.TCP

                self.clientDataPipe.put(["get", conn_type, self.openSockets])
                user_dict = self.clientDataRecvPipe.get(timeout=10000)
                client_informaion_queue_client_ids = copy.copy(list(self.clientInformationQueue.keys()))

                for client_id in client_informaion_queue_client_ids:
                    if client_id in user_dict.keys() and len(self.clientInformationQueue[
                                                                 client_id]) == 1:  # forces FTS core to be single source of truth
                        self.clientInformationQueue[client_id].append(user_dict[client_id])

                    elif client_id in user_dict.keys() and len(self.clientInformationQueue[client_id]) == 2:
                        self.clientInformationQueue[client_id][1] = user_dict[client_id]

                    elif client_id not in user_dict.keys():  # if the entry isn't present in FTS core than the client will be disconnected and deleted to maintain single source of truth
                        self.logger.debug(
                            "disconnection client " + str(client_id) + " because client was not in FTS core user_dict")
                        self.disconnect_socket(self.clientInformationQueue[client_id][0])
                        del self.clientInformationQueue[client_id]

                    else:
                        self.logger.error("the data for this client is invalid " + str(client_id))
            else:
                self.logger.critical("client data pipe is Full !")
        except Exception as e:
            self.logger.error("exception has been thrown getting client data from queue " + str(e))

    def newHandler(self, filename, log_level, log_format):
        handler = RotatingFileHandler(
            filename,
            maxBytes=loggingConstants.MAXFILESIZE,
            backupCount=loggingConstants.BACKUPCOUNT
        )
        handler.setFormatter(log_format)
        handler.setLevel(log_level)
        return handler

    def send_user_connection_geo_chat(self, clientInformation):
        ''' function to create and send pm to newly connected user

        :param clientInformation: the object containing information about the user to which the msg is sent
        :return:
        '''
        # TODO: refactor as it has a proper implementation of a PM to a user generated by the server
        from FreeTAKServer.controllers.SpecificCoTControllers.SendGeoChatController import SendGeoChatController
        from FreeTAKServer.model.RawCoT import RawCoT
        from FreeTAKServer.model.FTSModel.Dest import Dest
        import uuid
        if OrchestratorConstants().DEFAULTCONNECTIONGEOCHATOBJ != None:
            ChatObj = RawCoT()
            ChatObj.xmlString = f'<event><point/><detail><remarks>{OrchestratorConstants().DEFAULTCONNECTIONGEOCHATOBJ}</remarks><marti><dest/></marti></detail></event>'

            classobj = SendGeoChatController(ChatObj, AddToDB=False)
            instobj = classobj.getObject()
            instobj.modelObject.detail._chat.chatgrp.setuid1(clientInformation.modelObject.uid)
            dest = Dest()
            dest.setcallsign(clientInformation.modelObject.detail.contact.callsign)
            instobj.modelObject.detail.marti.setdest(dest)
            instobj.modelObject.detail._chat.setchatroom(clientInformation.modelObject.detail.contact.callsign)
            instobj.modelObject.detail._chat.setparent("RootContactGroup")
            instobj.modelObject.detail._chat.setid(clientInformation.modelObject.uid)
            instobj.modelObject.detail._chat.setgroupOwner("True")
            instobj.modelObject.detail.remarks.setto(clientInformation.modelObject.uid)
            instobj.modelObject.setuid(
                'GeoChat.' + 'SERVER-UID.' + clientInformation.modelObject.detail.contact.callsign + '.' + str(
                    uuid.uuid1()))
            instobj.modelObject.detail._chat.chatgrp.setid(clientInformation.modelObject.uid)
            classobj.reloadXmlString()
            # self.get_client_information()
            self.sent_message_count += 1
            SendDataController().sendDataInQueue(None, instobj, self.clientInformationQueue)
            return 1
        else:
            return 1

    def clientConnected(self, raw_connection_information: RawCoT):
        """ this controls the client connection sequence calling methods which perform the following,
            instantiating the client object
            sharing the client with core
            adding the client to the db
            sending the connection message

        :param raw_connection_information:
        :return:
        """
        try:
            import copy
            # temporarily broken
            # self.check_for_dead_sockets()
            from FreeTAKServer.controllers.DatabaseControllers.EventTableController import EventTableController
            clientPipe = None
            self.logger.info(loggingConstants.CLIENTCONNECTED)
            clientInformation = self.ClientInformationController.intstantiateClientInformationModelFromConnection(
                raw_connection_information, clientPipe)
            if clientInformation == -1:
                self.logger.info("client had invalid connection information and has been disconnected")
                return -1
            sock = clientInformation.socket
            clientInformation.socket = None
            clint_info_clean = copy.deepcopy(clientInformation)
            clientInformation.socket = sock
            self.logger.debug("client conn A")
            if self.checkOutput(clientInformation):
                pass
            else:
                raise Exception('error in the creation of client information')
            self.openSockets += 1
            # breaks ssl
            try:
                if hasattr(clientInformation.socket, 'getpeercert'):
                    cn = "placeholder"
                else:
                    cn = None
                CoTRow = EventTableController().convert_model_to_row(clientInformation.modelObject)
                self.dbController.create_user(uid=clientInformation.modelObject.uid,
                                              callsign=clientInformation.modelObject.detail.contact.callsign,
                                              IP=clientInformation.IP, CoT=CoTRow, CN=cn)
            except Exception as e:
                print(e)
                self.logger.error(
                    'there has been an error in a clients connection while adding information to the database ' +
                    str(e))
            # self.logger.info(loggingConstants.CLIENTCONNECTEDFINISHED + str(clientInformation.modelObject.detail.contact.callsign))
            print("adding client")
            self.logger.debug("client conn B")
            self.add_service_user(client_information=clientInformation)
            self.clientInformationQueue[clientInformation.modelObject.uid] = [clientInformation.socket]
            print("client added")
            self.send_user_connection_geo_chat(clientInformation)
            self.logger.debug("client conn C")
            return clientInformation
        except Exception as e:
            self.logger.warning(loggingConstants.CLIENTCONNECTEDERROR + str(e))
            return -1

    def emergencyReceived(self, processedCoT):
        try:
            if processedCoT.status == loggingConstants.ON:
                self.internalCoTArray.append(processedCoT)
                self.logger.debug(loggingConstants.EMERGENCYCREATED)
            elif processedCoT.status == loggingConstants.OFF:
                for CoT in self.internalCoTArray:
                    if CoT.type == "Emergency" and CoT.modelObject.uid == processedCoT.modelObject.uid:
                        self.internalCoTArray.remove(CoT)
                        self.logger.debug(loggingConstants.EMERGENCYREMOVED)
        except Exception as e:
            self.logger.error(loggingConstants.EMERGENCYRECEIVEDERROR + str(e))

    def dataReceived(self, raw_cot: RawCoT):
        """this will be executed in the event that the use case for the CoT isn't specified in the orchestrator

        :param raw_cot: the CoT to be processed and shared
        """
        try:
            # this will check if the CoT is applicable to any specific controllers
            raw_cot = self.XMLCoTController.determineCoTType(raw_cot)

            # the following calls whatever controller was specified by the above function
            module = importlib.import_module('FreeTAKServer.controllers.SpecificCoTControllers.' + raw_cot.CoTType)
            CoTSerializer = getattr(module, raw_cot.CoTType)
            # TODO: improve way in which the dbController is passed to CoTSerializer
            raw_cot.dbController = self.dbController
            processedCoT = CoTSerializer(raw_cot).getObject()

            # this statement checks if the data type is a user update and if so it will be saved to the associated client object
            if raw_cot.CoTType == 'SendUserUpdateController':
                # find entry with this uid
                self.update_client_information(client_information=processedCoT)
            sender = processedCoT.clientInformation
            # this will send the processed object to a function which will send it to connected clients
            '''try:
                # TODO: method of determining if CoT should be added to the internal array should
                #  be improved
                if processedCoT.type == "Emergency":
                    self.emergencyReceived(processedCoT)
                else:
                    pass
            except Exception as e:
                return -1'''
            return processedCoT
        except Exception as e:
            self.logger.error(loggingConstants.DATARECEIVEDERROR + str(e))
            return -1

    def sendInternalCoT(self, client):
        try:
            pass
            """if len(self.internalCoTArray) > 0:
                for processedCoT in self.internalCoTArray:
                    SendDataController().sendDataInQueue(processedCoT.clientInformation, processedCoT, {client.modelObject.uid: self.clientInformationQueue[client.modelObject.uid]})
            else:
                pass
            self.send_active_emergencys(client)
            return 1"""
        except Exception as e:
            self.logger.error(loggingConstants.MONITORRAWCOTERRORINTERNALSCANERROR + str(e))
            return -1

    def send_active_emergencys(self, client):
        """
        this function needs to be cleaned up however it's functionality is as follows
        it query's the DB for active emergency's at which point it iterates over all
        emergency objects, transforms them into model objects and then xmlStrings
        finally the object is sent to the client.

        :param client: client to send active emergency too
        """
        try:

            from FreeTAKServer.model.SpecificCoT.SendEmergency import SendEmergency
            from lxml import etree
            emergencys = self.dbController.query_ActiveEmergency()
            for emergency in emergencys:
                emergencyobj = SendEmergency()
                modelObject = Event.emergecyOn()

                filledModelObject = SqlAlchemyObjectController().convert_sqlalchemy_to_modelobject(emergency.event,
                                                                                                   modelObject)
                # emergencyobj.setXmlString(XMLCoTController().serialize_model_to_CoT(filledModelObject))
                emergencyobj.setXmlString(
                    etree.tostring((XmlSerializer().from_fts_object_to_format(filledModelObject))))
                print(emergencyobj.xmlString)
                emergencyobj.setModelObject(filledModelObject)
                self.sent_message_count += 1
                SendDataController().sendDataInQueue(None, emergencyobj, [client])

        except Exception as e:
            import traceback
            self.logger.error(traceback.format_exc())
            self.logger.error('an exception has been thrown in sending active emergencies ' + str(e))

    def clientDisconnected(self, client_information: User):
        """this method is responsible for handling the disconnection of clients

        :param client_information:
        :return:
        """
        import traceback
        print('disconnecting client')
        try:
            if hasattr(client_information, "clientInformation"):
                client_information = client_information.clientInformation
            sock = self.clientInformationQueue[client_information.user_id][0]
        except Exception as e:
            self.logger.critical("getting sock from client information queue failed " + str(e))
        try:
            self.logger.debug('client ' + client_information.m_presence.modelObject.uid + ' disconnected ' + "\n".join(
                traceback.format_stack()))
        except Exception as e:
            self.logger.critical("there was an error logging disconnection information " + str(e))
        try:
            del self.clientInformationQueue[client_information.user_id]
        except Exception as e:
            self.logger.critical("client removal failed " + str(e))
        try:
            self.ActiveThreadsController.removeClientThread(client_information)
            self.dbController.remove_user(query=f'uid = "{client_information.user_id}"')
        except Exception as e:
            self.logger.critical(
                'there has been an error in a clients disconnection while adding information to the database ' + str(e))
            pass
        try:
            self.remove_service_user(client_information=client_information)
            # working
            # time.sleep(1)
            print('stage 1 c')
            self.disconnect_socket(sock)

            self.logger.info(loggingConstants.CLIENTDISCONNECTSTART)
            # TODO: remove string
            tempXml = RawCoT()
            tempXml.xmlString = '<event><detail><link uid="{0}"/></detail></event>'.format(
                client_information.user_id).encode()
            disconnect = SendDisconnectController(tempXml)
            self.get_client_information()
            self.sent_message_count += 1
            self.messages_to_core_count += 1
            SendDataController().sendDataInQueue(disconnect.getObject().clientInformation, disconnect.getObject(),
                                                 self.clientInformationQueue, self.CoTSharePipe)
            self.logger.info(loggingConstants.CLIENTDISCONNECTEND + str(
                client_information.m_presence.modelObject.uid))
            return 1
        except Exception as e:
            import traceback
            import sys, linecache
            exc_type, exc_obj, tb = sys.exc_info()
            f = tb.tb_frame
            lineno = tb.tb_lineno
            filename = f.f_code.co_filename
            linecache.checkcache(filename)
            line = linecache.getline(filename, lineno, f.f_globals)
            self.logger.error(loggingConstants.CLIENTCONNECTEDERROR + " " + str(e) + " on line: " + line)

    def disconnect_socket(self, sock: socket.socket) -> None:
        """this method is responsible for disconnecting all socket objects

        :param sock: socket object to be disconnected
        """
        print('\n\n disconnecting client \n\n')
        try:
            sock.shutdown(socket.SHUT_RDWR)
        except Exception as e:
            self.logger.error(
                'error shutting socket down in client disconnection ' + str(e) + "\n".join(traceback.format_stack()))
        try:
            sock.close()
        except Exception as e:
            self.logger.error(
                'error closing socket in client disconnection ' + str(e) + "\n".join(traceback.format_stack()))

    def monitor_raw_cot(self, data: RawCoT) -> object:
        """ this method takes as input a sent CoT and calls it's associated function

        this method supports three handlers defined in XMLCoTController which handle, connect, disconnect
        and misc messages respectively

        :param data:
        :return:
        """
        try:
            if isinstance(data, int):
                return None
            else:
                cot = XMLCoTController(logger=self.logger).determineCoTGeneral(data)
                handler = getattr(self, cot[0])
                output = handler(cot[1])
                output.clientInformation = self.clientInformationQueue[data.clientInformation][1]
                return output
        except Exception as e:
            self.logger.error(loggingConstants.MONITORRAWCOTERRORB + str(e))
            return -1

    def checkOutput(self, output):
        """ this method checks whether or not the return data was valid

        :param output: any type which was returned by a function
        :rtype: bool indicating whether the output was valid or not
        """
        if output != -1 and output is not None and isinstance(output, object):
            return True
        else:
            return False

    def loadAscii(self):
        ascii()

    def mainRunFunction(self, clientData, receiveConnection, sock, pool, event, clientDataPipe,
                        ReceiveConnectionKillSwitch, CoTSharePipe, ssl=False):

        """ This is the central method which is responsable for the functioning of the CoT service's it iterates over
        two main sub services,
        ReceiveConnections, which receives new connections to the server,
        ClientReceptionHandler, which receives data from connected clients,

        for each of these methods the respective data handlers are called and the serialization process begins

        :param clientData:
        :param receiveConnection:
        :param sock:
        :param pool:
        :param event:
        :param clientDataPipe:
        :param ReceiveConnectionKillSwitch:
        :param CoTSharePipe:
        :param ssl:
        """
        print('server started')
        if ssl:
            print("\n\n running ssl \n\n")
        else:
            threading.Thread(target=self.monitor).start()
        self.ssl = ssl
        import datetime
        import time
        receiveconntimeoutcount = datetime.datetime.now()
        lastprint = datetime.datetime.now()
        start_timer = time.time() - 60
        while event.is_set():
            self.CoTSharePipe = CoTSharePipe
            try:
                if ssl == True:
                    pass
                self.clientDataPipe = clientDataPipe
                if event.is_set():
                    try:
                        if ReceiveConnectionKillSwitch.is_set():
                            try:
                                receiveConnection.successful()
                            except:
                                pass
                            ReceiveConnectionKillSwitch.clear()
                            receiveConnection = pool.apply_async(ReceiveConnections().listen,
                                                                 (sock,))
                        else:
                            # print('receiving connection')
                            receiveConnectionOutput = receiveConnection.get(timeout=0.01)
                            self.connection_received += 1
                            print(self.connection_received)
                            receiveConnection = pool.apply_async(ReceiveConnections().listen, (sock, ssl,))
                            receiveconntimeoutcount = datetime.datetime.now()
                            lastprint = datetime.datetime.now()
                            CoTOutput = self.handel_connection_data(receiveConnectionOutput)

                    except multiprocessing.TimeoutError:

                        if (datetime.datetime.now() - receiveconntimeoutcount) > datetime.timedelta(
                                seconds=60) and ssl == True:
                            from multiprocessing.pool import ThreadPool
                            try:
                                pass
                            except Exception as e:
                                print(str(e))
                        elif ssl == True and (datetime.datetime.now() - lastprint) > datetime.timedelta(seconds=30):
                            print('time since last reset ' + str(datetime.datetime.now() - receiveconntimeoutcount))
                            lastprint = datetime.datetime.now()
                        else:
                            pass
                    except Exception as e:
                        self.logger.error('exception in receive connection within main run function ' + str(e))

                    try:
                        clientDataOutput = clientData.get(
                            timeout=0.01)  # attempt to retrieve data from the client reception hndler

                        if self.checkOutput(clientDataOutput) and isinstance(clientDataOutput, list):
                            if clientDataOutput != []:  # just added so log exist of most recent client data output and the time it was sent
                                recent_client_data_output = (clientDataOutput, time.time())
                            self.received_message_count += len(
                                clientDataOutput)  # add the length of this list to the number of received messages
                            CoTOutput = self.handel_regular_data(clientDataOutput)
                        else:
                            clientData = pool.apply_async(ClientReceptionHandler().startup,
                                                          (self.clientInformationQueue,))
                            raise Exception(
                                'client reception handler has returned data which is not of type list data is ' + str(
                                    clientDataOutput))
                        self.get_client_information()
                        clientData = pool.apply_async(ClientReceptionHandler().startup, (self.clientInformationQueue,))
                    except multiprocessing.TimeoutError:
                        pass
                    except Exception as e:
                        self.logger.info('exception in receive client data within main run function ' + str(e))
                        pass
                    try:
                        for x in range(100):
                            if not CoTSharePipe.empty():

                                data = CoTSharePipe.get()
                                self.handel_shared_data(data)
                            else:
                                break
                    except Exception as e:
                        self.logger.error(
                            'there has been an excepion in the handling of data supplied by the rest API ' + str(e))
                        pass
                else:
                    self.stop()
                    break
                try:
                    if time.time() > start_timer + 60:
                        start_timer = time.time()
                        self.logger.debug(str('mainRunFunction is running'))
                        # self.logger.debug('CoTSharePipe is full ' + str(CoTSharePipe.full()))
                        # self.logger.debug('clientDataPipe is full ' + str(clientDataPipe.full()))
                        if 'recent_client_data_output' in locals():
                            self.logger.debug('most recent client data ' + str(recent_client_data_output))
                            self.logger.debug(
                                'time since last valid data ' + str(time.time() - recent_client_data_output[1]))
                            self.logger.debug(
                                'content of last valid data ' + str(recent_client_data_output[0][0].xmlString))
                        self.logger.debug('client dict: ' + str(self.clientInformationQueue))
                except Exception as e:
                    self.logger.error("the periodic debug message has thrown an error " + str(e))
            except Exception as e:
                self.logger.info('there has been an uncaught error thrown in mainRunFunction' + str(e))
                pass
        self.stop()

    def handel_shared_data(self, modelData):
        """ this method is responsible for receiving and forwarding data shared via IPC

        :param modelData:
        :return:
        """
        try:
            # print('\n \n handling shared data \n \n')
            # print('data received within orchestrator '+str(modelData.xmlString))
            self.get_client_information()
            self.messages_from_core_count += 1
            if hasattr(modelData, 'clientInformation'):
                self.sent_message_count += 1
                output = SendDataController().sendDataInQueue(modelData.clientInformation, modelData,
                                                              self.clientInformationQueue)
            #

            # elif isinstance(modelData, User):
            #    self.internalCoTArray.append(modelData.m_presence)

            # this runs in the event of a new connection
            else:
                self.sent_message_count += 1
                output = SendDataController().sendDataInQueue(None, modelData,
                                                              self.clientInformationQueue)
        except Exception as e:
            self.logger.error("data base connection error " + str(e))
            print(e)

    def handel_regular_data(self, clientDataOutput: RawCoT):
        """ this method initiates the serializtion and distribution of
        "regular" data being sent by clients. regular data entails data which
        is neither a new connection nor a disconnection.

        :argument clientDataOutput: list of RawCoT objects

        :rtype: None

        """
        try:
            for clientDataOutputSingle in clientDataOutput:
                try:
                    if not self.ssl:
                        print('handling reg data')
                    if clientDataOutputSingle == -1:
                        continue
                    CoTOutput = self.monitor_raw_cot(clientDataOutputSingle)
                    self.logger.info("cot serialized " + str(CoTOutput.modelObject.uid) )
                    if CoTOutput == 1:
                        continue
                    elif self.checkOutput(CoTOutput):
                        self.get_client_information()
                        self.sent_message_count += 1
                        self.messages_to_core_count += 1
                        output = SendDataController().sendDataInQueue(CoTOutput.clientInformation, CoTOutput,  # pylint: disable=no-member; isinstance checks that CoTOutput is of proper type
                                                                      self.clientInformationQueue, self.CoTSharePipe)
                        if self.checkOutput(output) and isinstance(output, tuple) == False:
                            pass
                        elif isinstance(output, tuple):
                            self.logger.error('issue sending data to client now disconnecting')
                            self.clientDisconnected(output[1])

                        else:
                            self.logger.error('send data failed in main run function with data ' + str(
                                CoTOutput.xmlString) + ' from client ' + CoTOutput.clientInformation.modelObject.detail.contact.callsign)  # pylint: disable=no-member; isinstance checks that CoTOutput is of proper type and should thus have all specified members

                    else:
                        raise Exception('error in general data processing')
                except Exception as e:
                    self.logger.info(
                        'exception in client data, data processing within main run function ' + str(
                            e) + ' data is ' + str(CoTOutput))
                    pass
                except Exception as e:
                    self.logger.info(
                        'exception in client data, data processing within main run function ' + str(
                            e) + ' data is ' + str(clientDataOutput))
        except Exception as e:
            self.logger.info("there has been an error iterating client data output " + str(e))
            return -1
        return 1

    def handel_connection_data(self, receive_connection_output: RawCoT) -> None:
        """this method should be called to initiate the process for receiving new connection data
        :rtype: None
        :param receive_connection_output: a RawCoT object from a newly connected client
        """
        try:
            print('handling conn data')
            if receive_connection_output == -1:
                return None

            CoTOutput = self.monitor_raw_cot(receive_connection_output)
            if CoTOutput != -1 and CoTOutput != None:
                self.sent_message_count += 1
                output = SendDataController().sendDataInQueue(CoTOutput, CoTOutput,
                                                              self.clientInformationQueue, self.CoTSharePipe)
                if self.checkOutput(output):
                    self.logger.debug('connection data from client ' + str(
                        CoTOutput.modelObject.detail.contact.callsign) + ' successfully processed')
                else:
                    raise Exception('error in sending data')
            else:
                pass
        except Exception as e:
            self.logger.error('exception in receive connection data processing within main run function ' + str(
                e) + ' data is ' + str(CoTOutput))
            return -1
        self.sendInternalCoT(CoTOutput)
        return 1

    def start(self, IP, CoTPort, Event, clientDataPipe, ReceiveConnectionKillSwitch, RestAPIPipe, clientDataRecvPipe):
        """this method should be overridden by all inheriting classes and should implement the variables
        type hinted in this abstract class, finally it should be responsible for initiating the mainRunFunction
        """
        self.logger
        self.dbController: DatabaseController
        self.pool: multiprocessing.pool.ThreadPool
        self.clientDataRecvPipe: multiprocessing.Queue

    def stop(self):
        self.clientDataPipe.close()
        self.pool.terminate()
        self.pool.close()
        self.pool.join()

    def monitor(self):
        """ this method, which should be run in a thread executes a logging process every 15 seconds.
        The conents of the log entry will contain the following information:
            Messages Sent,
            Messages Received,
            Messages shared with core,
            Messages received from core,
            clients connected currently
        """
        logging_interval = 15

        while True:
            time.sleep(15)
            try:
                self.logger.debug(f"messages sent to clients in {logging_interval} seconds: {self.sent_message_count}")
                self.logger.debug(
                    f"messages received from clients in {logging_interval} seconds: {self.received_message_count}")
                self.logger.debug(
                    f"messages shared with core in {logging_interval} seconds: {self.messages_to_core_count}")
                self.logger.debug(
                    f"messages shared with core in {logging_interval} seconds: {self.messages_from_core_count}")
                self.logger.debug(f"number of connected client: {str(len(self.clientInformationQueue.keys()))}")
                self.sent_message_count = 0
                self.received_message_count = 0
                self.messages_to_core_count = 0
                self.messages_from_core_count = 0
            except Exception as e:
                self.logger.critical("logging service failed with exception " + str(e))


"""if __name__ == "__main__":

    parser = argparse.ArgumentParser(description=OrchestratorConstants().FULLDESC)
    parser.add_argument(OrchestratorConstants().COTPORTARG, type=int, help=OrchestratorConstants().COTPORTDESC,
                        default=OrchestratorConstants().COTPORT)
    parser.add_argument(OrchestratorConstants().IPARG, type=str, help=OrchestratorConstants().IPDESC,
                        default=OrchestratorConstants().IP)
    parser.add_argument(OrchestratorConstants().APIPORTARG, type=int, help=OrchestratorConstants().APIPORTDESC,
                        default=DataPackageServerConstants().APIPORT)
    args = parser.parse_args()
    CreateStartupFilesController()
    Orchestrator().start(args.IP, args.CoTPort, args.APIPort)"""
