import os
import sys
import schrodinger
from schrodinger.infra import jobhub
# Appframework2 modules
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.ui.qt import config_dialog
from schrodinger.ui.qt import swidgets
from schrodinger.ui.qt import table_helper
from schrodinger.ui.qt.appframework2 import settings
from schrodinger.ui.qt.appframework2 import taskwidgets
from schrodinger.ui.qt.config_dialog import DISP_NAMES
from schrodinger.ui.qt.standard import constants
maestro = schrodinger.get_maestro()
#===============================================================================
# Constants
#===============================================================================
CONFIG_DIALOG_HELP = 'MM_TOPIC_JOB_START_DIALOG'
#===============================================================================
# Sub-widgets
#===============================================================================
#===============================================================================
# Job Bar
#===============================================================================
[docs]class JobBar(taskwidgets.TaskBar):
    """
    A re-implementation of the standard Schrodinger job bar. To use, simply
    instantiate and connect a job runner.
    :cvar SETTINGS_BUTTON_CLASS: the class used for the settings ("gear") button
    :vartype SETTINGS_BUTTON_CLASS: :class:JobSettingsButton
    """
    SETTINGS_BUTTON_CLASS = JobSettingsButton
[docs]    def __init__(self,
                 runner=None,
                 label_text='Job name:',
                 button_text='Run',
                 task_reset=True):
        taskwidgets.TaskBar.__init__(self,
                                     runner,
                                     label_text,
                                     button_text,
                                     task_reset=task_reset) 
[docs]    def setup(self):
        taskwidgets.TaskBar.setup(self)
        self.settings_btn = self.SETTINGS_BUTTON_CLASS(
            task_reset=self.task_reset)
        self.spinner = JobMonitorButton()
        self.toolbar = QtWidgets.QToolBar() 
[docs]    def layOut(self):
        taskwidgets.TaskBar.layOut(self)
        self.main_layout.addWidget(self.toolbar)
        if sys.platform == "darwin":
            style = self.styleSheet()
            self.setStyleSheet(style +
                               "QToolBar{background: none; border: 0px;}")
        self.toolbar.addWidget(self.name_lbl)
        self.toolbar.addWidget(self.name_le)
        self.toolbar.addWidget(self.settings_btn)
        self.toolbar.addWidget(self.spinner)
        self.main_layout.addWidget(self.start_btn) 
[docs]    def onStartPressed(self):
        if not self.settings_btn.validateAndApplyConfig():
            return False
        taskwidgets.TaskBar.onStartPressed(self)  
[docs]class MiniJobBar(JobBar):
[docs]    def setup(self):
        JobBar.setup(self)
        self.main_layout = QtWidgets.QVBoxLayout() 
[docs]    def layOut(self):
        taskwidgets.TaskUIWidget.layOut(self)
        self.main_layout.addWidget(self.name_lbl)
        self.run_layout = QtWidgets.QHBoxLayout()
        self.main_layout.addLayout(self.run_layout)
        self.run_layout.addWidget(self.name_le)
        self.run_layout.addWidget(self.start_btn)
        self.line = QtWidgets.QFrame()
        self.line.setFrameShape(QtWidgets.QFrame.HLine)
        self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.main_layout.addWidget(self.line)
        self.bottom_layout = QtWidgets.QHBoxLayout()
        self.bottom_layout.addStretch()
        self.bottom_layout.addWidget(self.settings_btn)
        self.bottom_layout.addWidget(self.spinner)
        self.main_layout.addLayout(self.bottom_layout)
        self.main_layout.setSpacing(3)
        self.bottom_layout.setSpacing(0)
        self.run_layout.setSpacing(0)  
#===============================================================================
# Job Table (Mini monitor)
#===============================================================================
IGNORE_STATUSES = set(
    ["finished", "killed", "completed", "incorporated", 'written'])
FAILED_STATUSES = set(["died", "failed"])
[docs]class JobTableColumns(taskwidgets.TaskTableColumns):
    HEADERS = ['Job Name', 'Status'] 
[docs]class JobTableModel(taskwidgets.TaskTableModel):
    COLUMN = JobTableColumns
    @table_helper.data_method(QtCore.Qt.BackgroundColorRole)
    def _backgroundColor(self, col, task, role):
        # Status job attribute, except for completed jobs, for which
        # ExitStatus attribute is returned.
        jobstatus = task.status()
        status_enum = jobhub.get_status_string_to_enum(jobstatus)
        color = jobhub.get_status_color_as_hex(status_enum)
        return QtGui.QColor(color) 
[docs]class FilteredJobTableModel(QtCore.QSortFilterProxyModel):
    """
    A simple proxy model to filter out completed jobs, which should not appear
    in the mini monitor
    """
[docs]    def __init__(self, parent=None):
        super(FilteredJobTableModel, self).__init__(parent)
        # maintain Qt4 dynamicSortFilter default
        self.setDynamicSortFilter(False) 
[docs]    def filterAcceptsRow(self, source_row, source_parent):
        index = self.sourceModel().index(source_row, JobTableColumns.STATUS,
                                         source_parent)
        return self.sourceModel().data(index) not in IGNORE_STATUSES  
#=========================================================================
# Basic ConfigDialog Class
#=========================================================================
CDSuper = settings.BaseOptionsPanel
[docs]class ConfigDialog(CDSuper):
    """
    A re-implementation of the Schrodinger Job Settings dialog.
    """
[docs]    def __init__(self, jobrunner):
        self.runner = jobrunner
        self.options = jobrunner.jobOptions()
        self.requested_action = config_dialog.RequestedAction.DoNothing
        CDSuper.__init__(self) 
[docs]    def setPanelOptions(self):
        CDSuper.setPanelOptions(self)
        self.title = 'Job Settings'
        self.help_topic = CONFIG_DIALOG_HELP 
[docs]    def setup(self):
        CDSuper.setup(self)
        self.setupOutputGroup()
        self.setupJobGroup() 
[docs]    def setupOutputGroup(self):
        if not self.options.incorporation:
            self.output_grp = None
            return
        self.output_grp = QtWidgets.QGroupBox('Output')
        self.output_layout = QtWidgets.QGridLayout()
        layout = self.output_layout
        self.output_grp.setLayout(layout)
        layout.addWidget(QtWidgets.QLabel('Incorporate:'))
        self.incorporation_selector = IncorporationSelector()
        self.setAlias('disp', self.incorporation_selector)
        layout.addWidget(self.incorporation_selector, 0, 1)
        layout.addItem(
            QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding), 0,
            2) 
[docs]    def setupJobGroup(self):
        self.job_grp = QtWidgets.QGroupBox('Job')
        self.job_layout = QtWidgets.QGridLayout()
        layout = self.job_layout
        self.job_grp.setLayout(self.job_layout)
        self.jobname_le = taskwidgets.TaskNameLineEdit(self.runner)
        self.jobname_le.textChanged.disconnect(self.jobname_le.onTextChanged)
        self.setAlias('jobname', self.jobname_le)
        layout.addWidget(QtWidgets.QLabel('Name:'), 0, 0)
        layout.addWidget(self.jobname_le, 0, 1, 1, 2)
        self.host_selector = HostSelector()
        self.host_selector.loadHosts(exclude_gpus=not self.options.gpus)
        cpus_layout = QtWidgets.QHBoxLayout()
        cpus_layout.addWidget(QtWidgets.QLabel('Total:'))
        self.cpus_sb = QtWidgets.QSpinBox()
        self.cpus_sb.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
        cpus_layout.addWidget(self.cpus_sb)
        self.cpus_units_lbl = QtWidgets.QLabel('')
        cpus_layout.addWidget(self.cpus_units_lbl)
        self.onHostChanged()
        if self.options.host:
            self.setAlias('host', self.host_selector, True)
            layout.addWidget(QtWidgets.QLabel('Host:'), 1, 0)
            layout.addWidget(self.host_selector, 1, 1)
            self.host_selector.currentIndexChanged.connect(self.onHostChanged)
        if self.options.cpus:
            self.setAlias('cpus', self.cpus_sb, True)
            layout.addLayout(cpus_layout, 1, 2) 
[docs]    def layOut(self):
        CDSuper.layOut(self)
        if self.output_grp:
            self.main_layout.addWidget(self.output_grp)
        if self.job_grp:
            self.main_layout.addWidget(self.job_grp) 
[docs]    def run(self):
        self.getConfigFromRunner()
        saved_config = self.runner.getNextConfig()
        accept = self._run()  # separate method for ease of testing
        if accept:
            self.applyConfigToRunner()
        else:
            # Revert job config if user cancels
            self.runner.setConfig(saved_config) 
    def _run(self):
        # This single line has its own method so it can easily be mocked out
        return CDSuper.run(self)
[docs]    def getConfigFromRunner(self):
        config = self.runner.getNextConfig()
        settings = {}
        for alias in self.settings_aliases:
            settings[alias] = getattr(config, alias)
        self.applyAliasedSettings(settings) 
[docs]    def applyConfigToRunner(self):
        settings = self.getAliasedSettings()
        config = self.runner.getNextConfig()
        config.applySettings(settings)
        self.runner.setConfig(config) 
[docs]    def onHostChanged(self):
        host = self.host_selector.currentHost()
        self.cpus_units_lbl.setText(host.units())
        self.cpus_sb.setRange(1, host.maxNum()) 
[docs]    def getPersistenceKey(self, alias):
        """
        Overrides the parent method so that settings keys are based on the
        job runner, not the config dialog class (which would result in multiple
        job runners sharing the same job settings). See parent class for more
        information.
        """
        module = self.runner.__module__
        classname = self.runner.__class__.__name__
        key = '%s-%s-configdialog-%s' % (module, classname, alias)
        return key  
#=========================================================================
# Config Dialog Widgets
#=========================================================================
[docs]class HostSelector(QtWidgets.QComboBox):
[docs]    def loadHosts(self, hosts=None, exclude_gpus=True):
        self.clear()
        if hosts is None:
            hosts = config_dialog.get_hosts(excludeGPGPUs=exclude_gpus)
        for host in hosts:
            self.addItem(host.label())
        self.hosts = hosts 
[docs]    def currentHost(self):
        host = self.hosts[self.currentIndex()]
        return host 
[docs]    def setCurrentHost(self, host):
        index = self.hosts.index(host)
        self.setCurrentIndex(index) 
[docs]    def af2SettingsGetValue(self):
        return self.currentHost().name 
[docs]    def af2SettingsSetValue(self, name):
        for host in self.hosts:
            if host.name == name:
                break
        else:
            self.setCurrentIndex(0)
            return
        self.setCurrentHost(host)  
[docs]class IncorporationSelector(QtWidgets.QComboBox):
[docs]    def __init__(self):
        QtWidgets.QComboBox.__init__(self)
        for key, name in DISP_NAMES.items():
            self.addItem(name, key) 
[docs]    def af2SettingsGetValue(self):
        return self.itemData(self.currentIndex()) 
[docs]    def af2SettingsSetValue(self, disp):
        index = self.findData(disp)
        if index == -1:
            raise ValueError('Disposition not found: %s' % disp)
        self.setCurrentIndex(index)  
if __name__ == "__main__":
    cd = ConfigDialog()
    cd.run()