import HydrusConstants as HC
import Queue
import threading
import traceback
import weakref
import wx
import wx.lib.newevent

( PubSubEvent, EVT_PUBSUB ) = wx.lib.newevent.NewEvent()

class HydrusPubSub( object ):
    
    def __init__( self ):
        
        self._pubsubs = []
        self._callables = []
        
        self._lock = threading.Lock()
        
        self._topics_to_objects = {}
        self._topics_to_method_names = {}
        
    
    def _GetCallables( self, topic ):
        
        callables = []
        
        if topic in self._topics_to_objects:
            
            try:
                
                objects = self._topics_to_objects[ topic ]
                
                for object in objects:
                    
                    method_names = self._topics_to_method_names[ topic ]
                    
                    for method_name in method_names:
                        
                        if hasattr( object, method_name ):
                            
                            try:
                                
                                callable = getattr( object, method_name )
                                
                                callables.append( callable )
                                
                            except wx.PyDeadObjectError: pass
                            except TypeError as e:
                                
                                if '_wxPyDeadObject' not in str( e ): raise
                                
                            
                        
                    
                
            except: pass
            
        
        return callables
        
    
    def NoJobsQueued( self ):
        
        with self._lock:
            
            return len( self._pubsubs ) == 0
            
        
    
    def WXProcessQueueItem( self ):
        
        # we don't want to map a topic to its callables until the previous topic's callables have been fully executed
        # e.g. when we start a message with a pubsub, it'll take a while (in independant thread-time) for wx to create
        # the dialog and hence map the new callable to the topic. this was leading to messages not being updated
        # because the (short) processing thread finished and entirely pubsubbed before wx had a chance to boot the
        # message.
        
        do_callable = False
        
        with self._lock:
            
            if len( self._callables ) > 0:
                
                ( callable, args, kwargs ) = self._callables.pop( 0 )
                
                do_callable = True
                
            else:
                
                ( topic, args, kwargs ) = self._pubsubs.pop( 0 )
                
                callables = self._GetCallables( topic )
                
                self._callables = [ ( callable, args, kwargs ) for callable in callables ]
                
                for i in range( len( self._callables ) ): wx.PostEvent( HC.app, PubSubEvent() )
                
            
        
        # do this _outside_ the lock, lol
        if do_callable: callable( *args, **kwargs )
        
    
    def pub( self, topic, *args, **kwargs ):
        
        with self._lock:
            
            self._pubsubs.append( ( topic, args, kwargs ) )
            
            wx.PostEvent( HC.app, PubSubEvent() )
            
        
    
    def sub( self, object, method_name, topic ):
        
        with self._lock:
            
            if topic not in self._topics_to_objects: self._topics_to_objects[ topic ] = weakref.WeakSet()
            if topic not in self._topics_to_method_names: self._topics_to_method_names[ topic ] = set()
            
            self._topics_to_objects[ topic ].add( object )
            self._topics_to_method_names[ topic ].add( method_name )
            
        
    
    def WXpubimmediate( self, topic, *args, **kwargs ):
        
        with self._lock:
            
            callables = self._GetCallables( topic )
            
            for callable in callables: callable( *args, **kwargs )
            
        
    