import os
import sys
import re
import subprocess
import signal

class CsoundServer:
    """
    Handles launching the Csound Server located at the following serverLocation
    path.
    """
    
    # defaults ... 
    serverPath = "/home/olpc/Server"
    serverName = "csoundserver"
    
    def __init__(self, path = None, name = None):
        """
        Notice the option to set the path and name
        """
        self.callback = None
        self.serverProcess = None
        self.successfulLaunch = False

        if path != None:
            self.setServerPath(path)

        if name != None:
            self.setServerName(name)

    def didLaunch(self):
        """
        Informs the caller whether we successfully launched the server or not
        """
        return self.successfulLaunch
        
    def setServerPath(self, path):
        """
        override the default path
        """
        self.serverPath = path

    def setServerName(self, name):
        """
        override the default name
        """
        self.serverName = name
        
    def setMessageCallback(self, callback, userData = None):
        """
        set the message callback method and its userData
        """
        self.callback = [ callback, userData ]
        
    def run(self):
        """
        continue to grab messages from the server's std out and send them
        to the callback (if any)
        """        
        try:            
            while 1:
                line = self.serverProcess.stdout.readline()
                if not line:
                    break

                if self.callback != None:
                    self.callback[0](line, self.callback[1])
                                    
        except Exception, msg:
            print "CsoundServer Exception:", msg
            
    def launch(self, port = 7000, sampleRate = 44100, debugMode = 1, periodSize = 4000, msgs = 1):
        """ 
        We attempt to run the server in its own process.  If this succeeds,
        we scan the std output of the server to see if launched successfully
        before starting the main listening thread.  If the server outputs a bind
        error, it failed to launch, so we return false        
        
        port - the port the server will listen to
        sampleRate - the master sample rate performances will conform to
        debugMode - 1 for enabled, 0 for disabled
        periodSize - the buffer size in samples the server uses 
                    (smaller = faster response, but more CPU)
        msgs - whether the server sends stdout messages from the csound instances
                back to the clients - turning this off may slightly improve performance
        """
        validStart = re.compile("Accepting connections on port")
        bindError = re.compile("bind: Address already in use")
        
        #argString = '-sr '+str(sampleRate)
        #argString += ' -port '+str(port)
        #argString += ' -debug '+str(debugMode)
        #argString += ' -period '+str(periodSize)
        #argString += ' -msgs '+str(msgs)
                                
        self.serverProcess = subprocess.Popen( [self.serverPath+"/"+self.serverName, 
            '-sr', str(sampleRate), '-port', str(port), 
            '-debug', str(debugMode), '-period', str(periodSize), '-msgs', str(msgs)], stdout=-1,
            cwd = self.serverPath)   
        
        
                                        
        while 1:
            line = self.serverProcess.stdout.readline()
            #print "line:", line,
            
            if not line:
                break

            if self.callback != None:
                self.callback[0](line, self.callback[1])

            match = re.findall(validStart, line)
            if match:
                self.successfulLaunch = True
                break                

            match = re.findall(bindError, line)
            if match:
                break
            
        if self.successfulLaunch == True:
            return self.serverProcess.stdout
            

        return None

    def processID(self):
        """
        get the server's process ID
        """
        return self.serverProcess.pid
        
    def kill(self):        
        """
        Used to forcefully terminate the server

        NOTE: not sure this works as it should
        """
        if self.serverProcess.pid:
            print "killing server process"
            os.kill( self.serverProcess.pid, signal.SIGTERM )


        
