# -*- coding: utf-8 -*-

# Copyright Martin Manns
# Distributed under the terms of the GNU General Public License

# --------------------------------------------------------------------
# pyspread is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyspread is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyspread.  If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------

"""

**Provides**

 * :class:`MacroPanel`

"""

import ast
from io import StringIO
from sys import exc_info
from traceback import print_exception

from PyQt6.QtCore import Qt, QModelIndex
from PyQt6.QtGui import QColor
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox, QSplitter
from PyQt6.QtWidgets import QTextEdit

try:
    from pyspread.lib.spelltextedit import SpellTextEdit
    from pyspread.lib.exception_handling import get_user_codeframe
except ImportError:
    from lib.spelltextedit import SpellTextEdit
    from lib.exception_handling import get_user_codeframe


class MacroPanel(QDialog):
    """The macro panel"""

    def __init__(self, parent, code_array):
        super().__init__()

        self.parent = parent
        self.code_array = code_array

        self._init_widgets()
        self._layout()

        self.update_()

        self.default_text_color = self.result_viewer.textColor()
        self.error_text_color = QColor("red")

        self.button_box.clicked.connect(self.on_apply)

    def _init_widgets(self):
        """Inititialize widgets"""

        font_family = self.parent.settings.macro_editor_font_family
        self.macro_editor = SpellTextEdit(self, font_family=font_family)

        self.result_viewer = QTextEdit(self)
        self.result_viewer.setReadOnly(True)

        self.splitter = QSplitter(Qt.Orientation.Vertical, self)

        self.splitter.addWidget(self.macro_editor)
        self.splitter.addWidget(self.result_viewer)

        self.button_box = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Apply)

    def _layout(self):
        """Layout dialog widgets"""

        layout = QVBoxLayout(self)
        layout.addWidget(self.splitter)
        layout.addWidget(self.button_box)

        self.setLayout(layout)

    def _is_invalid_code(self) -> str:
        """Preliminary code check

        Returns a string with the error message if code is not valid Python.
        If the code runs without errors, an empty string is returned.

        """

        try:
            ast.parse(self.code_array.macros)

        except Exception:
            # Grab the traceback and return it
            stringio = StringIO()
            excinfo = exc_info()
            # usr_tb will more than likely be none because ast throws
            # SytnaxErrors as occurring outside of the current execution frame
            usr_tb = get_user_codeframe(excinfo[2]) or None
            print_exception(excinfo[0], excinfo[1], usr_tb, None, stringio)
            return stringio.getvalue()
        else:
            return ''

    def on_apply(self):
        """Event handler for Apply button"""

        self.code_array.macros = self.macro_editor.toPlainText()

        err = self._is_invalid_code()
        if err:
            self.update_result_viewer(err=err)
        else:
            self.update_result_viewer(*self.code_array.execute_macros())
            self.parent.grid.model.dataChanged.emit(QModelIndex(), QModelIndex())

        self.parent.grid.gui_update()

    def update_(self):
        """Update macro content"""

        self.macro_editor.setPlainText(self.code_array.macros)
        self.on_apply()

    def update_result_viewer(self, result: str = "", err: str = ""):
        """Update event result following execution by main window

        :param result: Text to be shown in the result viewer in default color
        :param err: Text to be shown in the result viewer in error text color

        """

        self.result_viewer.clear()

        if result:
            self.result_viewer.append(result)
        if err:
            self.result_viewer.setTextColor(self.error_text_color)
            self.result_viewer.append(err)
            self.result_viewer.setTextColor(self.default_text_color)
