from __future__ import print_function
import proxybus
import time
import threading
import sys

import xml.etree.ElementTree as ET
import os

def normalize_tags(elem):
    elem.tag = elem.tag.lower()
    for attr, value in elem.attrib.items():
        norm_attr = attr.lower()
        if norm_attr != attr:
            elem.set(norm_attr,value)
            elem.attrib.pop(attr)
    for child in elem:
        normalize_tags(child)


def parse_and_get_normalized_root(xmlfile):
    if os.path.exists(xmlfile):
        tree = ET.parse(xmlfile)
        root = tree.getroot()
        normalize_tags(root)
    else:
        root = None
    return root


debug = False
class ProxyBusDriver(object):
    def __init__(self):
        self.name= 'Abstract Bus Driver'
        self.t= None
        self.mutex = threading.Lock()
        self.stopRequest= True
        self.usclockperiod = pow(2,32) - 1
        self.icdxmlfile = proxybus.__proxy_icdxmlfile__
        self.version = proxybus.__proxy_version__
        self.injectable_errors= ()
        proxybus.setStartCallback(proxybus.__proxy_identifier__,self.start)
        proxybus.setStopCallback(proxybus.__proxy_identifier__,self.stop)
        proxybus.setSendCallback(proxybus.__proxy_identifier__,self.send)
        proxybus.setUsClockPeriodCallback(proxybus.__proxy_identifier__,lambda : int(self.usclockperiod))
        proxybus.setOnErrorInjectionListCallback(proxybus.__proxy_identifier__,lambda : (self.injectable_errors))

        if sys.version_info.major < 3:
            setattr(self,'timeclock',getattr(time,'clock'))
        else:
            setattr(self,'timeclock',getattr(time,'perf_counter'))
        self.ticktime = self.getticktime()

        self.overclock_factor = None

    __del__ = lambda self: None

    def start(self):
        print (self.name, "start...",end='')
        if not self.open_comm(): return False
        print (self.name, "thread start...",end='')
        self.clean()
        if self.t is not None and self.t.is_alive():
            self.raiseError('when running! thread id=%d'%self.t.ident,3)
        else:
            self.stopRequest= False
            self.t= threading.Thread(name=self.name, target=self._backgroundLoop) #, args=(self,))
            self.t.start()
            print ('new thread id=', self.t.ident)
        return self.t.is_alive()

    def stop(self):
        print (self.name, "stop...")
        self.acquireLock()
        self.stopRequest= True
        self.releaseLock()
        return True

    def clean(self):
        self.acquireLock()
        if self.t is not None and self.t.is_alive():
            print ('thread id=', self.t.ident, self.t.is_alive(),end='')
            self.t.join(1.0)
            if self.t.is_alive():
                print ('busy')
                self.raiseError("Unable to join python thread...",3)
            else:
                print ('Ok')
        else:
            print ('but no thread alive')
        self.releaseLock()

    def raiseError(self,msg,severiy):
        proxybus.onBusError(proxybus.__proxy_identifier__,str(msg),int(severiy))

    def forward_msg(self, m_id, m_data):
        proxybus.onReceive(proxybus.__proxy_identifier__,m_id,m_data)

    def set_log_auto(self, m_auto):
        proxybus.onSetBusLogAuto(proxybus.__proxy_identifier__,m_auto)

    def log_rx(self, m_id, data ):
        proxybus.onBusLog(proxybus.__proxy_identifier__,m_id,False,data)

    def log_tx(self, m_id, data ):
        proxybus.onBusLog(proxybus.__proxy_identifier__,m_id,True,data)

    def acquireLock(self):
        ret =False
        if self.mutex:
            ret= self.mutex.acquire(False)
        return ret

    def releaseLock(self):
        if self.mutex:
            try:
                self.mutex.release()
            except threading.ThreadError:
                print("already unlocked...")

    def getticktime(self):
        """!
        @brief return average time ( sec ) for os tick time
        @param self     : The object pointer
        """
        total=0
        for i in range(10):
           start=time.time()
           time.sleep(0.001)
           total += time.time() - start
        return total/10

    def sleep(self,sec,inaccuracy=None):
        """!
        @brief perform a more precise sleep based on inaccuracy due to os tick time or inaccuracy passed as argument
        @param self       : The object pointer
        @param inaccuracy : the amount of time to reach by using open loop cpu time
        """
        begin=self.timeclock()
        end=begin + sec
        s=sec - self.ticktime if inaccuracy is None else inaccuracy
        time.sleep(s if(s>0) else 0)
        while self.timeclock() < end:
            continue
        return self.timeclock()-begin

    def setNotifyOverClock(self,factor):
        """!
        @brief set the parameter to calculate the overclock time and notify the when the extra time
               of the loop iteration is greater
        @param self   : The object pointer
        @param factor : floating: with range [0:1] used to calculate the extra iteration time
                        None: reset to default (no check is performed)
        """
        if factor is not None and ( factor > 0 and factor <= 1 ):
            self.overclock_factor = factor
        else:
            self.overclock_factor = None

    def setLogTimeTagStringClbk(self,callback):
        proxybus.setLogTimeTagStringCallback(proxybus.__proxy_identifier__,callback)

    def _backgroundLoop(self):
        """!
        @brief it is the thread function that implement the open loop with period time based on usclockperiod
        """
        sleep_time = (self.usclockperiod/1000000.0)
        over = None if self.overclock_factor is None else sleep_time * self.overclock_factor
        #print self.usclockperiod,sleep_time
        while True:
            begin=self.timeclock()
            if self.acquireLock():
                try:
                    self.iteration()
                except:
                    self.raiseError(sys.exc_info(),3)
                    print (self.name, 'error, thread id=', self.t.ident, 'exiting',sys.exc_info())
                    break
                finally:
                    self.releaseLock()
            if self.stopRequest: break
            end = self.timeclock()
            if debug is True : print (end - begin)
            dif = sleep_time - (end - begin)
            if dif > 0 : self.sleep(dif)
            else:
               if over is not None and abs(dif) > over:
                  s="Bus Overclock {} {}".format(over * 100,abs(dif))
                  print(s)
                  self.raiseError(s,2)

            proxybus.onClock(proxybus.__proxy_identifier__)

        print ('closing connection....')
        self.close_comm()

    def send(self, m_id, m_data, m_err):
        return False

    def open_comm(self):
        return False

    def close_comm(self):
        pass

    def iteration(self):
        pass
