"""
allows multiple selection in a large list

Contact : marco@opengis.ch

.. note:: This program 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 2 of the License, or
     (at your option) any later version.
"""

__author__ = 'marco@opengis.ch'
__revision__ = '$Format:%H$'
__date__ = '9/07/2013'

from qgis.PyQt.QtWidgets import (QGroupBox,
                                 QListWidget,
                                 QPushButton,
                                 QSizePolicy,
                                 QLabel,
                                 QHBoxLayout,
                                 QVBoxLayout,
                                 QListWidget,
                                 QAbstractItemView)
from qgis.PyQt.QtGui import QFont
from qgis.PyQt.QtCore import Qt, QSize, pyqtSignal


class ListMultiSelectWidget(QGroupBox):

    """Widget to show two parallel lists and move elements between the two

    usage from code:
        self.myWidget = ListMultiSelectWidget(title='myTitle')
        self.myLayout.insertWidget(1, self.myWidget)
    usage from designer:
        insert a QGroupBox in your UI file
        optionally give a title to the QGroupBox
        promote it to ListMultiSelectWidget
    """

    selection_changed = pyqtSignal()

    def __init__(self, parent=None, title=None):
        QGroupBox.__init__(self)
        self.setTitle(title)

        self.selected_widget = None
        self.unselected_widget = None
        self._setupUI()

        # connect actions
        self.select_all_btn.clicked.connect(self._select_all)
        self.deselect_all_btn.clicked.connect(self._deselect_all)
        self.select_btn.clicked.connect(self._select)
        self.deselect_btn.clicked.connect(self._deselect)

        self.unselected_widget.itemDoubleClicked.connect(self._select)
        self.selected_widget.itemDoubleClicked.connect(self._deselect)

    def get_selected_items(self):
        """
        :return list with all the selected items text
        """
        return self._get_items(self.selected_widget)

    def get_unselected_items(self):
        """
        :return list with all the unselected items text
        """
        return self._get_items(self.unselected_widget)

    def add_selected_items(self, items):
        """
        :param items list of strings to be added in the selected list
        """
        self._add_items(self.selected_widget, items)

    def add_unselected_items(self, items):
        """
        :param items list of strings to be added in the unselected list
        """
        self._add_items(self.unselected_widget, items)

    def set_selected_items(self, items):
        """
        :param items list of strings to be set as the selected list
        """
        self._set_items(self.selected_widget, items)

    def set_unselected_items(self, items):
        """
        :param items list of strings to be set as the unselected list
        """
        self._set_items(self.unselected_widget, items)

    def clear(self):
        """
        removes all items from selected and unselected
        """
        self.set_selected_items([])
        self.set_unselected_items([])

    def addItem(self, item):
        """
        This is for Processing
        :param item: string to be added in the unselected list
        """
        self.add_unselected_items([item])

    def addItems(self, items):
        """
        This is for Processing
        :param items: list of strings to be added in the unselected list
        """
        self.add_unselected_items(items)

    def _get_items(self, widget):
        for i in range(widget.count()):
            yield widget.item(i).text()

    def _set_items(self, widget, items):
        widget.clear()
        self._add_items(widget, items)

    def _add_items(self, widget, items):
        widget.addItems(items)

    def _select_all(self):
        self.unselected_widget.selectAll()
        self._do_move(self.unselected_widget, self.selected_widget)

    def _deselect_all(self):
        self.selected_widget.selectAll()
        self._do_move(self.selected_widget, self.unselected_widget)

    def _select(self):
        self._do_move(self.unselected_widget, self.selected_widget)

    def _deselect(self):
        self._do_move(self.selected_widget, self.unselected_widget)

    def _do_move(self, fromList, toList):
        for item in fromList.selectedItems():
            prev_from_item = fromList.item(fromList.row(item) - 1)
            toList.addItem(fromList.takeItem(fromList.row(item)))
            fromList.scrollToItem(prev_from_item)
        self.selection_changed.emit()

    def _setupUI(self):
        self.setSizePolicy(
            QSizePolicy.Preferred, QSizePolicy.Preferred)

        self.setMinimumHeight(180)

        self.main_horizontal_layout = QHBoxLayout(self)

        italic_font = QFont()
        italic_font.setItalic(True)

        # unselected widget
        self.unselected_widget = QListWidget(self)
        self._set_list_widget_defaults(self.unselected_widget)
        unselected_label = QLabel()
        unselected_label.setText('Unselected')
        unselected_label.setAlignment(Qt.AlignCenter)
        unselected_label.setFont(italic_font)
        unselected_v_layout = QVBoxLayout()
        unselected_v_layout.addWidget(unselected_label)
        unselected_v_layout.addWidget(self.unselected_widget)

        # selected widget
        self.selected_widget = QListWidget(self)
        self._set_list_widget_defaults(self.selected_widget)
        selected_label = QLabel()
        selected_label.setText('Selected')
        selected_label.setAlignment(Qt.AlignCenter)
        selected_label.setFont(italic_font)
        selected_v_layout = QVBoxLayout()
        selected_v_layout.addWidget(selected_label)
        selected_v_layout.addWidget(self.selected_widget)

        # buttons
        self.buttons_vertical_layout = QVBoxLayout()
        self.buttons_vertical_layout.setContentsMargins(0, -1, 0, -1)

        self.select_all_btn = SmallQPushButton('>>')
        self.deselect_all_btn = SmallQPushButton('<<')
        self.select_btn = SmallQPushButton('>')
        self.deselect_btn = SmallQPushButton('<')
        self.select_btn.setToolTip('Add the selected items')
        self.deselect_btn.setToolTip('Remove the selected items')
        self.select_all_btn.setToolTip('Add all')
        self.deselect_all_btn.setToolTip('Remove all')

        # add buttons
        spacer_label = QLabel()  # pragmatic way to create a spacer with
        # the same height of the labels on top
        # of the lists, in order to align the
        # buttons with the lists.
        self.buttons_vertical_layout.addWidget(spacer_label)
        self.buttons_vertical_layout.addWidget(self.select_btn)
        self.buttons_vertical_layout.addWidget(self.deselect_btn)
        self.buttons_vertical_layout.addWidget(self.select_all_btn)
        self.buttons_vertical_layout.addWidget(self.deselect_all_btn)

        # add sub widgets
        self.main_horizontal_layout.addLayout(unselected_v_layout)
        self.main_horizontal_layout.addLayout(self.buttons_vertical_layout)
        self.main_horizontal_layout.addLayout(selected_v_layout)

    def _set_list_widget_defaults(self, widget):
        widget.setAlternatingRowColors(True)
        widget.setSortingEnabled(True)
        widget.setDragEnabled(True)
        widget.setDragDropMode(QAbstractItemView.DragDrop)
        widget.setDragDropOverwriteMode(False)
        widget.setDefaultDropAction(Qt.MoveAction)
        widget.setSelectionMode(QAbstractItemView.MultiSelection)


class SmallQPushButton(QPushButton):

    def __init__(self, text):
        QPushButton.__init__(self)
        self.setText(text)
        buttons_size_policy = QSizePolicy(
            QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.setSizePolicy(buttons_size_policy)
        self.setMaximumSize(QSize(30, 30))
