"""Protein structure viewer (ProtView) plugin."""

from pathlib import Path
import json
from typing import Optional

from qtpy import QtWidgets, QtCore, QtGui

try:  # moview can run inside or outside chisurf
    import chisurf
    import chisurf.settings as _cs_settings
    from chisurf.gui.widgets.general import open_files as _cs_open_files
    from chisurf.structure import Structure as _ChiSurfStructure
except Exception:  # pragma: no cover - standalone moview
    chisurf = None  # type: ignore[assignment]
    _cs_settings = None
    _cs_open_files = None
    _ChiSurfStructure = None

from .config import _DISPLAY_CONFIG
from .view import ProtView
from .ss import assign_ss_c3_from_atoms, assign_ss_c3_from_file


# Plugin name as it appears in the Plugins menu
# The "Structure:" prefix groups it with other structure tools.
name = "Structure:ProtView (protein viewer)"


def _fallback_open_files(parent: Optional[QtWidgets.QWidget] = None) -> list[str]:
    """Fallback file dialog when chisurf's open_files helper is not available."""

    files, _ = QtWidgets.QFileDialog.getOpenFileNames(
        parent,
        "Open structure file",
        "",
        "Structure files (*.pdb *.ent *.gro *.cif *.mmcif);;All files (*.*)",
    )
    return [str(f) for f in files]


def _simple_load_pdb_coords(path: str):
    """Very small PDB/HETATM parser returning a list of (x, y, z) tuples.

    This is only used in standalone moview mode when chisurf's Structure and
    IMP-based readers are not available. It ignores all metadata and only
    extracts Cartesian coordinates.
    """

    coords: list[tuple[float, float, float]] = []
    with open(path, "rt", encoding="utf-8", errors="ignore") as fh:
        for line in fh:
            if not (line.startswith("ATOM") or line.startswith("HETATM")):
                continue
            try:
                x = float(line[30:38])
                y = float(line[38:46])
                z = float(line[46:54])
            except ValueError:
                continue
            coords.append((x, y, z))
    if not coords:
        raise ValueError(f"No atom coordinates found in {path!r}")
    return coords


_SEQ_COLOR_ROLE = QtCore.Qt.UserRole + 100


class ProtViewPluginWindow(QtWidgets.QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("ProtView - Protein Viewer")

        self._structure: object | None = None

        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(4, 4, 4, 4)
        layout.setSpacing(4)

        # ------------------------------------------------------------------
        # Toolbar (all buttons as tool buttons)
        # ------------------------------------------------------------------
        toolbar = QtWidgets.QHBoxLayout()

        self.button_open = QtWidgets.QToolButton(self)
        self.button_open.setText("Open")
        self.button_open.setToolTip("Open structure file")

        self.button_plane = QtWidgets.QToolButton(self)
        self.button_plane.setText("Plane")
        self.button_plane.setCheckable(True)
        self.button_plane.setChecked(False)
        self.button_plane.setToolTip("Toggle reference plane")

        self.button_color = QtWidgets.QToolButton(self)
        self.button_color.setText("Color AA")
        self.button_color.setCheckable(True)
        self.button_color.setChecked(False)
        self.button_color.setToolTip("Color amino acids by residue type")

        self.button_color_ss = QtWidgets.QToolButton(self)
        self.button_color_ss.setText("Color SS")
        self.button_color_ss.setCheckable(True)
        self.button_color_ss.setChecked(False)
        self.button_color_ss.setToolTip(
            "Color 3D geometry by secondary structure (helix/strand/coil)"
        )

        # Global representation toggles (independent)
        self.button_rep_cartoon = QtWidgets.QToolButton(self)
        self.button_rep_cartoon.setText("Cartoon")
        self.button_rep_cartoon.setCheckable(True)
        self.button_rep_cartoon.setChecked(True)
        self.button_rep_cartoon.setToolTip("Toggle cartoon ribbon (CA tube)")
        self.button_rep_cartoon.setObjectName("protviewRepCartoon")

        self.button_rep_atoms = QtWidgets.QToolButton(self)
        self.button_rep_atoms.setText("Atoms")
        self.button_rep_atoms.setCheckable(True)
        self.button_rep_atoms.setChecked(False)
        self.button_rep_atoms.setToolTip("Toggle atoms/balls representation")
        self.button_rep_atoms.setObjectName("protviewRepAtoms")

        self.button_rep_sticks = QtWidgets.QToolButton(self)
        self.button_rep_sticks.setText("Sticks")
        self.button_rep_sticks.setCheckable(True)
        self.button_rep_sticks.setChecked(False)
        self.button_rep_sticks.setToolTip("Toggle sticks (bond) representation")
        self.button_rep_sticks.setObjectName("protviewRepSticks")

        self.button_rep_trace = QtWidgets.QToolButton(self)
        self.button_rep_trace.setText("Trace")
        self.button_rep_trace.setCheckable(True)
        self.button_rep_trace.setChecked(False)
        self.button_rep_trace.setToolTip("Toggle CA trace line")
        self.button_rep_trace.setObjectName("protviewRepTrace")

        self.button_surface = QtWidgets.QToolButton(self)
        self.button_surface.setText("Surf")
        self.button_surface.setCheckable(True)
        self.button_surface.setChecked(False)
        self.button_surface.setToolTip("Toggle surface representation")

        self.button_info = QtWidgets.QToolButton(self)
        self.button_info.setText("Info")
        self.button_info.setCheckable(True)
        self.button_info.setChecked(True)
        self.button_info.setToolTip("Toggle system info panel")

        self.button_display_cfg = QtWidgets.QToolButton(self)
        self.button_display_cfg.setText("Cfg")
        self.button_display_cfg.setToolTip(
            "Open ProtView display configuration JSON in the system editor"
        )

        toolbar.addWidget(self.button_open)
        toolbar.addWidget(self.button_plane)
        toolbar.addWidget(self.button_color)
        toolbar.addWidget(self.button_color_ss)
        toolbar.addWidget(self.button_rep_cartoon)
        toolbar.addWidget(self.button_rep_atoms)
        toolbar.addWidget(self.button_rep_sticks)
        toolbar.addWidget(self.button_rep_trace)
        toolbar.addWidget(self.button_surface)
        toolbar.addWidget(self.button_info)
        toolbar.addWidget(self.button_display_cfg)
        toolbar.addStretch(1)
        layout.addLayout(toolbar)

        # ------------------------------------------------------------------
        # Sequence viewer (per-residue selection)
        # ------------------------------------------------------------------
        self.seq_list = QtWidgets.QListWidget(self)
        self.seq_list.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
        font = QtGui.QFont("Courier New")
        font.setPointSize(9)
        self.seq_list.setFont(font)
        # Show amino acids horizontally in a single, compact row with
        # horizontal scrolling so the sequence is easier to read while
        # keeping per-residue selection.
        self.seq_list.setFlow(QtWidgets.QListView.LeftToRight)
        self.seq_list.setWrapping(False)
        self.seq_list.setUniformItemSizes(True)
        self.seq_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.seq_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        # Keep the sequence band compact yet tall enough for two lines
        self.seq_list.setFixedHeight(54)
        self.seq_list.setStyleSheet(
            "QListWidget::item:selected{background: transparent; color: inherit;}"
            "QListWidget::item:selected:!active{background: transparent; color: inherit;}"
        )
        layout.addWidget(self.seq_list)

        # ------------------------------------------------------------------
        # Viewer + system-info panel (split horizontally)
        # ------------------------------------------------------------------
        self.viewer = ProtView(self)
        layout.addWidget(self.viewer, 1)

        # Keep sequence selection and 3D picking in sync.
        try:
            self.viewer.residueSelectionChanged.connect(
                self.on_viewer_residue_selection_changed
            )
        except Exception:
            pass

        # ------------------------------------------------------------------
        # Connections
        # ------------------------------------------------------------------
        self.button_open.clicked.connect(self.on_open_structure)
        self.button_plane.toggled.connect(self.viewer.set_plane_visible)
        self.button_color.toggled.connect(self.on_color_aa_toggled)
        self.button_color_ss.toggled.connect(self.on_color_ss_toggled)
        self.button_rep_cartoon.toggled.connect(self.on_rep_cartoon_toggled)
        self.button_rep_atoms.toggled.connect(self.on_rep_atoms_toggled)
        self.button_rep_sticks.toggled.connect(self.viewer.set_sticks_visible)
        self.button_rep_trace.toggled.connect(self.viewer.set_trace_visible)
        self.button_surface.toggled.connect(self.viewer.set_surface_visible)
        self.button_info.toggled.connect(self.on_toggle_info_panel)
        self.button_display_cfg.clicked.connect(self.on_open_display_config)

        # Update system-info text when sequence selection changes.
        self.seq_list.itemSelectionChanged.connect(self.on_sequence_selection_changed)

        self._ss_codes: list[str] | None = None

        try:
            self.viewer.set_system_info_visible(self.button_info.isChecked())
        except Exception:
            pass

    # ------------------------------------------------------------------
    # Actions
    # ------------------------------------------------------------------
    def on_open_structure(self):
        """Open a structure file and display it in the viewer."""

        if _cs_open_files is not None:
            filenames = _cs_open_files(
                description="Open structure file",
                file_type=(
                    "Structure files (*.pdb *.ent *.gro *.cif *.mmcif);;"
                    "All files (*.*)"
                ),
            )
        else:
            filenames = _fallback_open_files(self)
        if not filenames:
            return

        path = filenames[0]
        try:
            if _ChiSurfStructure is not None:
                # Full ChiSurf path: use Structure wrapper (IMP-based reader).
                structure = _ChiSurfStructure(filename=str(path), verbose=True)
                self._structure = structure
                # Invalidate any cached secondary-structure codes when loading a
                # new structure.
                self._ss_codes = None
                self.viewer.set_structure(structure)
            else:
                # Standalone moview: minimal PDB reader that feeds raw
                # coordinates to the viewer. Sequence info and SS will be
                # unavailable in this mode.
                coords = _simple_load_pdb_coords(str(path))
                self._structure = None
                self._ss_codes = None
                self.viewer.set_coordinates(coords)
            self._update_sequence_view()
            self._update_system_info()
        except Exception as e:
            try:
                chisurf.logging.warning(
                    "ProtViewPluginWindow.on_open_structure: failed to load '%s': %s",
                    path,
                    e,
                )
            except Exception:
                pass
            QtWidgets.QMessageBox.warning(
                self,
                "Failed to load structure",
                f"Could not load structure from:\n{path}\n\n{e}",
            )

    def on_toggle_info_panel(self, checked: bool) -> None:
        try:
            self.viewer.set_system_info_visible(bool(checked))
        except Exception:
            return

        # When turning the info panel on, refresh the system summary so the
        # overlay is immediately populated for the currently loaded structure.
        if checked:
            try:
                self._update_system_info()
            except Exception:
                pass

    def on_rep_cartoon_toggled(self, checked: bool) -> None:
        """Toggle cartoon representation for selected or all residues."""

        if self.viewer is None:
            return
        idx = self._selected_residue_indices()
        if not idx:
            try:
                self.viewer.set_cartoon_visible(bool(checked))
            except Exception:
                pass
            return
        try:
            self.viewer.set_residue_representation(idx, cartoon=bool(checked))
        except Exception:
            pass

    def on_rep_atoms_toggled(self, checked: bool) -> None:
        """Toggle atoms/balls representation for selected or all residues."""

        if self.viewer is None:
            return
        idx = self._selected_residue_indices()
        if not idx:
            try:
                self.viewer.set_atoms_visible(bool(checked))
            except Exception:
                pass
            return
        try:
            self.viewer.set_residue_representation(idx, ball=bool(checked))
        except Exception:
            pass

    def on_color_aa_toggled(self, checked: bool) -> None:
        """Toggle coloring by amino-acid type in the 3D view."""

        # Turning AA-coloring on should turn SS-coloring off to avoid
        # conflicting modes.
        if checked and self.button_color_ss.isChecked():
            self.button_color_ss.blockSignals(True)
            self.button_color_ss.setChecked(False)
            self.button_color_ss.blockSignals(False)

        try:
            self.viewer.set_color_mode("by_residue" if checked else "single")
        except Exception:
            pass

    def on_color_ss_toggled(self, checked: bool) -> None:
        """Toggle coloring by secondary structure in both sequence and 3D."""

        # Turning SS-coloring on disables plain AA-coloring.
        if checked and self.button_color.isChecked():
            self.button_color.blockSignals(True)
            self.button_color.setChecked(False)
            self.button_color.blockSignals(False)

        if not checked:
            # When disabling SS coloring, fall back to AA or single.
            try:
                mode = "by_residue" if self.button_color.isChecked() else "single"
                self.viewer.set_color_mode(mode)
            except Exception:
                pass
            return

        # Ensure we have secondary-structure codes; _get_secondary_structure_codes
        # will cache them the first time it's called.
        n = 0
        try:
            seq_codes = self.viewer.residue_oneletter
        except Exception:
            seq_codes = None
        if seq_codes is not None:
            try:
                n = len(seq_codes)
            except Exception:
                n = 0
        if n <= 0:
            # Fallback: try residue_names length if available.
            try:
                res_names = self.viewer.residue_names
                if res_names is not None:
                    n = len(res_names)
            except Exception:
                n = 0
        ss_codes = self._get_secondary_structure_codes(n) if n > 0 else None

        if ss_codes is None:
            return

        try:
            self.viewer.set_secondary_structure_codes(ss_codes)
            self.viewer.set_color_mode("by_secondary_structure")
        except Exception:
            pass

    def on_open_display_config(self) -> None:
        """Open the ProtView display configuration in a built-in editor."""

        try:
            if _cs_settings is not None:
                json_path = _cs_settings.get_path("settings") / "protview_display.json"
            else:
                json_path = Path(__file__).with_name("protview_display.json")
        except Exception:
            json_path = Path(__file__).with_name("protview_display.json")

        dlg = _ProtViewConfigEditor(self, json_path=json_path, viewer=self.viewer)
        dlg.exec_()

    def _update_sequence_view(self) -> None:
        self.seq_list.clear()

        seq_codes = getattr(self.viewer, "residue_oneletter", None)
        res_names = getattr(self.viewer, "residue_names", None)

        if seq_codes is None and res_names is None:
            self.seq_list.addItem("(no sequence information)")
            self.seq_list.setEnabled(False)
            return

        self.seq_list.setEnabled(True)

        n = 0
        if seq_codes is not None:
            n = len(seq_codes)
        elif res_names is not None:
            n = len(res_names)

        if n <= 0:
            self.seq_list.addItem("(no residues)")
            return

        ss_codes = self._get_secondary_structure_codes(n)
        digits = len(str(max(1, n)))
        text_template = "{idx}\n{aa}"

        for i in range(n):
            one = seq_codes[i] if seq_codes is not None else "?"
            ss = ss_codes[i] if ss_codes is not None and i < len(ss_codes) else "C"

            idx_label = str(i + 1).rjust(digits)
            item = QtWidgets.QListWidgetItem()
            item.setText(text_template.format(idx=idx_label, aa=one))

            if ss == "H":
                bg = QtGui.QColor(60, 90, 200)
                fg = QtGui.QColor(255, 255, 255)
            elif ss == "E":
                bg = QtGui.QColor(200, 70, 70)
                fg = QtGui.QColor(255, 255, 255)
            else:
                bg = QtGui.QColor(220, 220, 200)
                fg = QtGui.QColor(0, 0, 0)

            item.setBackground(QtGui.QBrush(bg))
            item.setForeground(QtGui.QBrush(fg))
            item.setTextAlignment(QtCore.Qt.AlignCenter)
            item.setData(_SEQ_COLOR_ROLE, (bg, fg))

            res_name = (
                str(res_names[i]) if res_names is not None and i < len(res_names) else "?"
            )
            item.setToolTip(f"{i+1}: {res_name} ({one}), SS={ss}")

            self.seq_list.addItem(item)

        self._apply_sequence_selection_styles(set())

    def _update_system_info(self) -> None:
        if self._structure is None:
            lines = ["(no system loaded)"]
        else:
            s = self._structure
            try:
                n_atoms = s.n_atoms
            except Exception:
                n_atoms = "?"
            try:
                n_res = s.n_residues
            except Exception:
                n_res = "?"
            try:
                r_g = s.radius_gyration
            except Exception:
                r_g = "?"

            lines = [
                "System: protein",
                "",
                f"File: {getattr(s, 'filename', '?')}",
                f"Atoms: {n_atoms}",
                f"Residues: {n_res}",
                f"Radius of gyration: {r_g}",
            ]

            # Append information about currently selected residues, if any.
            sel_idx = self._selected_residue_indices()
            if sel_idx:
                try:
                    seq_codes = self.viewer.residue_oneletter
                except Exception:
                    seq_codes = None
                try:
                    res_names = self.viewer.residue_names
                except Exception:
                    res_names = None

                desc: list[str] = []
                for i in sel_idx:
                    res_no = i + 1
                    name = (
                        str(res_names[i])
                        if res_names is not None and i < len(res_names)
                        else "?"
                    )
                    one = (
                        str(seq_codes[i])
                        if seq_codes is not None and i < len(seq_codes)
                        else "?"
                    )
                    desc.append(f"{res_no}: {name} ({one})")

                lines.append("")
                lines.append("Selected residues:")
                lines.append(", ".join(desc))

        text = "\n".join(str(x) for x in lines)
        try:
            self.viewer.set_system_info_text(text)
        except Exception:
            pass

    def on_sequence_selection_changed(self) -> None:
        """Sync 3D selection and info text when the sequence selection changes."""

        # Map QListWidget selection to residue indices
        idx = self._selected_residue_indices()
        try:
            self._apply_sequence_selection_styles(set(idx))
        except Exception:
            pass
        try:
            self.viewer.set_selected_residues(idx)
        except Exception:
            # Even if the 3D viewer update fails, keep the info text in sync.
            pass

        try:
            self._update_system_info()
        except Exception:
            pass

    def on_viewer_residue_selection_changed(self, indices) -> None:
        """Update sequence selection to match picks from the 3D viewer."""

        if not isinstance(indices, (list, tuple)):
            try:
                indices = list(indices)
            except Exception:
                indices = []

        self.seq_list.blockSignals(True)
        try:
            self.seq_list.clearSelection()
            for i in indices:
                try:
                    row = int(i)
                except Exception:
                    continue
                if 0 <= row < self.seq_list.count():
                    item = self.seq_list.item(row)
                    if item is not None:
                        item.setSelected(True)
        finally:
            self.seq_list.blockSignals(False)

        try:
            self._apply_sequence_selection_styles({int(i) for i in indices})
        except Exception:
            pass

        try:
            self._update_system_info()
        except Exception:
            pass

    def _selected_residue_indices(self) -> list[int]:
        if self.seq_list.count() == 0:
            return []
        rows = [idx.row() for idx in self.seq_list.selectedIndexes()]
        return sorted(set(r for r in rows if r >= 0))

    def _sequence_config(self) -> dict:
        try:
            return _DISPLAY_CONFIG.get("sequence", {})
        except Exception:
            return {}

    @staticmethod
    def _color_from_rgba(values, default):
        try:
            seq = list(values)
        except Exception:
            seq = list(default)
        if len(seq) < 4:
            seq = list(default)
        r, g, b, a = seq[:4]
        try:
            color = QtGui.QColor.fromRgbF(float(r), float(g), float(b), float(a))
        except Exception:
            color = QtGui.QColor.fromRgbF(*default)
        return color

    def _apply_sequence_selection_styles(self, selected_rows: set[int]) -> None:
        if self.seq_list.count() == 0:
            return
        seq_cfg = self._sequence_config()
        sel_bg = self._color_from_rgba(
            seq_cfg.get("selection_color", [1.0, 0.95, 0.4, 1.0]),
            [1.0, 0.95, 0.4, 1.0],
        )
        sel_fg = self._color_from_rgba(
            seq_cfg.get("selection_text_color", [0.1, 0.1, 0.1, 1.0]),
            [0.1, 0.1, 0.1, 1.0],
        )
        for row in range(self.seq_list.count()):
            item = self.seq_list.item(row)
            if item is None:
                continue
            palette = item.data(_SEQ_COLOR_ROLE)
            if isinstance(palette, tuple) and len(palette) == 2:
                base_bg, base_fg = palette
            else:
                base_bg = QtGui.QColor(220, 220, 200)
                base_fg = QtGui.QColor(0, 0, 0)
            if row in selected_rows:
                item.setBackground(QtGui.QBrush(sel_bg))
                item.setForeground(QtGui.QBrush(sel_fg))
            else:
                item.setBackground(QtGui.QBrush(base_bg))
                item.setForeground(QtGui.QBrush(base_fg))

    def _apply_representation_to_selection(self, cartoon=None, ball=None) -> None:
        idx = self._selected_residue_indices()
        if not idx:
            return
        try:
            self.viewer.set_residue_representation(idx, cartoon=cartoon, ball=ball)
        except Exception as e:
            try:
                chisurf.logging.warning(
                    "ProtViewPluginWindow._apply_representation_to_selection failed: %s",
                    e,
                )
            except Exception:
                pass

    def _get_secondary_structure_codes(self, n_res: int) -> list[str] | None:
        """Return a list of secondary-structure codes (H/E/C) for residues.

        Delegates to :func:`assign_ss_c3_from_file` in :mod:`ss`, which
        implements a simplified DSSP-style assignment inspired by PyDSSP.
        If anything fails, returns ``None`` and the sequence view falls back
        to plain coloring.
        """

        if n_res <= 0 or self._structure is None:
            return None
        if self._ss_codes is not None:
            return self._ss_codes

        atoms = getattr(self._structure, "atoms", None)
        if atoms is None:
            return None

        # Use the already-loaded ChiSurf atoms array instead of reparsing the
        # PDB/mmCIF file. This avoids IO issues and keeps SS assignment fully
        # decoupled from mdtraj/Biopython.
        codes = assign_ss_c3_from_atoms(atoms, n_res)
        if not codes:
            return None

        self._ss_codes = codes
        return self._ss_codes


class _ProtViewConfigEditor(QtWidgets.QDialog):

    def __init__(self, parent: QtWidgets.QWidget | None, json_path: Path | None, viewer: ProtView | None) -> None:
        super().__init__(parent)
        self._json_path = json_path
        self._viewer = viewer

        self.setWindowTitle("ProtView Display Configuration")
        self.resize(800, 600)

        layout = QtWidgets.QVBoxLayout(self)

        self._edit = QtWidgets.QPlainTextEdit(self)
        font = QtGui.QFont("Courier New")
        font.setPointSize(9)
        self._edit.setFont(font)
        layout.addWidget(self._edit, 1)

        btn_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.Save | QtWidgets.QDialogButtonBox.Cancel,
            parent=self,
        )
        layout.addWidget(btn_box)

        btn_box.accepted.connect(self._on_save)
        btn_box.rejected.connect(self.reject)

        self._load_from_disk()

    def _load_from_disk(self) -> None:
        if self._json_path is None:
            self._edit.setPlainText("// Could not locate protview_display.json")
            self._edit.setReadOnly(True)
            return
        try:
            with self._json_path.open("r", encoding="utf-8") as fh:
                text = fh.read()
        except Exception as e:
            self._edit.setPlainText(f"// Failed to read {self._json_path}: {e}")
            self._edit.setReadOnly(True)
            return

        self._edit.setReadOnly(False)
        self._edit.setPlainText(text)

    def _on_save(self) -> None:
        if self._json_path is None:
            self.reject()
            return

        text = self._edit.toPlainText()
        try:
            # Validate JSON before writing.
            json.loads(text)
        except Exception as e:
            QtWidgets.QMessageBox.warning(
                self,
                "Invalid JSON",
                f"The configuration is not valid JSON:\n{e}",
            )
            return

        try:
            with self._json_path.open("w", encoding="utf-8") as fh:
                fh.write(text)
        except Exception as e:
            QtWidgets.QMessageBox.warning(
                self,
                "Failed to save configuration",
                f"Could not write to {self._json_path}:\n{e}",
            )
            return

        # Reload display configuration globally so future redraws see it.
        try:
            from chisurf.gui.widgets import protview as _pv_mod

            if hasattr(_pv_mod, "reload_display_config"):
                _pv_mod.reload_display_config()
        except Exception:
            pass

        # Ask the current viewer, if any, to refresh its view.
        if self._viewer is not None:
            try:
                if getattr(self._viewer, "_coords", None) is not None:
                    self._viewer._update_view()  # type: ignore[attr-defined]
            except Exception:
                pass

        self.accept()


if __name__ == "plugin":
    # When launched via the ChiSurf plugin system, __name__ is set to
    # "plugin" and a QApplication is already running.
    window = ProtViewPluginWindow()
    window.resize(1000, 700)
    window.show()
