## ------------------------------------------------------------------------
## iSiLK - A python-based graphical user interface for SiLK
## ------------------------------------------------------------------------
## Copyright (c) 2007-2008 Carnegie Mellon University. All Rights Reserved.
## ------------------------------------------------------------------------
## Authors: Andrew Kompanek (ajk@cert.org)
## ------------------------------------------------------------------------
## GNU General Public License (GPL) Rights pursuant to Version 2, June 1991
## Government Purpose License Rights (GPLR) pursuant to DFARS 252.225-7013
## ------------------------------------------------------------------------
## See the LICENSE file included in this distribution
## ------------------------------------------------------------------------

import wx, ws, re
import config.fgloboptions as config
import config.fgloboptions
import config.silk as silkconfig
from controls.timerangepanel import TimeRangePanel
# Date range and time utilities
from config.dateutils import DateRange
import silk
import pdb
from analysisresult import ResultRunContext
import datetime as dt

class ChooseSensorsDialog(wx.Dialog):
    def __init__(self, optionspanel):
        wx.Dialog.__init__(self, optionspanel.GetTopLevelParent(),
            title="Choose Sensors", size = (500,400), 
            style=wx.RESIZE_BORDER|wx.DEFAULT_DIALOG_STYLE)
        self.optionspanel = optionspanel
        self.list = wx.CheckListBox(self, -1)
        btnClearAll = ws.SmButton(self, -1, "Clear All")
        btnSelAll = ws.SmButton(self, -1, "Select All")
        szSelBtns = wx.BoxSizer(wx.HORIZONTAL)
        szSelBtns.Add(btnSelAll, flag=wx.ALL, border=4)
        szSelBtns.Add(btnClearAll, flag=wx.ALL, border=4)
        szBtns = self.CreateButtonSizer(wx.OK|wx.CANCEL)
        szOuter = wx.GridBagSizer()
        szOuter.SetEmptyCellSize((0,0))
        szOuter.Add(self.list, (0,0), (1,1), flag=wx.ALL|wx.EXPAND)
        szOuter.Add(szSelBtns, (1,0), (1,1), flag=wx.ALL|wx.ALIGN_RIGHT, border=4)
        szOuter.Add(wx.StaticLine(self, style=wx.LI_HORIZONTAL), (2,0), (1,1), flag=wx.EXPAND)
        szOuter.Add(szBtns, (3,0), (1,1), flag=wx.ALIGN_RIGHT|wx.ALL, border=8)
        self.SetSizerAndFit(szOuter)
        szOuter.AddGrowableCol(0)
        szOuter.AddGrowableRow(0)
        self.Bind(wx.EVT_CHECKLISTBOX, self.OnClickItem, self.list)
        self.Bind(wx.EVT_BUTTON, self.OnClearAll, btnClearAll)
        self.Bind(wx.EVT_BUTTON, self.OnSelAll, btnSelAll)
        self.Bind(wx.EVT_INIT_DIALOG, self.OnInitDialog)
        self.SetSize((500,400))
        print "Finished binding"
        
    def OnInitDialog(self, event):
        self.sensor_info = self.optionspanel.GetSensorInfo()
        self.sensors = self.sensor_info.keys()
        self.list.SetItems(self.sensors)
        for (i, sensor) in enumerate(self.sensors):
            if self.sensor_info[sensor]:
                self.list.Check(i, True)

    def GetResult(self):
        result = []
        for (i, c) in enumerate(self.sensors):
            if self.list.IsChecked(i):
                result.append(c)
        return result
                
    def OnClickItem(self, event):
        if len(self.GetResult()) == 0:
            self.FindWindowById(wx.ID_OK).Enable(False)
        else:
            self.FindWindowById(wx.ID_OK).Enable(True)

    def OnClearAll(self, event):
        for i in range(0, len(self.sensors)):
            self.list.Check(i, False)
        self.FindWindowById(wx.ID_OK).Enable(False)
        
    def OnSelAll(self, event):
        for i in range(0, len(self.sensors)):
            self.list.Check(i, True)
        self.FindWindowById(wx.ID_OK).Enable(True)            

class FGlobOptionsPanel(wx.Panel):
    
    def __init__(self, parent, showDetails, *args, **kargs):
       wx.Panel.__init__(self, parent, *args, **kargs)
       self.sensor_map = None
       self.sensors = None
       self.showDetails = showDetails
       self._lastSensorGroupSel = 0
       # Grab values for "canned" options
       dpList = config.fgloboptions.opts["CLASS_TYPE_OPTIONS"].keys()
       sgList = config.fgloboptions.opts["SENSOR_OPTIONS"].keys()
       self.timeList = config.fgloboptions.opts["DATE_RANGE_OPTIONS"].keys()
       
       #  self ---->> fglobTopBoxSizer
       #        |--- fglobTopBox
       #        |--- fglobPanel ---->> fglobSizer
       self.fglobTopBox = fglobTopBox = wx.StaticBox(self, -1, "Data files to search")
       fglobTopBoxSizer = wx.StaticBoxSizer(fglobTopBox, wx.HORIZONTAL)
       fglobPanel = wx.Panel(self)
       fglobSizer = wx.GridBagSizer(0,0)
       # This is necessary on Windows XP to ensure static lines aren't "fat"
       fglobSizer.SetEmptyCellSize((0,0))
       fglobTopBoxSizer.Add(fglobPanel)
       self.SetSizer(fglobTopBoxSizer)
       
       # Create controls for "fglob" panel
       tDataPool = wx.StaticText(fglobPanel, -1, "Data Pool (class/type)")
       self.cbDataPool = wx.ComboBox(fglobPanel, choices=dpList, style=wx.CB_DROPDOWN|wx.CB_READONLY)
       tSensorGroup = wx.StaticText(fglobPanel, -1, "Sensors")
       self.cbSensorGroup = wx.ComboBox(fglobPanel, -1, choices=sgList, style =wx.CB_DROPDOWN|wx.CB_READONLY)
       self.btnChooseSensors = ws.SmButton(fglobPanel, -1, "Choose...")
       tTimeRange = wx.StaticText(fglobPanel, -1, "Time Range to Query")
       
       self.cbRanges = wx.ComboBox(fglobPanel, -1, "Current Hour",
               choices=self.timeList, style=wx.CB_DROPDOWN|wx.CB_READONLY)
       self.trPanelPane = self.trPanel = TimeRangePanel(self, fglobPanel)
       # Layout the fglob controls
       row = 0
       fglobSizer.Add(tDataPool, (row,0), (1,1),
            wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_LEFT, border=6)
       fglobSizer.Add(self.cbDataPool, (row,1),(1,1),
            wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_LEFT | wx.EXPAND, border=6)
       row+=1
       szSensors = wx.BoxSizer(wx.HORIZONTAL)
       szSensors.Add(tSensorGroup, flag=wx.RIGHT, border=20)
       szSensors.Add(self.cbSensorGroup, flag=wx.RIGHT|wx.EXPAND, border=4)
       szSensors.Add(self.btnChooseSensors, flag=wx.ALIGN_RIGHT, border=4)
       fglobSizer.Add(szSensors, (row,0), (1,2),
            wx.ALL|wx.ALIGN_RIGHT, border=6)
       
       
       #fglobSizer.Add(tSensorGroup, (row,0),(1,1),
       #        wx.ALL|wx.ALIGN_LEFT, border=6)
       #fglobSizer.Add(self.cbSensorGroup,(row,1),(1,1),
    #        wx.ALL|wx.ALIGN_LEFT | wx.EXPAND, border=6)
       #row+=1
       #fglobSizer.Add(self.btnChooseSensors, (row,0), (1,1),
        #    wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_RIGHT, border=6)            
       row += 1
       line = wx.StaticLine(fglobPanel, -1, size=(300,-1), style=wx.LI_HORIZONTAL)
       fglobSizer.Add(line, (row,0), (1,2),
               flag=wx.EXPAND|wx.ALL|wx.ALIGN_CENTER, border=6)
       row+=1
       fglobSizer.Add(tTimeRange, (row,0), (1,1),
            wx.TOP|wx.BOTTOM|wx.LEFT|wx.ALIGN_LEFT, border=6)
       fglobSizer.Add(self.cbRanges, (row,1),(1,1),
            wx.TOP|wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.ALIGN_LEFT|wx.EXPAND, border=6)
       row += 1
       fglobSizer.Add(self.trPanelPane, (row,0), (1,2),
            wx.LEFT|wx.ALIGN_LEFT, border=10)
       fglobPanel.SetSizerAndFit(fglobSizer)
       self.Layout()
       
       # Set default selections for the fglob panel
       self.cbSensorGroup.SetSelection(0)
       self.cbDataPool.SetSelection(0)
       self.cbRanges.SetSelection(0)
       self.cbRanges.Bind(wx.EVT_COMBOBOX, self.OnTimeRangeSelected)
       self.cbDataPool.Bind(wx.EVT_COMBOBOX, self.OnDataPoolSelected)
       self.cbSensorGroup.Bind(wx.EVT_COMBOBOX, self.OnSensorGroupSelected)
       self.btnChooseSensors.Bind(wx.EVT_BUTTON, self.OnChooseSensors)

    def ValidateOptions(self):
        self.trPanel.ValidateOptions()

    def GetOptions(self):
        # FIXME: make this work with actual groups
        opts = {}
        if self.sensors != None:
            sensors_str = ",".join(self.sensors)
            opts["sensors"] = sensors_str
        classTypeSel = self.cbDataPool.GetValue()
        opts.update(config.fgloboptions.opts["CLASS_TYPE_OPTIONS"][classTypeSel])
        opts.update(self.trPanel.GetOptions())
        return opts
    
    def SetOptions(self, opts):
        if "start_date" in opts:
            start_date = silk.parse_fglob_date(opts["start_date"])
            if "end_date" in opts:
                end_date = silk.parse_fglob_date(opts["end_date"])
            else:
                if start_date[3] == None:
                    start_date[3] = 23
                else:
                    end_date = start_date
            start_date = dt.datetime(start_date[0], start_date[1], start_date[2])
            start_date = start_date.timetuple()
            end_date = dt.datetime(end_date[0], end_date[1], end_date[2])        
            end_date = end_date.timetuple()
            self.trPanel.SetRange(start_date, end_date)
    
    def CopyState(self, other):
        self.cbDataPool.SetValue(other.cbDataPool.GetValue())
        
    def OnDataPoolSelected(self, evt):
        self.cbSensorGroup.SetSelection(0)
        self.sensors = None
        wx.PostEvent(self.GetParent(), ws.OptionChangeEvent(-1, None, None))
          
    # Returns a dictionary whose keys correspond to all the available
    # sensors for the currently selected class.  Value is a boolean
    # reflecting whether the sensor has been selected by the user.
    # FIXME: don't hardware ISR
    def GetSensorInfo(self):
        self.sensor_info = {}
        if self.sensor_map == None:
            busy = wx.BusyInfo("Querying server for available sensors...", self)
            problem_set = self.GetParent().GetSelectedProblemSet()
            context = None
            if not problem_set:
                context = silk.SimpleRunContext()
            else:
                context = ResultRunContext(problem_set,
                    runRemote=True, runDir=".")
            self.sensor_map = silk.get_sensor_map(context)
        if "class" in self.GetOptions():
            sclass = self.GetOptions()["class"]
        else:
            sclass = silk.get_default_class(context)
        # All available sensors
        choices = []
        if sclass.lower() == 'all':
            for key in self.sensor_map.keys():
                choices.extend( self.sensor_map[key])
        else:
            choices = self.sensor_map[sclass.lower()]
        # Prune choices based on the group
        if self.cbSensorGroup.GetSelection() == 0:
            # If user selected a sensor or he hasn't yet explicitly 
            # chosen sensors add it to the map
            for sensor in choices:
                if not self.sensors or (sensor in self.sensors):
                    self.sensor_info[sensor] = True
                else:
                    self.sensor_info[sensor] = False
            return self.sensor_info
        else:
            sel = self.cbSensorGroup.GetSelection()
            selVal = self.cbSensorGroup.GetString(sel)
            p = config.fgloboptions.opts["SENSOR_OPTIONS"][selVal]        
            pattern = re.compile(p)
            # If a sensor doesn't match the pattern for the group or the
            # user has explicitly unselected it, remove it
            for sensor in choices:
                if not pattern.match(sensor):
                    continue # not available in group
                elif self.sensors and (not sensor in self.sensors):
                    self.sensor_info[sensor] = False # not selected
                else:
                    self.sensor_info[sensor] = True
                
            return self.sensor_info
            
    def OnChooseSensors(self, evt):
        d = ChooseSensorsDialog(self)
        d.CenterOnParent()
        if d.ShowModal() == wx.ID_OK:
            self.sensors = d.GetResult()
        wx.PostEvent(self.GetParent(), ws.OptionChangeEvent(-1, None, None))
    
    def OnSensorGroupSelected(self, evt):
        self.sensors = None
        wx.PostEvent(self.GetParent(), ws.OptionChangeEvent(-1, None, None))
        
    def OnCustomSetting(self):
        self.cbRanges.SetSelection(self.cbRanges.GetCount()-1)
        wx.PostEvent(self.GetParent(), ws.OptionChangeEvent(-1, None, None))
    
    def OnTimeRangeSelected(self, evt):
        index = evt.GetSelection()
        # Last item always custom
        if index == (self.cbRanges.GetCount()-1):
            print "Selected custom - don't do anything"
        else:
            self.RefreshRange()
        wx.PostEvent(self.GetParent(), ws.OptionChangeEvent(-1, None, None))
    
    def RefreshRange(self):
        index = self.cbRanges.GetSelection()
        v = self.timeList[index]
        dr = DateRange(config.fgloboptions.opts["DATE_RANGE_OPTIONS"][v])
        r = dr.GetRangeTuple()
        self.trPanel.SetRange(r[0], r[1])
