# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)

"""History Widget."""

# Standard library imports
import os.path as osp
import re
import sys

# Third party imports
from qtpy.QtCore import Signal, Slot
from qtpy.QtWidgets import QVBoxLayout, QWidget

# Local imports
from spyder.api.config.decorators import on_conf_change
from spyder.api.translations import _
from spyder.api.widgets.main_widget import PluginMainWidget
from spyder.py3compat import is_text_string, to_text_string
from spyder.utils import encoding
from spyder.utils.sourcecode import normalize_eols
from spyder.widgets.findreplace import FindReplace
from spyder.widgets.simplecodeeditor import SimpleCodeEditor
from spyder.widgets.tabs import Tabs
from spyder.utils.stylesheet import PANES_TABBAR_STYLESHEET


# --- Constants
# ----------------------------------------------------------------------------
# Maximum number of lines to show
MAX_LINES = 1000

class HistoryWidgetActions:
    # Triggers
    MaximumHistoryEntries = 'maximum_history_entries_action'

    # Toggles
    ToggleWrap = 'toggle_wrap_action'
    ToggleLineNumbers = 'toggle_line_numbers_action'


class HistoryWidgetOptionsMenuSections:
    Main = 'main_section'


# --- Widgets
# ----------------------------------------------------------------------------
class HistoryWidget(PluginMainWidget):
    """
    History plugin main widget.
    """

    # Signals
    sig_focus_changed = Signal()
    """
    This signal is emitted when the focus of the code editor storing history
    changes.
    """

    def __init__(self, name, plugin, parent):
        super().__init__(name, plugin, parent)

        # Attributes
        self.editors = []
        self.filenames = []
        self.tabwidget = None
        self.dockviewer = None
        self.wrap_action = None
        self.linenumbers_action = None
        self.filenames = []
        self.font = None

        # Widgets
        self.tabwidget = Tabs(self)
        self.find_widget = FindReplace(self)

        # Setup
        self.tabwidget.setStyleSheet(self._tabs_stylesheet)
        self.find_widget.hide()

        # Layout
        layout = QVBoxLayout()
        layout.setSpacing(0)

        # TODO: Move this to the tab container directly
        if sys.platform == 'darwin':
            tab_container = QWidget(self)
            tab_container.setObjectName('tab-container')
            tab_layout = QVBoxLayout(tab_container)
            tab_layout.setContentsMargins(0, 0, 0, 0)
            tab_layout.addWidget(self.tabwidget)
            layout.addWidget(tab_container)
        else:
            layout.addWidget(self.tabwidget)

        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Signals
        self.tabwidget.currentChanged.connect(self.refresh)
        self.tabwidget.move_data.connect(self.move_tab)

    # --- PluginMainWidget API
    # ------------------------------------------------------------------------
    def get_title(self):
        return _('History')

    def get_focus_widget(self):
        return self.tabwidget.currentWidget()

    def setup(self):
        # Actions
        self.wrap_action = self.create_action(
            HistoryWidgetActions.ToggleWrap,
            text=_("Wrap lines"),
            toggled=True,
            initial=self.get_conf('wrap'),
            option='wrap'
        )
        self.linenumbers_action = self.create_action(
            HistoryWidgetActions.ToggleLineNumbers,
            text=_("Show line numbers"),
            toggled=True,
            initial=self.get_conf('line_numbers'),
            option='line_numbers'
        )

        # Menu
        menu = self.get_options_menu()
        for item in [self.wrap_action, self.linenumbers_action]:
            self.add_item_to_menu(
                item,
                menu=menu,
                section=HistoryWidgetOptionsMenuSections.Main,
            )

    def update_actions(self):
        pass

    @on_conf_change(option='wrap')
    def on_wrap_update(self, value):
        for editor in self.editors:
            editor.toggle_wrap_mode(value)

    @on_conf_change(option='line_numbers')
    def on_line_numbers_update(self, value):
        for editor in self.editors:
            editor.toggle_line_numbers(value)

    @on_conf_change(section='appearance', option=['selected', 'ui_theme'])
    def on_color_scheme_change(self, option, value):
        if option == 'ui_theme':
            value = self.get_conf('selected', section='appearance')

        for editor in self.editors:
            editor.set_color_scheme(value)

    # --- Public API
    # ------------------------------------------------------------------------
    def update_font(self, font, color_scheme):
        """
        Update font of the code editor.

        Parameters
        ----------
        font: QFont
            Font object.
        color_scheme: str
            Name of the color scheme to use.
        """
        self.color_scheme = color_scheme
        self.font = font

        for editor in self.editors:
            editor.set_font(font)
            editor.set_color_scheme(color_scheme)

    def move_tab(self, index_from, index_to):
        """
        Move tab.

        Parameters
        ----------
        index_from: int
            Move tab from this index.
        index_to: int
            Move tab to this index.

        Notes
        -----
        Tabs themselves have already been moved by the history.tabwidget.
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)

    def get_filename_text(self, filename):
        """
        Read and return content from filename.

        Parameters
        ----------
        filename: str
            The file path to read.

        Returns
        -------
        str
            Content of the filename.
        """
        # Avoid a possible error when reading the history file
        try:
            text, _ = encoding.read(filename)
        except (IOError, OSError):
            text = "# Previous history could not be read from disk, sorry\n\n"

        text = normalize_eols(text)
        linebreaks = [m.start() for m in re.finditer('\n', text)]

        if len(linebreaks) > MAX_LINES:
            text = text[linebreaks[-MAX_LINES - 1] + 1:]
            # Avoid an error when trying to write the trimmed text to disk.
            # See spyder-ide/spyder#9093.
            try:
                encoding.write(text, filename)
            except (IOError, OSError):
                pass

        return text

    def add_history(self, filename):
        """
        Create a history tab for `filename`.

        Parameters
        ----------
        filename: str
            History filename.
        """

        filename = encoding.to_unicode_from_fs(filename)
        if filename in self.filenames:
            return

        # Widgets
        editor = SimpleCodeEditor(self)

        # Setup
        language = 'py' if osp.splitext(filename)[1] == '.py' else 'bat'
        editor.setup_editor(
            linenumbers=self.get_conf('line_numbers'),
            language=language,
            color_scheme=self.get_conf('selected', section='appearance'),
            font=self.font,
            wrap=self.get_conf('wrap'),
        )
        editor.setReadOnly(True)
        editor.set_text(self.get_filename_text(filename))
        editor.set_cursor_position('eof')
        self.find_widget.set_editor(editor)

        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.filenames.append(filename)
        self.editors.append(editor)
        self.tabwidget.setCurrentIndex(index)
        self.tabwidget.setTabToolTip(index, filename)

        # Signals
        editor.sig_focus_changed.connect(self.sig_focus_changed)

    @Slot(str, str)
    def append_to_history(self, filename, command):
        """
        Append command to history tab.

        Parameters
        ----------
        filename: str
            History file.
        command: str
            Command to append to history file.
        """
        if not is_text_string(filename):  # filename is a QString
            filename = to_text_string(filename.toUtf8(), 'utf-8')

        index = self.filenames.index(filename)
        command = to_text_string(command)
        self.editors[index].append(command)

        if self.get_conf('go_to_eof'):
            self.editors[index].set_cursor_position('eof')

        self.tabwidget.setCurrentIndex(index)

    def refresh(self):
        """Refresh widget and update find widget on current editor."""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None

        self.find_widget.set_editor(editor)

    @property
    def _tabs_stylesheet(self):
        """
        Change style of tabs because we don't have a close button here.
        """
        tabs_stylesheet = PANES_TABBAR_STYLESHEET.get_copy()
        css = tabs_stylesheet.get_stylesheet()

        css['QTabBar::tab'].setValues(
            marginTop='1.0em',
            padding='4px'
        )

        css['QTabWidget::pane'].setValues(
            border='1px',
        )

        css['QTabWidget::left-corner'].setValues(
            left='0px',
        )

        css['QTabWidget::right-corner'].setValues(
            right='0px'
        )

        return str(tabs_stylesheet)


def test():
    """Run history widget."""
    from spyder.utils.qthelpers import qapplication
    from unittest.mock import MagicMock

    plugin_mock = MagicMock()
    plugin_mock.CONF_SECTION = 'historylog'

    app = qapplication(test_time=8)
    widget = HistoryWidget('historylog', plugin_mock, None)
    widget._setup()
    widget.setup()
    widget.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    test()
