import ctypes
from numpy.ctypeslib import ndpointer
import numpy
from _nidaq import *
from channels import *
from _constants import *
#patches the autogenerated functions for reading and writing samples to use numpy arrays
DAQmxReadAnalogF64.argtypes = [TaskHandle,int32,float64,
                                    bool32,ndpointer(dtype=numpy.float64,flags='CONTIGUOUS'), uInt32,
                                    POINTER(int32), POINTER(bool32)]
DAQmxWriteAnalogF64.argtypes = [TaskHandle, int32, bool32,
                                    float64, bool32, ndpointer(dtype=numpy.float64,flags='CONTIGUOUS'),
                                    POINTER(int32), POINTER(bool32)]

DAQmxReadBinaryI16.argtypes = [TaskHandle,int32,float64,
                                    bool32,ndpointer(dtype=numpy.int16,flags='CONTIGUOUS'), uInt32,
                                    POINTER(int32), POINTER(bool32)]
DAQmxWriteBinaryI16.argtypes = [TaskHandle,int32,float64,
                                    bool32,ndpointer(dtype=numpy.int16,flags='CONTIGUOUS'), uInt32,
                                    POINTER(int32), POINTER(bool32)]
DAQmxReadBinaryU16.argtypes = [TaskHandle,int32,float64,
                                    bool32,ndpointer(dtype=numpy.uint16,flags='CONTIGUOUS'), uInt32,
                                    POINTER(int32), POINTER(bool32)]
DAQmxWriteBinaryU16.argtypes = [TaskHandle,int32,float64,
                                    bool32,ndpointer(dtype=numpy.uint16,flags='CONTIGUOUS'), uInt32,
                                    POINTER(int32), POINTER(bool32)]

#define callbacks for nidaq events
_done_event_callback_type=ctypes.CFUNCTYPE(int32,TaskHandle,int32,ctypes.c_void_p)
_nsamples_event_callback_type=ctypes.CFUNCTYPE(int32,TaskHandle,int32,uInt32,ctypes.c_void_p)

def _read_string_property(function,*args):
    """wrapper to nidaq functions returning a string"""
    buffer_size=function(*(args+(None,0)))
    if buffer_size<0:
            raise DAQmxException(buffer_size)
    buffer=ctypes.create_string_buffer(buffer_size)
    err=function(*(args+(buffer,buffer_size)))
    if err<0:
        raise DAQmxException(err)
    return buffer.value

class DAQmxException(Exception):
    def __init__(self,error_code):
        self.error_code=error_code
        self.error_information=_read_string_property(DAQmxGetExtendedErrorInfo)
    def __str__(self):
        buff_size=DAQmxGetErrorString(self.error_code,None,0)
        buffer=ctypes.create_string_buffer(buff_size)
        DAQmxGetErrorString(self.error_code,buffer,buff_size)
        return self.error_information
        return buffer.value

class _DAQChannelDict(object):
    def __init__(self,task):
        self.task=task
        self.name_list=[]
        self.channel_dict={}
       
    def _update_name_list(self):
        channels=_read_string_property(DAQmxGetTaskChannels,self.task.task_handle)        
        self.name_list=map(str.strip,channels.split(','))
   
    def __len__(self):
        self._update_name_list()
        return len(self.name_list)
       
    def keys(self):
        self._update_name_list()
        return self.name_list
       
    def items(self):
        return [(k,self[k]) for k in self.keys()]
           
    def values(self):
        return [self[k] for k in self.keys()]
       
    def __getitem__(self,key):
        if key in self.channel_dict:
            return self.channel_dict[key]
        if key not in self.name_list:
            self._update_name_list()
        if key not in self.name_list:
            raise Exception()
        chan_type=int32()
        err=DAQmxGetChanType(self.task.task_handle,key,ctypes.byref(chan_type))
        if err<0:
            raise DAQmxException(err)
        if chan_type.value==DAQmx_Val_AI:
            channel=DAQmxAIChannel(self.task,key)
        if chan_type.value==DAQmx_Val_AO:
            channel=DAQmxAOChannel(self.task,key)
        if chan_type.value==DAQmx_Val_DI:
            channel=DAQmxDIChannel(self.task,key)
        if chan_type.value==DAQmx_Val_DO:
            channel=DAQmxDOChannel(self.task,key)
        if chan_type.value==DAQmx_Val_CI:
            channel=DAQmxCIChannel(self.task,key)
        if chan_type.value==DAQmx_Val_CO:
            channel=DAQmxCOChannel(self.task,key)
        self.channel_dict[key]=channel
        return channel
       
    def __repr__(self):
        return repr(self.keys())
       
class DAQmxTask(object):
    def __init__(self,task_name):
        self.task_name=task_name
        self.task_handle=TaskHandle()
        err=DAQmxCreateTask(task_name,ctypes.byref(self.task_handle))
        if err<0:
            raise DAQmxException(err)
        self.channels=_DAQChannelDict(self)
        self.timing=DAQmxTiming(self)
        self.start_trigger=DAQmxStartTrigger(self)
        self.reference_trigger=DAQmxReferenceTrigger(self)
        self.read=DAQmxReadProperties(self)
           
    def clear(self):
        err=DAQmxClearTask(self.task_handle)
        if err<0:
            raise DAQmxException(err)
           
    def task_control(self,action):
        err=DAQmxTaskControl(self.task_handle,action)
        if err<0:
            raise DAQmxException(err)
       
    def __del__(self):
        self.close()
       
    #controle de la tache
    def start(self):
        err=DAQmxStartTask(self.task_handle)
        if err<0:  
            raise DAQmxException(err)

    def stop(self):
        err=DAQmxStopTask(self.task_handle)
        if err<0:  
            raise DAQmxException(err)
           
    def _get_is_done(self):
        ret_val=bool32()
        err=DAQmxIsTaskDone(self.task_handle,ctypes.byref(ret_val))
        if err<0:
            raise DAQmxException(err)
        return retval.value
       
    is_done = property(_get_is_done)
    #DAQmx_Val_FiniteSamps
    def set_sample_clock(self,rate,samples,mode=DAQmx_Val_ContSamps,edge=DAQmx_Val_Rising,source=None ):
        """Set the parameters of the sample clock. source="""        
        err=DAQmxCfgSampClkTiming (self.task_handle,source,rate,edge,mode,samples)
        if err<0:
            raise DAQmxException(err)
       
    #analog channel read and write
    def readAnalogF64(self,buffer,samples,auto_start=True,timeout=1,fill_mode=DAQmx_Val_GroupByChannel):
        sampsread=int32()        
        err=DAQmxReadAnalogF64(self.task_handle,samples,timeout,DAQmx_Val_GroupByChannel,
                                        buffer,buffer.size,ctypes.byref(sampsread),None)
        if err<0:
            raise DAQmxException(err)
        return sampsread.value
       
    def readAnalogScalarF64(self,timeout=1,auto_start=True,fill_mode=DAQmx_Val_GroupByChannel):
        value=float64()
        err=DAQmxReadAnalogScalarF64(self.task_handle,timeout,ctypes.byref(value),None)
        if err<0:
            raise DAQmxException(err)
        return value.value
           
    def writeAnalogF64(self,buffer,samples,auto_start=False,timeout=1.0,fill_mode=DAQmx_Val_GroupByChannel):
        sampswritten=int32()
        err=DAQmxWriteAnalogF64(self.task_handle,samples,auto_start,timeout,DAQmx_Val_GroupByChannel,buffer,
            ctypes.byref(sampswritten),None)
        if err<0:
            raise DAQmxException(err)
        return sampswritten.value
       
    def writeAnalogScalarF64(self,value,timeout=1,auto_start=True,fill_mode=DAQmx_Val_GroupByChannel):
        err=DAQmxWriteAnalogScalarF64(self.task_handle,auto_start,timeout,value,None)
        if err<0:
            raise DAQmxException(err)
    #read and write binary data
    def readBinaryU16(self,timeout=1,fill_mode=DAQmx_Val_GroupByChannel):
        value=float64()
        err=DAQmxReadBinaryI16(self.task_handle,samples,timeout,fill_mode,
                                        buffer,buffer.size,ctypes.byref(sampsread),None)
        if err<0:
            raise DAQmxException(err)
        return value.value
       
    def readBinaryI16(self,timeout=1,auto_start=True,fill_mode=DAQmx_Val_GroupByChannel):
        value=float64()
        err=DAQmxReadBinaryI16(self.task_handle,samples,timeout,fill_mode,
                                        buffer,buffer.size,ctypes.byref(sampsread),None)
        if err<0:
            raise DAQmxException(err)
        return value.value    
       
    def readBinaryI32(self,timeout=1,fill_mode=DAQmx_Val_GroupByChannel):
        value=float64()
        err=DAQmxReadBinaryI32(self.task_handle,samples,timeout,fill_mode,
                                        buffer,buffer.size,ctypes.byref(sampsread),None)
        if err<0:
            raise DAQmxException(err)
        return value.value            
   
    def readBinaryU32(self,timeout=1,fill_mode=DAQmx_Val_GroupByChannel):
        value=float64()
        err=DAQmxReadBinaryU32(self.task_handle,samples,timeout,fill_mode,
                                        buffer,buffer.size,ctypes.byref(sampsread),None)
        if err<0:
            raise DAQmxException(err)
        return value.value
       
   
       
       
           
    def writeBinaryU16(self,buffer,samples,autostart=True,timeout=1,fill_mode=DAQmx_Val_GroupByChannel):
        sampsread=int32()
        err=DAQmxWriteBinaryU16(self.task_handle,samples,autostart,timeout,fill_mode,
                                        buffer,buffer.size,ctypes.byref(sampsread),None)
        if err<0:
            raise DAQmxException(err)
        return sampsread.value
       
    def writeBinaryI16(self,buffer,samples,auto_start=True,timeout=1,fill_mode=DAQmx_Val_GroupByChannel):
        sampsread=int32()
        err=DAQmxWriteBinaryI16(self.task_handle,samples,autostart,timeout,fill_mode,
                                        buffer,buffer.size,ctypes.byref(sampsread),None)
        if err<0:
            raise DAQmxException(err)
        return sampsread.value
       
   
           
   
       

    #channel creation functions
    def createAIVoltageChannel(self,physical_channel,channel_name=None,terminal_config=DAQmx_Val_Diff,min_val=-10,max_val=10):        
        err=DAQmxCreateAIVoltageChan(self.task_handle,physical_channel,channel_name,terminal_config,min_val,max_val,DAQmx_Val_Volts,None);
        if err<0:
            raise DAQmxException(err)  

    def createAOVoltageChannel(self,physical_channel,channel_name=None,min_val=-10,max_val=10):        
        err=DAQmxCreateAOVoltageChan(self.task_handle,physical_channel,channel_name,min_val,max_val,DAQmx_Val_Volts,None);
        if err<0:
            raise DAQmxException(err)  
   
    def createDOChannel(self,lines,channel_name=None,grouping=DAQmx_Val_ChanForAllLines ):        
        err=DAQmxCreateDOChan(self.task_handle,lines,channel_name,grouping);
        if err<0:
            raise DAQmxException(err)
           
    def createDIChannel(self,lines,channel_name=None,grouping=DAQmx_Val_ChanForAllLines ):        
        err=DAQmxCreateDOChan(self.task_handle,lines,channel_name,grouping);
        if err<0:
            raise DAQmxException(err)  
   
    def export_signal(id,terminal):
        err=DAQmxExportSignal(self.task_handle,id,terminal);
        if err<0:
            raise DAQmxException(err)  
   

           

    def register_done_event(self,callback):
        def wrapped_callback(task_handle,status,callback_data):
            ret_val=callback(self,status)
            if ret_val==None:
                ret_val=0
            return ret_val
        self._done_event_callback=_done_event_callback_type(wrapped_callback)
        err=DAQmxRegisterDoneEvent(self.task_handle,0,self._done_event_callback,None)
        if err<0:
            raise DAQmxException(err)    
     

    def register_nsamples_event(self,callback,samples,mode=DAQmx_Val_Acquired_Into_Buffer):
        def wrapped_callback(task_handle,mode,nsamples,callback_data):
            ret_val=callback(self,mode,nsamples)
            if ret_val==None:
                ret_val=0
            return ret_val
        self._nsamples_event_callback=_nsamples_event_callback_type(wrapped_callback)
        err=DAQmxRegisterEveryNSamplesEvent(self.task_handle,mode,samples,0,self._nsamples_event_callback,None)
        if err<0:
            raise DAQmxException(err)
       
       
    def _get_devices(self):
        pass
   
   
class DAQmxReadProperties(object):
    def __init__(self,task):
        self.task=task
       
    def _get_relative_to(self):
        ret_val=int32()
        err=DAQmxGetReadRelativeTo(self.task.task_handle,ctypes.byref(ret_val))
        if err<0:
            raise DAQmxException(err)
        return ret_val.value()
    def _set_relative_to(self,mode):
        err=DAQmxSetReadRelativeTo(self.task.task_handle,mode)
        if err<0:
            raise DAQmxException(err)
    def _del_relative_to(self):
        err=DAQmxResetv(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)
           
    def _get_channels_to_read(self):
        channel_list=_read_string_property(DAQmxGetReadChannelsToRead,self.task.task_handle)
        print channel_list
   
    relative_to=property(_get_relative_to,_set_relative_to,_del_relative_to)
       
class DAQmxTiming(object):
    def __init__(self,task):
        self.task=task    
       
    #sample mode
    def _get_sample_mode(self):
        ret_val=int32()
        err=DAQmxGetSampQuantSampMode(self.task.task_handle,ctypes.byref(ret_val))
        if err<0:
            raise DAQmxException(err)
        return ret_val.value()
    def _set_sample_mode(self,mode):
        err=DAQmxSetSampQuantSampMode(self.task.task_handle,mode)
        if err<0:
            raise DAQmxException(err)
    def _del_sample_mode(self):
        err=DAQmxResetSampQuantSampMode(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)
    #timing type
    def _get_sample_timing_type(self):
        ret_val=int32()
        err=DAQmxGetSampTimingType(self.task.task_handle,ctypes.byref(ret_val))
        if err<0:
            raise DAQmxException(err)
        return ret_val.value()
    def _set_sample_timing_type(self,samples):
        err=DAQmxSetSampTimingType(self.task.task_handle,samples)
        if err<0:
            raise DAQmxException(err)
    def _del_sample_timing_type(self):
        err=DAQmxResetSampTimingType(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)
    #samples per channel
    def _get_samples_per_channel(self):
        ret_val=int32()
        err=DAQmxGetSampQuantSampPerChan(self.task.task_handle,ctypes.byref(ret_val))
        if err<0:
            raise DAQmxException(err)
        return ret_val.value()
    def _set_samples_per_channel(self,samples):
        err=DAQmxSetSampQuantSampPerChan(self.task.task_handle,samples)
        if err<0:
            raise DAQmxException(err)
    def _del_samples_per_channel(self):
        err=DAQmxResetSampQuantSampPerChan(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)
           
    samples_per_channel=property(_get_samples_per_channel,_set_samples_per_channel,_del_samples_per_channel)    
    sample_mode=property(_get_sample_mode,_set_sample_mode,_del_sample_mode)
    sample_timing_type=property(_get_sample_timing_type,_set_sample_timing_type,_del_sample_timing_type)
   
class DAQmxStartTrigger(object):
    def __init__(self,task):
        self.task=task
       
    def _get_trigger_type(self):
        ret_val=int32()
        err=DAQmxGetStartTrigType(self.task.task_handle,ctypes.byref(ret_val))
        if err<0:
            raise DAQmxException(err)
        return ret_val.value
    def _set_trigger_type(self,samples):
        err=DAQmxSetStartTrigType(self.task.task_handle,samples)
        if err<0:
            raise DAQmxException(err)
    def _del_trigger_type(self):
        err=DAQmxResetStartTrigType(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)
    trigger_type=property(_get_trigger_type,_set_trigger_type,_del_trigger_type)
    #digital trigger properties
    def _get_digital_source(self):
        return _read_string_property(DAQmxGetDigEdgeStartTrigSrc,self.task.task_handle)
    def _set_digital_source(self,source):
        err=DAQmxSetDigEdgeStartTrigSrc(self.task.task_handle,source)
        if err<0:
            raise DAQmxException(err)
    def _del_digital_source(self):
        err=DAQmxResetDigEdgeStartTrigSrc(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)  
    digital_source=property(_get_digital_source,_set_digital_source,_del_digital_source)
    def _get_digital_edge(self):
        return _read_string_property(DAQmxGetDigEdgeStartTrigSrc ,self.task.task_handle)
    def _set_digital_edge(self,value):
        err=DAQmxSetDigEdgeStartTrigSrc(self.task.task_handle,value)
        if err<0:
            raise DAQmxException(err)
    def _del_digital_edge(self):
        err=DAQmxResetDigEdgeStartTrigSrc(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)
    digital_edge=property(_get_digital_edge,_set_digital_edge,_del_digital_edge)
           


class DAQmxReferenceTrigger(object):
    def __init__(self,task):
        self.task=task
       
    def _get_trigger_type(self):
        ret_val=int32()
        err=DAQmxGetRefTrigType(self.task.task_handle,ctypes.byref(ret_val))
        if err<0:
            raise DAQmxException(err)
        return ret_val.value
    def _set_trigger_type(self,samples):
        err=DAQmxSetRefTrigType(self.task.task_handle,samples)
        if err<0:
            raise DAQmxException(err)
    def _del_trigger_type(self):
        err=DAQmxResetRefTrigType(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)
    trigger_type=property(_get_trigger_type,_set_trigger_type,_del_trigger_type)
    def _get_pretrig_samples(self):
        ret_val=int32()
        err=DAQmxGetRefTrigPretrigSamples(self.task.task_handle,ctypes.byref(ret_val))
        if err<0:
            raise DAQmxException(err)
        return ret_val.value
    def _set_pretrig_samples(self,samples):
        print "set pretrig samples"
        err=DAQmxSetRefTrigPretrigSamples(self.task.task_handle,samples)
        if err<0:
            raise DAQmxException(err)
    def _del_pretrig_samples(self):
        err=DAQmxResetRefTrigPretrigSamples(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)
    pretrig_samples=property(_get_pretrig_samples,_set_pretrig_samples,_del_pretrig_samples)
    #digital trigger properties
    def _get_digital_source(self):
        return _read_string_property(DAQmxGetDigEdgeRefTrigSrc,self.task.task_handle)
    def _set_digital_source(self,source):
        err=DAQmxSetDigEdgeRefTrigSrc(self.task.task_handle,source)
        if err<0:
            raise DAQmxException(err)
    def _del_digital_source(self):
        err=DAQmxResetDigEdgeRefTrigSrc(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)  
    digital_source=property(_get_digital_source,_set_digital_source,_del_digital_source)
    #has errors
    def _get_digital_edge(self):
        ret_val=int32()
        err=DAQmxGetDigEdgeRefTrigEdge(self.task.task_handle,ctypes.byref(ret_val))
        if err<0:
            raise DAQmxException(err)
        return ret_val.value
    def _set_digital_edge(self,value):
        err=DAQmxSetDigEdgeRefTrigEdge(self.task.task_handle,value)
        if err<0:
            raise DAQmxException(err)
    def _del_digital_edge(self):
        err=DAQmxResetDigEdgeRefTrigEdge(self.task.task_handle)
        if err<0:
            raise DAQmxException(err)
    digital_edge=property(_get_digital_edge,_set_digital_edge,_del_digital_edge)
       
   
       
class DAQmxSystem(object):
    def __init__(self):
        self.devices=_DAQDeviceDict()
       
       
       
   
class DAQmxDevice(object):
    def __init__(self,device_name):
        category=ctypes.c_int32()
        err=DAQmxGetDevProductCategory(device_name,ctypes.byref(category))
        #will get an error if the device does not exist
        if err<0:
            raise DAQmxException(err)
        self.device_name=device_name
       
    def _get_product_type(self):
        return _read_string_property(DAQmxGetDevProductType,self.device_name)
    def _get_product_number(self):
        answer=uInt32()
        err=DAQmxGetDevProductNum(self.device_name,ctypes.byref(answer))
        if err<0:
            raise DAQmxException(err)
        return answer.value
   
    def _get_is_simulated(self):
        answer=bool32()
        err=DAQmxGetDevIsSimulated(self.device_name,ctypes.byref(answer))
        if err<0:
            raise DAQmxException(err)
        return answer.value
       
    def _get_ai_physical_channels(self):
        channels=_read_string_property(DAQmxGetDevAIPhysicalChans,self.device_name)        
        return map(str.strip,channels.split(','))      
    def _get_ao_physical_channels(self):
        channels=_read_string_property(DAQmxGetDevAOPhysicalChans,self.device_name)        
        return map(str.strip,channels.split(','))
       
    def _get_do_ports(self):
        ports=_read_string_property(DAQmxGetDevDOPorts,self.device_name)        
        return map(str.strip,ports.split(','))
    def _get_do_lines(self):
        lines=_read_string_property(DAQmxGetDevDOLines,self.device_name)        
        return map(str.strip,lines.split(','))    
   

    product_type=property(_get_product_type)
    product_number=property(_get_product_number)
    is_simulated=property(_get_is_simulated)
   
    ai_physical_channels=property(_get_ai_physical_channels)
    ao_physical_channels=property(_get_ao_physical_channels)
    do_ports=property(_get_do_ports)
    do_lines=property(_get_do_lines)
       
       
class _DAQDeviceDict(object):
    def __init__(self):
        self.name_list=[]
        self.channel_dict={}
       
    def _update_name_list(self):
        devices=_read_string_property(DAQmxGetSysDevNames)        
        self.name_list=map(str.strip,devices.split(','))
   
    def __len__(self):
        self._update_name_list()
        return len(self.name_list)
       
    def keys(self):
        self._update_name_list()
        return self.name_list
       
    def items(self):
        return [(k,self[k]) for k in self.keys()]
           
    def values(self):
        return [self[k] for k in self.keys()]
       
    def __getitem__(self,key):
        if key in self.channel_dict:
            return self.channel_dict[key]
        if key not in self.name_list:
            self._update_name_list()
        if key not in self.name_list:
            raise Exception()
     
        channel=DAQmxDevice(key)
        self.channel_dict[key]=channel
        return channel
       
    def __repr__(self):
        return repr(self.keys())
