#!/usr/bin/python

__doc__ = """
usage: 
  tango_monitor <device name> [ <attr regexp> ]*
  
  tango_monitor <attr_exp1> <attr_exp2> [ period=3000 ]
"""

import sys
import time
import traceback
import PyTango
import fandango as fn


class MyCallback(object):
    
    counter = 0
    
    def __init__(self):
        self.reset()
        
    def reset(self):
        self.t0 = time.time()
        self.m0 = fn.linos.get_process_memory()
        self.counters = fn.dicts.defaultdict(int)
        self.values = fn.dicts.defaultdict(str)
        self.dups = fn.dicts.defaultdict(int)
        self.ratios = fn.dicts.defaultdict(float)

    def push_event(self,event):
        try:
            MyCallback.counter+=1
            aname = fn.tango.get_normal_name(event.attr_name)
            tt = (time.time()-self.t0)
            self.counters[aname] = self.counters[aname] + 1
            self.ratios[aname] = self.counters[aname] / tt
            value = getattr(event.attr_value,'value',event.attr_value)
            epoch = (fn.ctime2time(event.attr_value.time) 
                     if hasattr(event.attr_value,'time') 
                     else fn.now())
            quality = getattr(event.attr_value,'quality','NO_Q')
            value = fn.shortstr(value)
            if self.values[aname] == value:
                self.dups[aname] += 1
                
            self.values[aname] = value
            value = str(value)[:40] + '...,%s'%str(quality)
            m1 = fn.linos.get_process_memory()
            print('%s:%s:%s = %s; %s; ct=%d/%d, Hz=%2.2f, dups=%d, leak=%2.1fKbps/%1.3fMb' 
                %  (fn.time2str(epoch),epoch,aname,value,event.event, 
                    self.counters[aname],
                    MyCallback.counter,
                    self.ratios[aname],
                    self.dups[aname],
                    (m1-self.m0)/(1e3*tt),
                    m1/1e6,
                    ))
        except:
            traceback.print_exc()
        

def monitor(*args, **kwargs):
    """
    monitor(device,[attributes])
    
    kwargs: events (event types), period (polling period)
    """
    # Old syntax (device, attrs regexp)
    print(args)
    if (fn.clmatch(fn.tango.retango,args[0]) and args[1:] and
            not any(fn.clmatch(fn.tango.retango,a) for a in args[1:])):
        args = [args[0]+'/'+a for a in args[1:]]

    attrs = fn.join(fn.find_attributes(a) for a in args)
    print('matched %d attributes: %s' % (len(attrs),attrs))
    
    # event filters (deprecated)
    events = kwargs.get('events',[PyTango.EventType.CHANGE_EVENT])

    eis,worked,failed = [],[],[]
    sources = []
    cb = MyCallback() #PyTango.utils.EventCallBack()
    
    models = [(a if '/' in a else d+'/'+a) for a in attrs]
    for a in models:
        (failed,worked)[bool(fn.tango.check_attribute_events(a))].append(a)


    USE_ES = 1
    if USE_ES:
        fn.EventThread.MinWait = 1e-2 #1e-4
        fn.EventThread.DEFAULT_PERIOD_ms = 300 #0.1        
    
    for m in models:
        try:
            if USE_ES:
                sources.append(fn.EventSource(m,enablePolling=True,
                    listeners=[cb], 
                    use_events=True,
                    polling_period=kwargs.get('period',3000),
                    keeptime=1e-2,
                    queued=True,
                    ))
            else:
                # for DeviceProxies
                d,a = m.rsplit('/',1)
                dp = fn.get_device(d,keep=True)
                #dp = PyTango.DeviceProxy(d)
                type_ = PyTango.EventType.CHANGE_EVENT
                #for e in events:
                eis.append((dp,
                    dp.subscribe_event(a,
                    type_,
                    cb,
                    #[],True,
                    )))
                worked.append(m)
        except:
            #print(fn.except2str())
            traceback.print_exc()
            failed.append(m)
    
    if USE_ES:
        fn.EventSource.get_thread().setup(period_ms=50,
                                          latency=5.,filtered=False,)
        fn.EventSource.get_thread().setLogLevel('INFO')

    print('%d attributes NOT provide events: %s' % (len(failed),failed))      
    print('%d attributes provide events: %s' % (len(worked),worked))
    print('-'*80 + '\n' + '-'*80)
    try:
        #cb.m0,cb.t0 = fn.linos.get_process_memory(),fn.now()
        ct = 0
        while True:
            if ct == 10:
                print('\n\n\n\n\nReset memory counter\n\n\n\n\n')
                cb.reset()
                MyCallback.counter = 0
            time.sleep(1)
            ct+=1
    except: #KeyboardInterrupt
        print(fn.except2str())
        print('-'*80)
        print('Unsubscribing ...')
        if USE_ES:
            for s in sources:
                s.removeListener(cb)
        else:
            for dp,ei in eis:
                dp.unsubscribe_event(ei)
                
        print "Finished monitoring"
        
    #[dp.unsubscribe_event(ei) for ei in eis];
        
if __name__ == '__main__':
    import sys
    try:
        args = sys.argv[1:]
        if not args or args[0] in ('help','--help','-h','-?'):
            raise Exception('No arguments provided!')
        opts = [a for a in args if '=' in a]
        args = [a.strip() for a in args if a not in opts] or ["state"]
        opts = dict((k.strip('-'),fn.str2type(v)) for k,v in (o.split('=',1) 
                                                   for o in opts))
        monitor(*args,**opts)
    except:
        print(fn.except2str())
        print(__doc__)
    

