text: import json
  import socket
  import threading
  
  from club_controller import app_config
  from club_controller.misc.config_manager import config_manager
  from club_controller.protocol.message_ids import (ClientMessageId,
                                                    ServerMessageId)
  from eventhandler import EventHandler
  
  from .client import ON_TIMEOUT_EVENT_MESSAGE
  from .client_provider import client_provider
  from .client_type_id import ClientTypeId
  
  ON_CLIENT_CONNECTED_EVENT_MESSAGE = "onClientConnected"
  ON_CLIENT_DISCONNECTED_EVENT_MESSAGE = "onClientDisconnected"
  
  
  class ClientUDPListener:
      def __init__(self):
          self.clients_lock = threading.Lock()
          self.clients = []
          self.event_handler = EventHandler(ON_CLIENT_CONNECTED_EVENT_MESSAGE, ON_CLIENT_DISCONNECTED_EVENT_MESSAGE)
          self.buffer_size = 1024
          self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
          self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
          self.sock.bind(('0.0.0.0', app_config.SERVER_UDP_PORT))
          config_manager.subscribe_on_new_config_loaded(self.load_clients_from_config)
          self.load_clients_from_config()
  
  
      def start_server_async(self):
          self.is_running = True
          while(self.is_running):
              if __debug__ and app_config.PRINT_UDP_STREAM_MESSAGES:
                  print("Waiting for new UDP message")
              value_pair = self.sock.recvfrom(self.buffer_size)
              message = value_pair[0]
              address = value_pair[1]
              if message.__len__() == 0:
                  print("Received message with len 0")
                  return
  
              message_id = message[0]
              message = message[1:]
              if __debug__ and app_config.PRINT_UDP_STREAM_MESSAGES:
                  print("Received messageId: " + str(message_id) + " with message: " + str(message))
              client_message_id = ClientMessageId(message_id)
              if(client_message_id == ClientMessageId.CONNECT):
                  self.handle_client_connection(message)
              elif(client_message_id == ClientMessageId.KEEPALIVE):
                  if __debug__ and app_config.PRINT_UDP_STREAM_MESSAGES:
                      print("Received KEEPALIVE from : " + str(address))
                  client = self.get_client_by_value("ip", address[0])
                  if client != None:
                      client.keep_alive()
                  else:
                      if __debug__ and app_config.PRINT_UDP_STREAM_MESSAGES:
                          print("Got KEEPALIVE from unregistered client: " + str(address))
  
              else:
                  if __debug__:
                      print("Received message: " + str(message) + " from: " + str(address))
                      print("Unkown messageId: " + str(message_id))
  
  
      def load_clients_from_config(self):
          self.clients_lock.acquire()
          self.clients = []
          try:
              print(config_manager.get_clients_config()['clients'])
              for client_json in config_manager.get_clients_config()['clients']:
                  known_client = client_provider.get(client_json["type_id"], **client_json)
                  if known_client != None:
                      self.clients.append(known_client)
              print("\nClient list file was loaded successfully\n")
          except:
              print("\nClient list file is corrupt\n")
          self.clients_lock.release()
  
  
      def get_new_client_uid(self):
          uid = 0
          while any(c.uid == uid for c in self.clients):
              uid += 1
          return uid
  
  
      def subscribe_on_client_connected(self, callback):
          self.event_handler.link(callback, ON_CLIENT_CONNECTED_EVENT_MESSAGE)
  
  
      def subscribe_on_client_disconnected(self, callback):
          self.event_handler.link(callback, ON_CLIENT_DISCONNECTED_EVENT_MESSAGE)
  
  
      def get_clients(self):
          return self.clients
  
  
      def get_all_clients_by_type_id(self, client_type_id : ClientTypeId):
          self.clients_lock.acquire()
          clients = list(filter(lambda c: c.type_id == int(client_type_id), self.clients))
          self.clients_lock.release()
          return clients
  
  
      def get_led_strip_clients(self):
          return self.get_all_clients_by_type_id(ClientTypeId.LED_STRIP_CLIENT)
  
  
      def get_client_by_value(self, key, value):
          self.clients_lock.acquire()
          client = None
          for c in self.clients:
              if c.__dict__[key] == value:
                  client = c
          self.clients_lock.release()
          return client
  
  
      def disconnect(self, client):
          if __debug__:
              print("Client disconnected: " + str(client.ip))
          client.send_message_id(ServerMessageId.DISCONNECT)
          self.event_handler.fire(ON_CLIENT_DISCONNECTED_EVENT_MESSAGE, client)
          self.get_client_by_value("uid", client.uid).is_connected = False
          if __debug__:
              self.print_client_list()
  
  
      def on_client_timeout(self, client):
          if __debug__:
              print("Client timeout: " + str(client.ip))
          self.disconnect(client)
  
  
      def handle_client_connection(self, message):
          message_json = json.loads(message)
          if __debug__:
              print("\nhandle_client_connection message_json: " + str(message_json))
          # check if client is already known
          known_client = self.get_client_by_value("mac", message_json["mac"])
          if __debug__:
              try:
                  print("known_client: " + str(known_client))
              except:
                  print("known_client is None")
          if known_client != None:
              if __debug__:
                      print("Client is already known with mac: " + str(known_client.mac))
              known_client.ip = message_json["ip"]
              known_client.port = message_json["port"]
              if known_client.is_connected:
                  known_client.send_message_id(ServerMessageId.ALREADY_CONNECTED)
                  return
              else:
                  self.clients_lock.acquire()
                  known_client.is_connected = True
                  self.clients_lock.release()
                  known_client.send_connection_message()
                  self.event_handler.fire(ON_CLIENT_CONNECTED_EVENT_MESSAGE, known_client)
                  known_client.event_handler.link(self.on_client_timeout, ON_TIMEOUT_EVENT_MESSAGE)
  
                  self.update_clients_config()
                  if __debug__:
                      print("Client re-connected: " + str(known_client.name))
          else:
              if not "name" in message_json:
                  message_json["name"] = "NAME NOT SET"
              # get and assign a new uid
              message_json["uid"] = self.get_new_client_uid()
              new_client = client_provider.get(ClientTypeId(message_json["type_id"]), **message_json)
              assert new_client != None
              self.clients_lock.acquire()
              new_client.is_connected = True
              self.clients.append(new_client)
              self.clients_lock.release()
              new_client.send_connection_message()
              self.event_handler.fire(ON_CLIENT_CONNECTED_EVENT_MESSAGE, new_client)
              new_client.event_handler.link(self.on_client_timeout, ON_TIMEOUT_EVENT_MESSAGE)
              self.update_clients_config()
              if __debug__:
                  print("New client connected with ip: " + str(new_client.ip))
  
          if __debug__:
              self.print_client_list()
  
  
      def update_client(self, new_client_json):
          client = self.get_client_by_value("uid", new_client_json["uid"])
          if client:
              self.clients_lock.acquire()
              client.update_from_json(new_client_json)
              self.clients_lock.release()
  
  
      def update_all_led_strips(self, new_data):
          for client in self.get_led_strip_clients():
              self.clients_lock.acquire()
              client.update_from_json(new_data)
              self.clients_lock.release()
  
  
      def update_clients_config(self):
          data = {}
          self.clients_lock.acquire()
          data["clients"] = list(map(lambda c : c.toJson(), self.clients))
          self.clients_lock.release()
          config_manager.get_clients_config().update(data)
  
  
      def print_client_list(self):
          print("\nClient list: " + str(list(map(lambda c: c.toJson(), self.get_clients()))) + "\n")
  
  
      def run(self):
          client_handler_thread = threading.Thread(target=self.start_server_async, name="Client-Handler-Thread")
          client_handler_thread.start()
  
  
      def stop(self):
          self.is_running
          for client in self.get_clients():
              client.stop()
          self.is_running = False
          self.update_clients_config()
  
          try:
              self.sock.shutdown(socket.SHUT_RDWR)
          except:
              print("Error while shutting down Client Handler server socket")
          try:
              self.sock.close()
          except:
              print("Error while closing Client Handler server socket")
  