## ------------------------------------------------------------------------
## 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 sys, string, re, pdb
import config.silk
import logging
import constants
import wx, ws, wx.lib.buttons, wx.html, wx.lib.stattext
from datacache import formatFieldAsString
# Dataset classes recognized by the DefaultViewFactory
from analysisresult import ProblemSet, AnalysisResult
from resultset import QueryResult
from resultset import CountResult
from resultset import UniqResult
from resultset import FilterResult
from graphresult import CountGraph
# Change events
# Graph view used by DefaultViewFactory
from controls.mplgraphpanel import GraphPanel
from controls.recordlistctrl import RecordListCtrl
from controls.problemsetpanel import ProblemSetPanel

def _format_count(val):
    val = float(val)
    fraction = float(val) - int(val)
    l = list(str(int(val)))
    l.reverse()
    s = []
    for i,d in enumerate(l):
        if i > 0 and i%3==0: 
            s.append(",")
        s.append(d)
    s.reverse()
    s = string.join(s,"")
    if fraction != 0:
        s = s + "." + "%02d" % (fraction*100)
    return s

class ViewFactory:
    def __init__(self):
        raise Exception("ViewFactory is abstract")
    def CreateView(self):
        raise Exception("Class implementing ViewFactory must implement CreateView")

class EmptyView(wx.Window):
    def __init__(self, *args, **kargs):
        wx.Window.__init__(self, *args, **kargs)
        self.SetBackgroundColour("sky blue")
        wx.StaticText(self, -1, "No View Available", (5,5))
    def RefreshContents(self):
        pass

import traceback
class DefaultViewFactory(ViewFactory):
    def __init__(self, viewbook):
        self.viewbook = viewbook
        
    def CreateView(self, dataset):
        if isinstance(dataset, ProblemSet):
            return ProblemSetPanel(self.viewbook, dataset)
            #return ProblemSetViewPage(self.viewbook, dataset)
        logging.debug("In View factory.  Creating view for %s", dataset)
        if isinstance(dataset, CountGraph):
            page = GraphViewPage(self.viewbook, dataset)
            logging.debug("Created GraphViewPage for CountGraph")
            return page   
        elif not isinstance(dataset, AnalysisResult):
                return EmptyView(self.viewbook)         
        else:
            page = RecordViewPage(self.viewbook, dataset)
            logging.debug("Created RecordViewPage")
            return page


class DataInfoPanel(wx.Panel):
    
    def __init__(self, parent, dataset):
        wx.Panel.__init__(self, parent, -1) #, style=wx.BORDER_SIMPLE) #BORDER_STATIC)
        self.dataset = dataset        
        self.showProgress = False
        self.showCommandLine = False
        info = self.dataset.GetInfo()        
        # Create controls to display name, local path, remote path and status
        self.title = wx.StaticText(self, -1, self.dataset.GetName())
        self.title.SetFont(ws.LgFont(wx.SWISS, wx.NORMAL, wx.BOLD))
        self.cmd = wx.StaticText(self, -1, "The command line will go here, indeed it will go here.")
        self.cmd.SetFont(ws.SmFont(wx.MODERN, wx.NORMAL, wx.NORMAL))
        labelcolor = wx.Colour(50,50,50)
        self.cmd.SetForegroundColour(labelcolor)
        self.status = ws.SmStaticLabel(self, -1, "Nothing")
        # Dowload and upload buttons
        self.localLabel = ws.SmStaticLabel(self, -1, "Local file:")
        self.localLabel.SetForegroundColour(labelcolor)
        self.localLabel.SetFont(ws.SmFont(wx.SWISS, wx.NORMAL, wx.BOLD))        
        self.localPath = ws.SmHyperlinkCtrl(self, -1, "Download file", "#download")
        self.remoteLabel = ws.SmStaticLabel(self, -1, "Remote file:")
        self.remoteLabel.SetForegroundColour(labelcolor)        
        self.remoteLabel.SetFont(ws.SmFont(wx.SWISS, wx.NORMAL, wx.BOLD))                
        self.remotePath =  ws.SmHyperlinkCtrl(self, -1, "Upload file", "#upload")
        self.percentComplete = 0
        self.progress = wx.Gauge(self, -1, 100, (-1, -1), (450, -1))
        self.szLoc = szLoc = wx.BoxSizer(wx.HORIZONTAL)
        szLoc.Add(self.localLabel, flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.ALIGN_LEFT,border=4)
        szLoc.Add(self.localPath, flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.ALIGN_LEFT,border=0)
        self.szRemote = szRemote = wx.BoxSizer(wx.HORIZONTAL)
        szRemote.Add(self.remoteLabel, flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.ALIGN_LEFT,border=4)        
        szRemote.Add(self.remotePath, flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.BOTTOM,border=0)
        self.szStatus = szStatus = wx.BoxSizer(wx.VERTICAL)
        szStatus.Add(self.status, flag=wx.TOP|wx.BOTTOM,border=2)
        szStatus.Add(self.progress)#, flag=wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.RIGHT|wx.EXPAND,border=0)
        self.sz = sz = wx.GridBagSizer(vgap=0)
        self.sz.SetEmptyCellSize((0,0))
        sz.Add(self.title,   (0,0), (1,2), flag=wx.TOP|wx.LEFT, border=8)
        sz.Add((0,0),        (1,0), (1,2), flag=wx.BOTTOM, border=4)        
        sz.Add(self.cmd,     (2,0), (1,2), flag=wx.LEFT|wx.BOTTOM, border=8)
        sz.Add(szLoc,        (3,0), (1,2), flag=wx.LEFT|wx.BOTTOM, border=8)
        sz.Add(szRemote,     (4,0), (1,2), flag=wx.LEFT|wx.BOTTOM, border=8)
        sz.Add(szStatus,     (5,0), (1,2), flag=wx.LEFT|wx.BOTTOM|wx.EXPAND, border=8)
        sz.Add((0,0),        (6,0), (1,2), flag=wx.BOTTOM, border=4)
        self.SetSizerAndFit(sz)
        sz.AddGrowableCol(0)
        self.Bind(wx.EVT_HYPERLINK, self.OnClickLocal, self.localPath)
        self.Bind(wx.EVT_HYPERLINK, self.OnClickRemote, self.remotePath)        
        self.SetBackgroundColour(wx.Color(255,255,255))
        self.ShowCommandLine(False)
        self.ShowProgress(False)
        self.RefreshContents()


    def OnMouseLeftDown(self, evt):
        print "Blah"
        
    def RefreshContents(self):
        local_path = " "
        remote_path = " No file "
        status_str = "%s" % self.dataset.GetStatus()
        
        showDownload = True
        # Set local and remote path strings
        if self.dataset.IsFileAvailable():
            if self.dataset.IsLocalFileAvailable():
                local_path = self.dataset.GetLocalPath()
                showDownload = False
                # FIXME: cleaner way to do this when I allow remote and local
                if (not isinstance(self.dataset, ProblemSet)) and \
                    config.silk.workRemotely() and (not self.dataset.IsToolResult()):
                        local_path = local_path + ".asc"
            if self.dataset.IsRemoteFileAvailable():
                remote_path = "ssh://%s%s" % (self.dataset.GetRemoteHost(),
                        self.dataset.GetRemotePath())
        # Set status string
        if self.dataset.IsCopying():
           if config.silk.workRemotely():        
               status_str = "Copying ascii file: "
               bytes = self.dataset.GetAscFileSize()
           else:
               status_str = "Copying binary file: "
               bytes = str(self.dataset.GetFileInfo("file-size"))

           try:
               bytes = int(bytes)
               status_str += "%s of %s bytes copied" % (formatFieldAsString("bytes",
                    self.dataset.GetBytesCopied()), formatFieldAsString("bytes", bytes))
           except:
               status_str = "(copying file of unknown size)"
        # Update progress bar
        if self.dataset.IsProcessing():
            self.ShowProgress(True)
            #self.progress.Show(True)
            if self.dataset.percentComplete:
                self.percentComplete = self.dataset.percentComplete
        elif self.dataset.IsCopying():
            try:
               #bytes = float(str(self.dataset.GetFileInfo("file-size")))
               self.percentComplete = 100 * float(self.dataset.GetBytesCopied())/bytes
               self.ShowProgress(True)
            except:
               self.percentComplete = 0
        elif self.dataset.IsLoading():
            self.ShowProgress(True)
        else:
            self.percentComplete = 100
            self.ShowProgress(False)

        # Set values for controls
        if self.dataset.IsDeleted():
            self.title.SetLabel("(Result Deleted)")
        else:
            self.title.SetLabel(self.dataset.GetName())
        
        if not showDownload:
            #self.localPath.SetLabel("")
            self.localPath.SetLabel(local_path)
            self.localPath.SetURL(local_path)
        self.remotePath.SetLabel(remote_path)
        self.remotePath.SetURL(remote_path)
        self.status.SetLabel(status_str)
        
        if self.dataset.IsDeleted():
            msg = "Error running command: %s" % self.dataset.GetLastCommandLine()
            self.cmd.SetLabel(msg)
        elif self.dataset.IsFileAvailable():
            self.cmd.SetLabel(self.dataset.GetLastCommandLine())
        else:
            self.cmd.SetLabel(self.dataset.GetCurrentCommand())
        self.progress.SetValue(self.percentComplete)
        self._adjustLayout()

    def _adjustLayout(self):
        downloaded = self.dataset.IsLocalFileAvailable()
        self.sz.Show(self.szLoc, not self.showProgress and downloaded)
        self.sz.Show(self.szRemote, not self.showProgress and not downloaded)
        self.sz.Show(self.szStatus, self.showProgress)
        
        if self.dataset.IsDeleted():
            self.sz.Show(self.szRemote, False)
        
        # Hack to make sure panel resizes properly to fit shown contents
        if wx.Platform == "__WXMAC__":
            self.SetSizerAndFit(self.sz)
        self.cmd.Wrap(640)


    def ShowProgress(self, show):
        self.showProgress = show
        self._adjustLayout()

    def ShowCommandLine(self, show):
        self.showCommandLine = show
        self._adjustLayout()        
 
    def OnClickLocal(self, event):
        wx.GetApp().ShowFile(self.dataset.GetLocalDir())
        
    def OnClickRemote(self, event):
        self.dataset.DownloadFile()

class ViewPage(wx.Panel):
    ''' Abstract superclass for a view displayed in a ViewListBook.  
        Subclasses should call _initializeContents from __init__.
        A ViewPage is composed of a DataInfoPanel that displays summary
        information and a windows responsible for display.   The child
        window should implement a RefreshContents() method that properly 
        updates its contents based on the given dataset. '''
    
    def __init__(self, parent, dataset):
        wx.Panel.__init__(self, parent, -1, style=wx.NO_BORDER)
        self.parent = parent
        self.view = None
        self.dataset = dataset
        
    def GetClipboardObject(self, format=constants.CLIPBOARD_FORMAT_DEFAULT):
        return None
        
    def IsClipboardFormatAvailable(self, format):
        return False

    def OnViewActivated(self, activated):
        pass
        
    def _initializeContents(self, view):
        logging.debug("In ViewPage._initializeContents")
        self.view = view
        self.infoPanel = DataInfoPanel(self, self.dataset)
        # Keep a fixed size panel
        (self.info_w, self.info_h) = self.infoPanel.GetBestSize()
        self.Bind(ws.EVT_UPDATE_STATUS, self.OnUpdateStatus)
        self.Bind(ws.EVT_LOG_COMMAND, self.OnLogCommand)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        
    def GetData(self):
        return self.dataset
        
    def OnLogCommand(self, evt):
        pass
        
    def GetStatusString(self):
        return ""

    def RefreshContents(self):
        if not self.view: return
        self.view.RefreshContents()
        self.infoPanel.RefreshContents()
        self.OnSize(None)
        
    def RebuildView(self):
        self.RefreshContents()

    def OnUpdateStatus(self, evt):
        self.RefreshContents()
        
    # HACK: sizers weren't working right.  But I have bigger problems.
    # see comment at top of explorer.py
    def OnSize(self, evt):
        if not self.view: return
        BORDER = 0#6
        (w,h) = self.GetClientSize()
        #print "w=%d" % w
        (self.info_w, self.info_h) = self.infoPanel.GetBestSize()#GetBestSize()
        # FIXME: Windows hack - don't know what's happening here
        if wx.Platform == "__WXMSW__":
            self.info_h = self.info_h + 8
        self.infoPanel._adjustLayout()
        self.infoPanel.SetSize((w-BORDER, self.info_h-BORDER*2))
        if wx.Platform == "__WXMSW__":
            self.infoPanel.SetPosition((0,0))
        else:
            self.infoPanel.SetPosition((0, BORDER))
            
        if wx.Platform == "__WXMSW__":
            self.view.SetSize((w - BORDER, h - self.info_h))
        else:    
            self.view.SetSize((w - BORDER, h - BORDER - self.info_h)) # - 6))
        if wx.Platform == "__WXMSW__":
            self.view.SetPosition((0, self.info_h))# + BORDER))            
        else:
            self.view.SetPosition((0, self.info_h + BORDER*2))
  
# Panel in the listbook for displaying record-oriented data.  
# Wraps a RecordListCtrl.  Can be used to display anything
# that implements the DataSet interface.
class RecordViewPage(ViewPage):
   def __init__(self, parent, dataset):
       ViewPage.__init__(self, parent, dataset)
       self.recCtrl = recCtrl = RecordListCtrl(self, dataset)
       self._initializeContents(recCtrl)
       self.Bind(ws.EVT_RESULT_CHANGED, self.OnResultChanged)
     
   def RefreshContents(self):
       self.view.RefreshContents()
       self.infoPanel.RefreshContents()
       self.OnSize(None)
       
   def RebuildView(self):
       self.view.RebuildView()
       self.infoPanel.RefreshContents()
       self.OnSize(None)

   def OnResultChanged(self, evt):
       print "In RecordViewPage.OnResultChanged"
       self.recCtrl.Refresh()
       
   def GetStatusString(self):
       #print "RecordViewPage.GetStatusString"
       if not self.dataset.IsFileAvailable():
           status_str = "Running analysis - %s" % self.dataset.GetPath()
       elif not self.dataset.IsLocalFileAvailable():
           count = _format_count(self.dataset.GetFileRecordCount())
           status_str = "%s records - not yet downloaded - %s" % (count, self.dataset.GetPath())
       else:
           count = _format_count(self.dataset.GetFileRecordCount())
           status_str = "%s records - %s" % (count, self.dataset.GetPath())
       return status_str
       
   def IsClipboardFormatAvailable(self, format):       
       return format in [constants.CLIPBOARD_FORMAT_DEFAULT, 
                         constants.CLIPBOARD_FORMAT_TEXT_TABLE,
                         constants.CLIPBOARD_FORMAT_LATEX_TABLE]
       
   def GetClipboardObject(self, format=constants.CLIPBOARD_FORMAT_DEFAULT):
       return self.recCtrl.GetClipboardObject(format=format)


class GraphViewPage(ViewPage):
    def __init__(self, parent, resultset):
        ViewPage.__init__(self, parent, resultset)
        self.resultset = resultset
        self.panel = GraphPanel(self, resultset)
        self._initializeContents(self.panel)
    def OnUpdateStatus(self, evt):
        pass
    def GetStatusString(self):
        return self.dataset.GetLocalPath()
    def IsClipboardFormatAvailable(self, format):
        if format==constants.CLIPBOARD_FORMAT_DEFAULT:
            return True
        else:
            return False
    def GetClipboardObject(self, format):
        print "Creating bitmap from %s" % self.resultset.GetLocalPath()
        bmp = wx.Bitmap(self.resultset.GetLocalPath(), wx.BITMAP_TYPE_PNG)
        return wx.BitmapDataObject(bmp)


class ProblemSetViewPage(ViewPage):
    def __init__(self, parent, problemset):
        ViewPage.__init__(self, parent, problemset)
        self._initializeContents(ProblemSetPanel(self, problemset))
    def OnUpdateStatus(self, evt):
        pass
    def GetStatusString(self):
        return self.dataset.GetLocalDir()


