## ------------------------------------------------------------------------
## 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
## ------------------------------------------------------------------------

from constants import *
import wx
from wx.lib.buttonpanel import ButtonPanel, ButtonInfo
import ws
import os, string, tempfile
from about import AboutBox
from datacache import formatFieldAsString
from analysisresult import AnalysisResult
from analysisresult import ProblemSet
from explorer import ProblemSetExplorer
from infoframe import InfoFrame
import config.silk as config
import xmlutils

class DataListBox(wx.VListBox):
    def __init__(self, browser, parent, *args, **kargs):
        wx.VListBox.__init__(self, parent, -1, *args, **kargs)
        self.browser = browser
        self.SetMargins((2,2))
        self.selectionColor = wx.Color(56, 117, 215)
        self.highlightColor = wx.Color(237, 243, 254)
        self.SetSelectionBackground(self.selectionColor)
        self.padding = 6
        self.bigItemLabelFont = ws.LgFont(wx.SWISS, wx.NORMAL, wx.BOLD)
        self.bigItemValueFont = ws.LgFont(wx.SWISS, wx.NORMAL, wx.NORMAL)
        self.itemLabelFont = ws.SmFont(wx.SWISS, wx.NORMAL, wx.BOLD)
        self.itemValueFont = ws.SmFont(wx.SWISS, wx.NORMAL, wx.NORMAL)
        self.labelFontColor = wx.Color(64,64,64)
        self.valueFontColor = wx.Color(0,0,0)
        self.selectedLabelFontColor = wx.Color(255,255,255)
        self.selectedValueFontColor = wx.Color(255,255,255)
        self.labelPadding = 8

    def OnMeasureItem(self, n):
        return 90

    def OnDrawItem(self, dc, rect, n):
        ds = self.browser.GetDataSet(n)
        lineHeight = 15
        labelAreaWidth = 75
        valueAreaWidth = rect.Width - labelAreaWidth

        # Adjust the rectangle based on padding
        rect.left = rect.left + self.padding
        rect.top = rect.top + self.padding
        rect.width = rect.width - self.padding * 2
        rect.height = rect.height - self.padding * 2

        # Draw the labels
        r = wx.Rect(rect.x, rect.y, rect.width, rect.height)
        r.SetWidth(labelAreaWidth)
        dc.SetFont(self.itemLabelFont)
        if self.GetSelection() == n:
            dc.SetTextForeground(self.selectedLabelFontColor)
        else:
            dc.SetTextForeground(self.labelFontColor)                    
        dc.SetFont(self.bigItemLabelFont)
        dc.DrawLabel("Title", r, wx.ALIGN_RIGHT | wx.ALIGN_TOP)
        r.Offset((0, lineHeight+2))
        dc.SetFont(self.itemLabelFont)
        dc.DrawLabel("Availability", r, wx.ALIGN_RIGHT | wx.ALIGN_TOP)
        r.Offset((0, lineHeight))        
        dc.DrawLabel("Where", r, wx.ALIGN_RIGHT | wx.ALIGN_TOP)
        r.Offset((0, lineHeight))
        dc.DrawLabel("Size", r, wx.ALIGN_RIGHT | wx.ALIGN_TOP)
        r.Offset((0, lineHeight))
        dc.DrawLabel("Status", r, wx.ALIGN_RIGHT | wx.ALIGN_TOP)
        r.Offset((0, lineHeight))

        # Grab the values
        recs = "?"
        bytes ="?"
        status = ds.GetStatus()
        # FIXME: better logical condition?
        if ds.IsRemoteFileAvailable():
            recs = str(ds.GetFileInfo("count-records"))
            bytes = str(ds.GetFileInfo("file-size"))
            size_val = "%s records (%s bytes in file)" % (formatFieldAsString("records", recs), formatFieldAsString("bytes", int(bytes)))
            # FIXME: should be handled in data set with asynchronous
            # update of status by *IT* in monitoring thread    
            if ds.IsCopying():
                status = "%s of %s bytes copied" % (formatFieldAsString("bytes", ds.GetBytesCopied()), formatFieldAsString("bytes", int(bytes)))
        else:
            # FIXME: File size so far (can we do this efficiently?)
            size_val = "--"
            
        where = ds.GetPath(True)
        if not where: where = "--"
        
        # Draw the values
        r = wx.Rect(rect.x+labelAreaWidth+self.labelPadding,
                    rect.y, rect.width, rect.height)        

        if self.GetSelection() == n:
            dc.SetTextForeground(self.selectedValueFontColor)
        else:
            dc.SetTextForeground(self.valueFontColor)  
        dc.SetFont(self.bigItemValueFont)          
        dc.DrawLabel(ds.GetName(), r, wx.ALIGN_LEFT | wx.ALIGN_TOP)
        dc.SetFont(self.itemValueFont)
        r.Offset((0, lineHeight+2))
        dc.DrawLabel(ds.GetLocationString(), r, wx.ALIGN_LEFT | wx.ALIGN_TOP)
        r.Offset((0, lineHeight))       
        dc.DrawLabel(where, r, wx.ALIGN_LEFT | wx.ALIGN_TOP)
        r.Offset((0, lineHeight))

        dc.DrawLabel(size_val, r, wx.ALIGN_LEFT | wx.ALIGN_TOP)
        r.Offset((0, lineHeight))
        dc.DrawLabel(status, r, wx.ALIGN_LEFT | wx.ALIGN_TOP)
        r.Offset((0, lineHeight))
        return
        
    def OnDrawBackground(self, dc, rect, n):
        if self.GetSelection() == n:
            dc.SetPen(wx.Pen(self.selectionColor, 1, wx.SOLID))
            dc.SetBrush(wx.Brush(self.selectionColor, wx.SOLID))
            dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight())            
        else:
            dc.SetPen(wx.TRANSPARENT_PEN)
            if n % 2 == 0:
                dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
            else:
                dc.SetBrush(wx.Brush(self.highlightColor, wx.SOLID))
            dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight())
            dc.DrawLine(rect.GetLeft(), rect.GetBottom(), rect.GetRight(), rect.GetBottom())
            return


wxEVT_INVOKE = wx.NewEventType() 
class InvokeEvent(wx.PyEvent): 
     def __init__(self, func, args, kwargs): 
         wx.PyEvent.__init__(self) 
         self.SetEventType(wxEVT_INVOKE) 
         self.__func = func 
         self.__args = args 
         self.__kwargs = kwargs 
     def invoke(self): 
         self.__func(*self.__args, **self.__kwargs)

class JobManager(wx.Frame):
    ''' Display all the active remote jobs.  Similar to the "Downloads" 
        manager concept for a web browser.'''
    def __init__(self, app):
        self.app = app
        self.items = []
        self.itemCount = 0
        wx.Frame.__init__(self,None, -1, "Analysis Jobs", pos=(50,50), size=(250,350),
                          )#style=wx.DEFAULT_FRAME_STYLE)
        # Invoke later implementation
        self.Connect(-1, -1, wxEVT_INVOKE, self.OnInvoke) 
        # Menus associated with this
        self.__initMenus__()
        # Two vertical elements: button panel and the list
        self.gbs = wx.FlexGridSizer(wx.VERTICAL)
        self.SetSizer(self.gbs)
        self.enableButtonPanel = False
        enableToolbar = False
        if enableToolbar:
            #self.CreateToolBar(style=wx.TB_TEXT)
            self.CreateToolBar(style=wx.TB_HORZ_LAYOUT)
            self.GetToolBar().AddLabelTool(ID_QUERY_BUILDER, "Query", wx.Bitmap("icons/query.gif", wx.BITMAP_TYPE_GIF))
            self.GetToolBar().AddLabelTool(ID_IMPORT_RESULT, "Import", wx.Bitmap("icons/grid.gif", wx.BITMAP_TYPE_GIF))
            self.GetToolBar().Realize()
        # Create the list box
        self.list = DataListBox(self, self, size=(440,450))
        self.list.SetItemCount(0)
        self.SetMinSize((440,520))
        # Create the status bar
        self.statusbar = self.CreateStatusBar(1, wx.ST_SIZEGRIP)
        self.statusbar.SetStatusText("No active data sets", 0)
        # FIXME: Layout isn't quite right, but if I move this to after
        # the adds I get a bus error!
        #self.gbs.RecalcSizes()
        self.gbs.Layout()
        self.gbs.Fit(self)
        self.gbs.Add(self.list, 0, flag=wx.EXPAND | wx.ALL)
        self.gbs.AddGrowableRow(0)
        self.gbs.AddGrowableCol(0)
        self.gbs.Layout()
        # Status message from data set while it's loading
        self.Bind(ws.EVT_UPDATE_STATUS, self.OnUpdateStatus)
        #Events associated with the frame itself
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        # Double click to explore
#        self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnExplore, self.list)
        # Selection change
        self.Bind(wx.EVT_LISTBOX, self.OnItemSelected, self.list)    

    def __initMenus__(self):
        # List of explorer windows
        self.explorers = {}    
        menuBar = wx.GetApp().CreateMenuBar()   
        self.SetMenuBar(menuBar)
        # Bind event handlers
        self.GetMenuBar().Enable(ID_ANALYSIS_COUNT, False)
        self.GetMenuBar().Enable(ID_ANALYSIS_TREND, False)
        self.GetMenuBar().Enable(ID_ANALYSIS_REFINE, False)        
        self.Bind(wx.EVT_MENU, self.OnQuery, id=ID_QUERY_BUILDER)
        self.Bind(wx.EVT_MENU, self.OnImport, id=ID_IMPORT_RESULT)
        self.Bind(wx.EVT_MENU, self.OnInfo, id=ID_SHOW_INFO)
        self.Bind(wx.EVT_MENU, self.OnShowJobs, id=ID_WINDOW_SHOW_DATASETS)
        self.Bind(wx.EVT_MENU, self.OnQueryBuilder, id=ID_WINDOW_SHOW_QUERY_BUILDER)
        self.Bind(wx.EVT_MENU, self.OnClose, id=ID_CLOSE_WINDOW)

    def OnInvoke(self, evt): 
        evt.invoke() 

    def InvokeLater(self, f, *args, **kwargs): 
        self.GetEventHandler().AddPendingEvent(InvokeEvent(f, args, kwargs))

    def OnItemSelected(self, event):
        index = self.list.GetSelection()
        self.selection = self.items[index]
        if self.enableButtonPanel: 
            self.btnExplore.Enable(True)

    def OnSize(self, evt):
        self.gbs.RecalcSizes()
        self.gbs.Layout()
        self.gbs.Fit(self.panel)
        evt.Skip()
        return

    def GetDataSet(self, index):
        return self.items[index]

    # Add a dataset for the dataset manager to display
    def AddDataSet(self, dataset):
        self.items.append(dataset)
        self.selection = dataset;
        self.itemCount = self.itemCount + 1
        self.list.SetItemCount(self.itemCount)
        self.list.SetSelection(self.itemCount-1)
        self.OnItemSelected(None)
        if self.itemCount==1:
            self.statusbar.SetStatusText("%d active data set" % self.itemCount)
        self.statusbar.SetStatusText("%d active data sets" % self.itemCount)

    def SetDataSetStatus(self, index, msg):
        self.items[index].SetStatus(msg)
        self.list.RefreshAll()
      
    def OnQuery(self, evt):
        wx.GetApp().DoShowQueryBuilder()
 
    def OnImport(self, evt):
        # Prompt user for a file to import
        # FIXME: Also allow filtering at this stage
        return
        # delete this
        dlg = wx.FileDialog(self, "Choose a file", "", "", "", style=wx.OPEN)
        rv = dlg.ShowModal()
        dirs = dlg.GetPath().split(os.sep)
        filename = dirs[len(dirs)-1]
        if rv == wx.ID_OK:
            dataset = DataSet(None,  {})
            dataset.RunImport(dlg.GetPath(), self)
            self.AddDataSet(dataset)

    def OnUpdateStatus(self, evt):
        self.list.RefreshAll()
        return
        
    def UpdateStatus(self, index, msg, complete=False):
        self.SetDataSetStatus(index, msg)

    def SetColumnMenuItemState(field, enabled=True, checked=False):
        self.columnMenuItems[field].Enable(enable)
        self.columnMenuItems[field].Check(checked)
                        
    def OnInfo(self, evt):
        index = self.list.GetSelection()
        dataset = self.items[index]
        pos = self.GetScreenPosition()
        pos = (pos.x + 50, pos.y + 50)
        info = InfoFrame(dataset, self, "Info for %s" % dataset.GetFileName(), pos = pos)
        info.Show(True)
        
    def GetList(self, app):
        return self.list
    
    def OnClose(self, evt):
        if wx.GetApp().quit:
            self.Destroy()
        else:
            self.Hide()
    
    def OnShowJobs(self, evt):
        self.Show()
        self.Raise()
    
    def OnQueryBuilder(self, evt):
        wx.GetApp().DoShowQueryBuilder()
