## ------------------------------------------------------------------------
## 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, sys, os, glob, textwrap, shutil
import StringIO
import config
import silk
import tools
import resultset
import pdb
import copy
import wx.lib.mixins.listctrl as listmix
from controls.previewcmdpanel import PreviewCommandPanel

# FIXME: what about those that don't take an argument?
# FIXME: this only works with arguments that take --
# parse command line string into a dictionary
def _parseArgs(val):
    opts = {}
    l  = val.split()
    for item in l:
        try:
            (a,v) = item.split("=")
            a = a.replace("--", "")
            opts[a] = v
        except:
            continue
    return opts
    
def _parseCommandLine(val):
    command = ""
    opts = {}
    l  = val.split()
    for item in l:
        try:
            (a,v) = item.split("=")
            a = a.replace("--", "")
            # Treat input and output switches differently
            if v != "$output" and v != "$input":
                opts[a] = v
                command = command + " --%s=$%s" % (a, a)
            else:
                command = command + " --%s=%s" % (a, v)
        except:
            command = command + " " + item
    return (command,opts)
    
def _unparseArgs(opts):
    args = ""
    for o in opts:
        args += "--%s=%s " % (o, opts[o])
    return args
    
class ParseError(Exception):
    def __init__(self, msg):
        Exception.__init__(self, msg)

def GetPluginsDir():        
    return os.path.join(config.getPreferencesDir(),
        "plugins")

def LoadGenericPlugin(filename_or_file):
    category = None
    commandLine = None
    defaultArgs = []
    try:
        f = open(filename_or_file)
    except:
        f = filename_or_file
    while True:
        l = f.readline()
        if not l:
            break
        l = l.rstrip("\n")
        l = l.rstrip(" ")
        l = l.lstrip(" ")
        if ":" in l:
            print "Extracting attr and val"
            attr = l[0:l.find(":")]
            val = l[len(attr)+1:len(l)]
            print "Processing plugin attribute: '%s' = '%s'" % (attr, val)
            if attr == 'tool-name':
                toolName = val
            elif attr == 'category':
                category = val
            elif attr == 'default-args':
                try:
                    defaultArgs = _parseArgs(val)
                except Exception, e:
                    print "Couldn't parse default-args"
                    raise ParseError("Couldn't default-args value: '%s'" % val)
            elif attr == 'command':
                commandLine = val
            elif attr == 'result-name':
                defaultName = val
        elif l == "":
            continue
        elif l[0] == "#":
            continue
        elif attr == "command":
            val = l.rstrip(" ")
            val = val.lstrip(" ")
            print "Appending command line text '%s' to '%s'" % (val, commandLine)
            commandLine = commandLine.rstrip("\\")
            commandLine += " "
        else:
            wx.GetApp().ShowError("Bad line '%s' in plugin file %s" % (l, filename))
            
    f.close()
    if category != None and commandLine != None:
        print "Creating Generic Plugin based on file"
        print "toolName = %s" % toolName
        print "defaultArgs = %s" % defaultArgs
        print "category = %s" % category
        print "commandLine = %s" % commandLine
        return CreateGenericPlugin(toolName, defaultArgs, category, commandLine)
    else:
        raise Exception("Incomplete plugin definition in %s" % filename)

def SaveGenericPlugin(cls, filename):
    f = open(filename, "w")
    s = cls._SerializeToString()
    f.write(s)
    f.close()
    
def DeleteGenericPlugin(cls):
    plugin_dir = os.path.join(config.getPreferencesDir(), "plugins")    
    plugin_filename = os.path.join(plugin_dir,
        cls._GetToolName().replace(" ", "_") + ".plugin")
    os.remove(plugin_filename)
    tools.UnregisterPlugin(cls)
    

def CreateGenericPlugin(toolName, defaultArgs, category, commandLine,
    defaultName="Untitled"):
    
    class OptionListCtrl(wx.ListCtrl,
           listmix.ListCtrlAutoWidthMixin,
           listmix.TextEditMixin): 
           def __init__(self, parent, ID, pos=wx.DefaultPosition,
                         size=wx.DefaultSize, style=0):
                wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
                listmix.ListCtrlAutoWidthMixin.__init__(self)
                listmix.TextEditMixin.__init__(self)
                
                self.InsertColumn(0, "Option", width=150)
                self.InsertColumn(1, "Value")
               
                self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnEdit)
                self.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.OnEdited)

           def OnEdit(self, evt):
               if evt.GetColumn() == 0:
                   evt.Veto()
                   
           def GetValue(self, item):
               option = str(self.GetItemText(item))
               value =  str(self.GetItem(item, 1).GetText())
               return (option, value)

           def OnEdited(self, evt):
               new_value = evt.GetText()
               index = evt.GetItem().GetId()
               option = self.GetItem(index, 0).GetText()
               self.GetParent().RefreshCommandLine(option, new_value)

    class GenericDialog(wx.Dialog):
        def __init__(self, parentResult, parent, *args, **kargs):
            wx.Dialog.__init__(self, parent, -1, *args, **kargs)
            self.Bind(wx.EVT_INIT_DIALOG, self.OnInitDialog) 
        # FIXME: add all the options processing stuff so it works right
        # Should handle defaults properly -- may mean fixing design 
        # for generic tool specific results

        # FIXME: for some reason values aren't showing up when dialog
        # when I do this in the constructor.
        def OnInitDialog(self, evt):
            print "In OnInitDialog"
            szDlg = wx.BoxSizer(wx.VERTICAL)
            self.opts = GenericDialog._defaultArgs
            self.indices = {}

            self.list = OptionListCtrl(self, -1,
                style=wx.LC_REPORT|wx.BORDER_NONE|wx.LC_SORT_ASCENDING,
                size=(320, 200))

            # Create name editor
            self._createNameEditor()

            # Create command line preview
            self.previewPanel = PreviewCommandPanel(self,  "Not yet initialized")

            for (i,option) in enumerate(self.opts):
                szCtrl = wx.BoxSizer(wx.HORIZONTAL)
                index = self.list.InsertStringItem(i, option)
                # FIX: convert to strings to be sure (unicode *should* work but
                # causes problems on Windows)
                self.list.SetStringItem(index, 0, str(option))
                self.list.SetStringItem(index, 1, str(self.opts[option]))
            szDlg.Add(self.list, flag=wx.ALL|wx.EXPAND, border=8)
            szDlg.Add(self.previewPanel, flag=wx.ALL|wx.EXPAND, border=8)
            szDlg.Add(self.szName, wx.ALL|wx.ALIGN_RIGHT|wx.EXPAND, border=10)
            szBtns = self.CreateButtonSizer(wx.OK|wx.CANCEL)
            self.FindWindowById(wx.ID_OK).SetLabel("Run Anaysis")
            szDlg.Add(szBtns, flag=wx.ALL|wx.ALIGN_RIGHT, border=8)
            self.SetSizerAndFit(szDlg)
            self.RefreshCommandLine()
            evt.Skip(False)

        def GetCommandLine(self):
            opts = self.GetOptions()
            return self._toolClass._GetCommandLines("$input", "$output", **opts)

        def RefreshCommandLine(self, option=None, new_value=None):
            opts = self.GetOptions()
            if option != None:
                opts[option] = new_value
            cmd = self._toolClass._GetCommandLines("$input", "$output", **opts)
            print "New command line cmd = %s" % cmd
            self.previewPanel.SetContent(cmd)

        def _createNameEditor(self):
            self.name = wx.TextCtrl(self, -1, "Untitled result", 
                validator=ws.NameValidator(), size=(320,-1))
            self.szName = szName = wx.BoxSizer(wx.HORIZONTAL)
            szName.Add(ws.SmStaticText(self, -1, "Name"),
                flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER, border=6)
            szName.Add(self.name, flag=wx.ALIGN_CENTER|wx.RIGHT|wx.LEFT|wx.EXPAND, border=10)

        def GetResultName(self):
            return self.name.GetValue()

        def SetOptions(self, opts):
            print "In SetOptions"
            self.opts = opts

        def GetOptions(self):
            opts = {}
            item = -1
            while True:
                item = self.list.GetNextItem(item, wx.LIST_NEXT_ALL, 
                    wx.LIST_STATE_DONTCARE)
                if item == -1:
                    break
                (option, value) = self.list.GetValue(item)
                #option = str(self.list.GetItemText(item)) #(item, 0).GetText())
                #value =  str(self.list.GetItem(item, 1).GetText())
                opts[option] = value
            return opts

    
    class GenericTool(tools.AnalysisTool):
        def __init__(self, **kargs):
            tools.AnalysisTool.__init__(self, **kargs)

        def CreateDialog(parentResult, parent, title):
            return GenericTool._dialogClass(parentResult, parent, title=title)
        CreateDialog = staticmethod(CreateDialog)

        def GetCategory():
            return GenericTool._category
        GetCategory = staticmethod(GetCategory)

        def CanRun(parentResult):
            if GenericTool._category == "Query":
                return True
            else:
                if not parentResult:
                    return False
                return parentResult.IsFilterResult()
        CanRun = staticmethod(CanRun)

        def _SerializeToString():
            s = ""
            s += "tool-name:%s\n" % GenericTool._toolName
            s += "category:%s\n" % GenericTool._category
            s += "command:%s\n" % GenericTool._commandLine
            s += "result-name:%s\n" % GenericTool._defaultName
            s += "default-args:%s\n" % _unparseArgs(GenericTool._defaultArgs)
            return s
        _SerializeToString = staticmethod(_SerializeToString)
        
        def _SerializeFromString(tool_str):
            f = StringIO.StringIO(tool_str)
            LoadGenericPlugin(f)
        
        def _GetToolName():
            return GenericTool._toolName
        _GetToolName = staticmethod(_GetToolName)
        
        def _GetDefaultArgs():
            return GenericTool._defaultArgs
        _GetDefaultArgs = staticmethod(_GetDefaultArgs)

        def _GetResultClass(self):
            return GenericTool._resultClass

        def _GetResultName(self):
            return GenericTool._defaultName
            
        def _GetCommandLineTemplate():
            return GenericTool._commandLine
        _GetCommandLineTemplate = staticmethod(_GetCommandLineTemplate)

        def _RunAnalysis(self, context, inputFileName, outputFileName):
            # Build the command line
            print "running with options %s" % self.opts
            #self.command = self._buildCommandLine(self.opts["command-line"],
            #    inputFileName, outputFileName)
            self.command = GenericTool._GetCommandLines(inputFileName,
                outputFileName, **self.opts)
            cmd = silk._build_commandline(context.runRemote,
                self.command, cmdDir=context.runDir)
            context._LogCommand(cmd)
            f = silk.exec_command(context, cmd + "")
            output = f.readlines()
            if f.close():
                raise silk.CommandError("Error running command", "%s\n\n%s" % (cmd, "".join(output)))
            # Get the file size (FIXME put in framework)
            if GenericTool._category in ["Query", "Filter"]:
                # Verify it's rwfilter output
                info = silk.fileinfo(context, outputFileName)            
                silk.cut(context, outputFileName, outputFileName+".asc")
            else:
                # Verify it's NOT rwfilter output
                try:
                    info = silk.fileinfo(context, outputFileName)
                except:
                    return
                raise silk.CommandError("Unexpected output",
                    "Output file appears to be an rwfilter binary file rather than a text file.")

        def _GetCommandLines(inputFileName, outputFileName, **kargs):
            cmd = GenericTool._commandLine
            if GenericTool._category != "Query":
                cmd = cmd.replace("$input", inputFileName)
            cmd = cmd.replace("$output", outputFileName)
            options = ""
            for k in kargs:
                var = "$%s" % k
                if var in cmd:
                    cmd = cmd.replace(var, kargs[k])
            return cmd + options
        _GetCommandLines = staticmethod(_GetCommandLines)
    
    # Restrict the to valid classes
    defaultResultClass = {
        "Query" : resultset.QueryResult,
        "Filter" : resultset.FilterResult,
        "Uniq" : resultset.UniqResult,
        "Count" : resultset.CountResult
    }
    # Only allow valid categories
    print defaultResultClass.keys()
    if not category in defaultResultClass.keys():
        raise Exception("Error instantiating plug-in: Unknown Plug-in category: '%s'" % category)                        
    resultClass = defaultResultClass[category]
    
    # Convert unicode to strings
    for k in defaultArgs:
        v = str(defaultArgs[k])
        del defaultArgs[k]
        k = str(k)
        defaultArgs[k] = v
    
    
    # Create a custom dialog class 
    dialogClass = copy.copy(GenericDialog)
    setattr(dialogClass, "_defaultArgs", defaultArgs)
    
    # Make a copy of the class and modify class variables
    cls = copy.copy(GenericTool)
    setattr(cls, '_category', category)
    setattr(cls, '_toolName', toolName)
    setattr(cls, '_defaultName', defaultName)
    setattr(cls, '_commandLine', commandLine)
    setattr(cls, '_resultClass', resultClass)
    setattr(cls, '_dialogClass', dialogClass)
    setattr(cls, "_defaultArgs", defaultArgs)    
    
    setattr(dialogClass, "_toolClass", cls)
    return cls

def CreateCustomPlugin(parent, parentResult):
    title = "Run custom command line"
    dlg = EditAndRunCustomToolDialog(parent, parentResult=parentResult, 
        title=title)
    dlg.CenterOnParent()
    if dlg.ShowModal() == wx.ID_OK:
        (command, defaultArgs) = _parseCommandLine(dlg.GetCommandLine())
        cls = CreateGenericPlugin(dlg.GetToolName(), defaultArgs,
            dlg.GetCategory(), command, dlg.GetDefaultName())
        return cls
    return None


def ShowCreatePluginDialog(parent, pluginClass=None):
    if pluginClass==None:
        title = "Create a command plug-in tool"
    else:
        title = "Edit a command plug-in tool"
    dlg = EditCustomToolDialog(parent, pluginClass, title=title)
    dlg.CenterOnParent()
    if dlg.ShowModal() == wx.ID_OK:
            
        plugin_dir = os.path.join(config.getPreferencesDir(), "plugins")
        plugin_filename = os.path.join(plugin_dir,
            dlg.GetToolName().replace(" ", "_") + ".plugin")

        d = wx.MessageDialog(dlg,
            textwrap.fill("This will save the tool in your plug-in directory (%s) \
and add it to your toolbar and the menu." % plugin_filename),
                    "Are you sure?",
                wx.YES_NO | wx.ICON_QUESTION)
        res = d.ShowModal()
        if res == wx.ID_YES:
            # Delete the old plugin
            if pluginClass:
                DeleteGenericPlugin(pluginClass)
            
            # Create the new plugin
            (command, defaultArgs) = _parseCommandLine(dlg.GetCommandLine())
            cls = CreateGenericPlugin(dlg.GetToolName(), defaultArgs,
                    dlg.GetCategory(), command, dlg.GetDefaultName())
            SaveGenericPlugin(cls, plugin_filename)
            return cls
    return None
    

def ManageCustomPlugins(parent):
    dlg = ManageCustomToolsDialog(parent)
    dlg.CenterOnParent()
    dlg.ShowModal()


class PluginListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
    def __init__(self, parent, pluginDir):
        wx.ListCtrl.__init__(self, parent, 
                 style = wx.LC_SINGLE_SEL|wx.LC_REPORT|wx.SIMPLE_BORDER|wx.LC_HRULES)
        self.pluginDir = pluginDir
        self.SetFont(ws.XSmFont(wx.SWISS, wx.NORMAL, wx.NORMAL))
        self.InsertColumn(0, "Name", width=150)
        self.InsertColumn(1, "File", width=150)
        listmix.ListCtrlAutoWidthMixin.__init__(self)
        self._listplugins()
        
    # Import all of the python modules in the given directory
    def _listplugins(self):
        self.DeleteAllItems()
        self._plugins = []
        files = glob.glob(os.path.join(self.pluginDir, "*.plugin"))        
        for path in files:
            print "Loading plugin '%s'" % path
            cls = LoadGenericPlugin(path)
            print "Loaded class with name '%s'" % cls._GetToolName()
            index = self.InsertStringItem(sys.maxint, cls._GetToolName())
            print "INserted string item"
            filename = os.path.split(path)[1]
            self.SetStringItem(index, 1, filename)
            print "Setting path to '%s'" % path
            self._plugins.append(cls)
            
    def Refresh(self):
        self._listplugins()
            
    def GetPlugin(self, index):
        return self._plugins[index]


class ManageCustomToolsDialog(wx.Dialog):
    def __init__(self, parent, *args, **kargs):
        wx.Dialog.__init__(self, parent, -1,
            title="Manage Plug-ins", *args, **kargs)
        self.list = PluginListCtrl(self, GetPluginsDir())
        sz = wx.GridBagSizer()
        sz.SetEmptyCellSize((0,0))
        self.szops = wx.BoxSizer(wx.HORIZONTAL)
        self.btndel = ws.SmButton(self, -1, "Delete")
        self.btnedit = ws.SmButton(self, -1, "Edit...")
        self.btnnew = ws.SmButton(self, -1, "New...")        
        self.btninstall = ws.SmButton(self, -1, "Install...")
        #self.btninstall.Enable(False)
        self.szops.Add(self.btndel, flag=wx.ALL, border=8)        
        self.szops.Add(self.btnedit, flag=wx.ALL, border=8)                        
        
        #self.szbtns = wx.BoxSizer(wx.HORIZONTAL)
        self.szops.Add(self.btnnew, flag=wx.ALL, border=8)                
        self.szops.Add(self.btninstall, flag=wx.ALL, border=8)

        szok = self.CreateButtonSizer(wx.OK)
        #self.szbtns.Add(szok, flag=wx.ALL, border=8)
                                
        sz.Add(self.list, (0,0), (1,2),
            flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=8)
        sz.Add(self.szops, (1,0), (1,2),
            flag=wx.LEFT|wx.ALIGN_RIGHT, border=4)
        sz.Add(wx.StaticLine(self, style=wx.LI_HORIZONTAL),
            (2,0), (1,2), flag=wx.EXPAND|wx.TOP|wx.BOTTOM, border=4)
        #sz.Add(self.szbtns, (3,0), (1,2),
        #    flag=wx.LEFT|wx.BOTTOM|wx.ALIGN_RIGHT, border=4)
        sz.Add(szok, (3,0), (1,2),
            flag=wx.EXPAND|wx.BOTTOM|wx.RIGHT|wx.TOP, border=8)
        sz.AddGrowableCol(0)
        sz.AddGrowableRow(0)
        self.SetSize((-1,480))
        self.SetSizer(sz)            
        self.FindWindowById(wx.ID_OK).SetDefault()
        self.FindWindowById(wx.ID_OK).SetLabel("Close")
        self.Bind(wx.EVT_BUTTON, self.OnDelete, self.btndel)
        self.Bind(wx.EVT_BUTTON, self.OnNew, self.btnnew)        
        self.Bind(wx.EVT_BUTTON, self.OnInstall, self.btninstall)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnEdit, self.list)
        self.Bind(wx.EVT_BUTTON, self.OnEdit, self.btnedit)
        self.list.Bind(wx.EVT_CHAR, self.OnChar)
        
    def OnEdit(self, evt):
        index = self.list.GetFirstSelected()
        if index == -1:
            return
        cls = self.list.GetPlugin(index)
        print "cls = %s" % cls
        cls = ShowCreatePluginDialog(self, cls)
        if cls: 
            tools.RegisterPlugin(cls._GetToolName(),
                "icons/tool.gif", cls, cls.GetCategory())
            self.list.Refresh()
            wx.GetApp().RefreshTools()

    def OnNew(self, evt):
        cls = ShowCreatePluginDialog(self)
        if cls: 
            tools.RegisterPlugin(cls._GetToolName(),
                "icons/tool.gif", cls, cls.GetCategory())
            self.list.Refresh()
            wx.GetApp().RefreshTools()
            
    def OnInstall(self, evt):
        dlg = wx.FileDialog(self, "Select plugin", 
            wildcard=("iSiLK Plugin|*.plugin"))
        rv = dlg.ShowModal()
        if rv != wx.ID_OK:
            return
        plugin_dir = os.path.join(config.getPreferencesDir(), "plugins")
        if not os.path.exists(plugin_dir):
            os.makedirs(plugin_dir)
        name = os.path.splitext(os.path.split(dlg.GetPath())[1])[0]
        new_path = os.path.join(GetPluginsDir(), name) + ".plugin"
        if os.path.exists(new_path):
            path = new_path
            i = 1
            while os.path.exists(path):
                path = os.path.join(plugin_dir,"%s_%d.plugin" % (name, i))
                i += 1
            new_path = path
        print "Copying %s to %s" % (dlg.GetPath(), new_path)
        shutil.copy(dlg.GetPath(), new_path)
        cls = LoadGenericPlugin(new_path)
        tools.RegisterPlugin(cls._GetToolName(),
            "icons/tool.gif", cls, cls.GetCategory())
        self.list.Refresh()
        wx.GetApp().RefreshTools()
            
    def OnChar(self, evt):
        print "In OnChar"
        if evt.GetKeyCode() in [wx.WXK_DELETE, wx.WXK_BACK]:
            self.DeleteSelection()
        else:
            evt.Skip(True)
            
    def OnDelete(self, evt):
        self.DeleteSelection()
        
    def DeleteSelection(self):
        index = self.list.GetFirstSelected()
        if index == -1:
            return
        cls = self.list.GetPlugin(index)
        d = wx.MessageDialog(self, 
            "This will permanently delete the plugin.",
                "Are you sure?",
            wx.YES_NO | wx.ICON_QUESTION)
        res = d.ShowModal()
        if res == wx.ID_YES:
            DeleteGenericPlugin(cls)
            self.list.Refresh()
            wx.GetApp().RefreshTools()

class EditCustomToolDialog(wx.Dialog):
    def __init__(self, parent, pluginClass=None, editName=True, *args, **kargs):
        wx.Dialog.__init__(self, parent, -1, *args, **kargs)
        self.editName = editName
        if self.editName:
            self.txtName = wx.TextCtrl(self, -1, "Untitled Result",
                validator=ws.NameValidator(), size=(480,-1))
        else:
            self.txtName = wx.TextCtrl(self, -1, "Untitled Tool",
                validator=ws.NameValidator(), size=(480,-1))
        

        cmdBox = wx.StaticBox(self, -1, "Command line template")
        self.txtCommand = ws.XSmTextCtrl(self, -1, "", size=(480,-1), style=wx.TE_MULTILINE)

        str_help = "Specify an arbitrary command line string to execute \
on the analysis system.  Use the strings $input and $output to refer \
to the input and output files respectively.  Be sure that any scripts \
and executables are in your PATH and the PATH for any users who will use \
this plug-in tool."
        help = ws.XSmStaticText(self, -1, str_help) 
        help.Wrap(475)
        # Output format choice
        categoryBox = wx.StaticBox(self, -1, "Category of tool")                
        self.rbQuery = wx.RadioButton(self, -1,
                " Query - SiLK binary output",
                style = wx.RB_GROUP)                
        self.rbFilter = wx.RadioButton(self, -1,
                " Filter-like analysis - SiLK binary output")
        self.rbUniq = wx.RadioButton(self, -1,
                " Uniq-like analysis Tool - ASCII output")
        self.rbCount = wx.RadioButton(self, -1,
                " Count-like time-series analysis - ASCII output")
        # Create buttons
        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.Button(self, wx.ID_CANCEL, "Cancel"), flag=wx.ALL, border=10)
        if pluginClass:
            self.btnCreate = btnCreate = wx.Button(self, wx.ID_OK, "Save changes") #, (110,-1), (100,-1))            
        else:
            self.btnCreate = btnCreate = wx.Button(self, wx.ID_OK, "Create plug-in") #, (110,-1), (100,-1))
        sz.Add(btnCreate, flag=wx.ALL, border=10)
        # Create is default button
        btnCreate.SetDefault()
        # Escape key or close causes cancel
        self.SetEscapeId(wx.ID_CANCEL)
        szDlg = wx.BoxSizer(wx.VERTICAL)
        if editName:
            szDlg.Add(wx.StaticText(self, -1, "Tool Name"),
                flag=wx.TOP|wx.LEFT|wx.RIGHT, border=16)   
        szDlg.Add((-1, 5), flag=wx.ALL, border=4)
        if editName:
            szDlg.Add(self.txtName,
                flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, border=16)  
        szCmd = wx.StaticBoxSizer(cmdBox, wx.VERTICAL) 
        szCmd.Add(help, flag=wx.LEFT|wx.RIGHT, border=8)
        szCmd.Add(self.txtCommand, flag=wx.ALL|wx.EXPAND, border=8)
        szDlg.Add(szCmd, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=16)

        # Categories
        szDlg.Add((-1, 5), flag=wx.ALL, border=4)
        szCat = wx.StaticBoxSizer(categoryBox, wx.VERTICAL)
        szCat.Add(self.rbQuery, flag=wx.ALL, border=8)        
        help3 = ws.XSmStaticText(self, -1, "The tool runs an rwfilter query and writes binary output to $output.")
        szCat.Add(help3, flag=wx.LEFT|wx.RIGHT, border=16)
        szCat.Add(self.rbFilter, flag=wx.ALL, border=8)        
        help2 = ws.XSmStaticText(self, -1, "The tool analyzes a binary data and writes binary output to $output.")
        szCat.Add(help2, flag=wx.LEFT|wx.RIGHT, border=16)
        szCat.Add(self.rbUniq, flag=wx.ALL, border=8)
        help_text = "Generates aggregate statistics and writes \
pipe-delimited ascii output to the file $output."
        help1 = ws.XSmStaticText(self, -1, help_text) #textwrap.fill(help_text, 80))
        help1.Wrap(475)        
        szCat.Add(help1, flag=wx.LEFT|wx.RIGHT, border=16)
        szCat.Add(self.rbCount, flag=wx.ALL, border=8)
        help_text = "Generates time-series data and writes \
pipe-delimited ascii output to the file $output."
        help1 = ws.XSmStaticText(self, -1, textwrap.fill(help_text, 80))
        szCat.Add(help1, flag=wx.LEFT|wx.RIGHT, border=16)
        szDlg.Add(szCat, flag=wx.LEFT|wx.RIGHT, border=16)
                
        if not editName:
            szDlg.Add(wx.StaticText(self, -1, "Result Name"),
                flag=wx.TOP|wx.LEFT|wx.RIGHT, border=16)   
            szDlg.Add(self.txtName,
                flag=wx.ALL|wx.EXPAND, border=16)              
        szDlg.Add(sz, flag=wx.ALL|wx.ALIGN_RIGHT, border=8)
        self.SetSizerAndFit(szDlg)
        if editName:
            self.txtName.SetFocus()
            self.txtName.SelectAll()
        else:
            self.txtCommand.SetFocus()
        self.Bind(wx.EVT_BUTTON, self.OnOK, btnCreate)
        self.rbQuery.SetValue(True)
        
        # set a flag to indicate new vs. edited plugin, default to new
        self.isEdit = False

        # Initialize from existing plugin class
        if pluginClass != None:
            self.pluginClass = pluginClass
            self.txtName.SetValue(pluginClass._GetToolName())
            self.isEdit = True
            
            # Grab the template
            cmd = pluginClass._GetCommandLineTemplate()
            # Substitute values into it
            # FIXME: centralize this code
            kargs = pluginClass._GetDefaultArgs()
            cmd = pluginClass._GetCommandLines("$input", "$output", **kargs)
            self.txtCommand.SetValue(cmd)
            if pluginClass.GetCategory() == "Query":
                self.rbQuery.SetValue(True)
            elif pluginClass.GetCategory() == "Filter":
                self.rbFilter.SetValue(True)
            elif pluginClass.GetCategory() == "Uniq":
                self.rbUniq.SetValue(True)
            else:
                self.rbCount.SetValue(True)
        
    def OnOK(self, evt):
        if self.Validate():
            if (self.IsModal()):
                self.EndModal(wx.ID_OK)
            else:
                self.SetReturnCode(wx.ID_OK)
                self.Show(False)
        
    def Validate(self):
        print "In Validate"
        if self.GetCategory() == "Query":
            if not "$output" in self.GetCommandLine():
                wx.GetApp().ShowError("Input Error",
                    "You must specify a command line to invoke that includes $output.") 
                return False
        else:
            if not "$input" in self.GetCommandLine() or not "$output" in self.GetCommandLine():
                wx.GetApp().ShowError("Input Error",
                    "You must specify a command line to invoke that includes $input and $output.")
                return False

        plugin_dir = os.path.join(config.getPreferencesDir(), "plugins")
        if not os.path.exists(plugin_dir):
            os.makedirs(plugin_dir)
        plugin_filename = os.path.join(plugin_dir,
            self.GetToolName().replace(" ", "_") + ".plugin")
        path = plugin_filename

        # If plugin file with that name exists, throw an error
        if not self.isEdited() and os.path.exists(plugin_filename):
            wx.GetApp().ShowError("Input Error",
                "A plug-in tool with this name already exists.")
            self.txtName.SetFocus()
            self.txtName.SelectAll()
            return False;

        return wx.Dialog.Validate(self)
        
    def GetCategory(self):
        if self.rbUniq.GetValue() == True:
            return "Uniq"
        elif self.rbFilter.GetValue() == True:
            return "Filter"
        else:
            print "--- Query ---"
            return "Query"
    
    def GetToolName(self):
        return self.txtName.GetValue()
        
    def GetDefaultName(self):
        return "Untitled custom tool result"
        
    def GetCommandLine(self):
        return self.txtCommand.GetValue()

    def isEdited(self):
        return self.isEdit

class EditAndRunCustomToolDialog(EditCustomToolDialog):
    def __init__(self, parent, pluginClass=None, parentResult=None,
        *args, **kargs):
        EditCustomToolDialog.__init__(self, parent, 
            pluginClass=pluginClass, editName=False, *args, **kargs)
        self.btnCreate.SetLabel("Run Analysis")
        if parentResult == None or parentResult.GetProblemSet() == parentResult:
            self.rbFilter.Enable(False)
            self.rbCount.Enable(False)
            self.rbUniq.Enable(False)
            
        


class TreeCtrlComboPopup(wx.combo.ComboPopup):

    # overridden ComboPopup methods

    def Init(self):
        self.value = None
        self.curitem = None


    def Create(self, parent):
        self.tree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT
                                |wx.TR_HAS_BUTTONS
                                |wx.TR_SINGLE
                                |wx.TR_LINES_AT_ROOT
                                |wx.SIMPLE_BORDER)
        self.tree.Bind(wx.EVT_MOTION, self.OnMotion)
        self.tree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)


    def GetControl(self):
        return self.tree


    def GetStringValue(self):
        if self.value:
            return self.tree.GetItemText(self.value)
        return ""


    def OnPopup(self):
        if self.value:
            self.tree.EnsureVisible(self.value)
            self.tree.SelectItem(self.value)


    def SetStringValue(self, value):
        # this assumes that item strings are unique...
        root = self.tree.GetRootItem()
        if not root:
            return
        found = self.FindItem(root, value)
        if found:
            self.value = found
            self.tree.SelectItem(found)


    def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
        return wx.Size(minWidth, min(200, maxHeight))


    # helpers

    def FindItem(self, parentItem, text):        
        item, cookie = self.tree.GetFirstChild(parentItem)
        while item:
            if self.tree.GetItemText(item) == text:
                return item
            if self.tree.ItemHasChildren(item):
                item = self.FindItem(item, text)
            item, cookie = self.tree.GetNextChild(parentItem, cookie)
        return wx.TreeItemId();


    def AddItem(self, value, parent=None):
        if not parent:
            root = self.tree.GetRootItem()
            if not root:
                root = self.tree.AddRoot("<hidden root>")
            parent = root

        item = self.tree.AppendItem(parent, value)
        return item


    def OnMotion(self, evt):
        # have the selection follow the mouse, like in a real combobox
        item, flags = self.tree.HitTest(evt.GetPosition())
        if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
            self.tree.SelectItem(item)
            self.curitem = item
        evt.Skip()


    def OnLeftDown(self, evt):
        # do the combobox selection
        item, flags = self.tree.HitTest(evt.GetPosition())
        if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
            self.curitem = item
            self.value = item
            self.Dismiss()
        evt.Skip()


