#OSC.py
#This includes the OSCMessage, MessageWrapper, and Dispatcher classes
#It imports the OSCUtilities class.

#To make a OSC Message
	#message = OSC.OSCMessage
#To change the address
	#message.setAddress()
#To append data
	#message.append()
#To create the callback dispatcher
	#callback = OSC.Dispatcher(None,None)
#To Add a Callback
	#callback.add(function,address,userdata)
#To Remove a Callback
	#callback.remove(address)
#Callbacks must take only one MessageWrapper object as a parameter
	#MessageWrappers have the following functions:
		#Returns the full address of the message: getAddress()
		#Returns the userdata: getUserData()
		#Returns the currentpath: getCurrentPath()
		#Returns the arguements: getArguements()	

#Messages that are missing bytes will throw an exception

#A message with address "/notification/go/stop" will call the callback for
#/notification, /notification/go, and /notification/go/stop

from OSCUtilities import *
import OSCUtilities


class OSCMessage:
    """Builds typetagged OSC messages."""
    def __init__(self):
        self.address  = ""
        self.typetags = ","
        self.message  = ""
        self.realAddress = ""

    def setAddress(self, address):
        self.address = address

    def append(self, argument, typehint = None):
        """
        Appends data to the message, updating the typetags based on
        the argument's type. If the argument is a blob (counted string)
        pass in 'b' as typehint.
        """

        if typehint == 'b':
            binary = OSCBlob(argument)
        else:
            binary = OSCArgument(argument)

        self.typetags = self.typetags + binary[0]
        self.rawAppend(binary[1])
	
    def rawAppend(self, data):
        """Appends raw data to the message.  Use append()."""
        self.message = self.message + data

	
    def getBinary(self):
        """Returns the binary message (so far) with typetags."""
        address  = OSCArgument(self.address)[1]
        typetags = OSCArgument(self.typetags)[1]
        length = len(address + typetags + self.message)
        binary  = struct.pack(">i", length)
        
        return binary + address + typetags + self.message

    def __repr__(self):
        return self.getBinary()
	

class MessageWrapper:
    def __init__(self,array):
        self._message = array
        self._currentPath = ""
        self._userdata = ""

    def display(self, str = '', addReturn = None):
        print str, self.currentPath(),
        for i in range(self.numArguments()):
            print self.argument(i),
        if addReturn != None:
            print

    def setCurrentPath(self, text):
        self._currentPath = text

    def getAddress(self):
        return self._message[0]

    def currentPath(self):
        return self._currentPath

    def argumentTypes(self):
        return self._message[1]

    def getArguments(self):
        return self._message[2:]

    def argument(self, index):
        return self._message[index + 2]

    def numArguments(self):
        return len( self._message[2:] )

    def userData(self):
        return self._userdata

    def setUserdata(self,text):
        self._userdata = text

    def argumentsFromOffset(self, offset = 0):
        return self._message[(2 + offset):]

    def stringChannel(self):
        """
        Assume this message is a CSOUND_STRING_CHANNEL channel
        argument 0 - is the type (should be CSOUND_STRING_CHANNEL)
        argument 1 - is the string        
        """
        return self.argument(1)
        
    def audioChannel(self):
        """
        Assume this message is a CSOUND_AUDIO_CHANNEL channel
        argument 0 - is the type (should be CSOUND_AUDIO_CHANNEL)
        argument 1 - is the number of samples in the blob
        argument 2 - is the blob data itself which should be network byte ordered floats
        we unpack the stream and return it as a convenient python collection
        """
        numSamples = self.argument(1)
        blob = self.argument(2)
        sizeofSample = struct.calcsize("f")
        return struct.unpack(">"+str(numSamples)+"f", blob)
        
class Dispatcher:
    def __init__(self, default = None, userdata = None):            
        self._children = {}
        self._default = default
        self._userdata = userdata
        self.responseWaitList = {}
        
    def handle(self, data, source = None):
        """
        Given OSC data, tries to call the callback with the
        right address.
        """
        self.dispatch(decodeOSC(data))
        
    def parse(self,name):
    	parsed = []
        start = -1
    	
        for i in range(0,len(name)):
            if name[i] == '/':
                if start != -1:
                    parsed.append(name[start:i])
                start = i
            if i == len(name) - 1:
                parsed.append(name[start:i+1])
        return parsed	
    
    def unparse(self,array):
    	unparsed = ""
        for i in range(0, len(array)):
            unparsed = unparsed + array[i]
        return unparsed			



    def addToResponseWaitList(self, address):        
        self.responseWaitList[ address ] = None

    def removeFromResponseWaitList(self, address):        
        del self.responseWaitList[ address ]

    def hasResponse(self, address):
        if address in self.responseWaitList:
            return self.responseWaitList[address] != None

        return False
        
    def responseForAddress(self, address):
        return self.responseWaitList[ address ]
        

    def dispatch(self, message):
        parsed = self.parse(message[0])        
        data = MessageWrapper(message)
    
        #print "dispatch: ", message[0]
        
        # are we waiting on any responses?
        if len(self.responseWaitList) > 0:
            # yes, so does it match anything in the list?
            if message[0] in self.responseWaitList:
                self.responseWaitList[ message[0] ] = data

        self.dispatchNew(parsed, data)	
                        
    def dispatchNew(self, parsed, data):
        #print "dispatchNew", parsed
        
        data.setCurrentPath(self.unparse(parsed))
        
        if self._default is not None:
            #print "calling callback: ",self._default
            data.setUserdata(self._userdata)
            self._default(data)
            
        if len(parsed) > 0:
            if parsed[0] in self._children:
                self._children[parsed[0]].dispatchNew(parsed[1:],data)
            #else:            	
            #    print "missing callback"

			
    def remove(self, name):
    	self.add(None, name)
		
    def add(self, callback, name, userdata = None):
        parsed = self.parse(name)

        #print "parsed", parsed
        #print "self._children", self._children
        
        if len(parsed) == 1:
            if callback == None:
                del self._children[parsed[0]]
            else:	
                self._children[parsed[0]] = Dispatcher(callback, userdata)
                #print "done", self._children
                
        if len(parsed) > 1:            
            if parsed[0] in self._children:
                #print "in children------------------"
                
                self._children[ parsed[0] ].add(callback, self.unparse(parsed[1:]), userdata)
            else:
                self._children[ parsed[0] ] = Dispatcher(None, None)
                self._children[ parsed[0] ].add(callback,self.unparse(parsed[1:]),userdata)
            
#            
