'''
Created on Oct 17, 2013

@author: leal
'''

import Queue
import threading
import subprocess
import time
import os


class Communicate(object):
    '''
    Class to communicate asynchronously with a Linux process.
    '''


    def __init__(self, executable, prompt, exitCommand = None):
        """
        
        @param executable:
        @param prompt: Any string output when program has finished to start
        @param exitCommand: if there is a exit command
        """

        
        self._executable = executable
        self._prompt = prompt
        self._exitCommand = exitCommand
        
        self._launch()
        
        self._outQueue = Queue.Queue()
        self._errQueue = Queue.Queue()

        self._startThreads()
        self._startExecutable()
    
    def _startThreads(self):
        self.outThread = threading.Thread(target=self._enqueueOutput, args=(self._subproc.stdout, self._outQueue))
        self.errThread = threading.Thread(target=self._enqueueOutput, args=(self._subproc.stderr, self._errQueue))
        
        self.outThread.daemon = True
        self.errThread.daemon = True
        
        self.outThread.start()
        self.errThread.start()
    
    def _startExecutable(self):
        print self._executable, 'is starting...'
        output = self._getOutput(self._outQueue)
        while output.find(self._prompt) < 0:
            time.sleep(0.2)
            output = self._getOutput(self._outQueue)
        print self._executable, 'is starting... Done!'
        
        errors = self._getOutput(self._errQueue)
        print 'Errors while starting:'
        print errors
        

    def _enqueueOutput(self, out, queue):
        for line in iter(out.readline, b''):
            queue.put(line)
        out.close()
    
    def _getOutput(self, outQueue):
        outStr = ''
        try:
            while True:  # Adds output from the Queue until it is empty
                outStr += outQueue.get_nowait()
        except Queue.Empty:
            return outStr
    
    def send(self,command):
        self._relaunchIfItIsNotRunning()
        self._subproc.stdin.write(command + os.linesep)        
        self._subproc.stdin.flush()        
    
    def receiveOutput(self):
        output = self._getOutput(self._outQueue)
        return output
    
    def receiveErrors(self):
        errors = self._getOutput(self._errQueue)
        return errors
    
    def communicate(self,command,waitTimeForTheCommandToGiveOutput=0.2):
        print 'Executing:', command
        self.send(command)
        time.sleep(waitTimeForTheCommandToGiveOutput)
        return [self.receiveOutput(),self.receiveErrors()]
    
    def exit(self):
        """
        Try to send the exit command to subprocess running.
        Kill it if it is still running
        """
        if self._isSubProcessRunning() and self._exitCommand is not None:
            self._subproc.stdin.write(self._exitCommand)
            self._subproc.stdin.write(os.linesep)
            self._subproc.stdin.flush()
            time.sleep(0.5)
        
        if self._isSubProcessRunning() :
            self._subproc.kill()
        time.sleep(0.1)
        print 'Done!'
    
    def _launch(self):
        self._subproc = subprocess.Popen(self._executable, stdin=subprocess.PIPE, stdout=subprocess.PIPE, 
                             stderr=subprocess.PIPE, shell=True, universal_newlines=True)
    def _relaunchIfItIsNotRunning(self):
        if not self._isSubProcessRunning():
            self._launch()
            self._outQueue = Queue.Queue()
            self._errQueue = Queue.Queue()   
            self._startThreads()
            self._startExecutable()
    
    def _isSubProcessRunning(self):
        """
        Just checks if the thread is running.
        """    
        # Check if child process has terminated. Set and return returncode attribute.
        if self._subproc.poll() is None:
            return True
        else:
            return False

    def __del__(self):
        if self._isSubProcessRunning() :
            self.exit()    

def test():
    executable = '/net/serhom/home/cs/richard/Free_Lamp81/START_lamp -nws'
    prompt = "loaded ..."
    exitCommand = "exit"
    
    lamp = Communicate(executable, prompt, exitCommand)
    #time.sleep(0.2)
    output,errors = lamp.communicate('print, "Hello, Python"', waitTimeForTheCommandToGiveOutput=0.2)
    print "Output: ", output
    print "Errors: ", errors
    
    lamp.exit();
    
    output,errors = lamp.communicate('print, "Hello, Python"', waitTimeForTheCommandToGiveOutput=0.2)
    print "Output: ", output
    print "Errors: ", errors
    

if __name__ == '__main__':
    test()
    
    
     
    
    
    

syntax highlighted by Code2HTML, v. 0.9.1