import matplotlib
matplotlib.use("WXAgg")
matplotlib.interactive(True)
from matplotlib.backends.backend_wxagg import Toolbar, FigureCanvasWxAgg
from matplotlib.figure import Figure
import numpy as np
import wx.lib.newevent
import threading
import comedi as c
import os
import struct

ID_STARTBUTTON = 101
ID_STOPBUTTON = 102
ID_CLEARBUTTON = 103
ID_OKBUTTON = 104
ID_CONFIGUREBUTTON = 105
X_INIT = 0
Y_INIT = 65535
BUFFER_SIZE = 1000
PLOT_ARRAY_MAX_SIZE = 5000
DEBUG=1



####### These elements are copied from comedilib's cmd.py - MM
cmdtest_messages = [
    "success",
    "invalid source",
    "source conflict",
    "invalid argument",
    "argument conflict",
    "invalid chanlist"]

def dump_cmd(cmd):
    print "---------------------------"
    print "command structure contains:"
    print "cmd.subdev : ", cmd.subdev
    print "cmd.flags : ", cmd.flags
    print "cmd.start :\t", cmd.start_src, "\t", cmd.start_arg
    print "cmd.scan_beg :\t", cmd.scan_begin_src, "\t", cmd.scan_begin_arg
    print "cmd.convert :\t", cmd.convert_src, "\t", cmd.convert_arg
    print "cmd.scan_end :\t", cmd.scan_end_src, "\t", cmd.scan_end_arg
    print "cmd.stop :\t", cmd.stop_src, "\t", cmd.stop_arg
    print "cmd.chanlist : ", cmd.chanlist
    print "cmd.chanlist_len : ", cmd.chanlist_len
    print "cmd.data : ", cmd.data
    print "cmd.data_len : ", cmd.data_len
    print "---------------------------"
######## End copied part



# this creates an UpdateCanvasEvent even class that will get created
# and sent from the worker thread to the main thread (which is the
# only place that updates to the GUI should happen...lest there be crashes)
(UpdateCanvasEvent, EVT_UPDATE_CANVAS) = wx.lib.newevent.NewEvent()

class MainWindow(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Free Period Calculation")

        self.arrayLock = threading.Semaphore(1)
        self.canvasLock = threading.Semaphore(1)
        self.dev = None
        self.subdev = None

        #create the preferences window
        self.prefwindow = self.createPreferencesWindow()

        self.figure = Figure((5, 4), 75)
        self.canvas = FigureCanvasWxAgg(self, -1, self.figure)
        self.toolbar = Toolbar(self.canvas)
        self.toolbar.Realize()

        self.sizerHoriz = wx.BoxSizer(wx.HORIZONTAL)

        self.startButton = wx.Button(self, ID_STARTBUTTON, "Start")
        wx.EVT_BUTTON(self, ID_STARTBUTTON, self.pressStartButton);

        self.stopButton = wx.Button(self, ID_STOPBUTTON, "Stop")
        self.stopButton.Enable(0)
        wx.EVT_BUTTON(self, ID_STOPBUTTON, self.pressStopButton);

        self.clearButton = wx.Button(self, ID_CLEARBUTTON, "Clear")
        wx.EVT_BUTTON(self, ID_CLEARBUTTON, self.pressClearButton);

        configureButton = wx.Button(self, ID_CONFIGUREBUTTON, "Configure")
        wx.EVT_BUTTON(self, ID_CONFIGUREBUTTON, self.pressConfigureButton)

        self.sizerHoriz.Add(self.startButton, 0, wx.ALL, 5)
        self.sizerHoriz.Add(self.stopButton, 0, wx.ALL, 5)
        self.sizerHoriz.Add(self.clearButton, 0, wx.ALL, 5)
        self.sizerHoriz.Add(configureButton, 0, wx.ALL, 5)

        # Use some sizers to see layout options
        self.sizerVert = wx.BoxSizer(wx.VERTICAL)
        self.sizerVert.Add(self.sizerHoriz, 0, wx.EXPAND)
        self.sizerVert.Add(self.canvas, 1, wx.EXPAND)
        self.sizerVert.Add(self.toolbar, 0, wx.GROW)

        #Bind the update canvas event to some function:
        self.Bind(EVT_UPDATE_CANVAS, self.onUpdateCanvas)

        #Layout sizers
        self.SetSizer(self.sizerVert)
        self.SetAutoLayout(1)
        self.sizerVert.Fit(self)
        self.initialize()
        self.Show(1)

    def GetToolBar(self):
        return self.toolbar

    def pressStartButton(self, event):
        print "Starting background thread."
        self.startButton.Enable(0)
        self.configuration = self.prefwindow.getConfiguration()
        
        #configure the comedi device
        self.dev = c.comedi_open(str(self.configuration['device']))
        #and get a file-descriptor for reading
        fd = c.comedi_fileno(self.dev)
        if fd<=0: raise Exception("Error obtaining Comedi device file descriptor: %s"%(c.comedi_perror('comedi_fileno')))
        self.subdev = 0
        if not self.dev: raise Exception("Error opening Comedi device: %s"%c.comedi_perror(str(self.configuration['device'])))
        #create channel list
        nbChans = 2
        myChanList = c.chanlist(nbChans)
        for i in range(nbChans):
            myChanList[i] = c.cr_pack(self.configuration['analog'][i][0],self.configuration['analog'][i][1],self.configuration['analog'][i][2])
        #create a command structure
        cmd = c.comedi_cmd_struct()
        ret = c.comedi_get_cmd_generic_timed(self.dev,self.subdev,cmd,nbChans,self.configuration['analog_period_ns'])
        if ret: raise Exception("Error comedi_get_cmd_generic failed")
        cmd.chanlist = myChanList # adjust for our particular context
        cmd.chanlist_len = nbChans
        cmd.scan_end_arg = nbChans
        cmd.stop_src = c.TRIG_NONE
        if cmd.stop_src==c.TRIG_COUNT: cmd.stop_arg=BUFFER_SIZE
        if DEBUG:
            print "command before testing"
            dump_cmd(cmd)
        #test our comedi command a few times.
        ret = 0
        for i in range(2):
            ret = c.comedi_command_test(self.dev,cmd)
            if DEBUG:
                print "Command test %d returns %s [%s]"%(i+1, ret, cmdtest_messages[ret])
                dump_cmd(cmd)
            if ret<0: raise Exception("comedi_command_test failed: %s"%(c.comedi_perror('comedi_command_test')))
        if ret <> 0:
            dump_cmd(cmd)
            raise Exception("Error preparing command")

        #Start the command
        ret = c.comedi_command(self.dev,cmd)
        if ret<>0: raise Exception("comedi_command failed... %s"%(c.comedi_perror('comedi_command')))
        
        self.helper = helperThread(self, fd)        
        self.helper.start()
        self.stopButton.Enable(1)
        print "Start button complete."

    def pressStopButton(self, event):
        self.stopButton.Enable(0)
        self.helper.stopFlag = 0
        self.helper.join()
        print "Background thread stopped."
        self.helper = None
        c.comedi_cancel(self.dev,self.subdev)
        self.startButton.Enable(1)

    def pressClearButton(self, event):
        self.arrayLock.acquire()
        self.xarray = np.zeros(PLOT_ARRAY_MAX_SIZE)
        self.yarray = np.zeros(PLOT_ARRAY_MAX_SIZE)
        self.line.set_data(self.xarray, self.yarray)
        self.arrayLock.release()
        evt = UpdateCanvasEvent()
        wx.PostEvent(self, evt)

    def pressConfigureButton(self, event):
        self.prefwindow.Show(1)
        self.prefwindow.MakeModal(1)

    def initialize(self):
        self.subplot = self.figure.add_subplot(111)
        self.xarray = np.zeros(PLOT_ARRAY_MAX_SIZE)
        self.yarray = np.zeros(PLOT_ARRAY_MAX_SIZE)
        self.line, = self.subplot.plot(self.xarray, self.yarray, '-')
        self.toolbar.update()

    def onUpdateCanvas(self, evt):
        print "Updating the canvas..."
        self.canvasLock.acquire()
        self.canvas.draw()
        self.toolbar.update()
        self.canvasLock.release()

    def createPreferencesWindow(self):
        prefwindow = PreferencesFrame(self, "Preferences")
        prefwindow.CentreOnParent(wx.BOTH)
        return prefwindow

class helperThread(threading.Thread):
    def __init__(self, window, fileDesc):
        threading.Thread.__init__(self)
        self.window = window
        self.fd = fileDesc
        
    def run(self):
        self.stopFlag = 1
        linBuffer = np.array([])
        
        while(self.stopFlag):
                try:
                    data = os.read(self.fd,BUFFER_SIZE)
                except IOError:
                    raise Exception("Fatal Error: %s"%(c.comedi_perror('read')))
                n = len(data)/2 # 2 bytes per 'H'
                structFormat = `n`+'H'
                if DEBUG: print "=> got %d data point"%n
                linBuffer = np.append(linBuffer,struct.unpack(structFormat,data))
                n = len(linBuffer)
                if DEBUG: print "==>linBuffer is now [%d] element"%n
                #calc the number of points once the data is deinterlaced
                mulLength = n
                if n%2>0:
                    mulLength -= 1
                twoDArray = linBuffer[:mulLength].reshape((-1,2)).transpose().copy()
                if mulLength<>n:
                    linBuffer = linBuffer[mulLength:(n-mulLength)].copy()
                else:
                    linBuffer = np.array([])
                
                
                self.window.arrayLock.acquire()
                self.window.xarray = twoDArray[0]
                self.window.yarray = twoDArray[1]
                self.window.line.set_data(self.window.xarray, self.window.yarray)
                self.window.subplot.autoscale(True, 'both', False)
                self.window.arrayLock.release()
                evt = UpdateCanvasEvent()
                wx.PostEvent(self.window, evt)


class PreferencesFrame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, -1, title)

        #p = wx.Panel(self, -1)
        vertSizer = wx.BoxSizer(wx.VERTICAL)
        titleLabel = wx.StaticText(self, -1, "Comedi Configuration")
        okButton = wx.Button(self, ID_OKBUTTON, "Save Changes")
        wx.EVT_BUTTON(self, ID_OKBUTTON, self.PressOkButton)

        sizer = wx.GridSizer(6, 2, 2, 2) #rows, columns, hgap, vgap

        vertSizer.Add(titleLabel, 0, wx.ALIGN_CENTER | wx.ALL, 8)
        vertSizer.Add(sizer)
        vertSizer.Add(okButton, 0, wx.ALIGN_RIGHT | wx.ALL, 15)

        deviceNameLabel = wx.StaticText(self, -1, "Device: ")
        self.deviceNameTextBox = wx.TextCtrl(self, -1, "/dev/comedi0")
        sizer.Add(deviceNameLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.deviceNameTextBox)

        xInputLabel = wx.StaticText(self, -1, "X Input Channel: ")
        self.xInputBox = wx.Choice(self, -1, choices=['0', '1', '2', '3', '4', '5', '6', '7'])
        self.xInputBox.SetSelection(0)
        sizer.Add(xInputLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.xInputBox)

        xInputRangeLabel = wx.StaticText(self, -1, "X Input Range: ")
        self.xInputRangeBox = wx.Choice(self, -1, choices=['0', '1', '2', '3'])
        self.xInputRangeBox.SetSelection(1)
        sizer.Add(xInputRangeLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.xInputRangeBox)

        xInputTypeLabel = wx.StaticText(self, -1, "X Input Type: ")
        self.xInputTypeBox = wx.Choice(self, -1, choices=['AREF_GROUND', 'AREF_COMMON', 'AREF_DIFF'])
        self.xInputTypeBox.SetSelection(2)
        sizer.Add(xInputTypeLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.xInputTypeBox)

        yInputLabel = wx.StaticText(self, -1, "Y Input Channel: ")
        self.yInputBox = wx.Choice(self, -1, choices=['0', '1', '2', '3', '4', '5', '6', '7'])
        self.yInputBox.SetSelection(2)
        sizer.Add(yInputLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.yInputBox)

        yInputRangeLabel = wx.StaticText(self, -1, "Y Input Range: ")
        self.yInputRangeBox = wx.Choice(self, -1, choices=['0', '1', '2', '3'])
        self.yInputRangeBox.SetSelection(1)
        sizer.Add(yInputRangeLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.yInputRangeBox)

        yInputTypeLabel = wx.StaticText(self, -1, "Y Input Type: ")
        self.yInputTypeBox = wx.Choice(self, -1, choices=['AREF_GROUND', 'AREF_COMMON', 'AREF_DIFF'])
        self.yInputTypeBox.SetSelection(2)
        sizer.Add(yInputTypeLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.yInputTypeBox)

        inputPeriodLabel = wx.StaticText(self, -1, "Samples per second: ")
        self.inputPeriodBox = wx.TextCtrl(self, -1, "3000")
        sizer.Add(inputPeriodLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.inputPeriodBox)

        self.SetSizer(vertSizer)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        self.Fit()

    def PressOkButton(self, event):
        self.Close(1)

    def OnCloseWindow(self, event):
        self.MakeModal(False)
        self.Show(0)
        print self.getConfiguration()
        #self.Destroy()

    def getConfiguration(self):
        config = dict()
        config["device"] = self.deviceNameTextBox.GetValue()

        config["analog_period_ns"] = int(1e9 / float(self.inputPeriodBox.GetValue()))

        x_channel = int(self.xInputBox.GetStringSelection())
        x_range = int(self.xInputRangeBox.GetStringSelection())
        x_aref_string = self.xInputTypeBox.GetStringSelection()
        x_aref=0
        if x_aref_string=='AREF_GROUND':
            x_aref=c.AREF_GROUND
        elif x_aref_string=='AREF_COMMON':
            x_aref=c.AREF_COMMON
        elif x_aref_string=='AREF_DIFF':
            x_aref=c.AREF_DIFF
        else:
            x_aref=c.AREF_OTHER

        y_channel = int(self.yInputBox.GetStringSelection())
        y_range = int(self.yInputRangeBox.GetStringSelection())
        y_aref_string = self.yInputTypeBox.GetStringSelection()
        y_aref=0
        if y_aref_string=='AREF_GROUND':
            y_aref=c.AREF_GROUND
        elif y_aref_string=='AREF_COMMON':
            y_aref=c.AREF_COMMON
        elif y_aref_string=='AREF_DIFF':
            y_aref=c.AREF_DIFF
        else:
            y_aref=c.AREF_OTHER

        config["analog"] = [[x_channel, x_range, x_aref], [y_channel, y_range, y_aref]]
        return config

app = wx.PySimpleApp()
frame = MainWindow()
app.MainLoop()
