import os
from past.utils import old_div
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
try:
    from schrodinger.infra.mm import mmfile_schrodinger_appdata_dir
except:
    mmfile_schrodinger_appdata_dir = None
try:
    from schrodinger.maestro import maestro
except:
    maestro = None
try:
    from schrodinger.protein.sequence import find_generalized_pattern
except:
    find_generalized_pattern = None
MSV_PATTERNS = None
FIND_COMBO_BOX = None
FIND_LINE_EDIT = None
FIND_TEXT_CHANGED = None
PATTERN_EDIT_DIALOG = None
DEAMIDATION_PATTERN = "Deamidation Site"
OXIDATION_PATTERN = "Oxidation Site"
GLYCOSYLATION_N_PATTERN = "Glycosylation Site"
PROTEOLYSIS_PATTERN = "Proteolysis Site"
DEAMIDATION_RGB = (255, 0, 0)
OXIDATION_RGB = (0, 255, 0)
PROTEOLYSIS_RGB = (0, 0, 255)
GLYCOSYLATION_N_RGB = (255, 0, 255)
pre_defined_pattern_colors = {
    DEAMIDATION_PATTERN: DEAMIDATION_RGB,
    GLYCOSYLATION_N_PATTERN: GLYCOSYLATION_N_RGB,
    PROTEOLYSIS_PATTERN: PROTEOLYSIS_RGB,
    OXIDATION_PATTERN: OXIDATION_RGB
}
[docs]def get_pre_defined_pattern_colors():
    """
    Return the global dictionary containing colors (rgb values) corresponding
    to the pre-defined patterns.
    This function is used by `reactive_protein_residues_gui.py`.
    """
    global pre_defined_pattern_colors
    return pre_defined_pattern_colors 
def _set_pre_defined_pattern_color(pattern_type, color):
    """
    Set the color (rgb values) for a pre-defined pattern.
    :param pattern_type: pattern type or name
    :type pattern_type: str
    :param color: rgb values of a color
    :type color: tuple(int, int, int)
    """
    global pre_defined_pattern_colors
    if pattern_type not in pre_defined_pattern_colors:
        return
    pre_defined_pattern_colors[pattern_type] = color
[docs]def writeMSVPatterns(patterns):
    if mmfile_schrodinger_appdata_dir:
        settings_path = mmfile_schrodinger_appdata_dir()
        settings_path = settings_path + os.sep + "msv"
        if not os.path.exists(settings_path):
            os.makedirs(settings_path)
        try:
            settings_file = settings_path + os.sep + "patterns"
            file = open(settings_file, "w")
            for name, pattern, hotspot, color in patterns:
                r, g, b = color
                rs = ('0' + hex(r)[2:])[-2:]
                gs = ('0' + hex(g)[2:])[-2:]
                bs = ('0' + hex(b)[2:])[-2:]
                color_str = '#' + rs + gs + bs
                if hotspot == "None":
                    file.write(name + "\n" + pattern + color_str + "\n")
                else:
                    file.write(name + "\n" + pattern + ":" + hotspot +
                               color_str + "\n")
            file.close()
        except:
            return False  # Error
    return True 
[docs]def readMSVPatterns():
    global MSV_PATTERNS
    default_patterns = [
        (DEAMIDATION_PATTERN, "x-[NQ]-[GASHMYD]", "2", DEAMIDATION_RGB),
        (GLYCOSYLATION_N_PATTERN, "N-{P}-[ST]-{P}", "1", GLYCOSYLATION_N_RGB),
        (PROTEOLYSIS_PATTERN, "x-D-x", "2", PROTEOLYSIS_RGB),
        (OXIDATION_PATTERN, "[HMCWY]", "1", OXIDATION_RGB)
    ]
    patterns = []
    if mmfile_schrodinger_appdata_dir:
        settings_path = mmfile_schrodinger_appdata_dir()
        try:
            settings_file = os.path.join(settings_path, "msv", "patterns")
            if not os.path.isfile(settings_file):
                settings_file = os.path.join(settings_path, "msv_patterns")
                if not os.path.isfile(settings_file):
                    raise RuntimeError("No settings file")
            fh = open(settings_file)
            lines = fh.readlines()
            fh.close()
            if not lines:
                raise RuntimeError("Settings file was empty")
            for index in range(old_div(len(lines), 2)):
                pattern = lines[2 * index + 1].rstrip()
                hotspot = "None"
                color = (255, 0, 0)
                if '#' in pattern:
                    try:
                        pos = pattern.find('#')
                        color_str = pattern[pos + 1:]
                        pattern = pattern[:pos]
                        color = (int(color_str[0:2],
                                     16), int(color_str[2:4],
                                              16), int(color_str[4:6], 16))
                    except:
                        color = (255, 0, 0)
                if ':' in pattern:
                    try:
                        pos = pattern.find(':')
                        hotspot = pattern[pos + 1:]
                        pattern = pattern[:pos]
                    except:
                        pass
                name = lines[2 * index].rstrip()
                patterns.append((name, pattern, hotspot, color))
                _set_pre_defined_pattern_color(name, color)
        except RuntimeError as err:
            patterns = default_patterns
            writeMSVPatterns(patterns)
    MSV_PATTERNS = patterns
    return patterns 
[docs]def findItemChanged(index):
    global MSV_PATTERNS
    global FIND_COMBO_BOX
    global FIND_LINE_EDIT
    global FIND_TEXT_CHANGED
    global PATTERN_EDIT_DIALOG
    index -= 2
    if index < 0:
        return
    if index < len(MSV_PATTERNS):
        FIND_COMBO_BOX.setCurrentIndex(0)
        FIND_LINE_EDIT.setText(MSV_PATTERNS[index][1])
        FIND_TEXT_CHANGED()
    else:
        FIND_COMBO_BOX.setCurrentIndex(0)
        PATTERN_EDIT_DIALOG.setPatterns(MSV_PATTERNS)
        PATTERN_EDIT_DIALOG.display() 
[docs]class PatternEditDialog(QtWidgets.QDialog):
    """
    This class implements simple pattern editor dialog.
    """
[docs]    def __init__(self, parent):
        # Initialize base class.
        QtWidgets.QDialog.__init__(self, parent)
        self.setWindowTitle("Edit Patterns")
        self.ok_pressed = False
        self.okButton = QtWidgets.QPushButton("OK")
        self.cancelButton = QtWidgets.QPushButton("Cancel")
        self.newButton = QtWidgets.QPushButton("Add New Pattern")
        self.deleteButton = QtWidgets.QPushButton("Delete Selected Rows")
        self.spacer = QtWidgets.QWidget()
        self.spacer.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                  QtWidgets.QSizePolicy.Preferred)
        self.buttonLayout = QtWidgets.QHBoxLayout()
        self.buttonLayout.addWidget(self.newButton)
        self.buttonLayout.addWidget(self.deleteButton)
        self.buttonLayout.addWidget(self.spacer)
        self.buttonLayout.addWidget(self.okButton)
        self.buttonLayout.addWidget(self.cancelButton)
        self.patternTable = QtWidgets.QTableWidget(0, 4)
        self.table_font = self.patternTable.font()
        self.table_font.setPointSize(10)
        self.patternTable.setFont(self.table_font)
        self.patternTable.setHorizontalHeaderLabels(
            ["Pattern Name", "Definition", "Hotspot", "Color"])
        self.patternTable.resizeColumnsToContents()
        self.patternTable.horizontalHeader().setSectionResizeMode(
            1, QtWidgets.QHeaderView.Stretch)
        self.patternTable.sortItems(1, QtCore.Qt.AscendingOrder)
        self.description = QtWidgets.QTextEdit(self)
        self.description.setReadOnly(True)
        self.description.setHtml(
            "<b>Double click a table cell to edit pattern name or definition.</b><br><br> \
<b>Pattern syntax</b><br><br> \
The patterns are defined using extended PROSITE syntax:<ul> \
<li> standard IUPAC one-letter codes are used for all amino acids \
<li> each element in a pattern is separated using '-' symbol \
<li> symbol 'x' is used for position where any amino acid is accepted \
<li> ambiguities are listed using the acceptable amino acids between \
square brackets, e.g. [ACT] means Ala, Cys or Thr \
<li>amino acids not accepted for a given position are indicated \
by listing them between curly brackets, e.g. {GP} means 'not Gly and not Pro' \
<li>repetition is indicated using parentheses, e.g. A(3) means Ala-Ala-Ala, \
x(2,4) means between 2 to 4 any residues \
<li>the following lowercase characters can be used as additional flags \
<ul> \
<li>'x': any amino acid \
<li>'a': acidic residue: [DE] \
<li>'b': basic residue: [KR] \
<li>'o': hydrophobic residue: [ACFILPWVY] \
<li>'p': aromatic residue: [WYF] \
<li>'s': solvent exposed residue \
<li>'h': helical residue \
<li>'e': extended residue \
<li>'f': flexible residue \
</ul></ul><br> \
<b>Pattern examples</b> \
<ul> \
<li> N-{P}-[ST] : Asn-X-Ser or Thr (X != Pro) \
<li> N[sf]-{P}[sf]-[ST][sf] : as above, but all residues flexible OR solvent exposed \
<li> Nsf-{P}sf-[ST]sf : as above, but all residues flexible AND solvent exposed \
<li> Ns{f} : Asn solvent exposed AND not in flexible region \
<li> N[s{f}] : Asn solvent exposed OR not in flexible region \
<li> [ab]{K}{s}f : acidic OR basic, with exception of Lys flexible AND not solvent exposed \
<li> Ahe : Ala helical AND extended - no match possible \
<li> A[he] : Ala helical OR extended \
<li> A{he} : Ala not helical nor extended \
<li> [ST] : Ser OR Thr \
<li> ST : Ser AND Thr - no match possible \
</ul>")
        self.layout = QtWidgets.QVBoxLayout(self)
        self.layout.addWidget(self.patternTable)
        self.layout.addWidget(self.description)
        self.layout.addLayout(self.buttonLayout)
        self.patternTable.resizeColumnToContents(1)
        self.patternTable.horizontalHeader().setStretchLastSection(False)
        self.patternTable.setWordWrap(False)
        self.patternTable.itemSelectionChanged.connect(self.selectionChanged)
        self.patternTable.cellDoubleClicked.connect(self.cellDoubleClicked)
        self.cancelButton.clicked.connect(self.cancelClicked)
        self.okButton.clicked.connect(self.okClicked)
        self.newButton.clicked.connect(self.addNewClicked)
        self.deleteButton.clicked.connect(self.deleteClicked)
        self.resize(self.patternTable.width() + 15, self.patternTable.height())
        self.selecting = False 
[docs]    def selectionChanged(self):
        if self.selecting:
            return
        self.selecting = True
        for item in self.patternTable.selectedItems():
            row = item.row()
            # Select the entire row.
            self.patternTable.setRangeSelected(
                QtWidgets.QTableWidgetSelectionRange(
                    row, 0, row,
                    self.patternTable.columnCount() - 1), True)
        self.selecting = False 
[docs]    def cellDoubleClicked(self, row, column):
        """
        User double clicked a cell.
        """
        global MSV_PATTERNS
        if column == 3:
            name, pattern, hotspot, color = MSV_PATTERNS[row]
            r, g, b = color
            color = QtWidgets.QColorDialog.getColor(
                initial=QtGui.QColor(r, g, b))
            if color:
                color = (color.red(), color.green(), color.blue())
                r, g, b = color
                _set_pre_defined_pattern_color(name, color)
                new_item = QtWidgets.QTableWidgetItem("")
                new_item.setData(QtCore.Qt.DecorationRole,
                                 QtGui.QColor(r, g, b))
                new_item.setFlags(QtCore.Qt.ItemIsSelectable |
                                  QtCore.Qt.ItemIsEnabled)
                self.patternTable.setItem(row, 3, new_item)
                MSV_PATTERNS[row] = (name, pattern, hotspot, color)
                writeMSVPatterns(MSV_PATTERNS) 
[docs]    def okClicked(self):
        """
        OK button callback
        """
        # Validate patterns
        valid = True
        for index in range(self.patternTable.rowCount()):
            name = str(self.patternTable.item(index, 0).text())
            pattern = str(self.patternTable.item(index, 1).text())
            hotspot = str(self.patternTable.item(index, 2).text())
            self.patternTable.item(index, 1).setForeground(QtCore.Qt.black)
            if hotspot != "None":
                try:
                    num = int(hotspot)
                except:
                    self.patternTable.setRangeSelected(
                        QtWidgets.QTableWidgetSelectionRange(
                            index, 0, index,
                            self.patternTable.columnCount() - 1), False)
                    self.patternTable.item(index,
                                           2).setForeground(QtCore.Qt.red)
                    valid = False
                    break
            if find_generalized_pattern(None, pattern,
                                        validate_pattern=True) != True:
                self.patternTable.setRangeSelected(
                    QtWidgets.QTableWidgetSelectionRange(
                        index, 0, index,
                        self.patternTable.columnCount() - 1), False)
                self.patternTable.item(index, 1).setForeground(QtCore.Qt.red)
                valid = False
                break
        if not valid:
            QtWidgets.QMessageBox.critical(
                self, "Invalid Pattern Syntax",
                "Please correct the pattern marked red.")
            return
        self.hide()
        self.ok_pressed = True 
[docs]    def deleteClicked(self):
        """
        Delete button callback
        """
        rows = self.patternTable.selectionModel().selectedRows()
        while rows:
            self.patternTable.removeRow(rows[0].row())
            rows = self.patternTable.selectionModel().selectedRows()
        pattern_list = self.createPatternList()
        updateFindComboBox(pattern_list) 
[docs]    def cancelClicked(self):
        """
        Cancel button callback
        """
        self.hide()
        self.ok_pressed = False 
[docs]    def addNewClicked(self):
        """
        Add new callback
        """
        row = self.patternTable.rowCount()
        self.patternTable.insertRow(row)
        new_item = QtWidgets.QTableWidgetItem("New Pattern")
        new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled |
                          QtCore.Qt.ItemIsEditable)
        self.patternTable.setItem(row, 0, new_item)
        new_item = QtWidgets.QTableWidgetItem("A-A-A")
        new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled |
                          QtCore.Qt.ItemIsEditable)
        self.patternTable.setItem(row, 1, new_item)
        new_item = QtWidgets.QTableWidgetItem("None")
        new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled |
                          QtCore.Qt.ItemIsEditable)
        self.patternTable.setItem(row, 2, new_item)
        new_item = QtWidgets.QTableWidgetItem("")
        new_item.setData(QtCore.Qt.DecorationRole, QtGui.QColor(255, 0, 0))
        new_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
        self.patternTable.setItem(row, 3, new_item)
        pattern_list = self.createPatternList()
        updateFindComboBox(pattern_list) 
[docs]    def setPatterns(self, pattern_list):
        self.patternTable.clear()
        self.patternTable.setRowCount(0)
        row = 0
        self.patternTable.setHorizontalHeaderLabels(
            ["Pattern Name", "Definition", "Hotspot", "Color"])
        for index, item in enumerate(pattern_list):
            name, pattern, hotspot, color = item
            self.patternTable.insertRow(row)
            new_item = QtWidgets.QTableWidgetItem(name)
            new_item.setFlags(QtCore.Qt.ItemIsSelectable |
                              QtCore.Qt.ItemIsEnabled |
                              QtCore.Qt.ItemIsEditable)
            self.patternTable.setItem(row, 0, new_item)
            new_item = QtWidgets.QTableWidgetItem(pattern)
            new_item.setFlags(QtCore.Qt.ItemIsSelectable |
                              QtCore.Qt.ItemIsEnabled |
                              QtCore.Qt.ItemIsEditable)
            self.patternTable.setItem(row, 1, new_item)
            new_item = QtWidgets.QTableWidgetItem(hotspot)
            new_item.setFlags(QtCore.Qt.ItemIsSelectable |
                              QtCore.Qt.ItemIsEnabled |
                              QtCore.Qt.ItemIsEditable)
            self.patternTable.setItem(row, 2, new_item)
            r, g, b = color
            new_item = QtWidgets.QTableWidgetItem("")
            new_item.setData(QtCore.Qt.DecorationRole, QtGui.QColor(r, g, b))
            new_item.setFlags(QtCore.Qt.ItemIsSelectable |
                              QtCore.Qt.ItemIsEnabled)
            self.patternTable.setItem(row, 3, new_item)
            row += 1
        self.patternTable.resizeColumnToContents(0) 
[docs]    def display(self):
        """
        Brings up the modal dialog. Returns None if cancelled,
        or the modified FeaturePattern, if OK.
        """
        self.exec()
        if self.ok_pressed:
            self.ok_pressed = False
            pattern_list = self.createPatternList()
            updateFindComboBox(pattern_list)
            return pattern_list
        # Cancel pressed:
        return MSV_PATTERNS 
[docs]    def createPatternList(self):
        global MSV_PATTERNS
        pattern_list = []
        for index in range(self.patternTable.rowCount()):
            name = str(self.patternTable.item(index, 0).text())
            pattern = str(self.patternTable.item(index, 1).text())
            hotspot = str(self.patternTable.item(index, 2).text())
            color = QtGui.QColor(
                self.patternTable.item(index, 3).data(QtCore.Qt.DecorationRole))
            color = (color.red(), color.green(), color.blue())
            pattern_list.append((name, pattern, hotspot, color))
        MSV_PATTERNS = pattern_list
        writeMSVPatterns(pattern_list)
        return pattern_list  
[docs]def updateFindComboBox(pattern_list):
    global FIND_COMBO_BOX
    if FIND_COMBO_BOX is None:
        return
    name_list = [x[0] for x in pattern_list]
    FIND_COMBO_BOX.clear()
    FIND_COMBO_BOX.addItem("Select Pattern...")
    FIND_COMBO_BOX.addItems(name_list)
    FIND_COMBO_BOX.addItem("Edit Patterns...")
    FIND_COMBO_BOX.insertSeparator(1)
    FIND_COMBO_BOX.insertSeparator(len(name_list) + 2)