'''
Server accepting packets from UDP clients

@author: Nathan & Bruno
@since: Spring 2011

'''

from os import path
import numpy as np
import csv, re
from datetime import datetime
from time import sleep
from pyglet.gl import *
from pyglet.clock import _default_time_function as current_time
# pylnco modules
from abstract.AbstractClasses import DrawableHUDSourceModule
from controller import getPathFromString

from socket import *
import SocketServer
import threading
import platform
if platform.system() == "Linux":
    import fcntl
    

def get_ip_address(ifname):
    if platform.system() == "Linux":
        s = socket(AF_INET, SOCK_DGRAM)
        return inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, # SIOCGIFADDR
                                 pack('256s', ifname[:15]))[20:24])
    else:
        return gethostbyname(gethostname()) #note, returns only first IP address (could be a problem if you have multiple devices)

        
class ModuleMain(DrawableHUDSourceModule):
    """
    Interface between EXPYVR and peripherals such as robots or BCIs based on UDP. It interacts over UDP to interpret UDP commands and 
    feed different components what they want.
    IF YOU WANT TO CONFIGURE THE UDP FOR A NOVEL DEVICE: add the name in the configuration options (line 45 of UDPModule.py),
    and then open UDPInterfaceHandler.py and add a new case in the if-elif options within the handle function that you will find at the bottom of the file (starting line 135)
    """
    
    defaultInitConf = {
        'name': 'udp_handler',
        'udpPort': 8989,
        'alternative_IP': '',
        'initCode': '',
        'logToCSV': True,
    }
    
    defaultRunConf = {
        'updateCode': "for p in packet: print p",
    }
    
    confDescription = [
        ('name', 'str', "Stroke Robot End Effector"),
        ('udpPort', 'int', "Port for UDP communication"),
        ('alternative_IP', 'str', "Attach the UDP server to this IP address (leave blank for default).\n You may need that if you have multiple network interfaces."),
        ('initCode', 'str', "Code (or filename) to initialize the module"),
        ('logToCSV', 'bool', "Save data to coma-separated-values file ( <modulename>_<date>.csv )"),
        ('updateCode', 'str', "Code (or filename) to be executed at each update.\n'packet' is the stack of packets (strings) received since last update,\n'client' is the stack of client addresse (strings) associated,\n'time' is the stack of times (sec) when each packet was received."),
        ('getData()', 'info', "Returns last received packet (client address, packet string)")
    ]
    
    def __init__(self, controller, initConfig=None, runConfigs=None):
        #SourceModule.__init__(self, controller, initConfig, runConfigs)
        DrawableHUDSourceModule.__init__(self, controller, initConfig, runConfigs)
        self.label = None 
        self.packets = {}
        self.clients = {}
        self.keys = []
        self.lastKey = 0
        self.frequency = 0.0
        self.lastdata = []

        # init log
        self.logActive = self.initConf['logToCSV']
        if self.logActive:
            now = datetime.today()
            self.csvLogger = csv.writer(open(path.join(self.controller.gLogger.Path, now.strftime('%y%m%d%H%M%S_') + self.initConf['name'] +  '.csv') , 'w'), lineterminator = '\n')
            line = [ 'expe_time', 'Routine', 'Condition', 'reception_time', 'client', 'packet']
            self.csvLogger.writerow(line)
        
        # Init network interface
        address = self.initConf['alternative_IP'] 
        pattern = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$", re.VERBOSE | re.IGNORECASE)
        if pattern.match(address) is None:
            self.log("WARNING: Invalid IP address provided '%s'" % (address) )
            address = get_ip_address('eth0')
        self.log("Using IP address %s:%i" % (address, self.initConf['udpPort']))
        self.UDP = UDPModuleServer((address, self.initConf['udpPort']), PacketHandler, self)
                       
        self.server_thread = threading.Thread(target=self.UDP.serve_forever)
        self.server_thread.setDaemon(True)
        self.lock = threading.Lock()

       # execute the code of initialization
        try:
            f = open(getPathFromString(self.initConf['initCode']))
        except IOError:
            exec( self.initConf['initCode'] )
        else:
            exec( f.read() )
            f.close()
            
        # compile the code to be executed by python interpreter at each update
        self.updateCode = {}
        for confName, conf in self.runConfs.items():
            code = ''
            try:
                f = open(getPathFromString(conf['updateCode']))
            except IOError:
                code = conf['updateCode']
            else:
                code = f.read() 
                f.close()
            if len(code) > 0:
                code += '\n'
                self.updateCode[confName] = compile( code, '<string>', 'exec' )
            else:
                self.updateCode[confName] = None
                
        # start the thread 
        self.server_thread.start()
                

    def draw(self, window_width, window_height, eye=-1):
        DrawableHUDSourceModule.draw(self, window_width, window_height)

        if self.label is None:
            self.label = pyglet.text.Label(text='',
                                       color=(0, 100, 255, 255),
                                       font_name='Lucida Console',
                                       font_size=12,
                                       anchor_x='left', anchor_y='center',
                                       width=600, multiline=False,
                                       x=100, y=100)
            
        self.label.text = "{0} packet : {1} ({2: =7.1f} Hz)".format( self.initConf['name'], str(self.getData()), self.getUpdateInterval() )
        self.label.draw() 

    def cleanup(self):
        DrawableHUDSourceModule.cleanup(self)
        self.UDP.shutdown()
        self.server_thread.join(1.0)
        
    def handle(self, udpPacket, clientAddress):

        if self.lock.acquire():
            # generate key from time (microsecond precision)
            key = long(current_time() * 1000000.0)
            # store data
            self.keys.append(key)
            self.packets[key] = udpPacket.strip()
            self.clients[key] = clientAddress
            
            # compute average frequency
            if self.frequency == 0:
                self.frequency = 1000000.0 / float(key - self.lastKey)
            else:
                self.frequency = 500000.0 / float(key - self.lastKey) + 0.5 * self.frequency
                
            # remember last key handled
            self.lastKey = key
            
            self.lock.release()
            # Necessary to give the main thread time to acquire lock
            sleep(0)

    def start(self, dt= 0, duration= -1, configName= None):
        DrawableHUDSourceModule.start(self, dt, duration, configName)
        pyglet.clock.schedule(self.update)  
        self.lastkey = long(current_time() * 1000000.0)

    def stop(self, dt= 0):
        DrawableHUDSourceModule.stop(self, dt)
        pyglet.clock.unschedule(self.update)

    def update(self, dt):
        """
        The update is called regularly
        """
        
        # make a local copy
        time = []
        packet = []
        client = []
        
        # access to the thread data (lock protected)
        if self.lock.acquire():
            for t in self.keys: 
                packet.append(self.packets[t])
                client.append(self.clients[t])
                time.append( float(t) / 1000000.0)
            # clear buffer
            self.keys = []
            self.packets = {}
            self.clients = {}
            # release lock
            self.lock.release()
            sleep(0)
            
        # logging of local data
        if self.logActive:
            for i in range(len(packet)):
                # log time & condition
                line = [ str("%.4f"%self.controller.gTimeManager.experimentTime()), self.controller._currentRoutine, self.controller._currentCondition ]
                # log data
                line.extend([ str("%.9f"%time[i]), client[i], packet[i]])
                self.csvLogger.writerow(line)     

        # Run update code on the local data
        if self.updateCode[self.activeConfName] is not None:
            exec( self.updateCode[self.activeConfName] )
            
        self.lastdata = [] if len(packet)<1 else [ client[-1], packet[-1] ]
            
    # Abstract methods for source modules 
    def getData(self):
        DrawableHUDSourceModule.getData(self) 
        return self.lastdata
        
    def getUpdateInterval(self):
        """
        For abstract interface (source module). 
        """
        DrawableHUDSourceModule.getUpdateInterval(self)
        return self.frequency

    
class PacketHandler(SocketServer.DatagramRequestHandler):
    """
    Handle incoming UDP packets by calling the module handle function
    """   
    def handle(self):
        if self.server.module is not None and self.server.module.started :
            self.server.module.handle(self.request[0], self.client_address[0])

class UDPModuleServer(SocketServer.UDPServer):    
    """
    A UDP server which knows the module that created it
    """   
    def __init__(self, server_address, handler_class=PacketHandler, module = None):
        SocketServer.UDPServer.__init__(self, server_address, handler_class)
        # remember parent module
        self.module = module
        return
    