from importlibs import *
"""
import struct
import ctypes
import array
import collections
import binascii
import sys
import os
import time
#import numpy
import mmap

endianness_dict = {
    "native-native":    '@',
    "native-standard":  '=',
    "little":           '<',
    "big":              '>',
    "network":          '!'
}
endian = endianness_dict.get(sys.byteorder, "native-standard")
"""


__name__ = "readplx"
__revision__ = "$Revision: 0.16 - 16 March 2010$"
__author__ = "Andrea Maffezzoli"



class FileHeaderC(ctypes.Structure):

    TSCountsUnitsC = ctypes.c_int * 5
    TSCountsChannelsC = TSCountsUnitsC * 130
    
    WFCountsUnitsC = ctypes.c_int * 5
    WFCountsChannelsC = WFCountsUnitsC * 130

    _fields_ = [("magicstring", ctypes.c_char * 4),
                ("Version", ctypes.c_int),
                ("Comment", ctypes.c_char * 128),
                ("ADFrequency", ctypes.c_int),
                ("NumDSPChannels", ctypes.c_int),
                ("NumEventChannels", ctypes.c_int),
                ("NumSlowChannels", ctypes.c_int),
                ("NumPointsWave", ctypes.c_int),
                ("NumPointsPreThr", ctypes.c_int),
                ("Year", ctypes.c_int),
                ("Month", ctypes.c_int),
                ("Day", ctypes.c_int),
                ("Hour", ctypes.c_int),
                ("Minute", ctypes.c_int),
                ("Second", ctypes.c_int),
                ("FastRead", ctypes.c_int),
                ("WaveformFreq", ctypes.c_int),
                ("LastTimestamp", ctypes.c_double),
                ("Trodalness", ctypes.c_byte),
                ("DataTrodalness", ctypes.c_byte),
                ("BitsPerSpikeSample", ctypes.c_byte),
                ("BitsPerSlowSample", ctypes.c_byte),
                ("SpikeMaxMagnitudeMV", ctypes.c_uint16),
                ("SlowMaxMagnitudeMV", ctypes.c_uint16),
                ("SpikePreAmpGain", ctypes.c_uint16),
                ("Padding", ctypes.c_char * 46),
                ("TSCounts", TSCountsChannelsC),
                ("WFCounts", WFCountsChannelsC),
                ("EVCounts", ctypes.c_int * 512)
                ]


class SpikeChannelHeaderC(ctypes.Structure):

    _fields_ = [("Name", ctypes.c_char * 32),
                ("SIGName", ctypes.c_char * 32),
                ("Channel", ctypes.c_int),
                ("WFRate", ctypes.c_int),
                ("SIG", ctypes.c_int),
                ("Ref", ctypes.c_int),
                ("Gain", ctypes.c_int),
                ("Filter", ctypes.c_int),
                ("Threshold", ctypes.c_int),
                ("Method", ctypes.c_int),
                ("NUnits", ctypes.c_int),
                ("Template", ctypes.c_short * (64 * 5)),
                ("Fit", ctypes.c_int * 5),
                ("SortWidth", ctypes.c_int),
                ("Boxes", ctypes.c_short * (5 * 2 * 4)),
                ("SortBeg", ctypes.c_int),
                ("Comment", ctypes.c_char * 128),
                ("Padding", ctypes.c_int * 11)
                ]


class EventChannelHeaderC(ctypes.Structure):

    _fields_ = [("Name", ctypes.c_char * 32),
                ("Channel", ctypes.c_int),
                ("Comment", ctypes.c_char * 128),
                ("Padding", ctypes.c_int * 33)
                ]


class ContinuousChannelHeaderC(ctypes.Structure):

    _fields_ = [("Name", ctypes.c_char * 32),
                ("Channel", ctypes.c_int),
                ("ADFreq", ctypes.c_int),
                ("Gain", ctypes.c_int),
                ("Enabled", ctypes.c_int),
                ("Gain", ctypes.c_int),
                ("PreAmpGain", ctypes.c_int),
                ("SpikeChannel", ctypes.c_int),
                ("Comment", ctypes.c_char * 128),
                ("Padding", ctypes.c_int * 11)
                ]


class DataBlockHeaderC(ctypes.Structure):

    _fields_ = [("Type", ctypes.c_int16),
                ("UpperByteOf5ByteTimestamp", ctypes.c_uint16),
                ("TimeStamp", ctypes.c_ulong),
                ("Channel", ctypes.c_int16),
                ("Unit", ctypes.c_int16),
                ("NumberOfWaveforms", ctypes.c_int16),
                ("NumberOfWordsInWaveform", ctypes.c_int16),
                ]


class readPlx(object):
    """Applying struct.Struct to unpack/decode binary data from plx files"""
    
    ###########################################################################
    # FileHeader
    ###########################################################################
    FileHeader = struct.Struct(endian + '4si128s14id4b3H46s650i650i512i')
    FileHeader_size = FileHeader.size

    ###########################################################################
    # Spike Channel Header
    ###########################################################################
    SpikeChannelHeader_struct = struct.Struct(endian + '32s32s9i320h6i40hi128s11i')
    SpikeChannelHeader_size = SpikeChannelHeader_struct.size

    ###########################################################################
    # Event Channel Header
    ###########################################################################
    EventChannelHeader_struct = struct.Struct(endian + '32si128s33i')
    EventChannelHeader_size = EventChannelHeader_struct.size

    ###########################################################################
    # Continuous Channel Header
    ###########################################################################
    ContinuousChannelHeader_struct = struct.Struct(endian + '32s6i128s28i')
    ContinuousChannelHeader_size = ContinuousChannelHeader_struct.size

    ###########################################################################
    # Data Block Header
    ###########################################################################
    DataBlockHeader_struct = struct.Struct(endian + 'hHL4h')
    DataBlockHeader_size = DataBlockHeader_struct.size

    ###########################################################################
    # Exporting timestamps in various formats: numpy.ndarray, array.array, list
    ###########################################################################
    func_output = {'numpy': lambda x: numpy.array(x, dtype = 'float32'),
                   '': lambda x: x,
                   'list': list}
    
    ###########################################################################
    # Denomination of channels units
    ###########################################################################
    UnitsNames = {0: 'Unsorted', 1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g'}
        
    def getDSPChannelsNames_dict(self):
        """Dict of {DSP Channel number: Channel Name}"""


        self.DSPChannelsNames_dict = dict((c.Channel, c.Name.translate(None, chr(0))) \
                                          for c in self.SpikeChannelHeader)

    def getTSChannelsCounts_dict(self):
        """Dict of {Channel Name: tuple of Channel TSCounts for all (five) Units}"""

        
        self.TSChannelsCounts_dict = collections.defaultdict(int)
        for i, j in self.DSPChannelsTimestamps_dict.iteritems():
            self.TSChannelsCounts_dict[i[0]] += len(j)
        
    def __init__(self, filename):

        self.filename = filename

    
    def __call__(self, Event_on = False, Continuous_on = False):
        
        self.f = open(self.filename, 'rb')

        #########################################################################
        # Preliminary information on the experiment and Channels data    
        #########################################################################
        self.readFileHeader()  
        self.readSpikeChannelHeader()

        #########################################################################
        # Usually the following two are not recorded and so not requested       
        #########################################################################
        if Event_on: self.readEventChannelHeader()
        if Continuous_on: self.readContinuousChannelHeader()

        #########################################################################
        # Finally get timestamps for every Channel/Unit pair      
        #########################################################################
        self.readDataBlockHeader()
        
        self.getTSChannelsCounts_dict()

        self.f.close()

    def readFileHeader(self): #, is_MEA256 = True):

        PlxFileHeader_temp = self.FileHeader.unpack(self.f.read(self.FileHeader_size))

        self.NumDSPChannelsFiring = 0
        self.TSCountsSum = 0
        _cchannels = []
        
        pos = pos_start = 26
        next_pos = 130 * 5
        for i in xrange(pos, pos + next_pos, 5):
            _cunits = PlxFileHeader_temp[i:i+5]
            _cchannels.append(FileHeaderC.TSCountsUnitsC(*_cunits))
            s = sum(_cunits)
            if s: self.NumDSPChannelsFiring += 1; self.TSCountsSum += s
        TSCChannelsC = FileHeaderC.TSCountsChannelsC(*_cchannels)
        
        pos += next_pos
        _wchannels = []
        for i in xrange(pos, pos + next_pos, 5):
            _wunits = PlxFileHeader_temp[i:i+5]
            _WCountsUnits = FileHeaderC.WFCountsUnitsC(*_wunits)
            _wchannels.append(_WCountsUnits)
        WFCChannelsC = FileHeaderC.WFCountsChannelsC(*_wchannels)
        pos += next_pos
        
        send_to_Header = PlxFileHeader_temp[:pos_start] + \
                        (TSCChannelsC, ) + (WFCChannelsC, ) + \
                        (PlxFileHeader_temp[pos:], )
        self.PlxFileHeader = FileHeaderC(*send_to_Header)

        
    def readSpikeChannelHeader(self):

        NumDSPChannels = self.PlxFileHeader.NumDSPChannels
        allSpikeChannelHeaderC  = SpikeChannelHeaderC * NumDSPChannels
        LengthTSCounts = len(self.PlxFileHeader.TSCounts[1:])

        self.DSPChannelsNames_dict = {}
        #self.DSPNamesChannels = []
        #self.DSPChannelsTimestamps_dict2 = {}
        _dspchannels = []
        start_pos = 11
        next_pos1 = start_pos + (64 * 5)
        next_pos2 = next_pos1 + 5
        next_pos3 = next_pos2 + 1
        next_pos4 = next_pos3 + (5 * 2 * 4)
        next_pos5 = next_pos4 + 2

        for i in xrange(NumDSPChannels):
            SpikeChannelHeader_temp = self.SpikeChannelHeader_struct.unpack(self.f.read(self.SpikeChannelHeader_size))
            send_to_Header = SpikeChannelHeader_temp[:start_pos] + \
                             (SpikeChannelHeader_temp[start_pos:next_pos1], ) + \
                             (SpikeChannelHeader_temp[next_pos1:next_pos2], ) + \
                             SpikeChannelHeader_temp[next_pos2:next_pos3] + \
                             (SpikeChannelHeader_temp[next_pos3:next_pos4], ) + \
                             SpikeChannelHeader_temp[next_pos4:next_pos5] + \
                             (SpikeChannelHeader_temp[next_pos5:], )
            SpikeChannelHeaderC_temp = SpikeChannelHeaderC(*send_to_Header)
            _dspchannels.append(SpikeChannelHeaderC_temp)
            self.DSPChannelsNames_dict[SpikeChannelHeaderC_temp.Channel] = SpikeChannelHeaderC_temp.Name
            
            """
            if send_to_Header[2] >= 1 + LengthTSCounts: continue
            _cunits = filter(bool, self.PlxFileHeader.TSCounts[1:][send_to_Header[2]-1])
            #_cunits = tuple(self.PlxFileHeader.TSCounts[1:][send_to_Header[2]-1])[1:]
            #print _cunits, len(_cunits)
            #if len(_cunits) > 3: print SpikeChannelHeaderC_temp.Name, _cunits, len(_cunits)
            for v, u in enumerate(_cunits):
                self.DSPChannelsTimestamps_dict2[SpikeChannelHeaderC_temp.Name, v+1] = numpy.zeros(u)
            """
            
            #self.DSPNamesChannels.append((SpikeChannelHeaderC_temp.Channel, \
            #                              SpikeChannelHeaderC_temp.Name, \
            #                              SpikeChannelHeaderC_temp.NUnits))
            
            #print i+1, SpikeChannelHeaderC_temp.Name, send_to_Header[2], send_to_Header[4:11],
            #if send_to_Header[2] <= len(self.PlxFileHeader.TSCounts[1:]): print _cunits
            #else: print ''    
        
        self.SpikeChannelHeader = allSpikeChannelHeaderC(*_dspchannels)
        #print sorted(self.DSPChannelsNames_dict.values())
        #print sorted(self.DSPChannelsTimestamps_dict2.keys())


    def readEventChannelHeader(self):

        #########################################################################
        # Actually it's not requested so do nothing
        #########################################################################
        EventChannelHeader_temp = self.EventChannelHeader_struct.unpack(self.f.read(self.EventChannelHeader_size))
        
    def readContinuousChannelHeader(self): 

        #########################################################################
        # Actually it's not requested so do nothing
        #########################################################################
        ContinuousChannelHeader_temp = self.ContinuousChannelHeader_struct.unpack(self.f.read(self.ContinuousChannelHeader_size))

    def readDataBlockHeader(self):

        #########################################################################
        # Although no Events have been recorded, it is possible to find chunks
        # of data structured as them, of course to be searched and then if
        # present discarded before to start reading the DataBlockHeader_structs 
        #########################################################################
        while True:
            file_pos = self.f.tell()
            try:
                DataBlockHeader_temp = self.DataBlockHeader_struct.unpack(self.f.read(self.DataBlockHeader_size))
                if DataBlockHeader_temp[0] in (1, 4, 5):
                    self.f.seek(file_pos)
                    NoWprod = 2 * DataBlockHeader_temp[-1] * DataBlockHeader_temp[-2]
                    break
                else:
                    self.f.seek(file_pos)
                    self.readEventChannelHeader()             
            except:
                self.readEventChannelHeader()

        #########################################################################
        # After have done the check above, finally start reading DataBlockHeader_structs 
        #########################################################################
        Freq = float(self.PlxFileHeader.ADFrequency)
        _DSPChannelsTimestamps_dict = collections.defaultdict(list)
        #self.DSPChannelsTimestamps_dict = collections.defaultdict(list)
        #DSPChannelsTSIndexes_dict = dict((_t, 0) for _t in self.DSPChannelsTimestamps_dict2)
        _f = self.f
        _fread = _f.read
        _DecodeFunc = self.DataBlockHeader_struct.unpack #<---or .unpack_from: it seems the same
        _DecodeChunkSize = self.DataBlockHeader_size
        _DSPChannelsNames_dict = self.DSPChannelsNames_dict

        lastfilepos = self.f.tell()
        filemmap = mmap.mmap(self.f.fileno(), 0, None, mmap.ACCESS_READ)
        filemmap.seek(lastfilepos, 1)
        
        #for i in xrange(self.TSCountsSum):
        while True:
            #Type, UpTs, TS, Ch, Unit, NoW, NoWiW = self.DataBlockHeader_struct.unpack_from(_f.read(self.DataBlockHeader_size))
            try:
                #Type, UpTs, TS, Ch, Unit, NoW, NoWiW = _DecodeFunc(_fread(_DecodeChunkSize))
                Type, UpTs, TS, Ch, Unit, NoW, NoWiW = _DecodeFunc(filemmap.read(_DecodeChunkSize))

                #########################################################################
                # To not take in account the 8 upper bits (on 40) of the timestamp,
                # comment the portion of code ...(UpTs << 32) / Freq + ...
                #########################################################################
                _t = (_DSPChannelsNames_dict[Ch], Unit)
                _TS = (UpTs << 32) / Freq + TS / Freq
                _DSPChannelsTimestamps_dict[_t].append(_TS)
                filemmap.seek(NoWprod, 1) #NoW * NoWiW * 2) #<--- _f.seek is two times slower than f.read
                #_fread(96) #<--- _f.seek is two times slower than f.read
            except: break
        
        filemmap.close()
            
        self.DSPChannelsTimestamps_dict = _DSPChannelsTimestamps_dict

    ###########################################################################
    # From http://wiki.python.org/moin/ctypes
    #      http://stackoverflow.com/questions/1825715/how-to-pack-and-unpack-using-ctypes-structure-str
    # After FAQ: How do I copy bytes to Python from a ctypes.Structure?
    #            How do I copy bytes to a ctypes.Structure from Python?
    ###########################################################################
    def send(self): return buffer(self)[:]
    
    def receiveSome(self, Cobj, IObytes):
        
        fit = min(len(IObytes), ctypes.sizeof(Cobj))
        ctypes.memmove(ctypes.addressof(Cobj), IObytes, fit)


"""
################
# Some tests
################
t_start = time.clock()
#plxdata = readPlx('test64.plx')
#plxdata = readPlx('D:\\neurocode\\9692 13gg100407 con8700s_AB 10uM-01\\MEAdata_plx\\9692 13gg100407 con8700s_AB 10uM-01.plx') #<--- Big file: 59sec.
#plxdata = readPlx('8755 25div_spikes-01_spl_t001.plx')

#plxdata = readPlx('i3192 22 div controllo 40KHz tutti_sub_mrg.plx')
#plxdata = readPlx('i3192 22 div controllo 40KHz A9-P16-01_sub.plx')
#plxdata = readPlx('i3192 22 div controllo 40KHz A1-R8-01_sub.plx')

#plxdata = readPlx('i3188 12 div 200u cont-1-126-01_sub.plx')
#plxdata = readPlx('i3188 12 div 200u cont0002_1_3MB.plx')
#plxdata = readPlx('i3188 12 div 200u cont0002_2_3MB.plx')
#plxdata = readPlx('i3188 12 div 200u cont0002_1_3MB_mrg.plx')

#plxdata = readPlx('test256.plx')
#plxdata = readPlx('test256small.plx')

plxdata = readPlx('D:\\neurocode\\8754 20div_100201-ctr-2uL NB-SCH23390 10-20uM-rec-SORTED-01\\MEAdata_plx\\8754 20div_100201-ctr-2uL NB-SCH23390 10-20uM-rec-SORTED-01.plx') 

plxdata()
print 'Time required: %f' % (time.clock() - t_start)

#for i, j in enumerate(plxdata.DSPNamesChannels): print i+1, j
#print plxdata.PlxFileHeader.NumDSPChannels
print plxdata.PlxFileHeader.NumDSPChannels, plxdata.NumDSPChannelsFiring, plxdata.TSCountsSum
print len(plxdata.PlxFileHeader.TSCounts[1:]), plxdata.PlxFileHeader.LastTimestamp
print len(plxdata.DSPChannelsTimestamps_dict)

for z, i in enumerate(sorted(plxdata.DSPChannelsTimestamps_dict)):
    print "%d) %s[%d] of %d from %d - first ts:%.6f, last ts: %.6f" % (z, i[0], i[1], \
                                                                       len(plxdata.DSPChannelsTimestamps_dict[i]), \
                                                                       plxdata.TSChannelsCounts_dict[i[0]], \
                                                                       plxdata.DSPChannelsTimestamps_dict[i][0], \
                                                                       plxdata.DSPChannelsTimestamps_dict[i][-1])

"""
"""
FiveDoubles = ctypes.c_int * 5
FourInt = 4 * FiveDoubles

class POINT(ctypes.Structure):
    
    _fields_ = [("x", 4 * ctypes.c_char), ("y", FourInt)]

l = []
for n, fi in zip(xrange(4), Fi):
    n5 = n*5
    v = range(n5, n5 + 5)
    fd = FiveDoubles(*v)
    #for z in fd: print z
    l.append(fd)
Fi = FourInt(*l)

#v =('ciao', Fi)
#p = POINT(*v)
p = POINT('ciao', Fi)
print p.x, p.y
for i in p.y:
    print i,
    for z in i: print z,
    print
"""


"""
######################################
# Some ex about how to appply struct
######################################
values = (1, 'ab', 2.7)
packed_data = s.pack(*values)
print s.size
unpacked_data = s.unpack(f.read(10))
print 'Unpacked Values:', unpacked_data
print 'Original values:', values
print 'Format string  :', s.format
print 'Uses           :', s.size, 'bytes'
print 'Packed Value   :', binascii.hexlify(packed_data)
packed_data = binascii.unhexlify('0100000061620000cdcc2c40')
s = struct.Struct('I 2s f')
unpacked_data = s.unpack(packed_data)
print 'Unpacked Values:', unpacked_data
"""

"""
---------------------------------------------------
NB: Conventions in the array module

Type code 	C Type 	Python Type 	Minimum size in bytes
'c' 	        char 	character 	1
'b' 	        signed char 	int 	1
'B' 	        unsigned char 	int 	1
'u' 	        Py_UNICODE      Unicode character 	2 (see note)
'h' 	        signed short 	int 	2
'H' 	        unsigned short 	int 	2
'i' 	        signed int 	int 	2
'I' 	        unsigned int 	long 	2
'l' 	        signed long 	int 	4
'L' 	        unsigned long 	long 	4
'f' 	        float 	        float 	4
'd' 	        double 	        float 	8

---------------------------------------------------
NB: Conventions in the struct module

Format 	C Type 	Python 	Notes
x 	pad byte 	no value 	 
c 	char 	string of length 1 	 
b 	signed char 	integer 	 
B 	unsigned char 	integer 	 
? 	_Bool 	bool 	(1)
h 	short 	integer 	 
H 	unsigned short 	integer 	 
i 	int 	integer 	 
I 	unsigned int 	integer or long 	 
l 	long 	integer 	 
L 	unsigned long 	long 	 
q 	long long 	long 	(2)
Q 	unsigned long long 	long 	(2)
f 	float 	float 	 
d 	double 	float 	 
s 	char[] 	string 	 
p 	char[] 	string 	 
P 	void * 	long

Character 	Byte order 	Size and alignment
@ 	native 	native
= 	native 	standard
< 	little-endian 	standard
> 	big-endian 	standard
! 	network (= big-endian) 	standard
"""

"""
#######################################
# Using Buffers from ctypes module
#######################################
import binascii

s = struct.Struct('I 2s f')
values = (1, 'ab', 2.7)
print 'Original:', values

print
print 'ctypes string buffer'

import ctypes
b = ctypes.create_string_buffer(s.size)
print 'Before  :', binascii.hexlify(b.raw)
s.pack_into(b, 0, *values)
print 'After   :', binascii.hexlify(b.raw)
print 'Unpacked:', s.unpack_from(b, 0)

print
print 'array'

import array
a = array.array('c', '\0' * s.size)
print 'Before  :', binascii.hexlify(a)
s.pack_into(a, 0, *values)
print 'After   :', binascii.hexlify(a)
print 'Unpacked:', s.unpack_from(a, 0)

s = struct.Struct('I 2s f')
values = (1, 'ab', 2.7)

t_start = time.clock()
b = ctypes.create_string_buffer(s.size)
for i in xrange(1000):
    s.pack_into(b, 0, *values)
    s.unpack_from(b, 0)
print 'Time required: %fs' % (time.clock() - t_start)

t_start = time.clock()
a = array.array('c', '\0' * s.size)
for i in xrange(1000):
    s.pack_into(a, 0, *values)
    s.unpack_from(a, 0)
print 'Time required: %fs' % (time.clock() - t_start)

#####################################
# Ex. of struct with namedtuple
#####################################
StudentStruct = struct.Struct('<10sHHb')
record = StudentStruct.pack('raymond   ', 4658, 264, 8)
#record = "raymond   \x32\x12\x08\x01\x08"
name, serialnum, school, gradelevel = struct.unpack('<10sHHb', record)
record = 'raymond   \x32\x12\x08\x01\x08'
print struct.unpack('<10sHHb', record)
Student = collections.namedtuple('Student', 'name serialnum school gradelevel')
newStudent = Student._make(struct.unpack('<10sHHb', record))
print newStudent
"""
