import wx, time, os
import oommflink_net as net
from oommflink_core import printableFileSize

#############
# CONSTANTS #
#############

#Abnormal font sizes
BIG_FONT = wx.Font(14, wx.FONTFAMILY_DEFAULT, style=wx.NORMAL, weight=wx.FONTWEIGHT_BOLD)
MBIG_FONT = wx.Font(12, wx.FONTFAMILY_DEFAULT, style=wx.NORMAL, weight=wx.FONTWEIGHT_NORMAL)
TINY_FONT = wx.Font(8, wx.FONTFAMILY_DEFAULT, style=wx.NORMAL, weight=wx.FONTWEIGHT_NORMAL)

def secsToDisplayTime(secs):
    strct = time.localtime(secs)
    return time.strftime("%H:%M %a %b %d %Y",strct)

############
# MAIN GUI #
############

class MainFrame(wx.Frame):
    def __init__(self, dataManager, GUIMustProcessLazyReactor = False):
        wx.Frame.__init__(self, None, -1, "OOMMFLink", size=(500,680))

        #Your link to the data core
        self.dataManager = dataManager

        self.dropTarget = UploadFileDropTarget(self)
        self.SetDropTarget(self.dropTarget)

        #Special on-close behavior
        self.Bind(wx.EVT_CLOSE, self.onClose)

        #For later
        self.downloadProgressBar = None
        self.lastNetstats = None
        self.connectionFailed = False

        #This timer allows users to work with textboxes without saving every char -
        #it'll wait until they've left them alone for a bit
        self.lazySaveTimer = LazySaveTimer(dataManager)


        #Is the internet our problem?
        if GUIMustProcessLazyReactor:
            #We're stupid and block, so we set up a timer to allow the Twisted reactor,
            #which *also* blocks, to be called occasionally.
            #Networking is latency-insensitive, so this should work OK
            #and is cleaner to read than a bucket of callFromThread calls.
            self.timer = wx.Timer(self, 1001)
            self.Bind(wx.EVT_TIMER, self.pingReactor, id=1001)
            self.timer.Start(150, False)

        # TODO: DropTarget for... well, all files, really.

        menubar = wx.MenuBar()
        about = wx.Menu()
        about.Append(999, 'About', 'Program information and license')
        menubar.Append(about, "About")
        self.SetMenuBar(menubar)

        self.Bind(wx.EVT_MENU, self.showAbout, id=999)

        #Now the organizational bits
        self.panel = panel = wx.Panel(self, -1)

        #As usual, the grand organization is vertical.
        sizer = wx.BoxSizer(wx.VERTICAL)

        ##
        ### Server Config + File Queue
        ##

        sectorTitle = wx.StaticText(panel, -1, "OOMMFLink")
        sectorTitle.SetFont(BIG_FONT)
        sizer.Add(sectorTitle, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10)

        #Show options for configuration of the server and port.

        serversizer = wx.GridBagSizer(12, 12)
        #And thus we make it size juuust right.
        serversizer.AddGrowableCol(2)
        serversizer.AddGrowableCol(3)
        serversizer.AddGrowableCol(4)
        serversizer.AddGrowableCol(6)
        serversizer.AddGrowableRow(1)
        #Totally gridbag.

        srvLabel = wx.StaticText(panel, -1, "Server")
        srvLabel.SetFont(MBIG_FONT)
        serversizer.Add(srvLabel, (0,0), (1,1), wx.ALIGN_CENTER)

        self.server = wx.TextCtrl(panel, 101, value=self.dataManager.conf["server"], size=(230, 20), style = wx.TE_CENTER)
        #This needs conf updates
        serversizer.Add(self.server, (0,1), (1,4), wx.EXPAND)
        self.Bind(wx.EVT_TEXT, self.updateServerAddress, id=101)

        portLabel = wx.StaticText(panel, -1, "Port")
        portLabel.SetFont(MBIG_FONT)
        serversizer.Add(portLabel, (0,5), (1,1), wx.ALIGN_CENTER)

        self.port = wx.TextCtrl(panel, 102, value=self.dataManager.conf["port"], style = wx.TE_CENTER)
        serversizer.Add(self.port, (0,6), (1,2), wx.EXPAND)
        self.Bind(wx.EVT_TEXT, self.updatePort, id=102)

        #The big one - make the list control

        self.fileList = UploadFilesListCtrl(panel, 111, self)
        serversizer.Add(self.fileList, (1,0), (1,8), wx.EXPAND)

        #Show options for configuration of username and batch name

        srvLabel = wx.StaticText(panel, -1, "User")
        srvLabel.SetFont(MBIG_FONT)
        serversizer.Add(srvLabel, (2,0), (1,1), wx.ALIGN_CENTER)

        self.username = wx.TextCtrl(panel, 103, value=self.dataManager.conf["username"], style = wx.TE_CENTER)
        #This needs conf updates
        serversizer.Add(self.username, (2,1), (1,3), wx.EXPAND)
        self.Bind(wx.EVT_TEXT, self.updateUsername, id=103)

        portLabel = wx.StaticText(panel, -1, "Batch")
        portLabel.SetFont(MBIG_FONT)
        serversizer.Add(portLabel, (2,4), (1,1), wx.ALIGN_CENTER)

        self.batchname = wx.TextCtrl(panel, 104, value="", style = wx.TE_CENTER)
        serversizer.Add(self.batchname, (2,5), (1,3), wx.EXPAND)
        self.Bind(wx.EVT_TEXT, self.updateBatchname, id=104)

        sizer.Add(serversizer, 1, wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 12)

        buttonsizer = wx.BoxSizer(wx.HORIZONTAL)

        #Hate drag and drop for some reason? We'll offer a button.
        self.getFilesButton = wx.Button(panel, 105, "Add Files", size=(130,30))
        buttonsizer.Add(self.getFilesButton, 0, wx.ALIGN_CENTER | wx.RIGHT, 40)
        self.Bind(wx.EVT_BUTTON, self.GUIGetFiles, id=105)

        self.getFilesButton = wx.Button(panel, 106, "Remove Files", size=(130,30))
        buttonsizer.Add(self.getFilesButton, 0, wx.ALIGN_CENTER | wx.RIGHT, 40)
        self.Bind(wx.EVT_BUTTON, self.fileList.removeFiles, id=106)

        #The button to do the actual upload
        self.sendBatchButton = wx.Button(panel, 107, "Upload", size=(130,30))
        buttonsizer.Add(self.sendBatchButton, 0, wx.ALIGN_CENTER)
        self.Bind(wx.EVT_BUTTON, self.uploadBatch, id=107)
        self.sendBatchButton.Disable() #And don't come back until you have a batchname.

        sizer.Add(buttonsizer, 0, wx.ALIGN_CENTER | wx.BOTTOM | wx.TOP, 10)
        sizer.Add(wx.StaticLine(panel, -1), 0, wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, 1)

        ##
        ### NetStat
        ##

        sectorTitle = wx.StaticText(panel, -1, "Server Status")
        sectorTitle.SetFont(BIG_FONT)
        sizer.Add(sectorTitle, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10)

        lastUpdateSizer = wx.BoxSizer(wx.HORIZONTAL)

        statText = wx.StaticText(panel, -1, "Last updated")
        lastUpdateSizer.Add(statText, 0, wx.ALIGN_RIGHT | wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 18)

        self.netStatTime = wx.StaticText(panel, -1, "(have never netstatted)")
        lastUpdateSizer.Add(self.netStatTime, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 18)

        self.netStatButton = wx.Button(panel, 201, "Update")
        lastUpdateSizer.Add(self.netStatButton, 0, wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL)

        self.Bind(wx.EVT_BUTTON, self.getNetstats, id=201)

        sizer.Add(lastUpdateSizer, 0,  wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10)

        self.netStatListCtrl = NetStatsListCtrl(panel, 111, self)
        
        sizer.Add(self.netStatListCtrl, 1, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 10)

        ##
        ### Finishing Up
        ##

        net.UploadManager.linkToGUI(self)

        panel.SetSizer(sizer)
        self.Show()

    def uploadBatch(self, evt):
        self.connectionFailed = False #We haven't tried; how would we know?
        #OK, let's make a cool progress bar.
        numChunks = self.dataManager.estimateChunkLoad()
        self.dataManager.upload()
        self.fileList.refreshData()
        self.downloadProgressBar = DownloadProgressBar(self, numChunks)
        self.downloadProgressBar.Show()

    def GUIGetFiles(self, evt):
        dlg = wx.FileDialog(self, "Choose Files to Upload", os.getcwd(), "", "All files (*.*)|*.*",wx.OPEN | wx.FD_MULTIPLE)
        if dlg.ShowModal() == wx.ID_OK and dlg.GetFilenames():
            for file in dlg.GetFilenames():
                self.dataManager.addFile(dlg.GetDirectory() + os.path.sep + file)
            self.fileList.refreshData()

    def updateServerAddress(self, evt):
        self.dataManager.confUpdate("server", evt.GetString(), lazy=True)
        self.lazySaveTimer.Reboot()

    def updatePort(self, evt):
        self.dataManager.confUpdate("port", evt.GetString(), lazy=True)
        self.lazySaveTimer.Reboot()

    def updateUsername(self, evt):
        self.dataManager.confUpdate("username", evt.GetString(), lazy=True)
        self.lazySaveTimer.Reboot()

    def updateBatchname(self, evt):
        self.dataManager.updateBatchname(evt.GetString())
        if len(evt.GetString()) > 0:
            self.sendBatchButton.Enable()
        else:
            self.sendBatchButton.Disable()

    def getNetstats(self, evt):
        self.connectionFailed = False #We haven't tried; how would we know?
        self.dataManager.getNetstats()
        self.netStatTime.SetLabel("...")

    def updateNetstats(self):
        self.netStatListCtrl.refreshData()
        self.netStatTime.SetLabel(time.strftime("%a %b %d %Y %H:%M",time.localtime()))

    def reportPathInUse(self, pathname):
        if self.downloadProgressBar:
            self.downloadProgressBar.abort()
        dial = wx.MessageDialog(self, "Batch name (%s) previously used! Please specify another batchname." % pathname,
                                "Warning!", wx.OK | wx.ICON_EXCLAMATION)
        dial.ShowModal()

    def announceFailedConnection(self):
        if not self.connectionFailed:
            self.connectionFailed = True
            self.netStatTime.SetLabel("Connection Bad!")
#            dial = wx.MessageDialog(self, "Could not connect to server - please verify information.",
#                                    "Connection Failure", wx.OK | wx.ICON_ERROR)
#            dial.ShowModal()
            

    def increaseUploadProgress(self):
        if self.downloadProgressBar:
            self.downloadProgressBar.quantumUpdate()

    def pingReactor(self, evt):
        net.UploadManager.netDoWork()

    def onClose(self, evt):
        #Make sure.
        net.UploadManager.reactorDown()
        self.Destroy()

    def showAbout(self, evt):
        info = wx.AboutDialogInfo()
        mydesc = """OOMMFLink is the remote client to the OOMMFq queueing system.
Its primary function is to transmit and automatically enqueue simulation files
and associated data. It may also be used to monitor running simulations in a
limited fashion."""
        mylicense = """OOMMFLink is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version."""
        info.SetName("OOMMFLink")
        info.SetVersion("0.1a")
        info.SetDescription(mydesc)
        info.SetLicense(mylicense)
        info.SetCopyright('(C) 2010 Mark Mascaro')
        info.SetWebSite('http://web.mit.edu/daigohji/projects/OOMMFTools/')
        wx.AboutBox(info)

class UploadFilesListCtrl(wx.ListCtrl):
    def __init__(self, parent, id, dataParent):
        wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT)

        #Again, we need a link to data
        self.dataManager = dataParent.dataManager

        self.InsertColumn(0, "File Name")
        self.SetColumnWidth(0, 400)
        self.InsertColumn(1, "Size", wx.LIST_FORMAT_RIGHT)
        self.SetColumnWidth(1, 50)

        self.refreshData()

    def refreshData(self):
        #Preserve selection items, since the worst that can happen is an add to the end of the list
        preserve = self.getSelected()
        #Relatively small amount of work to save an order-matching headache later.
        #Wouldn't do this to a big list; not complaining here.
        self.DeleteAllItems()

        for p, item in enumerate(self.dataManager.sendqueue):
            self.InsertStringItem(p, item)
            self.SetStringItem(p, 1, printableFileSize(item))

        for item in preserve:
            self.Select(item)

    def getSelected(self):
        #Return the indices of all selected items!
        itemIndex = -1
        selItems = []
        while True:
            itemIndex = self.GetNextSelected(itemIndex)
            if itemIndex == -1:
                break
            else:
                selItems.append(itemIndex)
        return selItems

    def removeFiles(self, evt):
        #This gets called by the parent, via the event handler, so it takes evt (ignored)
        self.dataManager.clearQueueItems(self.getSelected())
        self.refreshData()

class NetStatsListCtrl(wx.ListCtrl):
    def __init__(self, parent, id, dataparent):
        wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT)

        #Handle to the main GUI's data object
        self.dataManager = dataparent.dataManager

        self.InsertColumn(0, "Username")
        self.SetColumnWidth(0, 80)
        self.InsertColumn(1, "Filename")
        self.SetColumnWidth(1, 220)
        self.InsertColumn(2, "Start/End Time")
        self.SetColumnWidth(2, 160)

        #These watch for special cases where you need to refresh.
        self._lastRunCount = 0
        self._lastErrCount = 0

        self.refreshData()

    def refreshData(self):
        #Relatively small amount of work to save an order-matching headache later.
        #Wouldn't do this to a big list; not complaining here.
        self.DeleteAllItems()
        
        itemNum = 0
        
        for item in self.dataManager.netStats["runningSims"]:
            self.InsertStringItem(itemNum, item[0])
            self.SetStringItem(itemNum, 1, item[1])
            self.SetStringItem(itemNum, 2, "Started " + secsToDisplayTime(item[2]))
            self.SetItemBackgroundColour(itemNum, wx.GREEN)
            itemNum += 1
                        
        for item in self.dataManager.netStats["erroredSims"]:
            self.InsertStringItem(itemNum, item[0])
            self.SetStringItem(itemNum, 1, item[1])
            self.SetStringItem(itemNum, 2, "Error!")
            self.SetItemBackgroundColour(itemNum, wx.RED)
            itemNum += 1

        for item in self.dataManager.netStats["queuedSims"]:
            self.InsertStringItem(itemNum, item[0])
            self.SetStringItem(itemNum, 1, item[1])
            self.SetStringItem(itemNum, 2, "Enqueued.")
            itemNum += 1                        
                        
        for item in self.dataManager.netStats["doneSims"]:
            self.InsertStringItem(itemNum, item[0])
            self.SetStringItem(itemNum, 1, item[1])
            self.SetStringItem(itemNum, 2, "Done " + secsToDisplayTime(item[3]))
            self.SetItemBackgroundColour(itemNum, wx.LIGHT_GREY)
            itemNum += 1
            

#        #Highlight errored sims - these require attention!
#        for p, item in enumerate(self.dataManager.erroredSims):
#            #British.
#            self.SetItemBackgroundColour(p+len(self.dataManager.runningSims), wx.RED)

class UploadFileDropTarget(wx.FileDropTarget):
    def __init__(self, parent):
        wx.FileDropTarget.__init__(self)
        self.parent = parent

    def OnDropFiles(self, x, y, filenames):
        for filename in filenames:
            #For safety's sake, upload multiple files or a SINGLE DIRECTORY, no recursion
            #Anything else is blatantly destructive to file organization, and that needs to stop.

            #TODO: I suppose the entire framework could be reworked to transmit the directories down
            #with respect to the most unifying directory, rather than transmitting everything flat.
            #We'll save that for later.
            if os.path.isfile(filename) and not os.path.isdir(filename):
                self.parent.dataManager.addFile(filename)
            elif os.path.isdir(filename) and len(filenames) == 1:
                #We'll allow diving of a single directory.
                for subfile in os.listdir(filename):
                    subfile = filename + os.path.sep + subfile
                    if os.path.isfile(subfile) and not os.path.isdir(subfile):
                        self.parent.dataManager.addFile(subfile)

        #Call for a data refresh, since you've updated.
        self.parent.fileList.refreshData()


    def refreshTimes(self):
        #Sometimes, a sim changes out from under you.
        if not len(self.dataManager.runningSims) == self._lastRunCount and len(self.dataManager.erroredSims) == self._lastErrCount:
            self.refreshData()

        with qcore.QueueLock:
            for p, item in enumerate(self.dataManager.runningSims):
                #Convert the start-to-current time delta to something display-friendly
                self.SetStringItem(p, 3, qcore.secsToDuration(time.time(), item.startTime))


class DownloadProgressBar(wx.ProgressDialog):
    def __init__(self, parent, chunks):
        wx.ProgressDialog.__init__(self, "Upload Progress", "File upload in progress...", chunks, style=wx.PD_AUTO_HIDE)

        self.maxChunks = chunks
        self.chunk = 0
        self.parent = parent

    def abort(self):
        self.Update(self.maxChunks, "Aborting...")

    def quantumUpdate(self):
        self.chunk += 1
#        if self.chunk == self.maxChunks:
#            self.parent.refreshData()
        self.Update(self.chunk, "File upload in progress...")

class LazySaveTimer(wx.Timer):
    def __init__(self, alert):
        wx.Timer.__init__(self)
        self.Stop()
        self.alert = alert

    def Reboot(self):
        self.Start(2000, True)

    def Notify(self):
        self.alert.writeConf()
