## ------------------------------------------------------------------------
## 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
import ws
import time
import wx.lib.calendar
from config.dateutils import mkgmtime
from controls.filteroptionspanel import ValidationError

CAL_WIDTH = 220
CAL_HEIGHT = 140
CAL_WIDTH = 180
CAL_HEIGHT = 150
CAL_WIDTH = 160
CAL_HEIGHT = 120

MONTH_LIST = ["Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug",
              "Sept", "Oct", "Nov", "Dec"]

YEAR_LIST =  ["2010", "2009", "2008", "2007", "2006", "2005", "2004",
              "2003", "2002", "2001"]
dlg = None

class TimeRangePanel(wx.Panel):
    
    
   def ValidateRange(self, tm_start, tm_end):
       delta = mkgmtime(tm_end) - mkgmtime(tm_start)
       return delta >= 0
       
   def ValidateOptions(self):
       print "%s to %s" % (self.tm_start, self.tm_end)
       delta = mkgmtime(self.tm_end) - mkgmtime(self.tm_start)
       print "delta = %f" % delta
       if (mkgmtime(self.tm_end) - mkgmtime(self.tm_start)) < 0:
           raise ValidationError("Illegal date range", 
                    "Bad date values: start time must precede end time.",
                    self)
       return True

   def GetOptions(self):
     options = {}
     start_date = time.strftime("%Y/%m/%d:%H", self.tm_start)
     end_date = time.strftime("%Y/%m/%d:%H", self.tm_end)
     options["start-date"] = start_date
     if not (start_date == end_date):
        options["end-date"] =  end_date
     return options

   def OnStartYearSelected(self, evt):
      tm_start = self.tm_start; tm_end = self.tm_end     
      index = evt.GetSelection()
      year = int(self.cbStartYear.GetString(index))
      tm_start = list(tm_start)
      tm_start[0] = year
      tm_start = tuple(tm_start)
      # If the year is invalid don't do anything
      if not self.validateRange(tm_start, tm_end):
         pass
      # Otherwise do custom setting thing (HACK)
      self.SetRange(tm_start, tm_end)
      self.owner.OnCustomSetting()      
      evt.Skip()      

   def OnEndYearSelected(self, evt):
      tm_start = self.tm_start; tm_end = self.tm_end
      index = evt.GetSelection()
      year = int(self.cbEndYear.GetString(index))
      tm_end = list(tm_end)
      tm_end[0] = year
      # Make sure we deal with going from a non-leap to leap year in february
      t = wx.lib.calendar.Date(tm_end[0], tm_end[1], 1)
      if tm_end[2] > t.days_in_month: 
          tm_end[2] = t.days_in_month
      tm_end = tuple(tm_end)
      if not self.validateRange(tm_start, tm_end):
         pass
      self.SetRange(tm_start, tm_end)
      self.owner.OnCustomSetting()      
      evt.Skip()            
      
   def OnStartMonthSelected(self, evt):
      tm_end = self.tm_end; tm_start = self.tm_start
      index = evt.GetSelection()
      month = index + 1
      tm_start = list(tm_start)
      tm_start[2] = 1
      tm_start[1] = month
      tm_start = tuple(tm_start)
      # Set everything back
      if not self.validateRange(tm_start, tm_end):
         pass
      self.SetRange(tm_start, tm_end)
      self.owner.OnCustomSetting()      
      evt.Skip()

   def OnEndMonthSelected(self, evt):
      tm_start = self.tm_start; tm_end = self.tm_end      
      index = evt.GetSelection()
      month = index + 1
      tm_end = list(tm_end)
      t = wx.lib.calendar.Date(tm_end[0], month, 1)
      tm_end[2] = t.days_in_month
      tm_end[1] = month
      tm_end = tuple(tm_end)
      if not self.validateRange(tm_start, tm_end):
         pass
      self.SetRange(tm_start, tm_end)
      self.owner.OnCustomSetting()      
      evt.Skip()

   def GetDaysInMonth(self, y, m):
      return wx.lib.calendar.Date(y,m,1).days_in_month

   def SetRange(self, tm_start, tm_end):
      # Stow these away... we'll modify these to support custom ranges
      self.tm_start = tm_start
      self.tm_end = tm_end

      # Select correct month in the calendars and combos
      self.calStart.SetYear(tm_start[0])
      self.cbStartYear.SetSelection(self.cbStartYear.FindString(str(tm_start[0])))
      self.calStart.SetMonth(tm_start[1])
      self.cbStartMonth.SetSelection(tm_start[1]-1)
      self.calEnd.SetYear(tm_end[0])
      self.cbEndYear.SetSelection(self.cbEndYear.FindString(str(tm_end[0])))      
      self.calEnd.SetMonth(tm_end[1])
      self.cbEndMonth.SetSelection(tm_end[1]-1)      
      self.calStart.Refresh()
      self.calEnd.Refresh()
      # Clear the highlights on the calendar
      self.ClearCalSelection(self.calStart)
      self.ClearCalSelection(self.calEnd)      
      # Add highlights to calendar for the day range
      # Show range on start calendar
      same_month = False
      days_in_month = self.GetDaysInMonth(tm_start[0], tm_start[1])
      if tm_start[0] == tm_end[0] and tm_start[1] == tm_end[1]:
         same_month = True
      # Highlight days in between (and end day if same month)
      if same_month:
         self.calStart.AddSelect(range(tm_start[2]+1, tm_end[2]), 'WHITE', 'LIGHT BLUE')
         # Highlight last day
         self.calStart.AddSelect([tm_end[2]], 'WHITE', 'RED')
      else:
         days = range(tm_start[2], days_in_month+1)
         self.calStart.AddSelect(days, 'WHITE', 'LIGHT BLUE')
      # Highlight first day
      self.calStart.AddSelect([tm_start[2]], 'WHITE', 'GREEN')       
      self.calStart.Refresh()
      # Show range on end calendar
      if same_month:
         self.calEnd.AddSelect(range(tm_start[2]+1, tm_end[2]), 'WHITE', 'LIGHT BLUE')
         # Highlight first day
         self.calEnd.AddSelect([tm_start[2]], 'WHITE', 'GREEN')
      else:
         days = range(1, tm_end[2]+1) 
         self.calEnd.AddSelect(range(1, tm_end[2]), 'WHITE', 'LIGHT BLUE')
      # Highlight last day
      self.calEnd.AddSelect([tm_end[2]], 'WHITE', 'RED')
      self.calEnd.Refresh()

      # Set the time widgets
      start_hour = time.strftime("%H",tm_start)
      end_hour = time.strftime("%H",tm_end)
      self.txtStartHour.SetValue(int(start_hour))
      self.txtEndHour.SetValue(int(end_hour))
      
      # FIXME: allow minutes

      # FIXME: Cleaner way to do this and better place for it than 
      # this widget?
      opts = self.GetOptions()
      start_str = opts["start-date"]
      if "end-date" in opts:
          end_str = opts["end-date"]
      else:
          end_str = start_str
          
      if not tm_end:
          tm_end = tm_start
          
      days =  ((mkgmtime(tm_end)-mkgmtime(tm_start))+1)/(60.0*60.0*24.0)
      hours =  ((mkgmtime(tm_end)-mkgmtime(tm_start))+1)/(60.0*60.0)
      if hours < 0:
          msg = "! BAD DATE RANGE: start must come before end !"
      
      elif hours < 1:
          msg = "Selected less than one hour"
      elif days < 1:
            msg = "Selected %d hours (less than one day)" % int(hours)
      elif int(days) == 1:
         msg = "Selected %d hours (about %d day)" % (int(hours), int(days))
      else:
         msg = "Selected %d hours (about %d days)" % (int(hours), int(days))
      # Estimated query time in seconds
      #est_query_time = 30 * int(hours)
      #if est_query_time < 60:
      #    est_msg = "less than one minute"
      #elif est_query_time < 3600:
      #    est_msg = "%d minutes" % int (est_query_time/60)
      #else:
      #    est_msg = "%3.1f hours" % float (est_query_time/3600)
      #msg += " -- Aproximate query time is %s" % est_msg
      #msg = ""
      self.txtSelection.SetLabel(msg)
      #self.txtSelection.SetLabel("")
      
   def ClearCalSelection(self, cal):
      cal.select_list = []
      cal.Refresh()

   def OnSelectStartDay(self, evt):
      tm_start = self.tm_start; tm_end = self.tm_end
      tm_start = list(tm_start)
      tm_start[2] = evt.day
      tm_start[3] = 0
      tm_start = tuple(tm_start)
      if not self.validateRange(tm_start, tm_end):
         pass
      self.SetRange(tm_start, tm_end)
      # FIXME - generate actual events
      self.owner.OnCustomSetting()
      evt.Skip()
      return

   def validateRange(self, tm_start, tm_end):
      if (mkgmtime(tm_end) - mkgmtime(tm_start)) < 0:
         #dlg = wx.MessageDialog(self,
         #                       "Start date must come before end date.",
         #                       "Bad date range",
         #                       style=wx.ICON_EXCLAMATION)
         #dlg.ShowModal()
         self.SetRange(self.tm_start, self.tm_end)
         return False
      else:
         return True

   def OnSelectEndDay(self, evt):
       tm_start = self. tm_start; tm_end = self.tm_end      
       tm_end = list(tm_end)
       tm_end[2] = evt.day
       tm_end[3] = 23
       tm_end = tuple(tm_end)
       
       if not self.validateRange(tm_start, tm_end):
           pass
       # HACK - FIXME - shouldn't touch internals of calendar
       self.calEnd.SetSelDay([])
       self.calEnd.Update()
       self.calEnd.Refresh()
       self.SetRange(tm_start, tm_end)
       self.owner.OnCustomSetting()      
       evt.Skip()
       return

   def UpdateStartHour(self, hour):
      tm_start = list(self.tm_start)
      tm_start[3] = hour
      self.tm_start = tuple(tm_start)

   def UpdateEndHour(self, hour):
      tm_end = list(self.tm_end)
      tm_end[3] = hour
      self.tm_end = tuple(tm_end)

   def OnSpinStartHour(self, evt):
      hour = int(self.txtStartHour.GetValue())
      self.UpdateStartHour(hour)  
      self.SetRange(self.tm_start, self.tm_end)
      self.owner.OnCustomSetting()        

   def OnSpinEndHour(self, evt):
       hour = int(self.txtEndHour.GetValue())
       self.UpdateEndHour(hour)  
       self.SetRange(self.tm_start, self.tm_end)       
       self.owner.OnCustomSetting()       

   def __init__(self, owner, parent):
        wx.Panel.__init__(self, parent)
        self.owner = owner
        self.parent = parent
        self.sz = wx.GridBagSizer(4,4)
        # Controls for selecting month and year
        self.cbStartYear = wx.Choice(self,-1,(-1,-1), choices=YEAR_LIST)
        self.cbStartMonth = wx.Choice(self, -1, (-1,-1), choices=MONTH_LIST)
        self.cbStartMonth.SetSelection(2)
        # Controls for selecting time down timeo the hour and minute
        # FIXME: bundle these up in panels
        self.lblStartHour = wx.StaticText(self, -1, "Start hour (GMT):")
        self.txtStartHour = wx.SpinCtrl(self, -1, value="0", min=0,max=23,
            style=wx.SP_WRAP, size=(50,-1))
        self.lblEndHour = wx.StaticText(self, -1, "End hour (GMT):")
        self.txtEndHour = wx.SpinCtrl(self, -1, value="0", min=0,max=23,
            style=wx.SP_WRAP, size=(50,-1))

        self.txtStartHour.Bind(wx.EVT_SPINCTRL, self.OnSpinStartHour)
        #self.txtEndHour.Bind(wx.EVT_SPIN_UP, self.OnSpinEndHour)
        #self.txtStartHour.Bind(wx.EVT_SPIN_DOWN, self.OnSpinStartHour)
        self.txtEndHour.Bind(wx.EVT_SPINCTRL, self.OnSpinEndHour)        

        
        self.calStart = wx.lib.calendar.Calendar(self, -1, pos=(-1,-1),
                                                 size = (CAL_WIDTH, CAL_HEIGHT))
        self.calStart.SetMinSize((CAL_WIDTH,CAL_HEIGHT))
        self.calStart.HideTitle()
        self.cbEndYear = wx.Choice(self, -1, (-1,-1), choices=YEAR_LIST)
        self.cbEndMonth = wx.Choice(self, -1, (-1,-1), choices=MONTH_LIST)
        self.cbEndMonth.SetSelection(2)

        self.calEnd = wx.lib.calendar.Calendar(self, -1, pos=(-1,-1),
                                               size = (CAL_WIDTH, CAL_HEIGHT))
        self.calEnd.SetMinSize((CAL_WIDTH,CAL_HEIGHT))
        self.calEnd.HideTitle()
        self.cbStartMonth.Bind(wx.EVT_CHOICE, self.OnStartMonthSelected)
        self.cbEndMonth.Bind(wx.EVT_CHOICE, self.OnEndMonthSelected)
        self.cbStartYear.Bind(wx.EVT_CHOICE, self.OnStartYearSelected)
        self.cbEndYear.Bind(wx.EVT_CHOICE, self.OnEndYearSelected)
        self.calStart.Bind(wx.lib.calendar.EVT_CALENDAR, self.OnSelectStartDay)
        self.calEnd.Bind(wx.lib.calendar.EVT_CALENDAR, self.OnSelectEndDay)        

        i = 0; row = 0
        self.sz.Add(self.cbStartMonth, (row,i), (1,1),
            wx.ALIGN_BOTTOM|wx.ALIGN_RIGHT|wx.EXPAND|wx.TOP|wx.BOTTOM, 
            border=4); 
        self.sz.Add(self.cbStartYear,  (row,i+1), (1,1),
            wx.ALIGN_BOTTOM|wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM,
            border=4)
        i = 2; row = 0
        self.sz.Add(self.cbEndMonth, (row,i), (1,1),
            wx.ALIGN_BOTTOM|wx.ALIGN_RIGHT|wx.EXPAND|wx.TOP|wx.BOTTOM,
            border=4)
        self.sz.Add(self.cbEndYear, (row, i+1), (1,1),
            wx.ALIGN_BOTTOM|wx.ALIGN_LEFT|wx.TOP|wx.BOTTOM,
            border=4)
        row = 1
        self.sz.Add(self.calStart, (row,0), (1,2), wx.ALIGN_BOTTOM|wx.ALIGN_LEFT);
        self.sz.Add(self.calEnd, (row,2), (1,2), wx.ALIGN_BOTTOM|wx.ALIGN_LEFT);
        row = 2; 
        self.szStart = wx.GridBagSizer(0,0)
        self.szStart.Add(self.lblStartHour, (0,0), 
            flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, border=4)
        self.szStart.Add(self.txtStartHour, (0,1))
        self.szStart.Layout()
        self.szEnd = wx.GridBagSizer(0,0)
        self.szEnd.Add(self.lblEndHour, (0,0), 
            flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, border=4)
        self.szEnd.Add(self.txtEndHour, (0,1))
        self.szEnd.Layout() 
        self.sz.Add(self.szStart, (row,0), (1,2), wx.ALIGN_LEFT)
        self.sz.Add(self.szEnd, (row,2), (1,2), wx.ALIGN_LEFT)
        row = 3
        # Text summarizing selection
        self.txtSelection = wx.StaticText(self, -1, "",
            (-1,-1), (-1,-1), wx.ALIGN_TOP|wx.EXPAND)
        self.sz.Add(self.txtSelection, (row, 0), (1, 4), flag=wx.TOP|wx.EXPAND, border=2)
        # Finish up layout
        self.Layout()
        self.SetSizerAndFit(self.sz)
   
