import matplotlib
from wx.lib.agw import floatspin
matplotlib.use("WXAgg")
matplotlib.interactive(False)

from PhysioMonitor.ReaderThread import ReaderThread
from PhysioMonitor.TrendPlot import TrendPlot, doGetBPM, doGetMAX, doGetAVG, doGetHR, doGetLastPeakValue #@UnusedImport
from PhysioMonitor.TriggerPlot import triggerTypes
from PhysioMonitor.Utils import convertInVolts, UpdateCanvasEvent, \
    EVT_UPDATE_CANVAS
from SurgicalLogClasses import * #@UnusedWildImport
from SurgicalLogValidators import * #@UnusedWildImport
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure
from platform import system
from sys import maxint
from wx import xrc
import comedi #@UnresolvedImport
import gst #@UnresolvedImport
import sys


DEBUG = False

###############################################################################
# GUI classes
###############################################################################
class customValueDlgClass(wx.Dialog):
    def __init__(self, parent, pos, value, id = -1, title = '', name = wx.DialogNameStr): #@ReservedAssignment
        """Constructor"""
        style = wx.CAPTION
        size = wx.Size(125, 20)
        
        #call the parent constructor
        wx.Dialog.__init__(self, parent, id, title, pos, wx.DefaultSize, style, name)
        
        #add spin box
        # self.customValueBox = wx.SpinCtrl(self, size = size)
        self.customValueBox = floatspin.FloatSpin(self, id=wx.ID_ANY,
            pos=wx.DefaultPosition, size=size, style=0, value=0.0,
            min_val=None, max_val=None, increment=0.1, digits=2,
#            extrastyle=floatspin.FS_RIGHT, name="FloatSpin")
            name="FloatSpin")
        # self.customValueBox.SetRange(0, maxint)
        self.customValueBox.SetValue(value)
        self.customValueBox.SetFocus()
        # self.customValueBox.SetSelection(0, maxint)
        
        self.Fit()
        
        #bind keyboard listener
        if system() == 'Windows':
            self.customValueBox.Bind(wx.EVT_KEY_UP, self.onChar)
        else:
            for child in self.customValueBox.GetChildren():
                child.Bind(wx.EVT_KEY_UP, self.onChar)
            self.customValueBox.Bind(wx.EVT_KEY_UP, self.onChar)
            self.Bind(wx.EVT_KEY_UP, self.onChar)
    
    def onChar(self, event):
        #print "in onChar"
        key = event.GetKeyCode()
        #print "got code %d"%key
        
        if key == wx.WXK_ESCAPE:
            #print "got ESCAPE, closing modal with return code %d"%wx.ID_CANCEL
            self.EndModal(wx.ID_CANCEL)
            return
        
        if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
            #print "got RETURN, closing modal with return code %d"%wx.ID_OK
            self.EndModal(wx.ID_OK)
            return
        
        #print "got uninteresting key, skipping"
        event.Skip()
    
class drugPanelClass():
    def __init__(self, parent, drugName, drugDose = 0.0, logBox = None):
        """Constructor"""
        # drug info
        self.drugName = drugName
        self.drugDose = drugDose
        self.customDose = 0.0

        self.ALARM_DURATION = 10
        self.__alarmPipeline = gst.Pipeline("mypipeline")
        self.__alarmSound = gst.element_factory_make("audiotestsrc", "audio")
        self.__alarmPipeline.add(self.__alarmSound)
        self.__alarmSink = gst.element_factory_make("alsasink", "__alarmSink")
        self.__alarmPipeline.add(self.__alarmSink)
        self.__alarmSound.link(self.__alarmSink)
        self.__alarmSound.set_property("freq", 500)
        
        #GUI windows
        self.logBox = logBox
        self.panel = xrc.XmlResource.Get().LoadPanel(parent, 'drugPanel')
        self.label = self.panel.FindWindowByName("drugNameLabel")
        self.drugTimerLabel = self.panel.FindWindowByName("drugTimerLabel")
        self.fullDoseButton = self.panel.FindWindowByName("fullDoseButton")
        self.halfDoseButton = self.panel.FindWindowByName("halfDoseButton")
        self.quartDoseButton = self.panel.FindWindowByName("quartDoseButton")
        self.customDoseButton = self.panel.FindWindowByName("customDoseButton")
        self.alarmsRadioBox = self.panel.FindWindowByName("drugAlarmsRadioBox")
        
        #timers
        self.drugTimer = wx.Timer(self.panel, -1)
        self.alarmTimer = wx.Timer(self.panel, -1)
        #event times
        self.drugTime = None
        self.alarmTime = None
        
        #other variables
        self.normalLabelForeground = self.drugTimerLabel.GetForegroundColour()
        self.alarmLabelForeground = "#FF0000"
        
        label = "%s (%d uL)" % (self.drugName, self.drugDose)
        self.setLabel(label)
        self.panel.Bind(wx.EVT_BUTTON, self.onFullDoseButton, id = self.fullDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onHalfDoseButton, id = self.halfDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onQuartDoseButton, id = self.quartDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onCustomDoseButton, id = self.customDoseButton.GetId())
        self.panel.Bind(wx.EVT_TIMER, self.onDrugTimer, id = self.drugTimer.GetId())
        self.panel.Bind(wx.EVT_TIMER, self.onAlarmTimer, id = self.alarmTimer.GetId())
        self.drugTimerLabel.Bind(wx.EVT_LEFT_DCLICK, self.onDrugTimerLabelClick)
        self.label.Bind(wx.EVT_LEFT_DCLICK, self.onLabelClick)
        self.alarmsRadioBox.Bind(wx.EVT_RADIOBOX, self.onAlarmChoose, id = self.alarmsRadioBox.GetId())


    def getDrugName(self):
        return self.drugName

    def getDrugDose(self):
        return self.drugDose

    def setDrugName(self, value):
        self.drugName = value

    def setDrugDose(self, value):
        self.drugDose = value
    
    def getPanel(self):
        return self.panel
        
    def setLabel(self, value):
        self.label.SetLabel(value)

    def onLabelClick(self, event):
        editDrugDlg = xrc.XmlResource.Get().LoadDialog(None, "drugOptionsDlg")
        drugNameBox = editDrugDlg.FindWindowByName("drugNameTextBox")
        drugDoseBox = editDrugDlg.FindWindowByName("drugDoseSpinCtrl")
        editDrugDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
        editDrugDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)
        drugNameBox.SetValue(self.drugName)
        drugDoseBox.SetValue(self.drugDose)
        if editDrugDlg.ShowModal() == wx.ID_OK:
            self.drugName = drugNameBox.GetValue()
            self.drugDose = drugDoseBox.GetValue()
            label = "%s (%d uL)" % (self.drugName, self.drugDose)
            self.setLabel(label)
        editDrugDlg.Destroy()
    
    def onFullDoseButton(self, event):
#        print "in drugPanelClass.onFullDoseButton()" #DEBUG
        self.printDose(self.drugDose)
        self.startDrugTimer()
        
    def onHalfDoseButton(self, event):
        self.printDose(self.drugDose / 2)
        self.startDrugTimer()
    
    def onQuartDoseButton(self, event):
        self.printDose(self.drugDose / 4)
        self.startDrugTimer()
    
    def onCustomDoseButton(self, event):
        buttonRect = event.GetEventObject().GetScreenRect()
        newPos = wx.Point()
        newPos.x = buttonRect.GetLeft() + buttonRect.GetWidth() / 2
        newPos.y = buttonRect.GetTop() + buttonRect.GetHeight() / 2
        customValueDlg = customValueDlgClass(None, newPos, self.customDose)
        if customValueDlg.ShowModal() == wx.ID_OK:
            self.customDose = customValueDlg.customValueBox.GetValue()
            self.printDose(self.customDose)
            self.startDrugTimer()  
        customValueDlg.Destroy()
    
    def startDrugTimer(self):
#        print "drugPanelClass.startDrugTimer()" #DEBUG
        self.drugTime = time.time()
        self.drugTimer.Start(1000) #clock event every 1sec
        #reset the alarm
        self.alarmTime = None
        self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
        
    
    def printDose(self, dose):
        message = time.strftime("%H:%M\t", time.localtime())
        message += "{:>6.2f} uL".format(dose)
        message += "\t%s\t\t| \n" % (self.drugName)
        
        if isinstance(self.logBox, wx.TextCtrl):
            if (not self.logBox.IsEmpty()) and self.logBox.GetValue()[-1] <> '\n':
                self.logBox.AppendText('\n')
            self.logBox.AppendText(message)
            
            #AppendText doesn't seem to send a wx.EVT_TEXT to logBox
            #generate one
            event = wx.PyEvent()
            event.SetEventType(wx.wxEVT_COMMAND_TEXT_UPDATED)
            self.logBox.GetEventHandler().ProcessEvent(event)
            
        else:
            print message
    
    def onDrugTimer(self, event):
        if self.drugTime is not None:
            timeDiff = time.time() - self.drugTime
            self.drugTimerLabel.SetLabel(time.strftime("%M:%S", time.gmtime(timeDiff)))
            if self.alarmsRadioBox.GetSelection() > 0:
                try:
                    alarmTimeInMin = int(self.alarmsRadioBox.GetStringSelection(), 10)
                    if timeDiff / 60 >= alarmTimeInMin and self.alarmTime is None:
                        #alerts the user that the timer has reached the time set by their alarm
                        self.drugTimerAlarm()
                        #maybe do something here
#                        print "time limit has been reached!" #DEBUG
                        self.alarmAction()
                except:
                    pass

    def drugTimerAlarm(self):
        self.alarmTime = time.time()
        self.alarmTimer.Start(500)
        
                    
    def onAlarmTimer(self, event):
        if (self.alarmTime is None): #alarm has been reset while the timer was running
            self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
            self.__alarmPipeline.set_state(gst.STATE_NULL)
            self.alarmTimer.Stop()
        elif (self.alarmTimer.IsRunning()) and (time.time() - self.alarmTime < self.ALARM_DURATION):
            if self.drugTimerLabel.GetForegroundColour() == self.normalLabelForeground:
                self.__alarmPipeline.set_state(gst.STATE_PLAYING)
                self.drugTimerLabel.SetForegroundColour(self.alarmLabelForeground)
            else:
                self.__alarmPipeline.set_state(gst.STATE_NULL)
                self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
        else:
            self.drugTimerLabel.SetForegroundColour(self.alarmLabelForeground)
            self.__alarmPipeline.set_state(gst.STATE_NULL)
            self.alarmTimer.Stop()

    def alarmAction(self):
        pass
    
    def onDrugTimerLabelClick(self, event):
        self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
        self.alarmTimer.Stop()
        self.alarmTime = None
        self.drugTimer.Stop()
        self.drugTimerLabel.SetLabel("00:00")
        event.Skip()
    
    def onAlarmChoose(self, event):
        if self.alarmsRadioBox.GetSelection() == 3: #custom button
            buttonRect = event.GetEventObject().GetScreenRect()
            newPos = wx.Point()
            newPos.x = buttonRect.GetLeft() + buttonRect.GetWidth() / 2
            newPos.y = buttonRect.GetTop() + buttonRect.GetHeight() / 2
            try:
                customValue = int(self.alarmsRadioBox.GetItemLabel(3), 10)
            except ValueError:
                customValue = 0;     
            customValueDlg = customValueDlgClass(None, newPos, customValue)
            if customValueDlg.ShowModal() == wx.ID_OK:
                self.alarmsRadioBox.SetItemLabel(3, str(customValueDlg.customValueBox.GetValue()))
            customValueDlg.Destroy()
        
        #reset the alarm timer
        self.alarmTimer.Stop()
        self.alarmTime = None
        self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
        event.Skip()
    
    _drugName = property(getDrugName, setDrugName, None, "The name of the drug. This is the text that will be written in the log file")
    _drugDose = property(getDrugDose, setDrugDose, None, "The dosage in units of weight of the drug")
    

class drugPumpPanelClass(drugPanelClass):
    def __init__(self, parent, drugName, drugDose, logBox, pumpInterface):
        """Constructor"""
        # @type pump syringePumpClass
        self.pump = pumpInterface
        
        # drug info
        self.drugName = drugName
        self.drugDose = drugDose
        self.customDose = 0.0
        self.INJECTION_RATE = 0.25
        self.INJECTION_UNITS = 1
        self.ALARM_DURATION = 10
    
        self.__alarmPipeline = gst.Pipeline("mypipeline")
        self.__alarmSound = gst.element_factory_make("audiotestsrc", "audio")
        self.__alarmPipeline.add(self.__alarmSound)
        self.__alarmSink = gst.element_factory_make("alsasink", "__alarmSink")
        self.__alarmPipeline.add(self.__alarmSink)
        self.__alarmSound.link(self.__alarmSink)
        self.__alarmSound.set_property("freq", 500)

        self.currDirection = None
        self.currRate = None
        self.currUnits = None

        #GUI windows
        self.logBox = logBox
        self.panel = xrc.XmlResource.Get().LoadPanel(parent, 'drugPumpPanel')
        self.label = self.panel.FindWindowByName("drugNameLabel")
        self.startPerfButton = self.panel.FindWindowByName("startPerfButton")
        self.perfRateSpinCtrl = self.panel.FindWindowByName("perfRateSpinCtrl")
        self.perfUnitChoice = self.panel.FindWindowByName("perfUnitChoice")
        self.perfAmountLabel = self.panel.FindWindowByName("perfAmountLabel")
        self.drugTimerLabel = self.panel.FindWindowByName("drugTimerLabel")
        self.fullDoseButton = self.panel.FindWindowByName("fullDoseButton")
        self.halfDoseButton = self.panel.FindWindowByName("halfDoseButton")
        self.quartDoseButton = self.panel.FindWindowByName("quartDoseButton")
        self.customDoseButton = self.panel.FindWindowByName("customDoseButton")
        self.alarmsRadioBox = self.panel.FindWindowByName("drugAlarmsRadioBox")
        self.autoInjectCheckBox = self.panel.FindWindowByName("autoInjectCheckBox")

        #this is a hack to replace the wxSpinCtrl with a FloatSpin, but I don't
        #know how to put the FloatSpin in the XRC, so I will destroy the SpinCtrl
        #create a FloatSpin and put it in its place.
        self.sizer = self.perfRateSpinCtrl.GetContainingSizer()
        self.perfRateSpinCtrl.Destroy()
        self.perfRateSpinCtrl = floatspin.FloatSpin(self.panel, id=wx.ID_ANY,
            pos=wx.DefaultPosition, size=(95, -1), style=0, value=0.0,
            min_val=None, max_val=None, increment=0.1, digits=2,
#            extrastyle=floatspin.FS_RIGHT, name="FloatSpin")
            name="FloatSpin")
        self.sizer.Add(self.perfRateSpinCtrl, (1,1))



        #timers
        self.drugTimer = wx.Timer(self.panel, -1)
        self.alarmTimer = wx.Timer(self.panel, -1)
        #event times
        self.drugTime = None
        self.alarmTime = None

        #other variables
        self.normalLabelForeground = self.drugTimerLabel.GetForegroundColour()
        self.alarmLabelForeground = "#FF0000"

        label = "%s (%d uL)" % (self.drugName, self.drugDose)
        self.setLabel(label)
        
        if self.pump is not None:
            self.perfUnitChoice.SetItems(self.pump.getPossibleUnits())

        #event binding
        self.panel.Bind(wx.EVT_TOGGLEBUTTON, self.onStartPerfButton, id = self.startPerfButton.GetId())
        self.panel.Bind(wx.EVT_SPINCTRL, self.onPerfRateChange, id = self.perfRateSpinCtrl.GetId())
        self.panel.Bind(wx.EVT_CHOICE, self.onPerfUnitChange, id = self.perfUnitChoice.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onFullDoseButton, id = self.fullDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onHalfDoseButton, id = self.halfDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onQuartDoseButton, id = self.quartDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onCustomDoseButton, id = self.customDoseButton.GetId())
        self.panel.Bind(wx.EVT_TIMER, self.onDrugTimer, id = self.drugTimer.GetId())
        self.panel.Bind(wx.EVT_TIMER, self.onAlarmTimer, id = self.alarmTimer.GetId())
        self.drugTimerLabel.Bind(wx.EVT_LEFT_DCLICK, self.onDrugTimerLabelClick)
        self.label.Bind(wx.EVT_LEFT_DCLICK, self.onLabelClick)
        self.alarmsRadioBox.Bind(wx.EVT_RADIOBOX, self.onAlarmChoose, id = self.alarmsRadioBox.GetId())

        self.updateGUI()

    def updateGUI(self):
        pass #FIXME
        if self.pump is not None:
            #fill in properties based on the state of the pump
            self.currDirection = 1
            self.pump.setDirection(self.currDirection)
            self.currRate = self.pump.getRate()
            self.currUnits = self.pump.getUnits()

            #update GUI elements in consequence
            if not self.pump.isRunning():
                self.startPerfButton.SetValue(False)
                self.perfRateSpinCtrl.Enable(True)
                self.perfUnitChoice.Enable(True)
            else:
                self.startPerfButton.SetValue(True)
                self.perfRateSpinCtrl.Enable(False)
                self.perfUnitChoice.Enable(False)

            self.perfRateSpinCtrl.SetValue(self.currRate)
            self.perfUnitChoice.SetSelection(self.currUnits)
 
        else:
            self.panel.Enable(False)

    def onStartPerfButton(self, event):
        if self.startPerfButton.GetValue(): #Value is TRUE when pressed
            rate = float(self.perfRateSpinCtrl.GetValue())
            unitsIndex = self.perfUnitChoice.GetSelection()
            if self.pump is not None:
                try:
                    self.pump.setRate(rate, unitsIndex)
                    self.pump.clearTargetVolume()

#                    print "setting NULL color in onStartPerfButton()" #DEBUG
                    self.perfRateSpinCtrl.SetBackgroundColour(wx.NullColour)
                    #self.perfRateSpinCtrl.SetForegroundColour(wx.NullColour)
                    
                    self.currRate = self.pump.getRate()
                    self.currUnits = self.pump.getUnits()
                    
                    self.pump.start()

                    #write in the log
                    message = time.strftime("%H:%M\t", time.localtime())
                    message += "Start perf %s [%.3f %s]\t\t|\n" % (self.drugName, self.currRate, self.pump._units[self.currUnits])
                    self.logBox.AppendText(message)
                    
                except Exception as e:
                    print e
                    wx.Bell()
                    
                    self.perfRateSpinCtrl.SetFocus()
#                    print "setting RED color in onStartPerfButton()" #FIXME-this does not work
                    self.perfRateSpinCtrl.SetBackgroundColour(wx.RED)
                    #self.perfRateSpinCtrl.SetForegroundColour(wx.RED)
                    
            else: #there is no pump
                #write in the log
                message = time.strftime("%H:%M\t", time.localtime())
                message += "Start perf %s [%.3f %s]\t\t|\n" % (self.drugName, rate, '') #units
                self.logBox.AppendText(message)

            #disable the rate and unit controls
            self.perfRateSpinCtrl.Enable(False)
            self.perfUnitChoice.Enable(False)

            
        else: #unclick button
            if self.pump is not None:
                if self.pump.isRunning(): self.pump.stop()

            #reactivate rate and unit buttons
            self.perfRateSpinCtrl.Enable(True)
            self.perfUnitChoice.Enable(True)

            #write in the log
            message = time.strftime("%H:%M\t", time.localtime())
            message += "Stop perf %s\t\t\t|\n" % (self.drugName)
            self.logBox.AppendText(message)

        #finally, update GUI
        self.updateGUI()
    
    def onAlarmTimer(self, event):
        #i have to overload this function because I'm not calling the parent constructor
        #and therefore the gst objects are created in this object and not the parent's
        if (self.alarmTime is None): #alarm has been reset while the timer was running
            self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
            self.__alarmPipeline.set_state(gst.STATE_NULL)
            self.alarmTimer.Stop()
        elif (self.alarmTimer.IsRunning()) and (time.time() - self.alarmTime < self.ALARM_DURATION):
            if self.drugTimerLabel.GetForegroundColour() == self.normalLabelForeground:
                self.__alarmPipeline.set_state(gst.STATE_PLAYING)
                self.drugTimerLabel.SetForegroundColour(self.alarmLabelForeground)
            else:
                self.__alarmPipeline.set_state(gst.STATE_NULL)
                self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
        else:
            self.drugTimerLabel.SetForegroundColour(self.alarmLabelForeground)
            self.__alarmPipeline.set_state(gst.STATE_NULL)
            self.alarmTimer.Stop()

    def onPerfRateChange(self, event):
        pass

    def onPerfUnitChange(self, event):
        pass

    def alarmAction(self):
#        print "in drugPumpPanelClass.alarmAction()" #DEBUG
        drugPanelClass.alarmAction(self)
        #if auto-inject check box is checked
        if self.autoInjectCheckBox.GetValue():
#            print "injecting full dose" #DEBUG
            time.sleep(1)
            self.onFullDoseButton(None)
        
    def onFullDoseButton(self, event):
#        print "in drugPumpPanelClass.onFullDoseButton()" #DEBUG
        drugPanelClass.onFullDoseButton(self, event)
        self.injectVolume(self.drugDose)

    def onHalfDoseButton(self, event):
        drugPanelClass.onHalfDoseButton(self, event)
        self.injectVolume(self.drugDose / 2)

    def onQuartDoseButton(self, event):
        drugPanelClass.onQuartDoseButton(self, event)
        self.injectVolume(self.drugDose / 4)

    def onCustomDoseButton(self, event):
        drugPanelClass.onCustomDoseButton(self, event)
        self.injectVolume(self.customDose)

    def injectVolume(self, injVolume):
        if self.pump is not None:
            if DEBUG: print "direction is currently %d" % self.currDirection
            oldDirection = self.pump.getDirection()
            if self.pump.isRunning(): self.pump.stop()

            self.pump.setRate(self.INJECTION_RATE, self.INJECTION_UNITS)
            tempVolume = injVolume / 1000.0
            self.pump.setTargetVolume(tempVolume)
            self.pump.start()

            #wait for the injection to finish
            while self.pump.isRunning():
#                    print "injection still going, waiting..." #DEBUG
                time.sleep(0.25)

            self.pump.clearTargetVolume()
            self.pump.setRate(self.currRate, self.currUnits)        

            if oldDirection > 0:
                if DEBUG: print "direction before injection was %d, so restarting the pump" % oldDirection
                self.pump.start()
            else:
                if self.pump.isRunning():
                    self.pump.stop()
            
            self.updateGUI()

class otherDrugPanelClass(drugPanelClass):
    #this class is used when injecting a customizable amount of an other drug
    #than those defined in drugList
    def __init__(self, parent, logBox):
        #call parent constructor
        drugPanelClass.__init__(self, parent, "", 0.0, logBox)

        #remove ununsed dose buttons
        self.halfDoseButton.Destroy()
        self.quartDoseButton.Destroy()
        self.customDoseButton.Destroy()
        self.setLabel("Other drug...")
        self.fullDoseButton.SetLabel("Inject bolus")

    def onFullDoseButton(self, event):
        customValueDlg = customDrugDoseDlgClass(None, self.drugName, self.drugDose)
        if customValueDlg.ShowModal() == wx.ID_OK:
            self.drugDose = customValueDlg.customValueBox.GetValue()
            self.drugName = customValueDlg.customNameBox.GetValue()
            self.printDose(self.drugDose)
            self.startDrugTimer()  
        customValueDlg.Destroy()

    def onLabelClick(self, event):
        #event when dbl click on the label
        # in this case, do nothing
        pass

class customDrugDoseDlgClass(customValueDlgClass):
    def __init__(self, parent, drugName, drugDose):
        wx.Dialog.__init__(self, parent)

        sizer1 = wx.BoxSizer(wx.VERTICAL)
        sizer2 = wx.BoxSizer(wx.HORIZONTAL)
        sizer3 = wx.BoxSizer(wx.HORIZONTAL)

        label = wx.StaticText(self, id = -1, label = "Inject custom drug")

        self.customValueBox = wx.SpinCtrl(self, id = -1)
        self.customValueBox.SetRange(0, maxint)
        self.customValueBox.SetValue(drugDose)
        self.customValueBox.SetFocus()
        self.customValueBox.SetSelection(0, maxint)

        self.customNameBox = wx.TextCtrl(self, id = -1, value = drugName, size = (400, -1))

        self.okButton = wx.Button(self, id = wx.ID_OK, label = "OK")

        self.cancelButton = wx.Button(self, id = wx.ID_CANCEL, label = "Cancel")

        sizer2.Add(self.customValueBox, proportion = 0)
        sizer2.Add(self.customNameBox, proportion = 1, flag = wx.EXPAND)

        sizer3.Add(self.cancelButton)
        sizer3.Add(self.okButton)

        sizer1.Add(label, flag = wx.EXPAND | wx.ALIGN_LEFT | wx.BOTTOM | wx.TOP, border = 10)
        sizer1.Add(sizer2, flag = wx.EXPAND | wx.BOTTOM, border = 10)
        sizer1.Add(sizer3, flag = wx.ALIGN_RIGHT | wx.BOTTOM, border = 10)

        self.SetSizer(sizer1)
        self.Center()
        self.Fit()

        self.Bind(wx.EVT_KEY_UP, self.onChar)
        for child in self.GetChildren():
            child.Bind(wx.EVT_KEY_UP, self.onChar)
            for child in self.GetChildren():
                child.Bind(wx.EVT_KEY_UP, self.onChar)

        
class clockPanelClass():
    def __init__(self, parent, timeFormat = "%H:%M", dateFormat = "%b %d %Y", refreshPeriod = 500):
        self.timeFormat = timeFormat
        self.dateFormat = dateFormat
        self.refreshPeriod = refreshPeriod
        self.panel = xrc.XmlResource.Get().LoadPanel(parent, 'clockPanel')
        self.curTimeLabel = self.panel.FindWindowByName('curTimeLabel')
        self.curDateLabel = self.panel.FindWindowByName('curDateLabel')
        self.clockTimer = wx.Timer(self.panel, -1)
        self.clockTimer.Start(refreshPeriod)
        self.panel.Bind(wx.EVT_TIMER, self.onTimer)

    def getPanel(self):
        return self.panel

    def getCurTime(self):
        return self.curTime

    def getCurDate(self):
        return self.curDate

    def getTimeFormat(self):
        return self.timeFormat

    def getDateFormat(self):
        return self.dateFormat

    def getRefreshPeriod(self):
        return self.refreshPeriod

    def setTimeFormat(self, value):
        self.timeFormat = value

    def setDateFormat(self, value):
        self.dateFormat = value

    def setRefreshPeriod(self, value):
        self.refreshPeriod = value
        if self.clockTimer.IsRunning():
            self.clockTimer.Stop()
        self.clockTimer.Start(value)
    
    def onTimer(self, event):
        currentTimeDate = time.localtime()
        self.curTimeLabel.SetLabel(time.strftime(self.getTimeFormat(), currentTimeDate))
        self.curDateLabel.SetLabel(time.strftime(self.getDateFormat(), currentTimeDate))
        
                                   
    _timeFormat = property(getTimeFormat, setTimeFormat, None, "The format to output the time, in strftime format")
    _dateFormat = property(getDateFormat, setDateFormat, None, "The format to output the date, in strftime format")
    _refreshPeriod = property(getRefreshPeriod, setRefreshPeriod, None, "The refresh period of the clock, in ms")
    
class mainFrameClass(wx.Frame):
    def __init__(self, exp = expClass(), info = None, config = surgicalLogConfigClass(), pCO2Monitor = None, syringePump = None, aiConfig = None):
        wx.Frame.__init__(self, parent = None, id = -1, title = "Surgical Log", size = (800, 600), style = wx.DEFAULT_FRAME_STYLE)
        
        self.previousComment = "";
        self.exp = exp
        self.config = config
        self.pCO2Monitor = pCO2Monitor
        self.pump = syringePump
        self.aiConfig = aiConfig
        self.appInfo = info
        self.diagFile = None
        self.plots = []
        self.maxChanValues = []
        self.chanRanges = []
        self.arrayLock = threading.Semaphore(1)
        self.canvasLock = threading.Semaphore(1)
        self.helper = None
        self.dev = None
        self.subdev = None
        
        
        
        
        
        self.diagTimer = wx.Timer(self, -1)
        self.Bind(wx.EVT_TIMER, self.onDiagTimer, id = self.diagTimer.GetId())
        
        self.leftPanel = wx.Panel(self, id = -1)
        self.logBox = wx.TextCtrl(self, id = -1, style = wx.TE_MULTILINE)
        self.logBox.Hide()
#        font = self.logBox.GetFont()
#        if system()=='Darwin':
#            font.SetFaceName('Monaco')
#        if system()=='Windows':
#                font.SetFaceName('Courier New')
#        self.logBox.SetFont(font)
        
        clockPanel = clockPanelClass(self.leftPanel)
        self.newExpButton = wx.Button(self.leftPanel, -1, "Start new experiment")
        
        self.hbox = wx.BoxSizer(wx.HORIZONTAL) #sizer for left and right part
        self.vbox = wx.BoxSizer(wx.VERTICAL) #stack for clock and new exp button
        self.vbox2 = wx.BoxSizer(wx.VERTICAL) #stack for drugPanels
        self.vbox3 = wx.BoxSizer(wx.VERTICAL)#sizer for top button and canvas
        self.hbox2 = wx.BoxSizer(wx.HORIZONTAL) # sizer for the button on top
        
        #create matplotlib figure to put all the plots in
        self.figure = Figure()
        self.canvas = FigureCanvasWxAgg(self, -1, self.figure)
        
        #adjust color FIXME: this does not work as expected
        rgbtuple = clockPanel.panel.GetBackgroundColour().Get()
        clr = [c / 255. for c in rgbtuple]
        self.figure.set_facecolor(clr)
        self.figure.set_edgecolor(clr)
        self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))
        
#        self.figure.set_facecolor( 'w' )
#        self.figure.set_edgecolor( 'w' )
#        self.canvas.SetBackgroundColour( wx.Colour(255,255,255) )

        #if DEBUG: print '### Creating plots'
        PLOT_MARGIN = 0.05
        PLOT_TOTAL_WIDTH = 1.0
        PLOT_TOTAL_HEIGHT = 1.0
        N = int(self.aiConfig['nbChans'])
        #if DEBUG: print "there are %d plot to fit on a 1.0 height"%(self.aiConfig['nbChans'])
        xLow = PLOT_MARGIN * PLOT_TOTAL_WIDTH
        width = PLOT_TOTAL_WIDTH - 2 * PLOT_TOTAL_WIDTH * PLOT_MARGIN
        height = (PLOT_TOTAL_HEIGHT - (N + 1) * PLOT_MARGIN * PLOT_TOTAL_HEIGHT) / N
        yLow = (1.0 - PLOT_MARGIN) * PLOT_TOTAL_HEIGHT - height
        for i in range(int(self.aiConfig['nbChans'])): #@UnusedVariable
            #if DEBUG: print "plot [%d] rect is"%i,(xLow,yLow,width,height)
            plot = TrendPlot(self.figure, (xLow, yLow, width, height))
            self.plots.append(plot)
            self.figure.add_axes(plot)
            yLow -= height + 1 * PLOT_MARGIN * PLOT_TOTAL_HEIGHT

        #if DEBUG: print '### Done creating plots'
        
        
        ### Configure the different plots
        ### TODO: This should be pulled from a config dialog somewhere

        #plot 0: heart rate
        #if DEBUG: print '### adjusting plot 0 parameters [%s]'%self.plots[0]
        self.plots[0].autoscale(enable = True, axis = 'y')
        self.plots[0].timeWidth = 1.0
        #self.plots[0].set_ylim(-1,1) #should have not effect because autoscale on
        self.plots[0].triggerMode = triggerTypes.RISINGTHRESHOLD
        self.plots[0].autoDefineTrigger = True
        self.plots[0].set_title('EKG')
        self.plots[0].units = 'V'
        self.plots[0].scaling = 1.0
        self.plots[0].offset = 0.0
        self.plots[0].set_ylabel('%s (%s)' % (self.plots[0].get_title(), self.plots[0].units))
        self.plots[0].linecolor = 'b'
        self.plots[0].measurementFunc = doGetHR
        self.plots[0].measurementParameters = \
            (self.aiConfig['sampleFreq'],
             None,
             0.05)#refract period 50ms
        self.plots[0].measurementUnits = ''
        self.plots[0].measurePeriod = 20.0 #integrate measurement over 20sec
        self.plots[0].set_trend_ylim(250.0, 650.0)
        self.plots[0].enableAlarm()
        self.plots[0].setAlarmHigh(600.0)
        self.plots[0].setAlarmLow(300.0)
        self.plots[0].setAlarmPitch(2000)
        
        #plot 1: BP
        self.plots[1].autoscale(enable = True, axis = 'y')
        self.plots[1].timeWidth = 1.0
        #self.plots[1].set_ylim(-1,1) #should have not effect because autoscale on
        self.plots[1].triggerMode = triggerTypes.RISINGTHRESHOLD
        self.plots[1].autoDefineTrigger = True
        self.plots[1].set_title('Blood Pressure')
        self.plots[1].units = ''
        self.plots[1].scaling = 1.0
        self.plots[1].offset = 0.0
        self.plots[1].set_ylabel('%s (%s)' % (self.plots[1].get_title(), self.plots[1].units))
        self.plots[1].linecolor = 'b'
        self.plots[1].measurementFunc = doGetAVG
        self.plots[1].measurementParameters = ()
        self.plots[1].measurementUnits = ''
        self.plots[1].measurePeriod = 20.0 #integrate measurement over 20sec
        self.plots[1].disableAlarm()
#        self.plots[1].enableAlarm()
#        self.plots[1].setAlarmHigh(600.0)
#        self.plots[1].setAlarmLow(300.0)
#        self.plots[1].setAlarmPitch(2000)
        
        #plot 2: lung pressure
        #if DEBUG: print '### adjusting plot 1 parameters [%s]'%self.plots[2]
        self.plots[2].set_autoscale_on(False)
        self.plots[2].timeWidth = 15.0
        self.plots[2].remanence = 1
        self.plots[2].set_ylim(0.0, 20.0)
        self.plots[2].triggerMode = triggerTypes.AUTO
        self.plots[2].autoDefineTrigger = True
        self.plots[2].set_title('Lung Pressure')
        self.plots[2].units = 'cmH20'
        self.plots[2].scaling = 20.0
        self.plots[2].offset = 0.0
        self.plots[2].set_ylabel('%s (%s)' % (self.plots[2].get_title(), self.plots[2].units))        
        self.plots[2].linecolor = 'g'
        self.plots[2].measurementFunc = doGetLastPeakValue
        self.plots[2].measurementParameters = (5.0, -1000)
        self.plots[2].measurementUnits = self.plots[2].units
        self.plots[2].measurePeriod = 20.0 #integrate measurement over 20sec
        self.plots[2].set_trend_ylim(0.0, 20.0)
        
        #plot 3: pCO2
        #if DEBUG: print '### adjusting plot 2 parameters [%s]'%self.plots[3]
        self.plots[3].set_autoscale_on(False)
        self.plots[3].timeWidth = 15.0
        self.plots[3].remanence = 1
        self.plots[3].set_ylim(0.0, 6.0)
        self.plots[3].triggerMode = triggerTypes.AUTO
        self.plots[3].autoDefineTrigger = True
        self.plots[3].set_title('pCO2')
        self.plots[3].units = '%'
        #scaling and offset measured 20131008 by MM
        # precedently were 1.0/0.0
        self.plots[3].scaling = 1.065515
        self.plots[3].offset = -0.0199011
        self.plots[3].set_ylabel('%s (%s)' % (self.plots[3].get_title(), self.plots[3].units))
        self.plots[3].linecolor = 'g'
        self.plots[3].measurementFunc = doGetLastPeakValue
        self.plots[3].measurementParameters = (0.3, -1000)
        self.plots[3].measurementUnits = self.plots[3].units
        self.plots[3].measurePeriod = 20.0 #integrate measurement over 20sec
        self.plots[3].set_trend_ylim(0.0, 6.0)
        self.plots[3].enableAlarm()
        self.plots[3].setAlarmHigh(5.5)
        self.plots[3].setAlarmLow(3.0)
        self.plots[3].setAlarmPitch(1500)
        
        #plot 4: Temperature
        #if DEBUG: print '### adjusting plot 3 parameters [%s]'%self.plots[4]
        self.plots[4].set_autoscale_on(False)
        self.plots[4].timeWidth = 1.0
        self.plots[4].remanence = 1
        self.plots[4].set_ylim(36, 39)
        self.plots[4].triggerMode = triggerTypes.AUTO
        self.plots[4].set_title('Temperature')
        self.plots[4].units = 'C'
        self.plots[4].scaling = 29.9
        self.plots[4].offset = 6.02
        self.plots[4].set_ylabel('%s (%s)' % (self.plots[4].get_title(), self.plots[4].units))
        self.plots[4].linecolor = 'c'
        self.plots[4].measurementFunc = doGetAVG
        self.plots[4].measurementParameters = ()
        self.plots[4].measurementUnits = self.plots[4].units
        self.plots[4].measurePeriod = 20.0
        self.plots[4].set_trend_ylim(36.0, 39.0)
        self.plots[4].enableAlarm()
        self.plots[4].setAlarmHigh(37.5)
        self.plots[4].setAlarmLow(36.5)
        self.plots[4].setAlarmPitch(1000)
        
#        if DEBUG:
#            print '### Done adjusting parameters'
#            for plot in self.plots:
#                print "plot %s:"%plot
#                print "    autoscale: %s"%plot.get_autoscaley_on()
#                print "    linecolor: %s"%plot.linecolor
        
        
        #self.openDiagFileButton = wx.Button(self, -1, "Diagnostic file...")
        #self.openDiagFileButton.Disable();
        #self.diagFileLabel = wx.StaticText(self, -1)
        self.startButton = wx.Button(self, -1, "Start")
        self.stopButton = wx.Button(self, -1, "Stop")
        self.resetAlarmsButton = wx.Button(self,-1,"Reset All Alarms")
        self.startButton.Enable(False)
        self.stopButton.Enable(False)
        
        self.hbox2.Add(self.startButton, 1, flag = wx.EXPAND | wx.ALIGN_BOTTOM | wx.TOP, border = 10)
        self.hbox2.Add(self.stopButton, 1, flag = wx.EXPAND | wx.ALIGN_BOTTOM | wx.TOP, border = 10)
        self.hbox2.Add(self.resetAlarmsButton, 1, flag = wx.EXPAND | wx.ALIGN_BOTTOM | wx.TOP, border = 10)
        
        self.vbox3.Add(self.hbox2)
        #self.vbox3.Add(self.logBox,1,wx.EXPAND|wx.TOP, border=10)
        self.vbox3.Add(self.canvas, 1, wx.EXPAND)
        
        self.vbox.Add(clockPanel.getPanel(), 0, wx.EXPAND)
        self.vbox.Add(self.newExpButton, 0, flag = wx.EXPAND | wx.TOP | wx.BOTTOM, border = 10)

        self.createLeftPanel(False)

        self.vbox.Add(self.vbox2)
        self.leftPanel.SetSizer(self.vbox)

        self.hbox.Add(self.leftPanel, 0, flag = wx.EXPAND | wx.ALL, border = 10)
        self.hbox.Add(self.vbox3, 1, wx.EXPAND)
        
        self.SetSizer(self.hbox)
        self.Centre()
        
        # Menu Bar
        mainMenuBar = wx.MenuBar()
        fileMenu = wx.Menu()
        editMenu = wx.Menu()
        fileMenu.Append(id = wx.ID_SAVEAS)
        fileMenu.Append(id = wx.ID_ABOUT)
        fileMenu.AppendSeparator()
        fileMenu.Append(id = wx.ID_EXIT)
        editMenu.Append(id = wx.ID_PREFERENCES)
        mainMenuBar.Append(fileMenu, "&File")
        mainMenuBar.Append(editMenu, "&Edit")
        self.SetMenuBar(mainMenuBar)
        
        
        self.Bind(wx.EVT_MENU, self.onQuitMenu, id = wx.ID_EXIT)
        self.Bind(wx.EVT_MENU, self.onAboutMenu, id = wx.ID_ABOUT)
        self.Bind(wx.EVT_MENU, self.onPreferencesMenu, id = wx.ID_PREFERENCES)
        self.Bind(wx.EVT_MENU, self.onSaveAsMenu, id = wx.ID_SAVEAS)
        self.Bind(wx.EVT_CLOSE, self.onClose)
        self.Bind(wx.EVT_BUTTON, self.onNewExpButton, None, self.newExpButton.GetId())
        self.Bind(wx.EVT_BUTTON, self.onStartButton, None, id = self.startButton.GetId())
        self.Bind(wx.EVT_BUTTON, self.onStopButton, None, id = self.stopButton.GetId())
        self.Bind(wx.EVT_BUTTON, self.onResetAlarmsButton, None, id = self.resetAlarmsButton.GetId())
        self.logBox.Bind(wx.EVT_TEXT, self.onUpdateLog)
        #Bind the update canvas event to some function:
        self.Bind(EVT_UPDATE_CANVAS, self.onUpdateCanvas)

        #start PCO2Monitor thread
        if self.pCO2Monitor is not None:
            self.pCO2Monitor.start()
    
    def onStartButton(self, event):
        #reset plot
        for plot in self.plots:
            plot.reset()
        
        if self.aiConfig is None:
            print "ai device not configured, doing nothing"
            return
        
        if DEBUG: print "Start button has been pressed"
        self.startButton.Enable(0)
        
        #configure the comedi device
        self.dev = comedi.comedi_open(str(self.aiConfig['device']))
        if not self.dev:
            print "FATAL ERROR: cannot open comedi device %s: %s" % \
                (self.aiConfig['device'], comedi.comedi_strerror(comedi.comedi_errno()))
            sys.exit(1)
        #and get an appropriate subdevice
        self.subdev = self.aiConfig['subDevice']

        #get a file-descriptor for reading
        self.fd = comedi.comedi_fileno(self.dev)
        if self.fd <= 0: raise Exception("Error obtaining Comedi device file descriptor: %s" % (comedi.comedi_strerror(comedi.comedi_errno())))
        
        #create channel list
        myChanList = comedi.chanlist(int(self.aiConfig['nbChans']))
        for i in range(int(self.aiConfig['nbChans'])):
            myChanList[i] = comedi.cr_pack(self.aiConfig['channels'][i],
                                           self.aiConfig['gains'][i],
                                           self.aiConfig['refs'][i])
        #create a command structure
        cmd = comedi.comedi_cmd_struct()
        ret = comedi.comedi_get_cmd_generic_timed(self.dev,
                                                  self.subdev,
                                                  cmd,
                                                  int(self.aiConfig['nbChans']),
                                                  int(1.e9 / self.aiConfig['sampleFreq']))
        if ret: raise Exception("Error comedi_get_cmd_generic failed")
        cmd.chanlist = myChanList # adjust for our particular context
        cmd.chanlist_len = int(self.aiConfig['nbChans'])
        cmd.scan_end_arg = int(self.aiConfig['nbChans'])
        cmd.stop_src = comedi.TRIG_NONE #never stop
        
        #test our comedi command a few times.
        ret = 0
        for i in range(2):
            ret = comedi.comedi_command_test(self.dev, cmd)
            if ret < 0: raise Exception("comedi_command_test failed: %s" % (comedi.comedi_strerror(comedi.comedi_errno())))

        #Start the command
        ret = comedi.comedi_command(self.dev, cmd)
        if ret <> 0: raise Exception("comedi_command failed... %s" % (comedi.comedi_strerror(comedi.comedi_errno())))
        
        for chanNum, chanGain in zip(self.aiConfig['channels'], self.aiConfig['gains']):
            self.maxChanValues.append(comedi.comedi_get_maxdata(self.dev, self.subdev, chanNum))
            self.chanRanges.append(comedi.comedi_get_range(self.dev, self.subdev, chanNum, chanGain))
            
        #start reader thread
        if DEBUG: print "creating reader thread"
        self.helper = ReaderThread(self, self.fd, int(self.aiConfig['nbChans']))        
        self.helper.start()
        
        self.stopButton.Enable(1)
        if DEBUG: print "Start button complete."

    def onStopButton(self, event):
        self.stopButton.Enable(0)
        if self.helper is not None:
            self.helper.reading = False
            self.helper.join()
            if DEBUG: print "Background thread stopped."
            self.helper = None
        if self.dev is not None and self.subdev is not None:
            comedi.comedi_cancel(self.dev, self.subdev)
            
        #make sure the alarms are stopped as well
        for plot in self.plots:
            plot.resetAlarm()
        
        self.startButton.Enable(1)

    def onResetAlarmsButton(self, event):
        if DEBUG: print "Resetting all alarms..."
        for plot in self.plots:
            plot.resetAlarm()

    def onClearButton(self, event):
        self.arrayLock.acquire()
        self.currPos = 0
        self.trigPlot1.cla()
        self.trigPlot2.cla()
        self.arrayLock.release()
        evt = UpdateCanvasEvent()
        wx.PostEvent(self, evt)

    def initialize(self):
        self.onClearButton(None)
    
    def processNewData(self, inData):
        self.arrayLock.acquire()
        inData = convertInVolts(inData, self.maxChanValues, self.chanRanges)
        for plot, i in zip(self.plots, range(self.aiConfig['nbChans'])):
            plot.processNewData(inData[i, :])
            plot.relim()
        self.arrayLock.release()

    def onUpdateCanvas(self, evt):
        #if DEBUG: print "Updating the canvas..."
        self.canvasLock.acquire()
        self.canvas.draw()
        self.canvasLock.release()

    def createLeftPanel(self, enabled):
        # @type pumpDrug drugClass
        if len(self.exp.drugList) > 0:
            pumpDrug = self.exp.drugList[0]
        else:
            pumpDrug = drugClass("dummy", 0)
        self.drugPumpPanel = drugPumpPanelClass(self.leftPanel, pumpDrug.name, pumpDrug.dose, self.logBox, self.pump)
        self.drugPumpPanel.getPanel().Enable(enabled)
        self.vbox2.Add(self.drugPumpPanel.getPanel(), 0, flag = wx.BOTTOM, border = 10)

        self.drugPanels = []
        for drug in self.exp.drugList[1:]:
            drugPanel = drugPanelClass(self.leftPanel, drug.name, drug.dose, self.logBox)
            drugPanel.getPanel().Enable(enabled)
            self.drugPanels.append(drugPanel)
            self.vbox2.Add(drugPanel.getPanel(), 0, flag = wx.BOTTOM, border = 10)

        self.otherDrugPanel = otherDrugPanelClass(self.leftPanel, self.logBox)
        self.otherDrugPanel.getPanel().Enable(enabled)
        self.vbox2.Add(self.otherDrugPanel.getPanel(), 0, flag = wx.BOTTOM, border = 10)

        self.addCommentButton = wx.Button(self.leftPanel, -1, "Add comment...")
        self.addCommentButton.Enable(enabled)
        self.vbox2.Add(self.addCommentButton, 0, flag = wx.EXPAND)

        self.Bind(wx.EVT_BUTTON, self.onCommentButton, id = self.addCommentButton.GetId())

    def onClose(self, event):
        if self.pCO2Monitor is not None:
            self.pCO2Monitor.stop()
        self.onStopButton(event)
        if self.dev is not None:
            comedi.comedi_close(self.dev)
        self.Destroy()
      
    def onQuitMenu(self, event):
        self.Close()

    def onCommentButton(self, event):
        tempComment = wx.GetTextFromUser("Enter a comment:", "Enter a comment", self.previousComment)
        # @type tempComment str
        if len(tempComment) > 0:
            self.previousComment = tempComment
            message = time.strftime("%H:%M\t", time.localtime())
            message += "%s\t\t| \n" % (tempComment)

            if isinstance(self.logBox, wx.TextCtrl):
                self.logBox.AppendText(message)
                #AppendText doesn't seem to send a wx.EVT_TEXT to logBox
                #generate one
                _ = wx.PyEvent()
            else:
                print tempComment

    
    def onSaveAsMenu(self, event):
        fileSaveDlg = wx.FileDialog(None, "Choose file name", wildcard = "*.txt", style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
        if fileSaveDlg.ShowModal() == wx.ID_OK:
            filePath = fileSaveDlg.GetPath();
            if not self.logBox.SaveFile(filePath):
                errorDlg = wx.MessageDialog(None, "Error when trying to save file", style = wx.OK | wx.ICON_ERROR);
                errorDlg.ShowModal()
                errorDlg.Destroy()
        fileSaveDlg.Destroy()
    
    def onLoadDiagFile(self, event):
        openDiagFileDlg = wx.FileDialog(None, "Open diagnostic file")
        if openDiagFileDlg.ShowModal() == wx.ID_OK:
            self.diagFileName = openDiagFileDlg.GetPath();
            try:
                self.diagFile = open(self.diagFileName, 'r')
                self.diagFileLabel.SetLabel(self.diagFileName)
                print "creating diagLogClass()" #DEBUG
                self.diagLog = diagLogClass(self.diagFile)
                self.diagTimer.Start(60000)  # 1min
                self.onDiagTimer(None)
                
                self.openDiagFileButton.SetLabel("Stop parsing file")
                self.Unbind(wx.EVT_BUTTON, id = self.openDiagFileButton.GetId())
                self.Bind(wx.EVT_BUTTON, self.onStopParseDiagFile, id = self.openDiagFileButton.GetId())
            except Exception as ex:
                print ex #DEBUG
                self.diagFile = None
        openDiagFileDlg.Destroy()
    
    def onStopParseDiagFile(self, event):
        self.diagTimer.Stop()
        self.diagFile = None
        self.Unbind(wx.EVT_BUTTON, id = self.openDiagFileButton.GetId())
        self.openDiagFileButton.SetLabel("Diagnostic file...")
        self.diagFileLabel.SetLabel("")
        self.Bind(wx.EVT_BUTTON, self.onLoadDiagFile, id = self.openDiagFileButton.GetId())
    
    def onDiagTimer(self, event):       
        print "%s - in mainFrameClass.onDiagTimer()" % time.strftime("%H:%M:%S") #DEBUG

        #FIXME - this does not work very well
        if (not self.logBox.IsEmpty()) and self.logBox.GetValue()[-1] <> '\n':
            self.logBox.AppendText('\n')
        oxymeterData = self.diagLog.printNext()
        if len(oxymeterData) > 0:
            self.logBox.AppendText(oxymeterData)
            if self.pCO2Monitor is not None:
                #remove last carriage return to print CO2 monitor on same line
                self.logBox.Remove(self.logBox.GetInsertionPoint() - 2, self.logBox.GetInsertionPoint())
                avgCO2, avgBR = self.pCO2Monitor.getMeanValues()
                self.logBox.AppendText("%1.2f %% | %3.0f bpm | \n" % (avgCO2, avgBR))
    
    def onAboutMenu(self, event):
        wx.AboutBox(self.appInfo)
        
    def onNewExpButton(self, event):
        newExpDlg = xrc.XmlResource.Get().LoadDialog(self, "newExpDlg")
        newExpDlg.Fit()
        newExpDlg.Refresh()
        
        #find controls
        self.expDateBox = newExpDlg.FindWindowByName("expDateBox")
        self.expCommentsBox = newExpDlg.FindWindowByName("expCommentsBox")
        self.expDrugListBox = newExpDlg.FindWindowByName("expDrugListBox")
        
        self.mouseTypeBox = newExpDlg.FindWindowByName("mouseTypeBox")
        self.mouseSexBox = newExpDlg.FindWindowByName("mouseSexBox")
        self.mouseDoBBox = newExpDlg.FindWindowByName("mouseDoBBox")
        self.mouseWeightBox = newExpDlg.FindWindowByName("mouseWeightBox")
        
        self.addDrugButton = newExpDlg.FindWindowByName("addDrugButton")
        self.delDrugButton = newExpDlg.FindWindowByName("delDrugButton")
        self.editDrugButton = newExpDlg.FindWindowByName("editDrugButton")
        
        self.mouseTypeBox.Clear()
        self.mouseTypeBox.AppendItems(self.exp.mouseTypes)
        
        font = self.expDrugListBox.GetFont()
        if system() == 'Darwin':
            font.SetFaceName('Monaco')
        if system() == 'Windows':
                font.SetFaceName('Courier New')
        self.expDrugListBox.SetFont(font)
        
        #Bind buttons
        newExpDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
        newExpDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)
        newExpDlg.Bind(wx.EVT_BUTTON, self.onAddDrug, id = self.addDrugButton.GetId())
        newExpDlg.Bind(wx.EVT_BUTTON, self.onDelDrug, id = self.delDrugButton.GetId())
        newExpDlg.Bind(wx.EVT_BUTTON, self.onEditDrug, id = self.editDrugButton.GetId())
        self.expDrugListBox.Bind(wx.EVT_LEFT_DCLICK, self.onEditDrug)
        
        #load validator in controls
        self.expDateBox.SetValidator(ObjectAttrDateValidator(self.exp, "date", flRequired = True))
        self.expCommentsBox.SetValidator(ObjectAttrTextValidator(self.exp, "comments", flRequired = False))
        self.expDrugListBox.SetValidator(drugListValidator(self.exp, "drugList", flRequired = True))
        
        self.mouseTypeBox.SetValidator(ObjectAttrBellTextValidator(self.exp.mouse, "type", flRequired = True))
        self.mouseSexBox.SetValidator(ObjectAttrRadioBoxValidator(self.exp.mouse, "sex", formatter = mouseSexFormater()))
        self.mouseDoBBox.SetValidator(ObjectAttrDateValidator(self.exp.mouse, "dob", flRequired = False))
        self.mouseWeightBox.SetValidator(ObjectAttrSpinValidator(self.exp.mouse, "weight"))
        
        if newExpDlg.ShowModal() == wx.ID_OK:
            self.vbox2.Clear(True)
            self.createLeftPanel(True)

            self.startButton.Enable(True)
            self.leftPanel.SetSizerAndFit(self.vbox)
            self.leftPanel.Layout()
            self.Refresh()

            #keep mouseTypes
            try:
                self.exp.mouseTypes.remove(self.mouseTypeBox.GetValue())
            except ValueError:
                #mouse type not in the list
                pass
            self.exp.mouseTypes.insert(0, self.mouseTypeBox.GetValue())

            #FIXME check if value already exist in array

            
            #create record directory if needed
            if self.config.createFolder():
                folder = path.join(self.config.getBaseFolder(), self.exp.date.Format("%Y_%m_%d"))
                if not path.exists(folder):
                    os.makedirs(folder)
            
            #create registry entry if needed
            if self.config.updateReg():
                reg = wx.Config("Spike2", "CED")
                reg.SetPath("/Perso")
                reg.Write("recordPath", self.config.getBaseFolder())
                reg.Write("date", self.exp.date.Format("%Y_%m_%d"))
                reg.Flush()
                del reg
            
            # check if logFile already exists, and load content
            fileName = path.join(self.config.getBaseFolder(), self.exp.date.Format("%Y_%m_%d"), self.config.getLogFileName())
            if path.exists(fileName):
                self.logBox.LoadFile(fileName)
                self.logBox.AppendText('\n')
                self.logBox.AppendText('\n')
                self.logBox.AppendText('#' * 80)
                self.logBox.AppendText('\n')
                self.logBox.AppendText('\n')
            
            #write in text box
            if (not self.logBox.IsEmpty()) and self.logBox.GetValue()[-1] <> '\n':
                self.logBox.AppendText('\n')
            self.logBox.AppendText(self.exp.writeHeader())
            self.logBox.AppendText("\n")
            self.logBox.AppendText("\n")
            self.onUpdateLog(wx.PyEvent())
            
            self.startLogPos = self.logBox.GetInsertionPoint()
        
        newExpDlg.Destroy()
        
    def onAddDrug(self, event):
        editDrugDlg = xrc.XmlResource.Get().LoadDialog(None, "drugOptionsDlg")
        drugNameBox = editDrugDlg.FindWindowByName("drugNameTextBox")
        drugDoseBox = editDrugDlg.FindWindowByName("drugDoseSpinCtrl")
        editDrugDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
        editDrugDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)
        if editDrugDlg.ShowModal() == wx.ID_OK:
            drug = drugClass(drugNameBox.GetValue(), drugDoseBox.GetValue())
            self.exp.drugList.append(drug)
            self.expDrugListBox.Append(drug.printForList())
        editDrugDlg.Destroy()
        
        
    
    def onDelDrug(self, event):
        self.expDrugListBox.Delete(self.expDrugListBox.GetSelection())
    
    def onEditDrug(self, event):
        selected = self.expDrugListBox.GetSelection()
        if selected > wx.NOT_FOUND:
            drug = self.exp.drugList[selected]
            
            editDrugDlg = xrc.XmlResource.Get().LoadDialog(None, "drugOptionsDlg")
            drugNameBox = editDrugDlg.FindWindowByName("drugNameTextBox")
            drugDoseBox = editDrugDlg.FindWindowByName("drugDoseSpinCtrl")
            editDrugDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
            editDrugDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)
            
            drugNameBox.SetValue(drug.name)
            drugDoseBox.SetValue(drug.dose)
            
            if editDrugDlg.ShowModal() == wx.ID_OK:
                drug = drugClass(drugNameBox.GetValue(), drugDoseBox.GetValue())
                self.expDrugListBox.Delete(selected)
                self.expDrugListBox.Insert(drug.printForList(), selected)
                self.expDrugListBox.SetSelection(selected)
            editDrugDlg.Destroy()

    def onUpdateLog(self, event):
        #print "onUpdateLog"
        if self.config.autoSave:
            fileName = path.join(self.config.getBaseFolder(), self.exp.date.Format("%Y_%m_%d"), self.config.getLogFileName())
            if not self.logBox.SaveFile(fileName):
                errorDlg = wx.MessageDialog(None, "Error writing file '%s' to disk" % fileName, style = wx.OK | wx.ICON_ERROR)
                errorDlg.ShowModal()
                errorDlg.Destroy()
        event.Skip()

    def onPreferencesMenu(self, event):
        configDlg = xrc.XmlResource.Get().LoadDialog(None, "configDlg")
        
        baseFolderPicker = configDlg.FindWindowByName("baseFolderPicker")
        baseFolderBrowseButton = configDlg.FindWindowByName("baseFolderBrowseButton") #@UnusedVariable
        createFolderCheck = configDlg.FindWindowByName("createFolderCheck")
        updateRegCheck = configDlg.FindWindowByName("updateRegCheck")
        autoSaveCheck = configDlg.FindWindowByName("autoSaveCheck")
        configDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
        configDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)
        
        #configDlg.Bind(wx.EVT_BUTTON, self.onBaseFolderBrowse, id=baseFolderBrowseButton.GetId(), id2=baseFolderPicker.GetId())
        
        #init
        baseFolderPicker.SetPath(self.config.getBaseFolder())
        createFolderCheck.SetValue(self.config.createFolder())
        updateRegCheck.SetValue(self.config.updateReg())
        autoSaveCheck.SetValue(self.config.autoSave())
        
        if configDlg.ShowModal() == wx.ID_OK:
            #do stuff
            self.config.setBaseFolder(path.normcase(path.normpath(baseFolderPicker.GetPath())))
            self.config.setCreateFolder(createFolderCheck.GetValue())
            self.config.setUpdateReg(updateRegCheck.GetValue())
            self.config.setAutoSave(autoSaveCheck.GetValue())
        configDlg.Destroy()


class otherDrugDlgClass(wx.Dialog):
    def __init__(self, parent):
        """Constructor"""
        #call the parent constructor
        wx.Dialog.__init__(parent);

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, -1, "Save Changes")
        wx.EVT_BUTTON(self, okButton.GetId(), 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(0)
        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(0)
        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(1)
        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(0)
        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(0)
        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, "10000")
        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 = comedi.AREF_GROUND
        elif x_aref_string == 'AREF_COMMON':
            x_aref = comedi.AREF_COMMON
        elif x_aref_string == 'AREF_DIFF':
            x_aref = comedi.AREF_DIFF
        else:
            x_aref = comedi.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 = comedi.AREF_GROUND
        elif y_aref_string == 'AREF_COMMON':
            y_aref = comedi.AREF_COMMON
        elif y_aref_string == 'AREF_DIFF':
            y_aref = comedi.AREF_DIFF
        else:
            y_aref = comedi.AREF_OTHER

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