import os
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from .maestro import maestroCalculateMinimumDistance
from .maestro import maestroGetLigandAnnotations
ANALYZE_DIALOG = None
[docs]def analyzeBindingSiteDialog(parent=None):
    global ANALYZE_DIALOG
    if ANALYZE_DIALOG is None:
        ANALYZE_DIALOG = AnalyzeBindingSiteDialog(parent)
    return ANALYZE_DIALOG 
[docs]class AnalyzeBindingSiteDialog(QtWidgets.QDialog):
    """
    This class implements simple pattern editor dialog.
    """
[docs]    def __init__(self, parent):
        # Initialize base class.
        QtWidgets.QDialog.__init__(self, parent)
        self.setWindowTitle("Analyze Binding Site")
        self.okButton = QtWidgets.QPushButton("Analyze")
        self.cancelButton = QtWidgets.QPushButton("Close")
        self.exportButton = QtWidgets.QPushButton(
            "Export results to CSV file...")
        self.viewer = parent
        self.group = None
        self.current_sequence = None
        self.spacer = QtWidgets.QWidget()
        self.spacer.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                  QtWidgets.QSizePolicy.Preferred)
        self.min_range = QtWidgets.QSpinBox()
        self.min_range.setValue(3)
        self.min_range.setMinimum(0)
        self.min_range.setMaximum(100)
        self.min_range.setEnabled(True)
        self.min_label = QtWidgets.QLabel("Scan from:")
        self.min_label.setEnabled(True)
        self.max_range = QtWidgets.QSpinBox()
        self.max_range.setValue(10)
        self.max_range.setMinimum(0)
        self.max_range.setMaximum(100)
        self.max_range.setEnabled(True)
        self.max_label = QtWidgets.QLabel("to:")
        self.max_label.setEnabled(True)
        self.angstroms_label = QtWidgets.QLabel("angstroms")
        self.settingsLayout = QtWidgets.QHBoxLayout()
        self.settingsLayout.addWidget(self.min_label)
        self.settingsLayout.addWidget(self.min_range)
        self.settingsLayout.addWidget(self.max_label)
        self.settingsLayout.addWidget(self.max_range)
        self.settingsLayout.addWidget(self.angstroms_label)
        self.settingsLayout.addStretch()
        self.buttonLayout = QtWidgets.QHBoxLayout()
        self.buttonLayout.addWidget(self.exportButton)
        self.buttonLayout.addWidget(self.spacer)
        self.buttonLayout.addWidget(self.okButton)
        self.lowButtonLayout = QtWidgets.QHBoxLayout()
        self.lowButtonLayout.addStretch()
        self.lowButtonLayout.addWidget(self.cancelButton)
        self.resultsTable = QtWidgets.QTableWidget(0, 4)
        self.table_font = self.resultsTable.font()
        self.table_font.setPointSize(10)
        self.resultsTable.setFont(self.table_font)
        self.tableHeader = [
            "Distance (angstroms)", "Identity %", "Similarity %", "Homology %"
        ]
        self.resultsTable.setHorizontalHeaderLabels(self.tableHeader)
        self.resultsTable.resizeColumnsToContents()
        self.resultsTable.sortItems(1, QtCore.Qt.AscendingOrder)
        self.layout = QtWidgets.QVBoxLayout(self)
        self.ligand_selector_layout = QtWidgets.QHBoxLayout()
        self.layout.addLayout(self.ligand_selector_layout)
        self.seq_label = QtWidgets.QLabel("Sequence:")
        self.ligand_selector_layout.addWidget(self.seq_label)
        self.seq_choice = QtWidgets.QComboBox(self)
        self.seq_choice.currentIndexChanged.connect(self.seqCB)
        self.ligand_selector_layout.addWidget(self.seq_choice)
        self.lig_label = QtWidgets.QLabel("Ligand:")
        self.ligand_selector_layout.addWidget(self.lig_label)
        self.ligand_choice = QtWidgets.QComboBox(self)
        self.ligand_choice.currentIndexChanged.connect(self.ligCB)
        self.ligand_selector_layout.addWidget(self.ligand_choice)
        self.ligand_selector_layout.addStretch()
        self.tab_view = QtWidgets.QTabWidget(self)
        self.tab_view.currentChanged.connect(self.currentChangedCB)
        self.layout.addWidget(self.tab_view)
        self.layout.addLayout(self.lowButtonLayout)
        self.conservation_page = QtWidgets.QWidget()
        self.conservation_page_layout = QtWidgets.QVBoxLayout(
            self.conservation_page)
        self.conservation_page.setLayout(self.conservation_page_layout)
        self.value_layout = QtWidgets.QHBoxLayout()
        self.value_layout.addWidget(
            QtWidgets.QLabel(
                "Sequence identity threshold in selected columns:"))
        self.value_layout.addStretch()
        self.value_box = QtWidgets.QLineEdit("100 %")
        self.value_box.setReadOnly(True)
        self.value_box.setAlignment(QtCore.Qt.AlignRight)
        self.value_box.setMaxLength(5)
        self.value_box.setFixedWidth(60)
        self.value_layout.addWidget(self.value_box)
        self.conservation_page_layout.addLayout(self.value_layout)
        self.similarity_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal, self)
        self.similarity_slider.setTickInterval(5)
        self.similarity_slider.setMinimum(0)
        self.similarity_slider.setMaximum(100)
        self.similarity_slider.setValue(100)
        self.similarity_slider.setTickPosition(QtWidgets.QSlider.TicksBelow)
        self.similarity_slider.valueChanged.connect(self.similaritySliderCB)
        self.conservation_page_layout.addWidget(self.similarity_slider)
        self.range_layout = QtWidgets.QHBoxLayout()
        self.range_layout.addWidget(QtWidgets.QLabel("0 %"))
        self.range_layout.addStretch()
        self.range_layout.addWidget(QtWidgets.QLabel("100 %"))
        self.conservation_page_layout.addLayout(self.range_layout)
        self.conservation_page_layout.addStretch()
        self.cons_button_layout = QtWidgets.QHBoxLayout()
        self.button_remove_similar = QtWidgets.QPushButton(
            "Remove Similar Sequences")
        self.button_remove_similar.clicked.connect(
            self.removeSimilarSequencesCB)
        self.button_remove_divergent = QtWidgets.QPushButton(
            "Remove Divergent Sequences")
        self.button_remove_divergent.clicked.connect(
            self.removeDivergentSequencesCB)
        self.cons_button_layout.addWidget(self.button_remove_similar)
        self.cons_button_layout.addWidget(self.button_remove_divergent)
        self.conservation_page_layout.addLayout(self.cons_button_layout)
        self.distance_page = QtWidgets.QWidget()
        self.distance_page_layout = QtWidgets.QVBoxLayout(self.distance_page)
        self.distance_page.setLayout(self.distance_page_layout)
        self.tab_view.addTab(self.conservation_page, "Conservation Analysis")
        self.tab_view.addTab(self.distance_page, "Distance Analysis")
        self.distance_page_layout.addWidget(self.resultsTable)
        self.distance_page_layout.addSpacing(5)
        self.distance_page_layout.addLayout(self.settingsLayout)
        self.distance_page_layout.addSpacing(5)
        self.distance_page_layout.addLayout(self.buttonLayout)
        self.resultsTable.horizontalHeader().setStretchLastSection(True)
        self.resultsTable.setWordWrap(False)
        self.cancelButton.clicked.connect(self.cancelClicked)
        self.okButton.clicked.connect(self.okClicked)
        self.exportButton.clicked.connect(self.exportClicked)
        self.resize(self.resultsTable.width() + 15, self.resultsTable.height())
        self.save_dialog = None
        self.sequence = None
        self.seq_list = []
        self.ligand_list = []
        self.similarity_value = 100 
[docs]    def currentChangedCB(self, index):
        """
        Current tab changed callback.
        """
        self.updateLigand() 
[docs]    def ligCB(self, index):
        """
        Ligand combo box callback.
        """
        if not self.ligand_list or index >= len(self.ligand_list):
            return
        if self.viewer and self.ligand_list:
            self.viewer.contents_changed = True
            self.viewer.updateView()
            self.group.unselectAll()
            self.viewer.selectLigandContacts(ligand=self.ligand_list[index])
            self.group.expandSelection()
            self.similaritySliderCB(self.similarity_value) 
[docs]    def seqCB(self, index):
        """
        Sequence combo box callback.
        """
        if not self.seq_list or index >= len(self.seq_list):
            return
        self.current_sequence = self.seq_list[index]
        self.updateLigand()
        self.similaritySliderCB(self.similarity_value) 
[docs]    def removeSimilarSequencesCB(self):
        """
        Removes selected sequences. This function assumes similar sequences are
        selected in the group, so it deletes the selected sequences in the group
        and updates the dialog.
        """
        if not self.group or not self.group.reference:
            return
        self.group.reference.selected = False
        if self.viewer:
            self.viewer.deleteSelectedSequences()
        self.update() 
[docs]    def removeDivergentSequencesCB(self):
        """
        Removes de-selected sequences. This function assumes similar sequences
        are selected in the group, so it inverts the selection, deletes the
        selected sequences in the group and updates the dialog.
        """
        if not self.group or not self.group.reference:
            return
        self.group.invertSequenceSelection()
        self.group.reference.selected = False
        if self.viewer:
            self.viewer.deleteSelectedSequences()
        self.update() 
[docs]    def similaritySliderCB(self, value):
        if not self.group:
            return
        self.value_box.setText(str(value) + " %")
        self.similarity_value = value
        self.group.unselectAllSequences()
        self.group.selectRedundantSequences(value,
                                            reference=self.current_sequence,
                                            columns=True)
        if self.viewer:
            self.viewer.updateView() 
[docs]    def setSequence(self, sequence_group, sequence):
        self.sequence = sequence
        self.group = sequence_group 
[docs]    def updateLigand(self):
        """
        Updates ligand list in the dialog. Called when either
        group contents changes or current sequence changes.
        """
        if not self.group or not self.current_sequence:
            return
        self.group.unselectAllSequences()
        self.group.unselectAll()
        self.current_sequence.selected = True
        ligand_list = maestroGetLigandAnnotations(self.current_sequence)
        self.ligand_list = ligand_list
        self.ligand_choice.clear()
        self.ligand_choice.setCurrentIndex(0)
        if not ligand_list:
            return
        for lig in ligand_list:
            self.ligand_choice.addItem(lig.short_name)
        if self.viewer and ligand_list:
            self.viewer.contents_changed = True
            self.viewer.updateView()
            self.viewer.selectLigandContacts(ligand=ligand_list[0])
            self.group.expandSelection() 
[docs]    def update(self):
        """
        View update event.
        """
        self.seq_list = []
        self.ligand_list = []
        self.seq_choice.clear()
        self.seq_choice.setCurrentIndex(0)
        if not self.group:
            return
        for seq in self.group.sequences:
            if seq.isValidProtein():
                self.seq_choice.addItem(seq.short_name + "_" + seq.chain_id)
                self.seq_list.append(seq)
        if not self.seq_list:
            return
        self.current_sequence = self.seq_list[0]
        self.updateLigand()
        self.similaritySliderCB(self.similarity_value) 
[docs]    def okClicked(self):
        """
        OK button callback
        """
        if not self.sequence:
            return
        distances = maestroCalculateMinimumDistance(self.sequence)
        self.resultsTable.clearContents()
        for row in range(self.resultsTable.rowCount()):
            self.resultsTable.removeRow(0)
        min = self.min_range.value()
        max = self.max_range.value()
        initial = [False] * len(self.sequence.residues)
        for idx, res in enumerate(self.sequence.residues):
            initial[idx] = res.selected
        for test in range(min, max + 1):
            for idx, res in enumerate(self.sequence.residues):
                if distances[idx] <= test * test:
                    res.selected = True
            self.group.expandSelectionRef()
            id = 0.0
            sim = 0.0
            hom = 0.0
            cnt = 0
            for seq in self.group.sequences:
                if seq != self.group.reference and seq.isValidProtein():
                    id += seq.calcIdentity(self.sequence,
                                           self.group.consider_gaps, True)
                    sim += self.group.reference.calcSimilarity(
                        seq, self.group.consider_gaps, True)
                    hom += self.group.reference.calcHomology(
                        seq, self.group.consider_gaps, True)
                    cnt += 1
            if cnt == 0:
                break
            cnt = cnt * 0.01  # Convert to percents
            id /= cnt
            sim /= cnt
            hom /= cnt
            row = self.resultsTable.rowCount()
            self.resultsTable.insertRow(row)
            for col in range(4):
                new_item = QTableWidgetNumericalItem()
                new_item.setFont(self.table_font)
                new_item.setFlags(QtCore.Qt.ItemIsEnabled)
                if col == 0:
                    new_item.setData(QtCore.Qt.DisplayRole, test)
                elif col == 1:
                    new_item.setData(QtCore.Qt.DisplayRole, id)
                elif col == 2:
                    new_item.setData(QtCore.Qt.DisplayRole, sim)
                elif col == 3:
                    new_item.setData(QtCore.Qt.DisplayRole, hom)
                self.resultsTable.setItem(row, col, new_item)
        self.group.deselectAll()
        for idx, res in enumerate(self.sequence.residues):
            res.selected = initial[idx]
        self.resultsTable.resizeColumnsToContents()
        self.resultsTable.resizeColumnToContents(4) 
[docs]    def cancelClicked(self):
        """
        Cancel button callback
        """
        self.hide() 
[docs]    def exportClicked(self):
        """
        Export button callback
        """
        if self.resultsTable.rowCount() < 1:
            return False
        if not self.save_dialog:
            self.save_dialog = QtWidgets.QFileDialog()
            self.save_dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
            self.save_dialog.setOption(
                QtWidgets.QFileDialog.DontUseNativeDialog)
            self.save_dialog.setWindowTitle("Export Results")
            self.save_dialog.setViewMode(QtWidgets.QFileDialog.Detail)
            self.save_dialog.setNameFilters(["CSV File (*.csv)"])
        self.save_dialog.exec_()
        if self.save_dialog.result():
            file_names = self.save_dialog.selectedFiles()
            if len(file_names) < 1:
                return False
            file_name = file_names[0]
            if len(file_name) == 0:
                return False
            name, ext = os.path.splitext(str(file_name))
            if (not ext) or (ext != ".csv"):
                file_name += ".csv"
            outfile = open(file_name, "w")
            outfile.write("Distance,Identity,Similarity,Homology\n")
            for row in range(self.resultsTable.rowCount()):
                line = ""
                for col in range(self.resultsTable.columnCount()):
                    if col > 0:
                        line += ","
                    line += self.resultsTable.item(row, col).text()
                line += "\n"
                outfile.write(line)
            outfile.close() 
[docs]    def show(self):
        """
        Show and update the dialog.
        """
        self.update()
        QtWidgets.QDialog.show(self)