# -*- coding: utf-8 -*-
"""
/***************************************************************************
 DimensionsSliderDialog
                                 A QGIS plugin
 Dimensions Slider Plugin
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin                : 2018-07-05
        git sha              : $Format:%H$
        copyright            : (C) 2018 by Camptocamp
        email                : info@camptocamp.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import os
import itertools

from qgis.core import QgsProject, QgsMapLayerProxyModel
from qgis.gui import QgsMapLayerComboBox, QgsFieldComboBox

from qgis.PyQt import uic
from qgis.PyQt import QtWidgets
from qgis.PyQt.QtCore import pyqtSlot, Qt, QSortFilterProxyModel, QAbstractTableModel, QModelIndex, QVariant
from qgis.PyQt.QtWidgets import QHeaderView, QStyledItemDelegate, QAbstractItemView

from dimensions_selector.core import Dimension, LayerDimension
from dimensions_selector.gui.help import openHelp

FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), '..', 'ui', 'settings_dialog.ui'))


class BaseTableModel(QAbstractTableModel):

    def __init__(self, items, parent=None):
        super().__init__(parent)
        self._items = items
        self._configure()

    def items(self):
        return self._items

    def clear(self):
        self.beginResetModel()
        self._items = []
        self.endResetModel()

    def addItem(self, item):
        self.addItems([item])

    def addItems(self, items):
        self.beginInsertRows(QModelIndex(),
                             len(self._items),
                             len(self._items) + len(items) - 1)
        self._items = self._items + items
        self.endInsertRows()

    def removeRows(self, row, count, index=QModelIndex()):
        self.beginRemoveRows(index, row, row + count - 1)
        for i in range(row + count - 1, row + 1):
            self._items.pop(i)
        self.endRemoveRows()
        return True

    def columnIndex(self, column_name):
        for index, column in enumerate(self._columns):
            if column['name'] == column_name:
                return index

    def columnCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        return len(self._columns)

    def rowCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        try:
            return len(self._items)
        except TypeError:
            return 0

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return self._columns[section]['header']
            if orientation == Qt.Vertical:
                return str(section)

    def flags(self, index):
        column_def = self._columns[index.column()]
        if column_def['type'] == QVariant.Bool:
            return Qt.ItemFlags(Qt.ItemIsSelectable |
                                Qt.ItemIsEnabled |
                                Qt.ItemIsEditable |
                                Qt.ItemIsUserCheckable)
        else:
            return Qt.ItemFlags(Qt.ItemIsSelectable |
                                Qt.ItemIsEditable |
                                Qt.ItemIsEnabled)


class DimensionsTableModel(BaseTableModel):

    def _configure(self):
        self._columns = [{
            'name': 'name',
            'type': QVariant.String,
            'header': self.tr("Name")
        }, {
            'name': 'options',
            'type': QVariant.String,
            'header': self.tr("Choices")
        }, {
            'name': 'table',
            'type': QVariant.String,
            'header': self.tr("Table")
        }, {
            'name': 'value_field',
            'type': QVariant.String,
            'header': self.tr("Value field")
        }, {
            'name': 'label_field',
            'type': QVariant.String,
            'header': self.tr("Label field")
        }, {
            'name': 'active',
            'type': QVariant.Bool,
            'header': self.tr("Active")
        }]

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            if column_def['type'] == QVariant.Bool:
                return ''
            if column_def['name'] == 'table':
                return item.table.name() if item.table is not None else ''
            return getattr(item, column_def['name'])
        if role == Qt.EditRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            return getattr(item, column_def['name'])
        if role == Qt.CheckStateRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            if column_def['type'] == QVariant.Bool:
                return Qt.Checked if item.active else Qt.Unchecked

    def setData(self, index, value, role=Qt.EditRole):
        if role == Qt.EditRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            setattr(item, column_def['name'], value)
            self.dataChanged.emit(index, index)
        if role == Qt.CheckStateRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            setattr(item, column_def['name'], value == Qt.Checked)
        return True


class LayerDimensionsTableModel(BaseTableModel):

    def _configure(self):
        self._columns = [{
            'name': 'layer',
            'type': QVariant.String,
            'header': self.tr("Layer"),
            'persistentEditor': True
        }, {
            'name': 'name',
            'type': QVariant.String,
            'header': self.tr("Name")
        }, {
            'name': 'field',
            'type': QVariant.String,
            'header': self.tr("Field")
        }, {
            'name': 'active',
            'type': QVariant.Bool,
            'header': self.tr("Active"),
            'persistentEditor': True
        }]

    def hasItem(self, layer, name):
        for item in self._items:
            if item.layer == layer and item.name == name:
                return True

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            if column_def['type'] == QVariant.Bool:
                return ''
            if column_def['name'] == 'layer':
                return item.layer.name() if item.layer is not None else ''
            return getattr(item, column_def['name'])
        if role == Qt.EditRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            if column_def['name'] == 'layer':
                return item.layer
            return getattr(item, column_def['name'])
        if role == Qt.CheckStateRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            if column_def['type'] == QVariant.Bool:
                return Qt.Checked if item.active else Qt.Unchecked

    def setData(self, index, value, role=Qt.EditRole):
        if role == Qt.EditRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            setattr(item, column_def['name'], value)
            self.dataChanged.emit(index, index)
        if role == Qt.CheckStateRole:
            item = self._items[index.row()]
            column_def = self._columns[index.column()]
            setattr(item, column_def['name'], value == Qt.Checked)
        return True


class LayerDelegate(QStyledItemDelegate):

    def createEditor(self, parent, option, index):
        editor = QgsMapLayerComboBox(parent)
        editor.setFilters(QgsMapLayerProxyModel.VectorLayer)
        editor.layerChanged.connect(self.on_layerChanged)
        return editor

    def setEditorData(self, editor, index):
        if not editor:
            return
        value = index.model().data(index, Qt.EditRole)
        editor.setLayer(value)

    def setModelData(self, editor, model, index):
        if not editor:
            return
        model.setData(index, editor.currentLayer())

    def on_layerChanged(self, layer):
        self.commitData.emit(self.sender())


class FieldDelegate(QStyledItemDelegate):

    def __init__(self, layer_column_index, parent=None):
        super().__init__(parent)
        self._layer_column_index = layer_column_index

    def createEditor(self, parent, option, index):
        editor = QgsFieldComboBox(parent)
        layer_index = index.model().index(index.row(), self._layer_column_index)
        layer = index.model().data(layer_index, Qt.EditRole)
        editor.setLayer(layer)
        editor.fieldChanged.connect(self.on_fieldChanged)
        return editor

    def setEditorData(self, editor, index):
        if not editor:
            return
        value = index.model().data(index, Qt.EditRole)
        editor.setField(value)

    def setModelData(self, editor, model, index):
        if not editor:
            return
        model.setData(index, editor.currentField())

    def on_fieldChanged(self, layer):
        self.commitData.emit(self.sender())


class SettingsDialog(QtWidgets.QDialog, FORM_CLASS):

    def __init__(self, manager, parent=None):
        """Constructor."""
        super(SettingsDialog, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

        self.button_box.helpRequested.connect(self.openHelp)

        self.manager = manager

        self._dimensions_model = DimensionsTableModel([d.copy() for d in manager.dimensions()], self)

        dimensions_proxy_model = QSortFilterProxyModel(self)
        dimensions_proxy_model.setDynamicSortFilter(True)
        dimensions_proxy_model.setSourceModel(self._dimensions_model)

        self.dimensionsView.setSortingEnabled(True)
        self.dimensionsView.setModel(dimensions_proxy_model)
        table_index = self._dimensions_model.columnIndex('table')
        self.dimensionsView.setItemDelegateForColumn(table_index, LayerDelegate(self))
        value_field_index = self._dimensions_model.columnIndex('value_field')
        self.dimensionsView.setItemDelegateForColumn(value_field_index, FieldDelegate(table_index, self))
        label_field_index = self._dimensions_model.columnIndex('label_field')
        self.dimensionsView.setItemDelegateForColumn(label_field_index, FieldDelegate(table_index, self))
        self.dimensionsView.horizontalHeader().resizeSections(QHeaderView.ResizeToContents)
        self.dimensionsView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.dimensionsView.selectionModel().selectionChanged.connect(self.on_dimensionsView_selectionChanged)

        layers_dimensions = [
            d.copy() for d in itertools.chain.from_iterable(
                [manager.layer_dimensions(layer)
                 for layer in QgsProject.instance().mapLayers().values()])]

        self._layer_dimensions_model = LayerDimensionsTableModel(layers_dimensions, self)

        layer_dimensions_proxy_model = QSortFilterProxyModel(self)
        layer_dimensions_proxy_model.setSortCaseSensitivity(Qt.CaseInsensitive)
        layer_dimensions_proxy_model.setDynamicSortFilter(True)
        layer_dimensions_proxy_model.setSourceModel(self._layer_dimensions_model)
        layer_dimensions_proxy_model.setFilterKeyColumn(self._layer_dimensions_model.columnIndex('name'))
        layer_dimensions_proxy_model.sort(self._layer_dimensions_model.columnIndex('layer'))

        self.layerDimensionsView.setSortingEnabled(True)
        self.layerDimensionsView.setModel(layer_dimensions_proxy_model)
        layer_index = self._layer_dimensions_model.columnIndex('layer')
        self.layerDimensionsView.setItemDelegateForColumn(layer_index, LayerDelegate(self))
        field_index = self._layer_dimensions_model.columnIndex('field')
        self.layerDimensionsView.setItemDelegateForColumn(field_index, FieldDelegate(layer_index, self))
        self.layerDimensionsView.horizontalHeader().resizeSections(QHeaderView.ResizeToContents)
        self.layerDimensionsView.setSelectionBehavior(QAbstractItemView.SelectRows)

    def openHelp(self):
        openHelp()

    def selected_dimensions_names(self):
        """
        Returns list for dimension names which are selected in dimensionsView.
        """
        names = []
        selection = self.dimensionsView.selectionModel().selectedRows()
        for index in selection:
            model = self.dimensionsView.model()
            name = model.data(model.index(index.row(), self._dimensions_model.columnIndex('name')))
            names.append(name)
        return names

    def on_dimensionsView_selectionChanged(self, selected, deselected):
        if self.dimensionsView.selectionModel().hasSelection():
            pattern = '^{}$'.format('|'.join(self.selected_dimensions_names()))
        else:
            pattern = ''
        self.layerDimensionsView.model().setFilterRegExp(pattern)
        self.layerDimensionsView.horizontalHeader().resizeSections(QHeaderView.ResizeToContents)

    @pyqtSlot(name='on_addDimensionButton_clicked')
    def on_addDimensionButton_clicked(self):
        self._dimensions_model.addItem(Dimension())

    @pyqtSlot(name='on_removeDimensionButton_clicked')
    def on_removeDimensionButton_clicked(self):
        selection = self.dimensionsView.selectionModel()
        if not selection.hasSelection():
            return
        for index in reversed(selection.selectedRows()):
            self.dimensionsView.model().removeRows(index.row(), 1)

    @pyqtSlot(name='on_populateDimensionButton_clicked')
    def on_populateDimensionButton_clicked(self):
        items = []
        for name in self.selected_dimensions_names():
            for id, layer in QgsProject.instance().mapLayers().items():
                if layer.fields().lookupField(name) == -1:
                    continue
                if self._layer_dimensions_model.hasItem(layer, name):
                    continue
                items.append(LayerDimension(layer, name, name, True))
        self._layer_dimensions_model.addItems(sorted(items, key=lambda d: d.layer.name()))
        self.layerDimensionsView.horizontalHeader().resizeSections(QHeaderView.ResizeToContents)

    @pyqtSlot(name='on_addLayerDimensionButton_clicked')
    def on_addLayerDimensionButton_clicked(self):
        self._layer_dimensions_model.addItem(LayerDimension(None, '', '', True))

    @pyqtSlot(name='on_removeLayerDimensionButton_clicked')
    def on_removeLayerDimensionButton_clicked(self):
        selection = self.layerDimensionsView.selectionModel()
        if not selection.hasSelection():
            return
        for index in reversed(selection.selectedRows()):
            self.layerDimensionsView.model().removeRows(index.row(), 1)

    def accept(self):
        self.manager.set_dimensions(self._dimensions_model.items())
        for id, layer in QgsProject.instance().mapLayers().items():
            self.manager.set_layer_dimensions(
                layer,
                [d for d in self._layer_dimensions_model.items() if d.layer == layer]
            )
        self.manager.refresh_filters()
        super().accept()
