import os, time, threading, socket

import wx

import Preferences, Utils

#-----Toolbar-------------------------------------------------------------------

class MyToolBar(wx.ToolBar):
    def __init__(self, *_args, **_kwargs):
        wx.ToolBar.__init__(self, _kwargs['parent'], _kwargs['id'],
          style=wx.TB_HORIZONTAL | wx.NO_BORDER|Preferences.flatTools)
        self.toolLst = []
        self.toolCount = 0

    def AddTool(self, id, bitmap, toggleBitmap=wx.NullBitmap, shortHelpString='', isToggle=False):
        wx.ToolBar.AddTool(self, id, bitmap, toggleBitmap, isToggle=isToggle,
            shortHelpString=shortHelpString)

        self.toolLst.append(id)
        self.toolCount = self.toolCount + 1

    def AddTool2(self, id, bitmapname, shortHelpString='', toggleBitmap=wx.NullBitmap, isToggle=False):
        self.AddTool(id, Preferences.IS.load(bitmapname), toggleBitmap, shortHelpString, isToggle)

    def AddSeparator(self):
        wx.ToolBar.AddSeparator(self)
        self.toolLst.append(-1)
        self.toolCount = self.toolCount + 1

    def DeleteTool(self, id):
        wx.ToolBar.DeleteTool(self, id)
        self.toolLst.remove(id)
        self.toolCount = self.toolCount - 1

    def ClearTools(self):
        posLst = range(self.toolCount)
        posLst.reverse()
        for pos in posLst:
            self.DeleteToolByPos(pos)

        self.DisconnectToolIds()

        self.toolLst = []
        self.toolCount = 0

    def GetToolPopupPosition(self, id):
        margins = self.GetToolMargins()
        toolSize = self.GetToolSize()
        xPos = margins.x
        for tId in self.toolLst:
            if tId == id:
                return wx.Point(xPos, margins.y + toolSize.y)

            if tId == -1:
                xPos = xPos + self.GetToolSeparation()
            else:
                xPos = xPos + toolSize.x

        return wx.Point(0, 0)

    def PopupToolMenu(self, toolId, menu):
        self.PopupMenu(menu, self.GetToolPopupPosition(toolId))

    def DisconnectToolIds(self):
        for wid in self.toolLst:
            if wid != -1:
                self.GetParent().Disconnect(wid)

class EditorToolBar(MyToolBar):
    pass


# fields
sbfIcon, sbfBrwsBtns, sbfStatus, sbfCrsInfo, sbfProgress = range(5)

class EditorStatusBar(wx.StatusBar):
    """ Displays information about the current view. Also global stats/
        progress bar etc. """
    maxHistorySize = 250
    def __init__(self, *_args, **_kwargs):
        wx.StatusBar.__init__(self, _kwargs['parent'], _kwargs['id'], style=wx.ST_SIZEGRIP)
        self.SetFieldsCount(6)
        if wx.Platform == '__WXGTK__':
            imgWidth = 21
        else:
            imgWidth = 16

        self.SetStatusWidths([imgWidth, 36, 400, 25, 150, -1])

        rect = self.GetFieldRect(sbfIcon)
        self.img = wx.StaticBitmap(self, -1,
            Preferences.IS.load('Images/Shared/BoaLogo.png'),
            (rect.x+1, rect.y+1), (16, 16))
        self.img.Bind(wx.EVT_LEFT_DCLICK, self.OnShowHistory)

        rect = self.GetFieldRect(sbfBrwsBtns)
        #self.historyBtns = wx.SpinButton(self, -1, (rect.x+1, rect.y+1),
#                                                  (rect.width-2, rect.height-2))
        self.historyBtnBack = wx.BitmapButton(self, -1,
              Preferences.IS.load('Images/Shared/PreviousSmall.png'),
              (rect.x+1, rect.y+1), (rect.width/2-1, rect.height-2))
        self.historyBtnFwd = wx.BitmapButton(self, -1,
              Preferences.IS.load('Images/Shared/NextSmall.png'),
              (rect.x+1+rect.width/2, rect.y+1), (rect.width/2-1, rect.height-2))

        #self.historyBtns.SetToolTipString('Browse the Traceback/Error/Output window history.')
        tip = 'Browse the Traceback/Error/Output window history.'
        self.historyBtnBack.SetToolTipString(tip)
        self.historyBtnFwd.SetToolTipString(tip)
        #self.historyBtns.Bind(wx.EVT_SPIN_DOWN, self.OnErrOutHistoryBack, id=self.historyBtns.GetId())
        #self.historyBtns.Bind(wx.EVT_SPIN_UP, self.OnErrOutHistoryFwd, id=self.historyBtns.GetId())
        self.historyBtnBack.Bind(wx.EVT_BUTTON, self.OnErrOutHistoryBack, id=self.historyBtnBack.GetId())
        self.historyBtnFwd.Bind(wx.EVT_BUTTON, self.OnErrOutHistoryFwd, id=self.historyBtnFwd.GetId())

        self.erroutFrm = None

        self.progress = wx.Gauge(self, -1, 100)
        self.linkProgressToStatusBar()

        self.images = {'Info': Preferences.IS.load('Images/Shared/Info.png'),
                       'Warning': Preferences.IS.load('Images/Shared/Warning.png'),
                       'Error': Preferences.IS.load('Images/Shared/Error.png')}
        self.history = []
        self._histcnt = 0

    def destroy(self):
        self.images = None

    def setHint(self, hint, msgType='Info', ringBell=False):
        """ Show a status message in the statusbar, optionally rings a bell.

        msgType can be 'Info', 'Warning' or 'Error'
        """
        if not self.images:
            return
        self._histcnt = self._histcnt - 1
        if hint.strip():
            self.history.append( (msgType, time.strftime('%H:%M:%S',
              time.localtime(time.time())), hint, ringBell) )
        if len(self.history) > self.maxHistorySize:
            del self.history[0]

        self.SetStatusText(hint, sbfStatus)
        self.img.SetToolTipString(hint)
        self.img.SetBitmap(self.images[msgType])
        if ringBell: wx.Bell()

    def OnEditorNotification(self, event):
        self.setHint(event.message)

    logDlgs = {'Info': wx.LogMessage,
               'Warning': wx.LogWarning,
               'Error': wx.LogError}
    def OnShowHistory(self, event):
        hist = self.history[:]
        hp = HistoryPopup(self.GetParent(), hist, self.images)

    def linkProgressToStatusBar(self):
        rect = self.GetFieldRect(sbfProgress)
        self.progress.SetDimensions(rect.x+1, rect.y+1, rect.width -2, rect.height -2)

    def setColumnPos(self, value):
        self.SetStatusText(str(value), sbfCrsInfo)

    def OnErrOutHistoryBack(self, event):
        if self.erroutFrm:
            self.erroutFrm.stepBackInHistory()

    def OnErrOutHistoryFwd(self, event):
        if self.erroutFrm:
            self.erroutFrm.stepFwdInHistory()


def HistoryPopup(parent, hist, imgs):
    f = wx.MiniFrame(parent, -1, 'Editor status history', size = (350, 200))
    lc = wx.ListCtrl(f, style=wx.LC_REPORT | wx.LC_VRULES | wx.LC_NO_HEADER)
    lc.il = wx.ImageList(16, 16)
    idxs = {}
    for tpe, img in imgs.items():
        idxs[tpe] = lc.il.Add(img)
    lc.SetImageList(lc.il, wx.IMAGE_LIST_SMALL)
    lc.InsertColumn(0, 'Time')
    lc.InsertColumn(1, 'Message')
    lc.SetColumnWidth(0, 75)
    lc.SetColumnWidth(1, 750)
    for tpe, tme, msg, _bell in hist:
        lc.InsertImageStringItem(0, tme, idxs[tpe])
        lc.SetStringItem(0, 1, msg)
    f.Center()
    f.Show()
    wx.PostEvent(f, wx.SizeEvent(f.GetSize()))
    return f


#-----Model hoster--------------------------------------------------------------


wxID_MODULEPAGEVIEWCHANGE, wxID_MODULEPAGECLOSEVIEW = Utils.wxNewIds(2)

class ModulePage:
    """ Represents a notebook on a page of the top level notebook hosting
        the model instance. """
    def __init__(self, parent, model, defViews, views, idx, editor):
        self.editor = editor
        self.defViews = [(v, wx.NewId()) for v in defViews]
        self.adtViews = [(v, wx.NewId()) for v in views]
        self.viewIds = []
        self.model = model
        self.parent = parent
        self.notebook = wx.Notebook(parent, -1, style=wx.WANTS_CHARS | wx.CLIP_CHILDREN)
        self.notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChange, id=self.notebook.GetId())
        self.tIdx = idx
        self.updatePageName()

        self.windowId = wx.NewId()
        self.editor.winMenu.Append(self.windowId, self.getMenuLabel(),
              'Switch to highlighted file')
        self.editor.Bind(wx.EVT_MENU, self.editor.OnGotoModulePage, id=self.windowId)
        self.notebook.Bind(wx.EVT_MENU, self.OnDirectActionClose, id=wxID_MODULEPAGECLOSEVIEW)
        self.notebook.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)

        Class = model.__class__
##        if not editor.defaultAdtViews.has_key(cls):
##            cls = model.__class__.__bases__[0]

        tot = len(defViews) ##+ len(editor.defaultAdtViews.get(Class, []))
        if tot:
            stepsDone = 50.0
            editor.statusBar.progress.SetValue(int(stepsDone))
            step = (100 - stepsDone) / tot
            for View in defViews:
                self.addView(View)
                stepsDone = stepsDone + step
                editor.statusBar.progress.SetValue(int(stepsDone))

##            for View in editor.defaultAdtViews.get(Class, []):
##                self.addView(View)
##                stepsDone = stepsDone + step
##                editor.statusBar.progress.SetValue(int(stepsDone))

        if defViews:
            self.default = defViews[0].viewName
        else:
            self.default = None

        self.viewMenu = self.viewSelectionMenu()

        editor.statusBar.progress.SetValue(0)

    def destroy(self):
        """ Destroy all views, notepad pages and the view notebook."""
        self.disconnectEvts()
        self.editor.Disconnect(self.windowId)

        self.editor.winMenu.Delete(self.windowId)

        self.viewMenu.Destroy()

        for view in self.model.views.values():
            if view:
                view.close()
        self.notebook.DeleteAllPages()

        self.model.destroy()
        self.model = None

        self.notebook.Destroy()

##    def __del__(self):
##        print '__del__', self.__class__.__name__

    def __repr__(self):
        return '<%s: %s, %d>' %(self.__class__.__name__, os.path.basename(self.model.filename), self.tIdx)

    def updatePageName(self):
        """ Return a name that is decorated with () meaning never been saved
            and/or * meaning model modified ~ meaning view modified. """

        pageName = self.model.getPageName()

        if not self.model.savedAs: sa1, sa2 = '(', ')'
        else: sa1 = sa2 = ''

        if len(self.model.viewsModified): vm = '~'
        else: vm = ''

        if self.model.modified: m = '*'
        else: m = ''

        if self.model.transport and self.model.transport.stdAttrs['read-only']:
            ro = ' (read only)'
        else: ro = ''

        self.pageName = '%s%s%s%s%s%s%s%s' % (m, vm, sa1, pageName, ro, sa2, vm, m)

        return self.pageName

### decl getActiveView(self, idx : int) -> EditorView
    def getActiveView(self, idx=None):
        if idx is None: idx = self.notebook.GetSelection()
        if idx == -1: return None
        name = self.notebook.GetPageText(idx)
        if name and name[0] == '~': name = name[1:-1]
        try:
            return self.model.views[name]
        except KeyError:
            return None

### decl viewSelectionMenu(self) -> wx.Menu
    def viewSelectionMenu(self):
        menu = wx.Menu()
        for View, wId in self.defViews:
            menu.Append(wId, View.viewName)
        menu.AppendSeparator()
        for View, wId in self.adtViews:
            menu.Append(wId, View.viewName, '', View not in self.adtViews)

        return menu

    def connectEvts(self):
        for view, wId in self.defViews:
            self.editor.Bind(wx.EVT_MENU, self.editor.OnSwitchedToView, id=wId)
        for view, wId in self.adtViews:
            self.editor.Bind(wx.EVT_MENU, self.editor.OnToggleView, id=wId)

    def disconnectEvts(self):
        if self.model:
            for view, wId in self.defViews + self.adtViews:
                self.editor.Disconnect(wId)

    def setActiveViewsMenu(self):
        viewClss = [x.__class__ for x in self.model.views.values()]
        for view, wId in self.adtViews:
            self.viewMenu.Check(wId, view in viewClss)

    def addView(self, View, viewName=''):
        """ Add a view to the model and display it as a page in the notebook
            of view instances."""
        if not viewName: viewName = View.viewName
        if wx.Platform == '__WXGTK__':
            panel, view = Utils.wxProxyPanel(self.notebook, View, self.model)
            self.model.views[viewName] = view
            if View.docked:
                self.model.views[viewName].addToNotebook(self.notebook, viewName,
                        panel=panel)
        else:
            view = View(self.notebook, self.model)
            self.model.views[viewName] = view
            if View.docked:
                self.model.views[viewName].addToNotebook(self.notebook, viewName)

        return self.model.views[viewName]

    def refresh(self):
        pass
        # self.notebook.Refresh()

    def focus(self):
        """ Make this model page the currently selected page. """
        self.parent.SetSelection(self.tIdx)

    def removedPage(self, idx):
        """ Called on all ModulePages after a sibling ModulePage deletion.
            Decrements tIdx if bigger than idx. """
        if idx < self.tIdx:
            self.tIdx = self.tIdx - 1

    def addedPage(self, idx):
        if idx <= self.tIdx:
            self.tIdx = self.tIdx + 1

    def saveAs(self, filename):
        newFilename, success = self.editor.saveAsDlg(filename)
        if success:
            self.model.saveAs(newFilename)
            self.editor.updateModulePage(self.model, filename)
            self.editor.updateTitle()
        return success

    def saveOrSaveAs(self, forceSaveAs=False):
        model = self.model
        editor = self.editor
        if forceSaveAs or not model.savedAs:
            oldName = model.filename
            if self.saveAs(oldName) and (oldName != model.filename):
                self.rename(oldName, model.filename)

                editor.statusBar.setHint('%s saved.'%\
                      os.path.basename(model.filename))
        else:
            from Explorers.ExplorerNodes import TransportModifiedSaveError
            try:
                model.save()
            except TransportModifiedSaveError, err:
                choice = wx.MessageBox(str(err)+'\nDo you want to overwrite these '
                  'changes (Yes), reload your file (No) or cancel this operation '
                  '(Cancel)?', 'Overwrite newer file warning',
                  wx.YES_NO | wx.CANCEL | wx.ICON_WARNING)
                if choice == wx.YES:
                    model.save(overwriteNewer=True)
                elif choice == wx.NO:
                    raise TransportModifiedSaveError('Reload')
                elif choice == wx.CANCEL:
                    raise TransportModifiedSaveError('Cancel')

            editor.updateModulePage(model)
            editor.updateTitle()

            editor.statusBar.setHint('%s saved.'%\
                  os.path.basename(model.filename))

    def OnPageChange(self, event):
        viewIdx = event.GetSelection()
        if event.GetOldSelection() != viewIdx or wx.Platform == '__WXGTK__':
            self.editor.setupToolBar(viewIdx=viewIdx)
            view = self.getActiveView(viewIdx)
            if hasattr(view, 'OnPageActivated'):
                view.OnPageActivated(event)
        event.Skip()

    def OnRightDown(self, event):
        actView = self.getActiveView()

        doDirectMenuPopup = False
        for View, wid in self.adtViews:
            if isinstance(actView, View):
                doDirectMenuPopup = True
                break

        if not doDirectMenuPopup:
            from Views.EditorViews import CloseableViewMix
            if isinstance(actView, CloseableViewMix):
                doDirectMenuPopup = True

        if doDirectMenuPopup:
            directMenu = wx.Menu()
            directMenu.Append(wxID_MODULEPAGECLOSEVIEW, 'Close active view')

            self.notebook.PopupMenu(directMenu, event.GetPosition())
            directMenu.Destroy()
            return

    def OnDirectActionClose(self, event):
        actView = self.getActiveView()

        for View, wid in self.adtViews:
            if isinstance(actView, View):
                actView.deleteFromNotebook(self.default, actView.viewName)

                self.editor.mainMenu.Check(wid, False)
                return

        from Views.EditorViews import CloseableViewMix
        if isinstance(actView, CloseableViewMix):
            actView.OnClose(None)

    def rename(self, oldName, newName):
        del self.editor.modules[oldName]
        self.editor.modules[newName] = self

        item = self.editor.winMenu.FindItemById(self.windowId)
        item.SetText(self.getMenuLabel())
        self.editor.editorUpdateNotify()

    def getMenuLabel(self):
        return '%s (%s)'%(os.path.basename(self.model.filename),
                          self.model.filename)


socketPort = 50007
selectTimeout = 0.25
class Listener(threading.Thread):
    def __init__(self, editor, closed):
        #self.queue = queue
        self.editor = editor
        self.closed = closed
        threading.Thread.__init__(self)

    def run(self, host='127.0.0.1', port=socketPort):
        import socket
        from select import select
        # Open a socket and listen.
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            s.bind((host, port))
        except socket.error, err:
            self.closed.set()
            return

        s.listen(5)
        while 1:
            while 1:
                # Listen for 0.25 s, then check if closed is set. In that case,
                # end thread by returning.
                ready, dummy, dummy = select([s],[],[], selectTimeout)
                if self.closed.isSet():
                    return
                if ready:
                    break

            # Accept a connection, read the data and put it into the queue.
            conn, addr = s.accept()
            l = []
            while 1:
                data = conn.recv(1024)
                if not data: break
                l.append(data)
            name = ''.join(l)
            if name.strip():
                wx.CallAfter(self.editor.openOrGotoModule, name)
            conn.close()


def socketFileOpenServerListen(editor):
    closed = threading.Event()
    listener = Listener(editor, closed)
    listener.start()
    return closed, listener


if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = HistoryPopup(None, (), {})
    app.MainLoop()
