class ResultReader(threading.Thread):

    CONFIG_TYPE = 3
    TEMP_TYPE = 2
    ADCDATA_TYPE = 1
    ERROR_TYPE = 0

    # Private Methods ------------------------------------------------------------------------------

    def __init__(self, in_path, timeout = -1):
        threading.Thread.__init__(self, name = "Thread-ResultReader")

        # Directory to check for result-files
        self.path = in_path
        if not os.path.exists(self.path):
            raise IOError, "Error initialising ResultReader: Path does not exist"

        # The result-queue
        self.result_queue = []

        # Maximum Number of Results in Queue (if reached, ResultReader will sleep until results are read)
        self.max_result_queue_length = 10
        
        # Filename stuff
        self.filename = "job.%09d.result"

        # Kind of file (Error, ADC-Data...)
        self.__filetype = None

        # Quit main loop?
        self.quit_loop = False

        # Indicates thread being active (= reading, parsing, giving back results)
        self.is_idling = False

        # How many files have been read?
        self.files_read = 0

        # How long long shall the thread be put asleep, if no file is found?
        self.wait = 0.2

        # When shall the thread be quit, if not files are found?
        self.timeout = timeout
        self.timeout_counter = 0

        # class-intern result-object currently being processed
        self.tmp_result = None

        # Event-Objekt für Thread-Suspending
        self.event = threading.Event()          # Timeout
        self.event_lock = threading.Event()     # Thread locking

        # ResultReader reset?
        self.__reset = False

        # ResultReader started?
        self.__started = False



    def run(self):
        "Represents the threads activity: Checking the given directory for result-files"
        self.__started = True

        while not self.quit_loop:
            # Making ResultReader restartable and/or waiting for user to read pending results
            self.event_lock.wait()

            # Making sure it really quits, if ResultReader somehow was hold (like waiting for queue to empty)
            if self.quit_loop:
                break

            # Reset-quest from other Thread?
            if self.__reset:
                self.event_lock.clear()
                self.__do_reset()
                continue

            # Too many results pending?
            if self.get_number_of_results_pending() >= self.max_result_queue_length:
                self.event_lock.clear()
                #print "Waiting for results being read from Queue... %d >= %d" % (self.get_number_of_results_pending(), self.max_result_queue_length)
                continue                

            if os.access(os.path.join(self.path, self.filename % self.files_read), os.R_OK):
                #print "File found! " + os.path.join(self.path, self.filename % self.files_read)
                self.__readFile(os.path.join(self.path, self.filename % self.files_read))
                self.timeout_counter = 0
            else:
                #print "No File found, waiting... (" + str(self.wait) + ")"
                self.event.wait(self.wait)
                self.timeout_counter += self.wait
                #print self.timeout_counter
                
                if self.timeout_counter >= self.timeout and self.timeout is not -1:
                    print "Exception: ResultReader timeout!\n"
                    #print "Printing result-queue..."
                    #print self.result_queue
                    self.quit_loop = True




    def __readFile(self, in_filename):
        "Opens the found file and hands it to the parser"

        try:
            result_file = file(in_filename, "r")
            #print result_file

            # get date of last modification 
            self.result_job_date = datetime.fromtimestamp(os.stat(in_filename)[8])
            
            self.__parseFile(result_file)

            self.files_read += 1
            result_file.close()
            
        except IOError:
            self.__gui.new_log_message("ResultReader: File \"" + in_filename + "\" could not be opened.", "DH")

        except:
            result_file.close()
            raise



    def __parseFile(self, in_file):
        "Parses the given file, adding it to the result-queue"

        self.result = None
        self.result_description = { }

        self.result_job_number = None
        # Job Date is set in __read_file()
        self.__filetype = None

        # Expat XML-Parser & Binding handlers
        self.xml_parser = xml.parsers.expat.ParserCreate()
        self.xml_parser.StartElementHandler = self.__xmlStartTagFound
        self.xml_parser.CharacterDataHandler = self.__xmlCharacterDataFound
        self.xml_parser.EndElementHandler = self.__xmlEndTagFound

        # Parsing all cdata as one block
        self.xml_parser.buffer_text = True
        buffersize=self.xml_parser.buffer_size*2
        databuffer=in_file.read(buffersize)
        while databuffer!="":
            if self.__reset:
                self.event_lock.clear()
                self.__do_reset()
                return

            self.xml_parser.Parse(databuffer,False)
            databuffer=in_file.read(buffersize)

        self.xml_parser.Parse("",True)
        self.xml_parser = None

    # Callback when a xml start tag is found
    def __xmlStartTagFound(self, in_name, in_attribute):

        # General Result-Tag
        if in_name == "result":
            self.result_job_number = int(in_attribute["job"])
            # Job-Date is set in __read_file()

        # Description
        elif in_name == "description":
            self.result_description = in_attribute.copy()

        # ADC_Results
        elif in_name == "adcdata":
            self.__filetype = ResultReader.ADCDATA_TYPE

            self.adc_result_current_channel = 0
            self.adc_result_trailing_chars = ""
            self.try_base64 = False

            if self.result is None:
                self.result = ADC_Result()

                # Change number of channels of your adc-card here
                channels = 2
                self.result.create_data_space(channels, int(in_attribute["samples"]))
                
                self.result.set_sampling_rate(float(in_attribute["rate"]))
                self.result.set_job_id(self.result_job_number)
                self.result.set_job_date(self.result_job_date)

                self.result.set_description_dictionary(self.result_description.copy())
                
                self.adc_result_sample_counter = 0
            else:
                self.result.add_sample_space(int(in_attribute["samples"]))
                
        # Error_Results
        elif in_name == "error":
            self.__filetype = ResultReader.ERROR_TYPE
            
            self.result = Error_Result()
            self.result.set_job_id(self.result_job_number)
            self.result.set_job_date(self.result_job_date)

            self.result.set_description_dictionary(self.result_description.copy())

        # Temp_Results
        elif in_name == "temp":
            self.__filetype = ResultReader.TEMP_TYPE

            self.result = Temp_Result()
            self.result.set_job_id(self.result_job_number)
            self.result.set_job_date(self.result_job_date)            

        # Config_Results
        elif in_name == "conf":
            self.__filetype = ResultReader.CONFIG_TYPE

            self.result = Config_Result()
            self.result.set_job_id(self.result_job_number)
            self.result.set_job_date(self.result_job_date)
    

    def __xmlCharacterDataFound(self, in_cdata):

        # ADC_Result
        if self.__filetype == ResultReader.ADCDATA_TYPE:
            if self.try_base64 is True:
                self.adc_result_trailing_chars+=in_cdata
            else:
                try:
                    values=(self.adc_result_trailing_chars+in_cdata).split()
                    if not in_cdata[-1].isspace():
                        self.adc_result_trailing_chars=values.pop()
                    else:
                        self.adc_result_trailing_chars=""

                    for i in values:
                        self.result.set_ydata(self.adc_result_current_channel, self.adc_result_sample_counter, int(i))
                        # print "added value " + str(i) + " at: " + str(self.current_channel) + ", " + str(self.current_pos)
                        self.adc_result_current_channel = (self.adc_result_current_channel + 1) % self.result.get_number_of_channels()
                        if self.adc_result_current_channel == 0:
                            self.result.set_xdata(self.adc_result_sample_counter, self.adc_result_sample_counter / self.result.get_sampling_rate())
                            self.adc_result_sample_counter += 1
                except ValueError:
                    self.try_base64=True
                    self.adc_result_trailing_chars=in_cdata

        # Error_Result
        elif self.__filetype == ResultReader.ERROR_TYPE:
            tmp_string = self.result.get_error_message()
            if tmp_string is None: tmp_string = ""

            tmp_string += in_cdata
            self.result.set_error_message(tmp_string)

        # Temp_Results
        elif self.__filetype == ResultReader.TEMP_TYPE:
            pass

        # Config_Results
        elif self.__filetype == ResultReader.CONFIG_TYPE:
            pass



    def __xmlEndTagFound(self, in_name):
        if in_name == "adcdata":

            # ADC_Result
            if self.__filetype == ResultReader.ADCDATA_TYPE:
                if self.try_base64:
                    tmp_string=base64.standard_b64decode(self.adc_result_trailing_chars)
                    self.adc_result_trailing_chars = ""
                    tmp=numarray.fromstring(tmp_string, numarray.Int16)
                    del tmp_string
                    
                    expected_samples=self.result.index[-1][1]-self.result.index[-1][0]+1
                    if len(tmp)/2 != expected_samples:
                        self.__gui.new_log_message("ResultReader Warning: size missmatch %d!=%d samples"%(len(tmp)/2, expected_samples), "DH")

                    size_used=min(expected_samples,len(tmp)/2)
                    self.result.x[self.adc_result_sample_counter:]=(numarray.arange(expected_samples)+self.adc_result_sample_counter)/self.result.get_sampling_rate()
                    self.result.y[0][self.adc_result_sample_counter:self.adc_result_sample_counter+size_used]=tmp[0:size_used*2:2]
                    self.result.y[1][self.adc_result_sample_counter:self.adc_result_sample_counter+size_used]=tmp[1:size_used*2:2]
                    del tmp
                    if size_used<expected_samples:
                        # zero padding...
                        self.result.y[0][self.adc_result_sample_counter+size_used:self.adc_result_sample_counter+samples_expected]=numarray.zeros((samples_expected-size_used,))
                        self.result.y[1][self.adc_result_sample_counter+size_used:self.adc_result_sample_counter+samples_expected]=numarray.zeros((samples_expected-size_used,))
                    self.adc_result_sample_counter+=expected_samples
                        
                else:
                    if self.adc_result_trailing_chars!="":
                        self.__xmlCharacterDataFound(" ")
            return

        elif in_name == "result":
            self.__gui.new_log_message("Result Reader: Successfully parsed and saved %s" % os.path.join(self.path, self.filename % self.files_read), "DH")

        # Error_Result
        elif self.__filetype == ResultReader.ERROR_TYPE:
            self.__gui.new_log_message("Result Reader: Error Result parsed! (%s)" % os.path.join(self.path, self.filename % self.files_read), "DH")

        # Temp_Result
        elif self.__filetype == ResultReader.TEMP_TYPE:
            self.__gui.new_log_message("ResultReader: Temperature Result parsed!", "DH")

        # Config_Result
        elif self.__filetype == ResultReader.CONFIG_TYPE:
            self.__gui.new_log_message("ResultReader: Config Result parsed!", "DH")

        self.result_queue.append(self.result)   
        self.result=None

    def __do_reset(self):

        if len(self.result_queue) != 0:
            self.__gui.new_log_message("ResultReader Warning: Deleting %d results due reset-request!" % len(self.result_queue), "DH")
        
        # The result-queue
        self.result_queue = []
        
        # Quit main loop?
        self.quit_loop = False

        # Indicates thread being active (= reading, parsing, giving back results)
        self.is_idling = False

        # How many files have been read?
        self.files_read = 0

        # When shall the thread be quit, if not files are found?
        self.timeout_counter = 0

        # class-intern result-object currently being processed
        self.tmp_result = None

        # Type of result-file - Error or Result?
        self.result_type = -1 # Nothing so far

        # Setting back reset
        self.__reset = False

    # /Private Methods -----------------------------------------------------------------------------

    # Public Methods -------------------------------------------------------------------------------

    def start(self):
        "Overwritten start-method - allowing a reset ResultReader to be started again"
        if self.__started:
            self.event_lock.set()
        else:
            self.event_lock.set()
            threading.Thread.start(self)



    def get_next_result(self):
        "Returns the next result in queue"

        if len(self.result_queue) == 0: return None
        else:
            out_result = self.result_queue[0]
            del self.result_queue[0]
            
            if self.get_number_of_results_pending() < self.max_result_queue_length and not self.event_lock.isSet():
                self.event_lock.set()

            return out_result



    def get_number_of_results_pending(self):
        "Returns the number of results waiting to be returned"
        return len(self.result_queue)

    

    def quit_result_reader(self):
        self.quit_loop = True
        self.event.set()
        self.event_lock.set()


    
    def is_running(self):
        "Returns true, if the ResultReader is still running (no matter if idling or parsing etc...)"
        return not self.quit_loop



    def get_number_of_results_read(self):
        "Returns the number of result-files read"
        return self.files_read + 0



    def reset(self):
        "Resets the result reader, so it can be started again"
        # Waiting for ResultReader to finish parsing the current file (look into run() method)
        self.__reset = True


    def connect_gui(self, gui):
        "Connects the gui"
        self.__gui = gui
class ResultReader:
    """
    starts at some point and returns result objects until none are there
    """
    CONFIG_TYPE = 3
    TEMP_TYPE = 2
    ADCDATA_TYPE = 1
    ERROR_TYPE = 0

    def __init__(self, spool_dir=".", no=0, result_pattern="job.%09d.result", clear_jobs=False, clear_results=False):
        self.spool_dir = spool_dir
        self.start_no = no
        self.no = self.start_no
        self.result_pattern = result_pattern
        self.clear_jobs=clear_jobs
        self.clear_results=clear_results

    def __iter__(self):
        """
        get next job with iterator
        """
        expected_filename=os.path.join(self.spool_dir,self.result_pattern%(self.no))
        while os.access(expected_filename,os.R_OK):
            yield self.get_result_object(expected_filename)
            # purge result file
            if self.clear_results:
                if os.path.isfile(expected_filename): os.remove(expected_filename)
            if self.clear_jobs:
                if os.path.isfile(expected_filename[:-7]): os.remove(expected_filename[:-7])
            self.no+=1
            expected_filename=os.path.join(self.spool_dir,self.result_pattern%(self.no))
        return

    def get_result_object(self, in_filename):
        """
        get result object
        """
        # class-intern result-object currently being processed
        result_file = file(in_filename, "r")

        # get date of last modification 
        self.result_job_date = datetime.fromtimestamp(os.stat(in_filename)[8])
            
        self.__parseFile(result_file)
        
        result_file = None

        r=self.result
        self.result = None

        return r

    def __parseFile(self, in_file):
        "Parses the given file, adding it to the result-queue"

        self.result = None
        self.result_description = { }
        self.result_job_number = None
        # Job Date is set in __read_file()
        self.__filetype = None

        # Expat XML-Parser & Binding handlers
        self.xml_parser = xml.parsers.expat.ParserCreate()
        self.xml_parser.StartElementHandler = self.__xmlStartTagFound
        self.xml_parser.CharacterDataHandler = self.__xmlCharacterDataFound
        self.xml_parser.EndElementHandler = self.__xmlEndTagFound

        try:
            # Parsing all cdata as one block
            self.xml_parser.buffer_text = True
            buffersize=self.xml_parser.buffer_size*2
            databuffer=in_file.read(buffersize)
            while databuffer!="":
                self.xml_parser.Parse(databuffer,False)
                databuffer=in_file.read(buffersize)

            self.xml_parser.Parse("",True)
        except xml.parsers.expat.ExpatError:
            print "ToDo: proper parser error message"
            self.result = None
        self.xml_parser = None

    # Callback when a xml start tag is found
    def __xmlStartTagFound(self, in_name, in_attribute):

        # General Result-Tag
        if in_name == "result":
            self.result_job_number = int(in_attribute["job"])
            # Job-Date is set in __read_file()

        # Description
        elif in_name == "description":
            self.result_description = in_attribute.copy()

        # ADC_Results
        elif in_name == "adcdata":
            self.__filetype = ResultReader.ADCDATA_TYPE

            self.adc_result_current_channel = 0
            self.adc_result_trailing_chars = ""
            self.try_base64 = True 

            if self.result is None:
                self.result = ADC_Result()

                # Change number of channels of your adc-card here
                channels = 2
                self.result.create_data_space(channels, int(in_attribute["samples"]))
                
                self.result.set_sampling_rate(float(in_attribute["rate"]))
                self.result.set_job_id(self.result_job_number)
                self.result.set_job_date(self.result_job_date)

                self.result.set_description_dictionary(self.result_description.copy())
                
                self.adc_result_sample_counter = 0
            else:
                self.result.add_sample_space(int(in_attribute["samples"]))
                
        # Error_Results
        elif in_name == "error":
            self.__filetype = ResultReader.ERROR_TYPE
            
            self.result = Error_Result()
            self.result.set_job_id(self.result_job_number)
            self.result.set_job_date(self.result_job_date)

            self.result.set_description_dictionary(self.result_description.copy())

        # Temp_Results
        elif in_name == "temp":
            self.__filetype = ResultReader.TEMP_TYPE

            self.result = Temp_Result()
            self.result.set_job_id(self.result_job_number)
            self.result.set_job_date(self.result_job_date)            

        # Config_Results
        elif in_name == "conf":
            self.__filetype = ResultReader.CONFIG_TYPE

            self.result = Config_Result()
            self.result.set_job_id(self.result_job_number)
            self.result.set_job_date(self.result_job_date)
    
    def __xmlCharacterDataFound(self, in_cdata):

        # ADC_Result
        if self.__filetype == ResultReader.ADCDATA_TYPE:
            if self.try_base64:
                self.adc_result_trailing_chars+=in_cdata
            else:
                try:
                    values=(self.adc_result_trailing_chars+in_cdata).split()
                    if not in_cdata[-1].isspace():
                        self.adc_result_trailing_chars=values.pop()
                    else:
                        self.adc_result_trailing_chars=""

                        for i in values:
                            self.result.set_ydata(self.adc_result_current_channel, self.adc_result_sample_counter, int(i))
                            # print "added value " + str(i) + " at: " + str(self.current_channel) + ", " + str(self.current_pos)
                            self.adc_result_current_channel = (self.adc_result_current_channel + 1) % self.result.get_number_of_channels()
                            if self.adc_result_current_channel == 0:
                                self.result.set_xdata(self.adc_result_sample_counter, self.adc_result_sample_counter / self.result.get_sampling_rate())
                                self.adc_result_sample_counter += 1
                except ValueError:
                    self.try_base64=True
                    self.adc_result_trailing_chars=in_cdata

        # Error_Result
        elif self.__filetype == ResultReader.ERROR_TYPE:
            tmp_string = self.result.get_error_message()
            if tmp_string is None: tmp_string = ""

            tmp_string += in_cdata
            self.result.set_error_message(tmp_string)

        # Temp_Results
        elif self.__filetype == ResultReader.TEMP_TYPE:
            pass

        # Config_Results
        elif self.__filetype == ResultReader.CONFIG_TYPE:
            pass



    def __xmlEndTagFound(self, in_name):
        if in_name == "adcdata":

            # ADC_Result
            if self.__filetype == ResultReader.ADCDATA_TYPE:
                if self.try_base64:
                    tmp_string=base64.standard_b64decode(self.adc_result_trailing_chars)
                    self.adc_result_trailing_chars=""
                    tmp=numarray.fromstring(tmp_string, numarray.Int16,(len(tmp_string)/2))
                    del tmp_string
                    self.result.x[self.adc_result_sample_counter:]=(numarray.arange(tmp.size()/2)+self.adc_result_sample_counter)/self.result.get_sampling_rate()
                    self.result.y[0][self.adc_result_sample_counter:]=tmp[::2]
                    self.result.y[1][self.adc_result_sample_counter:]=tmp[1::2]
                    self.adc_result_sample_counter+=tmp.size()/2
                else:
                    if self.adc_result_trailing_chars!="":
                        self.__xmlCharacterDataFound(" ")
            return

        elif in_name == "result":
            pass

        # Error_Result
        elif self.__filetype == ResultReader.ERROR_TYPE:
            pass

        # Temp_Result
        elif self.__filetype == ResultReader.TEMP_TYPE:
            pass

        # Config_Result
        elif self.__filetype == ResultReader.CONFIG_TYPE:
            pass