import IDesign as apex
import socket
import struct
import urllib.request
import xml.etree.ElementTree as ET
import sys
import re
import logging
import logging.handlers

#Object to hold onto server information
class Server:
    def __init__(self, serialNo, Ip):
        self.serialNo = serialNo
        self.ip = Ip
        self.port = 9720
        self.players = []
        self.numOfChild = 0
        pass
    
    def addPlayer(self, playerName, MAC, child):
        #Reserve the first 3 slots of a server for the Parent players, add the rest in a random order.
        if child:
            self.players.insert(self.numOfChild + 3, Player(playerName, MAC))
            self.numOfChild += 1
        else:
            self.players.append(Player(playerName, MAC))
        pass


# Object to hold onto player information
class Player:
    def __init__(self, name, MAC):
        self.name = str(name)
        self.MAC = str(MAC)
        pass


# Global variables and objects
g_Servers = []



system_data = apex.get_systemdata()

# Set up logger
logger = logging.getLogger('APEX')
if True:
    handler = logging.handlers.SysLogHandler(address = ('127.0.0.1', 514))
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
else:
    logger.setLevel(logging.CRITICAL)

def dbg(msg):
    logger.debug("MS-3 " + msg)

def info(msg):
    logger.info("MS-3 " + msg)


# Function to rewrite the driver
def CreateDriver(driver):
    global g_sources_in_use
    global g_SerialNums

    for server in g_Servers:
        #Empty out the old info to start over.
        server.players.clear()

    #Parse all servers on network's player information and put them in the correct objects.
    ParsePlayers()

    g_sources_in_use = []

    serverSerialNo = driver.get_config_value("ServerHWSerialNo")
    server = FindServer(serverSerialNo)

    # Write necisarry XML
    config = writeConfig(driver, server)

    dbg("Config Written")

    devDescrip = writeDeviceDescription(driver)

    dbg("Dev Descrip Written")

    # Tell ID what files to use
    driver.set_config(config)
    driver.save()

    dbg("Config Saved")

    driver.set_device_description(devDescrip)
    driver.config_changed("")
    driver.save()

    dbg("Dev Descrip Saved")

#################################
# XML Writers
#################################
def writeConfig(driver, server):
    xml  = '<?xml version="1.0" encoding="utf-8" ?>'
    xml += '\t\t<configuration>'

    xml += writeServerConfig()

    # If there is a serial number already stored in the Apex file, set up the file to use that server.
    if (server):
        dbg("Server Serial No. -> " + server.serialNo)
        xml += ServerSelected(driver, server, False)
    # There is no serial number stored so this is a new file, set it up to have the 1st server found selected.
    else:
        driver.set_config_value("ServerHWSerialNo", g_Servers[0].serialNo)
        xml += ServerSelected(driver, g_Servers[0], True)

    xml += writeConstantConfig()

    xml += '\t\t</configuration>'

    info("APEX CONFIGURATION: " + xml)
    return xml

# Write the XML part that is always there, and never changes.
def writeConstantConfig():
    xml = ""
    xml += '\t\t<category name="Library" description="Library Browsing">\n'
    xml += '\t\t<setting type="mcstring" name="Select Action" variable="Library_SelectAction" default="nothing" description="What to do when a non-openable item is selected">\n'
    xml += '\t\t<choice name="Do Nothing (user action required)"    value="nothing"/>\n'
    xml += '\t\t<choice name="Play Exclusive (replace queue)"       value="load"/>\n'
    xml += '\t\t<choice name="Play Soon (insert into queue)"        value="insert"/>\n'
    xml += '\t\t<choice name="Play Later (append to queue)"         value="add"/>\n'
    xml += '\t\t<choice name="Smart Later (append tracks to queue)" value="smartadd"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting name="Busy Timeout (in seconds)" type="integer" variable="Library_FetchTimeout_InSec" default="5"/>\n'
    xml += '\t\t</category>\n'

    xml += '\t\t<category name="Keyboard" description="Keyboard behavior" hidden="true">\n'
    xml += '\t\t<setting type="string"  name="Cursor"  variable="KeyboardCursor"       default=""      description="The cursor to display at the end of the keyboard buffer."/>\n'
    xml += '\t\t<setting type="boolean" name="Blink"   variable="KeyboardCursorBlink"  default="false" description="Should the cursor simulate blinking?"/>\n'
    xml += '\t\t</category>\n'

    xml += '\t\t<category name="List Retrieval" description="How list data is retrieved" hidden="true">\n'
    xml += '\t\t<setting type="integer" name="Initial Size" variable="List_Fetch_InitialSize" default="20" description="On the first fetch, get this many items [Range: 10-100]"/>\n'
    xml += '\t\t<setting type="integer" name="Multiplier" variable="List_Fetch_Multiplier"    default="2" description="On each subsequent fetch (Size = Size * Multiplier) [Range: 1-10]"/>\n'
    xml += '\t\t<setting type="integer" name="Maximum Size" variable="List_Fetch_MaximumSize" default="500" description="We never fetch more than this many at a time [Range: 100-1000]"/>\n'
    xml += '\t\t</category>\n'

    xml += '\t\t<category name="Fetch Delays" description="How we moderate rapid repeated requests to refresh information" hidden="true">\n'
    xml += '\t\t<setting type="integer" name="Queue Status Delay (in millisecs)" variable="FetchDelay_QueueStatus_InMSec" default="1000" description="Wait this long from the last call before fetching [Range: 0-5000]"/>\n'
    xml += '\t\t<setting type="integer" name="Queue Entries Delay (in millisecs)" variable="FetchDelay_QueueEntries_InMSec" default="2000" description="Wait this long from the last call before fetching [Range: 0-10000]"/>\n'
    xml += '\t\t<setting type="integer" name="Sync Group Delay (in millisecs)" variable="FetchDelay_SyncGroups_InMSec" default="500" description="Wait this long from the last call before fetching [Range: 0-5000]"/>\n'
    xml += '\t\t</category>\n'

    xml += '\t\t<category name="Debug Settings" description="Driver debug options" hidden="true">\n'
    xml += '\t\t<setting type="mcinteger" name="Debug General Activity" variable="Debug_General" default="2" description="Enable debug output of general activities">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug HTTP Activity" variable="Debug_HTTP" default="2" description="Enable debug output of HTTP operations">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Keyboard" variable="Debug_Keyboard" default="2" description="Enable debug output of keyboard activity">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Core Feedback" variable="Debug_Feedback" default="2" description="Enable debug output of music server feedback">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Url Formatting" variable="Debug_URL" default="2" description="Enable debug output of URL construction">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Art Retrieval" variable="Debug_Art" default="2" description="Enable debug output of art retrieval">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Favorite Tool" variable="Debug_FavoriteTool" default="2" description="Enable debug output of favorite tool">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Sync Tool" variable="Debug_SyncTool" default="2" description="Enable debug output of server\'s sync tool">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug View-Player Map" variable="Debug_ViewPlayerMap" default="2" description="Enable debug output of server\'s view-player map">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Server Discovery" variable="Debug_ServerDiscovery" default="2" description="Enable debug output of server interface">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Server" variable="Debug_Server" default="2" description="Enable debug output of server interface">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Library Browsing" variable="Debug_Library" default="2" description="Enable debug output of library browsing">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Queue" variable="Debug_Queue" default="2" description="Enable debug output of queue status">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Queue Progress" variable="Debug_QueueProgress" default="2" description="Enable debug output of queue progress">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug Performance" variable="Debug_Performance" default="2" description="Enable debug output of general driver performance">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'

    xml += '\t\t<setting type="mcinteger" name="Debug System Configuration" variable="Debug_SysConfig" default="2" description="Enable debug output of read configuration values">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug System Functions" variable="Debug_SysFuncs" default="2" description="Enable debug output of system comands">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug System Variables" variable="Debug_SysVars" default="2" description="Enable debug output of system variables">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t<setting type="mcinteger" name="Debug System Events" variable="Debug_SysEvents" default="2" description="Enable debug output of system events">\n'
    xml += '\t\t<choice name="Critical"    value="0"/>\n'
    xml += '\t\t<choice name="Error"       value="1"/>\n'
    xml += '\t\t<choice name="Warning"     value="2"/>\n'
    xml += '\t\t<choice name="Information" value="3"/>\n'
    xml += '\t\t<choice name="Trace"       value="4"/>\n'
    xml += '\t\t</setting>\n'
    xml += '\t\t</category>\n'

    return xml

# Write the Server category
def writeServerConfig():
    

    # Create the config items for the processor (global stuff)
    xml = '\t\t<category name="Server" description="These are required so the driver can communicate with the audio server." hidden="false">'
    # xml += '\t\t<setting type="string"  name="MAC Address"  variable="ServerMACAddress"  description="The \'eth0\' MAC address (xx:xx:xx:xx:xx:xx) of the server. Find this in the Configurator\'s Information page."/>'
   
    if (len(g_Servers) > 0):
        xml += '\t\t<setting type="mcstring"  name="Serial Number"  default="' + g_Servers[0].serialNo + '" variable="ServerHWSerialNo" description="The Serial Number of the audio server.">'   
        for server in g_Servers:
            xml += '\t\t<choice name="' + str(server.serialNo) + '" value="' + str(server.serialNo) + '"/>'
    else:
        xml += '\t\t<setting type="mcstring"  name="Serial Number"  default="Null" variable="ServerHWSerialNo" description="The Serial Number of the audio server.">' 
        xml += '\t\t<choice name="No Servers Found" value="Null"/>'
    xml += '\t\t</setting>'
    xml += '\t\t</category>'

    return xml

def writeDeviceDescription(driver):
    serverSerialNo = driver.get_config_value("ServerHWSerialNo")
    server = FindServer(serverSerialNo)

    xml  = '<?xml version="1.0" encoding="utf-8" ?>\n'
    xml += '\t<device>\n'
    xml += '\t\t<sources>\n'
    xml += '\t\t\t<source name="Server" sourceid="Server" maxitems="1"/>\n'

    dbg("Dev DESCRIP SERVER" + str(server))
    if (server):
        if(len(server.players) > 1):
            xml += '\t\t\t<source name="Player" sourceid="Player" maxitems="' + str(len(server.players)) + '">\n'
        else:
            # TODO: While we cannot solve the Player 1 naming with maxitems="1", use 2 so that the driver can auto-program.
            # This will NEED to be fixed at a later time. 
            xml += '\t\t\t<source name="Player" sourceid="Player" maxitems="2">\n'
        xml += '\t\t\t\t<capabilities>\n'
        xml += '\t\t\t\t<audio roomvolumesource="true"/>\n'
        xml += '\t\t\t\t</capabilities>\n'
        xml += '\t\t\t\t<templates>\n'
        xml += '\t\t\t\t<template name="RTI Music"/>\n'
        xml += '\t\t\t\t<template name="Music Player"/>\n'
        xml += '\t\t\t\t</templates>\n'
        xml += '\t\t\t\t</source>\n'


    xml += '\t\t</sources>\n'
    xml += '\t</device>\n'

    info("Device Description: " + xml)
    return xml


#################################
# Callback Functions
#################################

# Function to be used as a callback for any time the Config is updated in ID
# Takes what variable was changed, and updates the xml/database accordingly
def Update(driver, changed):
    serverSerialNo = driver.get_config_value("ServerHWSerialNo")
    server = FindServer(serverSerialNo)

    dbg("Changed -> " + changed)


    if changed == "ServerHWSerialNo":
        xml  = '<?xml version="1.0" encoding="utf-8" ?>'
        xml += '\t\t<configuration>'

        xml += writeServerConfig()

        xml += ServerSelected(driver, server, True)

        xml += writeConstantConfig()

        xml += '\t\t</configuration>'

        driver.set_config(xml)
        info("UPDATED APEX CONFIGURATION: " + xml)

        devDescrip = writeDeviceDescription(driver)
        driver.set_device_description(devDescrip)

    elif changed.find("Player") != -1:
        PlayerSelected(driver, changed, server)

    driver.config_changed("")
    driver.save()

def SourceAdded(driver):
    dbg("Source Added")

def SourceDeleted(driver):
    dbg("Source Deleted")
#################################
# Other Functions
#################################

# Write the XML category for players for the selected server.
# Use the "new" parameter to distinguish between if this is a newly selected server, or the one that was already selected
#    This is used to determine if we should re set the player values to default or not
def ServerSelected(driver, server, new):
    xml = '\t<category name="Player Names" description="The names of the MS-3 players">'

    for i in range(0,len(server.players)):
        player = server.players[i]

        #This is a new server selected, rewrite to default values
        if new:
            dbg("New Server Selected: Writing Player -> " + str(i) + " MAC -> " + player.MAC)
            driver.set_config_value("Player" + str(i) + "_MAC", player.MAC)
            driver.set_config_value("Player" + str(i) + "_Name", player.name)
 
        xml += '\t\t<setting name="Player ' + str(i+1) + ' Name" type="mcstring" variable="Player' + str(i) + '_Name"  sourceid="Player' + str(i+1) + '"  default="' + player.name + '">'
        for player in server.players:
            xml += '\t\t<choice name="' + str(player.name) + '" value="' + str(player.name) + '"/>'
        xml += '\t\t</setting>'

    xml += '\t</category>'

    driver.set_config_value("Player_Count", str(len(server.players)))
    dbg("Sources In Use: " + str(len(server.players)))

    return xml

#Update the MAC address of the changed player.
def PlayerSelected(driver, changed, server):
    #Get the new player's name from the config
    playerName = driver.get_config_value(changed)
    dbg ("PlayerName -> " + playerName)

    #Find the FIRST player that matches that name
    try:
        newPlayer = next((player for player in server.players if player.name == playerName), None)

        #Pull out which variable changed so we can change the respective mac address as well.
        playerPattern = "Player(.*?)_"
        index = re.search(playerPattern, changed).group(1)

        dbg("PlayerSelected: Updating Player" + index + "_MAC -> " + newPlayer.MAC)
        driver.set_config_value("Player" + index + "_MAC", newPlayer.MAC)
    except:
        dbg('Player Selected Does not Exist.')
        apex.messagebox('Player selected does not exist, please try re-discovering your RTI Music Devices')

# Go through all of the players we have found, and put them in their parents "players" array.
def ParsePlayers():
    for server in g_Servers:     
        #Send request to the server for player information
        res = urllib.request.urlopen('http://' + str(server.ip) + ":" + str(server.port) + "/oms/api/1/manage/squeezelite?action=fetch")

        decoded = res.read().decode("utf-8")
        dbg(decoded)

        #Create a tree from the returned XML
        tree = ET.fromstring(decoded)

        #Go thru the XML tree and create player objects for each player in the tree
        for player in tree.iter('squeezelite_player'):
            playerName = player.get('name')
            MAC = player.get('mac_addr')
            ip = player.get('server_ip')

            #The player is part of this server so set its parent to this server
            if ip == "127.0.0.1": 
                server.addPlayer(str(playerName), str(MAC), False)
                dbg("ParsePlayers: Adding a player to server " + str(server.serialNo) + " player->" + str(playerName) + "/" + str(MAC))
            #The player is not part of this server so find the server which has that IP
            else:
                try:
                    playersParent = next((server for server in g_Servers if server.ip == ip), None)
                    # Add the player to the parents player list
                    playersParent.addPlayer(str(playerName), str(MAC), True)
                    dbg("ParsePlayers: Adding a child player to server " + str(playersParent.serialNo) + " player->" + str(playerName) + "/" + str(MAC))
                except:
                    dbg('Player is configured to an incompatible server. Check configuration.')
                    apex.messagebox("RTI Music Player is not set up to a server, please check your server configuration.", apex.MessageBoxStyles.MB_OK)

#################################
# Discovery Functions
#################################
def Discovery(driver):
    global g_Servers
    sysIps = []

    # Get all networks on the computer to search on.
    for ip in socket.getaddrinfo(host=socket.gethostname(), port=None, family=socket.AF_INET):   
        sysIps.append(ip[4][0])

    g_Servers.clear()

    disc_Ip = "224.0.0.251"
    # disc_Ip = '255.255.255.255'
    disc_Port = 9720
    
    # Go through all networks on the computer and discover.
    for ip in sysIps:
        #Set up the socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        #Join mcast group
        dbg("Discovery: Joining mcast group")
        mreq = struct.pack("4sl", socket.inet_aton(disc_Ip), socket.INADDR_ANY)
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

        #Bind to the network - this forces "Discover" to be sent on each network.
        sock.bind((ip, disc_Port))

        sock.settimeout(2)

        # Send data to the multicast group
        dbg('sending "Discover"')
        sock.sendto(b"Discover", (disc_Ip,disc_Port))

        # Look for responses from all recipients
        while True:
            dbg('waiting to receive')
            try:
                data, server = sock.recvfrom(1024)
                data = str(data)
                server = str(server)
                dbg("Response: " + data)
                if ((data.find("model=\"MS3\"") != -1) or (data.find("model=\"MS1\"") != -1)):
                    # Pull out the server serial number
                    serialPattern = "hardware_serial_number=\"(.*?)\""
                    serial = re.search(serialPattern, data).group(1)

                    # Pull out the server's IP 
                    ipPattern = "\'(.*?)\'"
                    ip = re.search(ipPattern, server).group(1)
                    dbg("Discovery: Found Server -> " + serial + " at " + ip)

                    # If that serial number is already in the list do not add it again.
                    if not any(server.serialNo == serial for server in g_Servers):
                        g_Servers.append(Server(serial, ip))                   
                else:
                    dbg("Discovery: Response Invalid")
            except socket.timeout:
                dbg('timed out on ' + str(ip))
                break
            else:
                dbg('received "%s" from %s' % (data, server))
        
        sock.close()


    if len(g_Servers) == 0:
        apex.messagebox("Could Not find any MS-3 Servers", apex.MessageBoxStyles.MB_OK)
    else:
        apex.messagebox("Found " + str(len(g_Servers)) + " Devices.", apex.MessageBoxStyles.MB_OK)
        CreateDriver(driver)


#################################
# Helper/Utility functions
#################################
def FindServer(serialNo):
    for server in g_Servers:
        if server.serialNo == serialNo:
            return server
    #If we made it out of the for loop there was no matches
    else: 
        return None

def FindDriver(driverName):
    global system_data

    devices = system_data.get_devices()
    all = devices.get_all_devices(True)
    for device in all:
        if device.name() == driverName:
            return device
    return None

def loop(x, xmlLine):
    dbg("Looping " + str(x) + " times")
    i = 1
    loop_xml = ""
    while i <= x:
        xmlSub = re.sub("%%y%%", str(i-1), xmlLine)
        loop_xml += (re.sub("%%x%%", str(i), xmlSub)) + "\n"
        i += 1
    
    return loop_xml

# Function to clean name's of objects from characters which break XML (", ', `)
def CleanName(name):
    cleanName = name.replace("&", "&amp;")
    cleanName = cleanName.replace("\"", "&quot;")
    cleanName = cleanName.replace("\'", "&apos;")
    cleanName = cleanName.replace("xml = '\t\t<", "&lt;")
    cleanName = cleanName.replace(">\n'", "&gt;")

    return cleanName

#################################
# Let's start the script
#################################

# Register the callbacks
apex.register_menu_callback("Search for new Devices", Discovery)
apex.register_updated_callback(Discovery)
apex.register_create_callback(Discovery)
apex.register_config_callback(Update)

# try:
apex.register_source_added_callback(SourceAdded)
apex.register_source_deleted_callback(SourceDeleted)

# callbacks are registered, the driver is 'loaded'
dbg("Driver is Loaded. ")
dbg("Python Version: " + sys.version)
dbg("Platform: " + sys.platform)

this_driver = FindDriver("RTI Music")

# dbg("Driver name: " + this_driver.name())
