#!/usr/bin/env python

# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################

"""This module provides base widget that displays a Qt view widget envolved
by a toolbar and a status bar (optional)
"""


__docformat__ = "restructuredtext"

from taurus.external.qt import Qt, compat

from taurus.qt.qtgui.util import ActionFactory
from taurus.qt.qtgui.base import TaurusBaseWidget


class BaseToolBar(Qt.QToolBar):
    def __init__(self, name=None, view=None, parent=None, designMode=False):
        if name is None:
            name = "Base toolbar"
        self._viewWidget = view or parent
        Qt.QToolBar.__init__(self, name, parent)
        self.setIconSize(Qt.QSize(16, 16))
        self.setFloatable(False)
        self.setMovable(False)

    def viewWidget(self):
        return self._viewWidget


class FilterToolBar(BaseToolBar):
    """Internal widget providing quick filter to be placed in a _QToolArea"""

    filterEdited = Qt.pyqtSignal("const QString &")
    filterChanged = Qt.pyqtSignal("const QString &")
    clearFilterTriggered = Qt.pyqtSignal()

    def __init__(self, view=None, parent=None, designMode=False):
        BaseToolBar.__init__(
            self,
            name="Taurus filter toolbar",
            view=view,
            parent=parent,
            designMode=designMode,
        )
        filterLineEdit = self._filterLineEdit = Qt.QLineEdit(self)
        filterLineEdit.setSizePolicy(
            Qt.QSizePolicy(Qt.QSizePolicy.Preferred, Qt.QSizePolicy.Preferred)
        )
        filterLineEdit.setToolTip("Quick filter")
        filterLineEdit.textChanged.connect(self.onFilterChanged)
        filterLineEdit.textEdited.connect(self.onFilterEdited)
        self.addWidget(filterLineEdit)

        af = ActionFactory()
        self._clearFilterAction = af.createAction(
            self,
            "Clear",
            icon=Qt.QIcon.fromTheme("edit-clear"),
            tip="Clears the filter",
            triggered=self.onClearFilter,
        )
        self.addAction(self._clearFilterAction)

    def getFilterLineEdit(self):
        return self._filterLineEdit

    def onClearFilter(self):
        self.getFilterLineEdit().setText("")
        self.clearFilterTriggered.emit()

    def onFilterChanged(self, text=None):
        text = text or self.getFilterLineEdit().text()
        self.filterChanged.emit(text)

    def onFilterEdited(self, text=None):
        text = text or self.getFilterLineEdit().text()
        self.filterEdited.emit(text)

    def setFilterText(self, text):
        self.getFilterLineEdit().setText(text)


class EditorToolBar(BaseToolBar):
    """Internal widget to be placed in a _QToolArea providing buttons for
    moving, adding and removing items from a view based widget
    """

    addTriggered = Qt.pyqtSignal()
    removeTriggered = Qt.pyqtSignal()
    moveTopTriggered = Qt.pyqtSignal()
    moveDownTriggered = Qt.pyqtSignal()
    moveUpTriggered = Qt.pyqtSignal()
    moveBottomTriggered = Qt.pyqtSignal()

    def __init__(self, view=None, parent=None, designMode=False):
        BaseToolBar.__init__(
            self,
            name="Taurus editor toolbar",
            view=view,
            parent=parent,
            designMode=designMode,
        )

        af = ActionFactory()
        self._addAction = af.createAction(
            self,
            "New item",
            icon=Qt.QIcon.fromTheme("list-add"),
            tip="Add new item",
            triggered=self.onAdd,
        )
        self._removeAction = af.createAction(
            self,
            "Remove item",
            icon=Qt.QIcon.fromTheme("list-remove"),
            tip="Remove item",
            triggered=self.onRemove,
        )
        self._moveTopAction = af.createAction(
            self,
            "To top",
            icon=Qt.QIcon.fromTheme("go-top"),
            tip="Move selected item to top",
            triggered=self.onMoveTop,
        )
        self._moveUpAction = af.createAction(
            self,
            "Move up",
            icon=Qt.QIcon.fromTheme("go-up"),
            tip="Move selected item up one level",
            triggered=self.onMoveUp,
        )
        self._moveDownAction = af.createAction(
            self,
            "Move down",
            icon=Qt.QIcon.fromTheme("go-down"),
            tip="Move selected item down one level",
            triggered=self.onMoveDown,
        )
        self._moveBottomAction = af.createAction(
            self,
            "To bottom",
            icon=Qt.QIcon.fromTheme("go-bottom"),
            tip="Move selected item to bottom",
            triggered=self.onMoveBottom,
        )
        self.addAction(self._addAction)
        self.addAction(self._removeAction)
        self.addAction(self._moveTopAction)
        self.addAction(self._moveUpAction)
        self.addAction(self._moveDownAction)
        self.addAction(self._moveBottomAction)
        # self.setStyleSheet("QWidget {background : red; }")

    def onAdd(self):
        self.addTriggered.emit()

    def onRemove(self):
        self.removeTriggered.emit()

    def onMoveTop(self):
        self.moveTopTriggered.emit()

    def onMoveUp(self):
        self.moveUpTriggered.emit()

    def onMoveDown(self):
        self.moveDownTriggered.emit()

    def onMoveBottom(self):
        self.moveBottomTriggered.emit()


class SelectionToolBar(BaseToolBar):

    selectAllTriggered = Qt.pyqtSignal()
    clearSelectionTriggered = Qt.pyqtSignal()

    def __init__(self, view=None, parent=None, designMode=False):
        BaseToolBar.__init__(
            self,
            name="Taurus selection toolbar",
            view=view,
            parent=parent,
            designMode=designMode,
        )

        af = ActionFactory()
        self._selectAllAction = af.createAction(
            self,
            "Select All",
            icon=Qt.QIcon.fromTheme("edit-select-all"),
            tip="Select all items",
            triggered=self.onSelectAll,
        )
        self._clearSelectionAction = af.createAction(
            self,
            "Clear selection",
            icon=Qt.QIcon.fromTheme("edit-clear"),
            tip="Clears current selection",
            triggered=self.onclearSelection,
        )

        self.addAction(self._selectAllAction)
        self.addAction(self._clearSelectionAction)

    def onSelectAll(self):
        self.selectAllTriggered.emit()

    def onclearSelection(self):
        self.clearSelectionTriggered.emit()


class RefreshToolBar(BaseToolBar):

    refreshTriggered = Qt.pyqtSignal()

    def __init__(self, view=None, parent=None, designMode=False):
        BaseToolBar.__init__(
            self,
            name="Taurus refresh toolbar",
            view=view,
            parent=parent,
            designMode=designMode,
        )

        af = ActionFactory()
        self._refreshAction = af.createAction(
            self,
            "Refresh",
            icon=Qt.QIcon.fromTheme("view-refresh"),
            tip="Refresh view",
            triggered=self.onRefresh,
        )
        self.addAction(self._refreshAction)

    def onRefresh(self):
        self.refreshTriggered.emit()


class PerspectiveToolBar(BaseToolBar):

    perspectiveChanged = Qt.pyqtSignal(compat.PY_OBJECT)

    def __init__(self, perspective, view=None, parent=None, designMode=False):
        BaseToolBar.__init__(
            self,
            name="Taurus refresh toolbar",
            view=view,
            parent=parent,
            designMode=designMode,
        )
        self._perspective = perspective
        view = self.viewWidget()
        b = self._perspective_button = Qt.QToolButton(self)
        b.setToolTip("Perspective selection")
        b.setPopupMode(Qt.QToolButton.InstantPopup)
        b.setToolButtonStyle(Qt.Qt.ToolButtonTextBesideIcon)

        menu = Qt.QMenu("Perspective", b)
        b.setMenu(menu)
        af = ActionFactory()
        for persp, persp_data in view.KnownPerspectives.items():
            label = persp_data["label"]
            icon = Qt.QIcon.fromTheme(persp_data["icon"])
            tip = persp_data["tooltip"]
            action = af.createAction(
                self,
                label,
                icon=icon,
                tip=tip,
                triggered=self.onSwitchPerspective,
            )
            action.perspective = persp
            menu.addAction(action)
            if persp == perspective:
                b.setDefaultAction(action)

        self._perspectiveAction = self.addWidget(b)

    def switchPerspectiveButton(self):
        """Returns the QToolButton that handles the switch perspective.

        :return: the switch perspective tool button
        :rtype: PyQt5.Qt.QToolButton
        """
        return self._perspective_button

    def onSwitchPerspective(self):
        action = self.sender()
        self._perspective = action.perspective
        self._perspective_button.setDefaultAction(action)
        self.perspectiveChanged.emit(action.perspective)

    def perspective(self):
        return self._perspective


class QBaseModelWidget(Qt.QMainWindow):
    """A pure Qt widget designed to display a Qt view widget (QTreeView for
    example), envolved by optional toolbar and statusbar.
    The Qt model associated with the internal Qt view widget should be a
    :class:`taurus.qt.qtcore.model.TaurusBaseModel`
    """

    KnownPerspectives = {}
    DftPerspective = None

    itemClicked = Qt.pyqtSignal(compat.PY_OBJECT, int)
    itemDoubleClicked = Qt.pyqtSignal(compat.PY_OBJECT, int)
    itemSelectionChanged = Qt.pyqtSignal()
    currentItemChanged = Qt.pyqtSignal(compat.PY_OBJECT, compat.PY_OBJECT)

    def __init__(
        self,
        parent=None,
        designMode=False,
        with_filter_widget=True,
        with_selection_widget=True,
        with_refresh_widget=True,
        perspective=None,
        proxy=None,
    ):
        Qt.QMainWindow.__init__(self, parent)
        self.setWindowFlags(Qt.Qt.Widget)
        self._baseQModel = None
        self._toolBars = []

        if with_filter_widget:
            if isinstance(with_filter_widget, (bool, int)):
                self._with_filter_widget = FilterToolBar
            else:
                self._with_filter_widget = with_filter_widget
        else:
            self._with_filter_widget = None

        if with_selection_widget:
            if isinstance(with_selection_widget, (bool, int)):
                self._with_selection_widget = SelectionToolBar
            else:
                self._with_selection_widget = with_selection_widget
        else:
            self._with_selection_widget = None

        if with_refresh_widget:
            if isinstance(with_refresh_widget, (bool, int)):
                self._with_refresh_widget = RefreshToolBar
            else:
                self._with_refresh_widget = with_refresh_widget
        else:
            self._with_refresh_widget = None

        self._proxyModel = proxy

        toolBars = self.createToolArea()
        self._viewWidget = self.createViewWidget()
        statusbar = self.createStatusBar()

        for toolBar in toolBars:
            # toolBar.addSeparator()
            self.addToolBar(toolBar)
        self.setContentsMargins(0, 0, 0, 0)
        self.setCentralWidget(self._viewWidget)
        self.setStatusBar(statusbar)

        if perspective is None:
            perspective = self.DftPerspective

        if len(self.KnownPerspectives) > 1:
            p_bar = self._perspectiveBar = PerspectiveToolBar(
                perspective, view=self, parent=self
            )
            p_bar.perspectiveChanged.connect(self.onSwitchPerspective)
            self.addToolBar(p_bar)
        else:
            self._perspectiveBar = None
        self._setPerspective(perspective)

    def createViewWidget(self, klass=None):
        raise NotImplementedError

    def createStatusBar(self):
        sb = Qt.QStatusBar()
        sb.setSizeGripEnabled(False)
        return sb

    def createToolArea(self):
        tb = []  # tb = self._toolArea = QToolArea(self)
        if self._with_filter_widget:
            f_bar = self._filterBar = self._with_filter_widget(
                view=self, parent=self
            )
            f_bar.filterChanged.connect(self.onFilterChanged)
            tb.append(f_bar)
        else:
            self._filterBar = None

        if self._with_selection_widget:
            s_bar = self._selectionBar = self._with_selection_widget(
                view=self, parent=self
            )
            s_bar.selectAllTriggered.connect(self.onSelectAll)
            s_bar.clearSelectionTriggered.connect(self.onClearSelection)
            tb.append(s_bar)
        else:
            self._selectionBar = None

        if self._with_refresh_widget:
            r_bar = self._refreshBar = self._with_refresh_widget(
                view=self, parent=self
            )
            r_bar.refreshTriggered.connect(self.onRefreshModel)
            tb.append(r_bar)
        else:
            self._refreshBar = None

        return tb

    def getPerspectiveBar(self):
        return self._perspectiveBar

    def getFilterBar(self):
        return self._filterBar

    def getSelectionBar(self):
        return self._selectionBar

    def getRefreshBar(self):
        return self._refreshBar

    def onRefreshModel(self):
        self.getQModel().refresh(True)

    def onSelectAll(self):
        view = self.viewWidget()
        view.selectAll()

    def onClearSelection(self):
        view = self.viewWidget()
        view.clearSelection()

    def _onClicked(self, index):
        """Emits an "itemClicked" signal with with the clicked item and column
        as arguments
        """
        item = self._mapToSource(index).internalPointer()
        self.itemClicked.emit(item, index.column())

    def _onDoubleClicked(self, index):
        """Emits an "itemDoubleClicked" signal with the clicked item and column
        as arguments
        """
        item = self._mapToSource(index).internalPointer()
        self.itemDoubleClicked.emit(item, index.column())

    def viewWidget(self):
        return self._viewWidget

    def getQModel(self):
        return self.viewWidget().model()

    def getBaseQModel(self):
        return self._baseQModel

    def usesProxyQModel(self):
        return isinstance(self.getQModel(), Qt.QAbstractProxyModel)

    def _mapToSource(self, index):
        if not self.usesProxyQModel():
            return index
        model = self.getQModel()
        while isinstance(model, Qt.QAbstractProxyModel):
            index = model.mapToSource(index)
            model = model.sourceModel()
        return index

    def setQModel(self, qmodel):

        self._baseQModel = qmodel
        while isinstance(self._baseQModel, Qt.QAbstractProxyModel):
            self._baseQModel = self._baseQModel.sourceModel()

        view = self.viewWidget()
        old_selection_model = view.selectionModel()
        if old_selection_model is not None:
            old_selection_model.currentChanged.disconnect(
                self.viewCurrentIndexChanged
            )
            old_selection_model.selectionChanged.disconnect(
                self.viewSelectionChanged
            )
        view.setModel(qmodel)
        new_selection_model = view.selectionModel()
        if new_selection_model is not None:
            new_selection_model.currentChanged.connect(
                self.viewCurrentIndexChanged
            )
            new_selection_model.selectionChanged.connect(
                self.viewSelectionChanged
            )
        view.setCurrentIndex(view.rootIndex())
        self._updateToolBar()

    def viewSelectionChanged(self, selected, deselected):
        self.itemSelectionChanged.emit()

    def viewCurrentIndexChanged(self, current, previous):
        # if there is a proxy model we have to translate the selection
        base_current = self._mapToSource(current)
        base_previous = self._mapToSource(previous)

        self._updateToolBar(current)

        if base_current.isValid():
            currentTaurusTreeItem = base_current.internalPointer()
        else:
            currentTaurusTreeItem = None

        if base_previous.isValid():
            previousTaurusTreeItem = base_previous.internalPointer()
        else:
            previousTaurusTreeItem = None
        self.currentItemChanged.emit(
            currentTaurusTreeItem, previousTaurusTreeItem
        )

    def _updateToolBar(self, current=None, previous=None):
        pass

    def selectedItems(self):
        """Returns a list of all selected non-hidden items

        :return:
        :rtype: list<TaurusTreeItem>
        """
        view = self.viewWidget()
        return [
            self._mapToSource(index).internalPointer()
            for index in view.selectedIndexes()
        ]

    def onFilterChanged(self, filter):
        if not self.usesProxyQModel():
            return
        proxy_model = self.getQModel()
        if len(filter) > 0 and filter[0] != "^":
            filter = "^" + filter
        proxy_model.setFilterRegExp(filter)
        # proxy_model.setFilterFixedString(filter)
        # proxy_model.setFilterWildcard(filter)
        # self.update()

    def refresh(self):
        self.getQModel().refresh()

    # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
    # Perspective handling
    # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    def perspective(self):
        return self._perspectiveBar.perspective()

    def onSwitchPerspective(self, perspective):
        old_qmodel = self.getQModel()
        self._setPerspective(perspective)

        # set the selectables as they where in the previous model
        if hasattr(old_qmodel, "selectables"):
            self.getQModel().setSelectables(old_qmodel.selectables())

        # set the taurus model (if any) to the qmodel
        if hasattr(self, "getModelObj"):
            taurusModel = self.getModelObj()
            if taurusModel is not None:
                self.getQModel().setDataSource(taurusModel)

    def _setPerspective(self, perspective):
        qmodel_classes = self.KnownPerspectives[perspective]["model"]
        # reversed
        qmodel_class, qmodel_proxy_classes = (
            qmodel_classes[-1],
            qmodel_classes[-2::-1],
        )
        qmodel = qmodel_class(parent=self)
        qmodel_source = qmodel
        if self._proxyModel is None:  # applies the chain of proxies
            for qmodel_proxy_class in qmodel_proxy_classes:
                qproxy = qmodel_proxy_class(self)
                qproxy.setSourceModel(qmodel_source)
                qmodel_source = qproxy
        else:
            self._proxyModel.setSourceModel(qmodel_source)
            qmodel_source = self._proxyModel
        self.setQModel(qmodel_source)

    # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
    # QMainWindow overwriting
    # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    def addToolBar(self, toolbar):
        Qt.QMainWindow.addToolBar(self, toolbar)
        self._toolBars.append(toolbar)

    def insertToolBar(self, before, toolbar):
        if isinstance(before, Qt.QToolBar):
            index = self._toolBars.index(before)
        else:
            index = before
            before = self._toolBars[before]
        Qt.QMainWindow.insertToolBar(self, before, toolbar)
        self._toolBars.insert(index, toolbar)


class TaurusBaseModelWidget(TaurusBaseWidget):
    """A class:`taurus.qt.qtgui.base.TaurusBaseWidget` that connects to a
    taurus model. It must be used together with
    class:`~taurus.qt.qtgui.base.QBaseModelWidget`
    """

    def __init__(self, designMode=False):  # , perspective=None, proxy=None):
        name = self.__class__.__name__
        self.call__init__(TaurusBaseWidget, name, designMode=designMode)

    # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
    # TaurusBaseWidget overwriting
    # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    def setModel(self, m, **kwargs):
        TaurusBaseWidget.setModel(self, m, **kwargs)

        view, modelObj = self.viewWidget(), self.getModelObj(**kwargs)
        model = view.model()
        if model is None:
            return
        model.setDataSource(modelObj)

    # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
    # QT property definition
    # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~

    #: This property holds the unique URI string representing the model name
    #: with which this widget will get its data from. The convention used for
    #: the string can be found :ref:`here <model-concept>`.
    #:
    #: **Access functions:**
    #:
    #:     * :meth:`TaurusBaseWidget.getModel`
    #:     * :meth:`TaurusBaseWidget.setModel`
    #:     * :meth:`TaurusBaseWidget.resetModel`
    #:
    #: .. seealso:: :ref:`model-concept`
    model = Qt.pyqtProperty(
        "QString",
        TaurusBaseWidget.getModel,
        setModel,
        TaurusBaseWidget.resetModel,
    )
