'''
Created on 2 Oct 2012
 
@author: francis
'''
 
from PyQt4 import QtGui, QtCore, uic, Qt
from PyRQ.Core.QueueServer.QueueServerDetails import QueueServerDetails
from PyRQ.Ui.qt4.RRQDebugger.AboutDialog import AboutDialog
from PyRQ.Ui.qt4.RRQDebugger.FiltererEnablers import Enablers
from PyRQ.Ui.qt4.RRQDebugger.GlobalActionsModel import GlobalActionsModel
from PyRQ.Ui.qt4.RRQDebugger.InternalState import InternalState
from PyRQ.Ui.qt4.RRQDebugger.PyRQSelectorDialog import PyRQSelectorDialog
from PyRQ.Ui.qt4.RRQDebugger.RRQTab import RRQTab
from PyRQ.Ui.qt4.RRQDebugger.Scripter import Scripter
from PyRQ.Ui.qt4.RRQDebugger.SetHostDialog import SetHostDialog
from Queue import Empty
from multiprocessing.synchronize import Semaphore, RLock
import logging
import os
import pickle
import shutil
import threading
import time
import traceback
 
class RRQDebugger(QtGui.QMainWindow):
    RESOURCE_NAME = "MainWindow.ui"
    DEFAULT_SIZE_WIDTH = 1268
    DEFAULT_SIZE_HEIGHT = 648
    r"""
    Setup and configure a remote PyRQ debugging.
    """
    def __init__(self, resourcesPath, details=[], quiet=True, host="127.0.0.1"):
        super(RRQDebugger, self).__init__()
        self.resourcesPath = resourcesPath
        self.settings = QtCore.QSettings()
        self.settings.setPath(Qt.QSettings.IniFormat, Qt.QSettings.UserScope, "RRQDebugger")
        self.state = InternalState(self.settings)
        self.loggingLevel = logging.DEBUG
        self.logger = self._getLogger("Debugger", self.loggingLevel)
        self.tabs = []
        self._backupDetails = QueueServerDetails(host, 11223)
        self.author = "root"
        self.lock = RLock()
        self.qReader = None    #    threading.Thread: Multiplex over the queues and emit their results into the qt4-evt-loop.
        self.terminateQReader = False
        self.details = details
        self.quiet = quiet
        self.host = host
        self.globalActions = None
        self._createDir()
    def _createDir(self):
        self.path = os.path.realpath(".RRQDebugger")
        try:    shutil.rmtree(self.path)
        except: pass
        os.mkdir(self.path)
    def show(self):
        super(RRQDebugger, self).show()
        self.connect(self, QtCore.SIGNAL('Initialized()'), self._onMainWindowReady, QtCore.Qt.QueuedConnection)
        self.connect(self, QtCore.SIGNAL('data(PyQt_PyObject)'), self._onData, QtCore.Qt.QueuedConnection)
        #    Connect Menu items:
        self.connect(self.actionSet_Host, QtCore.SIGNAL('triggered()'), self._onSetHost, QtCore.Qt.QueuedConnection)
        self.connect(self.actionE_Xit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'), QtCore.Qt.QueuedConnection)
        self.connect(self.action_Save, QtCore.SIGNAL('triggered()'), self._onSave, QtCore.Qt.QueuedConnection)
        self.connect(self.action_Load, QtCore.SIGNAL('triggered()'), self._onLoad, QtCore.Qt.QueuedConnection)
        self.connect(self.action_Clear, QtCore.SIGNAL('triggered()'), self._onClearSettings, QtCore.Qt.QueuedConnection)
        self.connect(self.action_add, QtCore.SIGNAL('triggered()'), self._onAdd, QtCore.Qt.QueuedConnection)
        self.connect(self.action_remove, QtCore.SIGNAL('triggered()'), self._onRemove, QtCore.Qt.QueuedConnection)
        self.connect(self.action_About, QtCore.SIGNAL('triggered()'), self._onAbout, QtCore.Qt.QueuedConnection)
        self.connect(self.action_Create, QtCore.SIGNAL('triggered()'), self._onActionCreate, QtCore.Qt.QueuedConnection)
        self.connect(self.action_Close, QtCore.SIGNAL('triggered()'), self._onActionClose, QtCore.Qt.QueuedConnection)
        self.connect(self.action_Put, QtCore.SIGNAL('triggered()'), self._onActionPut, QtCore.Qt.QueuedConnection)
        self.connect(self.action_Get, QtCore.SIGNAL('triggered()'), self._onActionGet, QtCore.Qt.QueuedConnection)
        self.connect(self.action_QSize, QtCore.SIGNAL('triggered()'), self._onActionQSize, QtCore.Qt.QueuedConnection)
        self.connect(self.action_MaxQSize, QtCore.SIGNAL('triggered()'), self._onActionMaxQSize, QtCore.Qt.QueuedConnection)
        #    DBus:
        iconPath = os.path.join(self.resourcesPath, "icons", "app.png")
        self.setWindowIcon(QtGui.QIcon(iconPath))
        self.showHostAddress()
        self._createGlobalActions()
        self._createScripter()
        self.tabWidget = QtGui.QTabWidget(self)
        self.splitter2 = QtGui.QSplitter(QtCore.Qt.Vertical)
        self.splitter2.addWidget(self.globalActions)
        self.splitter2.addWidget(self.scripter)
        self.splitter1 = QtGui.QSplitter(QtCore.Qt.Horizontal)
        self.splitter1.addWidget(self.splitter2)
        self.splitter1.addWidget(self.tabWidget)
        self.splitter1.setOpaqueResize(False)
        self.splitter1.show()
        self.setCentralWidget(self.splitter1)
        self._loadUi()
        self._checkMenu()
        #    Asynchronously start the app - allows welcome screen to be shown etc.
        self.emit(QtCore.SIGNAL('Initialized()'))
    def _createScripter(self):
        self.scripter = Scripter(self)
        path = os.path.join(self.resourcesPath, Scripter.RESOURCE_NAME)
        uic.loadUi(path, self.scripter)
        self.scripter.show()
    def _createGlobalActions(self):
        self.globalActions = GlobalActionsModel(self)
        path = os.path.join(self.resourcesPath, GlobalActionsModel.RESOURCE_NAME)
        uic.loadUi(path, self.globalActions)
        self.globalActions.show()
    def _onActionCreate(self):
        tab = self.tabWidget.currentWidget()
        if tab!=None:
            tab._showActionDialog(Enablers.CREATE_START)
    def _onActionClose(self):
        tab = self.tabWidget.currentWidget()
        if tab!=None:
            tab._showActionDialog(Enablers.CLOSE_START)
    def _onActionPut(self):
        tab = self.tabWidget.currentWidget()
        if tab!=None:
            tab._showActionDialog(Enablers.PUT_START)
    def _onActionGet(self):
        tab = self.tabWidget.currentWidget()
        if tab!=None:
            tab._showActionDialog(Enablers.GET_START)
    def _onActionQSize(self):
        tab = self.tabWidget.currentWidget()
        if tab!=None:
            tab._showActionDialog(Enablers.QSIZE_START)
    def _onActionMaxQSize(self):
        tab = self.tabWidget.currentWidget()
        if tab!=None:
            tab._showActionDialog(Enablers.MAXQSIZE_START)
    def showHostAddress(self):
        self.statusbar.showMessage("host: %(H)s"%{"H":self.host})
    def closeEvent(self, event):
        if self.state.isDirty():
            #   Ask to save the details.
            mb = QtGui.QMessageBox()
            mb.setText("PyRQ config has changed.")
            mb.setInformativeText("Do you want to save the changes?")
            mb.setStandardButtons(QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel)
            mb.setDefaultButton(QtGui.QMessageBox.Save)
            result = mb.exec_()
            if result==QtGui.QMessageBox.Save:
                self._save()
                self.state.notDirty()
            elif result==QtGui.QMessageBox.Discard:
                pass
            elif result==QtGui.QMessageBox.Cancel:
                return
        self._teardown()
        event.accept()
    def _onMainWindowReady(self):
        self._onLoad()
#        import pydevd
#        pydevd.settrace(stdoutToServer = True, stderrToServer = True)
        if len(self.details)>0:
            for details in self.details:
                self._doAdd(details)
    def _onSetHost(self):
        mb = SetHostDialog(self.host, parent=self)
        path = os.path.join(self.resourcesPath, SetHostDialog.RESOURCE_NAME)
        uic.loadUi(path, baseinstance=mb)
        mb.setupUi()
        result = mb.exec_()
        if result==QtGui.QDialog.Rejected:
            return
        host = mb.host()
        if host and len(host)>0:
            self.host = host
            self.showHostAddress()
    def _onAbout(self):
        details = []
        with self.lock:
            for tab in self.tabs:
                details.append((tab.getQueueServerDetails(), tab.getName(), (tab.iface!=None)))
        mb = AboutDialog(self.resourcesPath, self.host, details, parent=self)
        path = os.path.join(self.resourcesPath, AboutDialog.RESOURCE_NAME)
        uic.loadUi(path, baseinstance=mb)
        mb.setupUi()
        mb.exec_()
    def _onAdd(self):
        try:
            details = self.tabWidget.currentWidget().getDetails()
        except:
            details = self._backupDetails
        mb = PyRQSelectorDialog(self, self.quiet, parent=self)
        path = os.path.join(self.resourcesPath, PyRQSelectorDialog.RESOURCE_NAME)
        uic.loadUi(path, baseinstance=mb)
        mb.setupUi(port=int(details.port())+1, host=details.host())
        result = mb.exec_()
        if result==QtGui.QDialog.Rejected:
            return
        details = mb.details()
        if self._backupDetails==None:
            self.details = details
        self._doAdd(details)
    def _onRemove(self):
        #    Remove the currently selected tab.
        index = self.tabWidget.currentIndex()
        tab = self.tabWidget.currentWidget()
        self.tabs.remove(tab)
        self.tabWidget.removeTab(index)
        self.state.remove(tab.getDetails())
        if len(self.tabs)==0:
            self.globalActions.pushButton_Execute.setEnabled(False)
        tab.close()
        self._checkMenu()
    def _doAdd(self, details):
#        import pydevd
#        pydevd.settrace(stdoutToServer = True, stderrToServer = True)
        try:
            self.state.add(details)
        except PyRQSelectorDialog.Failed:
            return False
        else:
            self._createTab(details)
            self.globalActions.pushButton_Execute.setEnabled(True)
            return True
    def _onLoad(self):
        #    Teardown everything and rebuild.
        self._teardown()
        self._doLoad()
    def _onClearSettings(self):
        self.settings.clear()
    def _doLoad(self):
        self._load()
        self._build()
    def _onSave(self):
        self._save()
    def _save(self):
        self.state.store()
    def _load(self):
        self.state.retrieve()
    def _saveUi(self, settings=None):
        if settings==None:
            settings = self.settings
        settings.beginGroup("ui")
        try:
            settings.setValue("backupDetails", bytearray(pickle.dumps(self._backupDetails)))
            settings.setValue("size", self.size())
            settings.setValue("pos", self.pos())
            settings.setValue("state", self.saveState())
            settings.setValue("tabIndex", self.tabWidget.currentIndex())
            settings.setValue("splitter1", self.splitter1.saveState())
            settings.setValue("splitter2", self.splitter2.saveState())
            settings.beginGroup("tabs")
            try:
                for tab in self.tabs:
                    tab.saveUi(settings)
            finally:
                self.settings.endGroup()
            settings.beginGroup("actions")
            try:
                self.globalActions.saveUi(settings)
            finally:
                settings.endGroup()
            settings.beginGroup("scripter")
            try:
                self.scripter.saveUi(settings)
            finally:
                settings.endGroup()
            settings.setValue("author", self.author)
        finally:
            settings.endGroup()
    def _loadUi(self, settings=None):
        if settings==None:
            settings = self.settings
        settings.beginGroup("ui")
        try:
            try:
                value = settings.value("backupDetails", type=bytearray)
                value = str(value)
                details = pickle.loads(value)
            except Exception, _e:
                details = self._backupDetails
            self._backupDetails = details
            self.restoreState(self.settings.value("state").toByteArray())
            self.resize(settings.value("size", defaultValue=QtCore.QSize(RRQDebugger.DEFAULT_SIZE_WIDTH, RRQDebugger.DEFAULT_SIZE_HEIGHT)).toSize())
            self.move(settings.value("pos", QtCore.QPoint(0, 0)).toPoint())
            (index, isValid) = settings.value("tab1Index", 0).toInt()
            if isValid:
                self.tabWidget.setCurrentIndex(index)
            self._restoreSplitter("splitter1")
            self._restoreSplitter("splitter2")
            settings.beginGroup("actions")
            try:
                self.globalActions.loadUi(settings)
            finally:
                settings.endGroup()
            settings.beginGroup("scripter")
            try:
                self.scripter.loadUi(settings)
            finally:
                settings.endGroup()
            self.author = settings.value("author", self.author).toString()
        finally:
            settings.endGroup()
    def _restoreSplitter(self, name):
        value = self.settings.value(name)
        if value.isValid():
            getattr(self, name).restoreState(value.toByteArray())
    def _teardown(self):
        #    Save the window dims, etc:
        self._saveUi()
        #    remove all tabs, tearing down their QueueServer.
        for tab in self.tabs:
            tab.close()
        self.tabs = []
        #    Close our QueueServer receptor multiplexer:
        try:
            self.terminateQReader = True
            self.qReader.join()
        except Exception, _e:
            pass
        self.qReader = None
        self.tabWidget.clear()
    def _build(self):
        #    based on the self.state, rebuild the UI.
        for details in self.state.getIterator():
            self._createTab(details)
    def _createTab(self, details):
        #    Create a new RRQTab
        tab = RRQTab(self, details)
        path = os.path.join(self.resourcesPath, RRQTab.RESOURCE_NAME)
        uic.loadUi(path, baseinstance=tab)
        tab.show()
        self.tabs.append(tab)
        self.tabWidget.insertTab(0, tab, tab.getName())
        self.tabWidget.setCurrentIndex(0)
        self._startQReader()
        self._checkMenu()
    def _startQReader(self):
        if self.qReader==None:
            self.terminateQReader = False
            startMutex = Semaphore(0)
            self.qReader = threading.Thread(target=self.run, args=[startMutex, self._getLogger("QReader.thread", self.loggingLevel)])
            self.qReader.setName("qReader")
            self.qReader.setDaemon(True)
            self.qReader.start()
            startMutex.acquire()
    def _getLogger(self, name, loggingLevel):
        logger = logging.getLogger("%(R)s"%{"R":name})
        try:
            if len(logger.handlers)==0:
                loggerHandler = logging.StreamHandler()
                loggerHandler.setLevel(loggingLevel)
                formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
                loggerHandler.setFormatter(formatter)
                logger.addHandler(loggerHandler)
            logger.setLevel(loggingLevel)
        except Exception, _e:
            pass
        return logger
    def run(self, startMutex, logger):
        logger.warn("Starting")
        startMutex.release()
        def process(name, data):
            #logger.info("Got debugging data: %(D)s"%{"D":data})
            return (name, data)
        try:
            while self.terminateQReader==False:
                events = []
                with self.lock:
                    for tab in self.tabs:
                        q = tab.getQ()
                        name = tab.getName()
                        try:
                            data =  q.get(block=False)
                        except Empty:
                            pass
                        except Exception, _e:
                            pass
                        else:
                            events.append(process(name, data))
                if len(events)>0:
                    self.emit(QtCore.SIGNAL("data(PyQt_PyObject)"), events)
                else:
                    time.sleep(0.1)
        except Exception, _e:
            logger.exception("d'oh:\r\n%(T)s"%{"T":traceback.format_exc()})
        finally:
            logger.warn("Terminating")
    def _onData(self, events):
        #self.logger.info("Got debugging data for %(N)s events..."%{"N":len(events)})
        for (clientName, data) in events:
            self.__onData(clientName, data)
    def __onData(self, clientName, data):
        with self.lock:
            for tab in self.tabs:
                if tab.getName()==clientName:
                    tab.data(data)
                    return
        self.logger.warn("Received data for a stale PyRQ: %(N)s"%{"N":clientName})
    def _checkMenu(self):
        #    FIXME: This doesn't work!!!
        if len(self.tabs)>0:
            self.menu_Queue.setEnabled(True)
        else:
            self.menu_Queue.setEnabled(False)