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

"""
***************************************************************************
    FieldsMappingWidget.py
    ---------------------
    Date                 : October 2014
    Copyright            : (C) 2014 by Arnaud Morvan
    Email                : arnaud dot morvan at camptocamp dot 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.                                   *
*                                                                         *
***************************************************************************
"""

__author__ = 'Arnaud Morvan'
__date__ = 'October 2014'
__copyright__ = '(C) 2014, Arnaud Morvan'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'

import os

from collections import OrderedDict

from PyQt4 import uic
from PyQt4 import QtCore, QtGui

from qgis.core import QgsExpression
from qgis.gui import QgsFieldExpressionWidget

from processing.tools import dataobjects

pluginPath = os.path.dirname(__file__)
WIDGET, BASE = uic.loadUiType(
    os.path.join(pluginPath, 'widgetFieldsMapping.ui'))


class FieldsMappingModel(QtCore.QAbstractTableModel):

    fieldTypes = OrderedDict([
        (QtCore.QVariant.Int, "Integer"),
        (QtCore.QVariant.Double, "Double"),
        (QtCore.QVariant.String, "String"),
        (QtCore.QVariant.DateTime, "Date"),
        (QtCore.QVariant.LongLong, "Double"),
        (QtCore.QVariant.Date, "Date")])

    columns = [
        {'name': 'name', 'type': QtCore.QVariant.String},
        {'name': 'type', 'type': QtCore.QVariant.Type},
        {'name': 'length', 'type': QtCore.QVariant.Int},
        {'name': 'precision', 'type': QtCore.QVariant.Int},
        # {'name': 'comment', 'type': QtCore.QVariant.String},
        {'name': 'expression', 'type': QgsExpression}]

    def __init__(self, parent=None):
        super(FieldsMappingModel, self).__init__(parent)
        self._mapping = []
        self._errors = []
        self._layer = None

    def mapping(self):
        return self._mapping

    def setMapping(self, value):
        self.beginResetModel()
        self._mapping = value
        self.testAllExpressions()
        self.endResetModel()

    def testAllExpressions(self):
        self._errors = [None for i in xrange(0, len(self._mapping))]
        for row in xrange(0, len(self._mapping)):
            self.testExpression(row)

    def testExpression(self, row):
        self._errors[row] = None
        field = self._mapping[row]
        expression = QgsExpression(field['expression'])
        if expression.hasParserError():
            self._errors[row] = expression.parserErrorString()
            return
        if self._layer is None:
            return
        dp = self._layer.dataProvider()
        for feature in dp.getFeatures():
            expression.evaluate(feature, dp.fields())
            if expression.hasEvalError():
                self._errors[row] = expression.evalErrorString()
                return
            break

    def layer(self):
        return self._layer

    def setLayer(self, layer):
        self._layer = layer
        self.testAllExpressions()

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

    def rowCount(self, parent=QtCore.QModelIndex()):
        if parent.isValid():
            return 0
        return self._mapping.__len__()

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self.columns[section]['name'].title()
            if orientation == QtCore.Qt.Vertical:
                return section

    def flags(self, index):
        flags = (QtCore.Qt.ItemIsSelectable
                 + QtCore.Qt.ItemIsEditable
                 + QtCore.Qt.ItemIsEnabled)

        return QtCore.Qt.ItemFlags(flags)

    def data(self, index, role=QtCore.Qt.DisplayRole):
        column = index.column()

        if role == QtCore.Qt.DisplayRole:
            field = self._mapping[index.row()]
            column_def = self.columns[column]
            value = field[column_def['name']]

            fieldType = column_def['type']
            if fieldType == QtCore.QVariant.Type:
                if value == QtCore.QVariant.Invalid:
                    return ''
                return self.fieldTypes[value]
            return value

        if role == QtCore.Qt.EditRole:
            field = self._mapping[index.row()]
            column_def = self.columns[column]
            value = field[column_def['name']]
            return value

        if role == QtCore.Qt.TextAlignmentRole:
            fieldType = self.columns[column]['type']
            if fieldType in [QtCore.QVariant.Int]:
                hAlign = QtCore.Qt.AlignRight
            else:
                hAlign = QtCore.Qt.AlignLeft
            return hAlign + QtCore.Qt.AlignVCenter

        if role == QtCore.Qt.ForegroundRole:
            column_def = self.columns[column]
            if column_def['name'] == 'expression':
                brush = QtGui.QBrush()
                if self._errors[index.row()]:
                    brush.setColor(QtCore.Qt.red)
                else:
                    brush.setColor(QtCore.Qt.black)
                return brush

        if role == QtCore.Qt.ToolTipRole:
            column_def = self.columns[column]
            if column_def['name'] == 'expression':
                return self._errors[index.row()]

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if role == QtCore.Qt.EditRole:
            field = self._mapping[index.row()]
            column = index.column()
            column_def = self.columns[column]
            field[column_def['name']] = value
            if column_def['name'] == 'expression':
                self.testExpression(index.row())
            self.dataChanged.emit(index, index)
        return True

    def insertRows(self, row, count, index=QtCore.QModelIndex()):
        self.beginInsertRows(index, row, row + count - 1)

        for i in xrange(0, count):
            field = self.newField()
            self._mapping.insert(row + i, field)
            self._errors.insert(row + i, None)
            self.testExpression(row)

        self.endInsertRows()
        return True

    def removeRows(self, row, count, index=QtCore.QModelIndex()):
        self.beginRemoveRows(index, row, row + count - 1)

        for i in xrange(row + count - 1, row + 1):
            self._mapping.pop(i)
            self._errors.pop(i)

        self.endRemoveRows()
        return True

    def newField(self, field=None):
        if field is None:
            return {'name': '',
                    'type': QtCore.QVariant.Invalid,
                    'length': 0,
                    'precision': 0,
                    'expression': ''}

        return {'name': field.name(),
                'type': field.type(),
                'length': field.length(),
                'precision': field.precision(),
                'expression': field.name()}

    def loadLayerFields(self, layer):
        self.beginResetModel()

        self._mapping = []
        if layer is not None:
            dp = layer.dataProvider()
            for field in dp.fields():
                self._mapping.append(self.newField(field))
        self.testAllExpressions()

        self.endResetModel()


class FieldDelegate(QtGui.QStyledItemDelegate):

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

    def createEditor(self, parent, option, index):
        column = index.column()

        fieldType = FieldsMappingModel.columns[column]['type']
        if fieldType == QtCore.QVariant.Type:
            editor = QtGui.QComboBox(parent)
            for key, text in FieldsMappingModel.fieldTypes.iteritems():
                editor.addItem(text, key)

        elif fieldType == QgsExpression:
            editor = QgsFieldExpressionWidget(parent)
            editor.setLayer(index.model().layer())
            editor.fieldChanged.connect(self.on_expression_fieldChange)

        else:
            editor = QtGui.QStyledItemDelegate.createEditor(self, parent, option, index)

        editor.setAutoFillBackground(True)
        return editor

    def setEditorData(self, editor, index):
        if not editor:
            return

        column = index.column()
        value = index.model().data(index, QtCore.Qt.EditRole)

        fieldType = FieldsMappingModel.columns[column]['type']
        if fieldType == QtCore.QVariant.Type:
            editor.setCurrentIndex(editor.findData(value))

        elif fieldType == QgsExpression:
            editor.setField(value)

        else:
            QtGui.QStyledItemDelegate.setEditorData(self, editor, index)

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

        column = index.column()

        fieldType = FieldsMappingModel.columns[column]['type']
        if fieldType == QtCore.QVariant.Type:
            value = editor.itemData(editor.currentIndex())
            if value is None:
                value = QtCore.QVariant.Invalid
            model.setData(index, value)

        elif fieldType == QgsExpression:
            (value, isExpression, isValid) = editor.currentField()
            model.setData(index, value)

        else:
            QtGui.QStyledItemDelegate.setModelData(self, editor, model, index)

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)

    def on_expression_fieldChange(self, fieldName):
        self.commitData.emit(self.sender())


class FieldsMappingPanel(BASE, WIDGET):

    def __init__(self, parent=None):
        super(FieldsMappingPanel, self).__init__(parent)
        self.setupUi(self)

        self.addButton.setIcon(
            QtGui.QIcon(':/images/themes/default/mActionAdd.svg'))
        self.deleteButton.setIcon(
            QtGui.QIcon(':/images/themes/default/mActionRemove.svg'))
        self.upButton.setIcon(
            QtGui.QIcon(':/images/themes/default/mActionArrowUp.png'))
        self.downButton.setIcon(
            QtGui.QIcon(':/images/themes/default/mActionArrowDown.png'))
        self.resetButton.setIcon(
            QtGui.QIcon(':/images/themes/default/mIconClear.png'))

        self.model = FieldsMappingModel()
        self.fieldsView.setModel(self.model)

        self.model.rowsInserted.connect(self.on_model_rowsInserted)
        self.fieldsView.setItemDelegate(FieldDelegate())

        self.updateLayerCombo()

    def setLayer(self, layer):
        self.model.setLayer(layer)
        if self.model.rowCount() == 0:
            self.on_resetButton_clicked()
        else:
            dlg = QtGui.QMessageBox(self)
            dlg.setText("Do you want to reset the field mapping?")
            dlg.setStandardButtons(
                QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes
                                                  + QtGui.QMessageBox.No))
            dlg.setDefaultButton(QtGui.QMessageBox.No)
            if dlg.exec_() == QtGui.QMessageBox.Yes:
                self.on_resetButton_clicked()

    def value(self):
        return self.model.mapping()

    def setValue(self, value):
        self.model.setMapping(value)

    @QtCore.pyqtSlot(bool, name='on_addButton_clicked')
    def on_addButton_clicked(self, checked=False):
        rowCount = self.model.rowCount()
        self.model.insertRows(rowCount, 1)
        index = self.model.index(rowCount, 0)
        self.fieldsView.selectionModel().select(index,
            QtGui.QItemSelectionModel.SelectionFlags(QtGui.QItemSelectionModel.Clear
                                                     + QtGui.QItemSelectionModel.Select
                                                     + QtGui.QItemSelectionModel.Current
                                                     + QtGui.QItemSelectionModel.Rows))
        self.fieldsView.scrollTo(index)
        self.fieldsView.scrollTo(index)

    @QtCore.pyqtSlot(bool, name='on_deleteButton_clicked')
    def on_deleteButton_clicked(self, checked=False):
        sel = self.fieldsView.selectionModel()
        if not sel.hasSelection():
            return

        indexes = sel.selectedRows()
        for index in indexes:
            self.model.removeRows(index.row(), 1)

    @QtCore.pyqtSlot(bool, name='on_upButton_clicked')
    def on_upButton_clicked(self, checked=False):
        sel = self.fieldsView.selectionModel()
        if not sel.hasSelection():
            return

        row = sel.selectedRows()[0].row()
        if row == 0:
            return

        self.model.insertRows(row - 1, 1)

        for column in xrange(0, self.model.columnCount()):
            srcIndex = self.model.index(row + 1, column)
            dstIndex = self.model.index(row - 1, column)
            value = self.model.data(srcIndex, QtCore.Qt.EditRole)
            self.model.setData(dstIndex, value, QtCore.Qt.EditRole)

        self.model.removeRows(row + 1, 1)

        sel.select(self.model.index(row - 1, 0),
                   QtGui.QItemSelectionModel.SelectionFlags(QtGui.QItemSelectionModel.Clear
                                                            + QtGui.QItemSelectionModel.Select
                                                            + QtGui.QItemSelectionModel.Current
                                                            + QtGui.QItemSelectionModel.Rows))

    @QtCore.pyqtSlot(bool, name='on_downButton_clicked')
    def on_downButton_clicked(self, checked=False):
        sel = self.fieldsView.selectionModel()
        if not sel.hasSelection():
            return

        row = sel.selectedRows()[0].row()
        if row == self.model.rowCount() - 1:
            return

        self.model.insertRows(row + 2, 1)

        for column in xrange(0, self.model.columnCount()):
            srcIndex = self.model.index(row, column)
            dstIndex = self.model.index(row + 2, column)
            value = self.model.data(srcIndex, QtCore.Qt.EditRole)
            self.model.setData(dstIndex, value, QtCore.Qt.EditRole)

        self.model.removeRows(row, 1)

        sel.select(self.model.index(row + 1, 0),
                   QtGui.QItemSelectionModel.SelectionFlags(QtGui.QItemSelectionModel.Clear
                                                            + QtGui.QItemSelectionModel.Select
                                                            + QtGui.QItemSelectionModel.Current
                                                            + QtGui.QItemSelectionModel.Rows))

    @QtCore.pyqtSlot(bool, name='on_resetButton_clicked')
    def on_resetButton_clicked(self, checked=False):
        self.model.loadLayerFields(self.model.layer())
        self.openPersistentEditor(
            self.model.index(0, 0),
            self.model.index(self.model.rowCount() - 1,
                             self.model.columnCount() - 1))
        self.resizeColumns()

    def resizeColumns(self):
        header = self.fieldsView.horizontalHeader()
        header.resizeSections(QtGui.QHeaderView.ResizeToContents)
        for section in xrange(0, header.count()):
            size = header.sectionSize(section)
            fieldType = FieldsMappingModel.columns[section]['type']
            if fieldType == QgsExpression:
                header.resizeSection(section, size + 100)
            else:
                header.resizeSection(section, size + 20)

    def openPersistentEditor(self, topLeft, bottomRight):
        return
        for row in xrange(topLeft.row(), bottomRight.row() + 1):
            for column in xrange(topLeft.column(), bottomRight.column() + 1):
                self.fieldsView.openPersistentEditor(self.model.index(row, column))
                editor = self.fieldsView.indexWidget(self.model.index(row, column))
                if isinstance(editor, QtGui.QLineEdit):
                    editor.deselect()
                if isinstance(editor, QtGui.QSpinBox):
                    lineEdit = editor.findChild(QtGui.QLineEdit)
                    lineEdit.setAlignment(QtCore.Qt.AlignRight or QtCore.Qt.AlignVCenter)
                    lineEdit.deselect()

    def on_model_rowsInserted(self, parent, start, end):
        self.openPersistentEditor(
            self.model.index(start, 0),
            self.model.index(end, self.model.columnCount() - 1))

    def updateLayerCombo(self):
        layers = dataobjects.getVectorLayers()
        layers.sort(key=lambda lay: lay.name())
        for layer in layers:
            self.layerCombo.addItem(layer.name(), layer)

    @QtCore.pyqtSlot(bool, name='on_loadLayerFieldsButton_clicked')
    def on_loadLayerFieldsButton_clicked(self, checked=False):
        layer = self.layerCombo.itemData(self.layerCombo.currentIndex())
        if layer is None:
            return
        self.model.loadLayerFields(layer)
