# -*- coding: utf-8 -*-
"""
    Copyright (C) 2008-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de>

    partially based on C++ code from:
    Copyright (C) 2006 Mauricio Piacentini <mauricio@tabuleiro.com>

    Libkmahjongg 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.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""

from qt import Qt, QSize
from qt import QWidget, QHBoxLayout, QVBoxLayout, \
    QPushButton, QSpacerItem, QSizePolicy, \
    QTreeView, QFont, QAbstractItemView, QHeaderView
from qt import QModelIndex
from rule import Ruleset, PredefinedRuleset, RuleBase, ParameterRule, BoolRule
from util import uniqueList
from mi18n import i18n, i18nc, i18ncE, english
from differ import RulesetDiffer
from common import Debug, Internal
from tree import TreeItem, RootItem, TreeModel
from dialogs import Sorry
from modeltest import ModelTest
from genericdelegates import RightAlignedCheckboxDelegate, ZeroEmptyColumnDelegate
from statesaver import StateSaver
from guiutil import decorateWindow


class RuleRootItem(RootItem):

    """the root item for the ruleset tree"""

    def columnCount(self):
        return len(self.rawContent)


class RuleTreeItem(TreeItem):

    """generic class for items in our rule tree"""
    # pylint: disable=abstract-method
    # we know content() is abstract, this class is too

    def columnCount(self):
        """can be different for every rule"""
        if hasattr(self, 'colCount'):
            return self.colCount  # pylint: disable=no-member
        else:
            return len(self.rawContent)

    def ruleset(self):
        """returns the ruleset containing this item"""
        item = self
        while not isinstance(item.rawContent, Ruleset):
            item = item.parent
        return item.rawContent


class RulesetItem(RuleTreeItem):

    """represents a ruleset in the tree"""

    def __init__(self, content):
        RuleTreeItem.__init__(self, content)

    def content(self, column):
        """return content stored in this item"""
        if column == 0:
            return self.rawContent.name
        return ''

    def columnCount(self):
        return 1

    def remove(self):
        """remove this ruleset from the model and the database"""
        self.rawContent.remove()

    def tooltip(self):
        """the tooltip for a ruleset"""
        return self.rawContent.description


class RuleListItem(RuleTreeItem):

    """represents a list of rules in the tree"""

    def __init__(self, content):
        RuleTreeItem.__init__(self, content)

    def content(self, column):
        """return content stored in this item"""
        if column == 0:
            return self.rawContent.name
        return ''

    def tooltip(self):
        """tooltip for a list item explaining the usage of this list"""
        ruleset = self.ruleset()
        return '<b>' + i18n(ruleset.name) + '</b><br><br>' + \
            i18n(self.rawContent.description)


class RuleItem(RuleTreeItem):

    """represents a rule in the tree"""

    def __init__(self, content):
        RuleTreeItem.__init__(self, content)

    def content(self, column):
        """return the content stored in this node"""
        colNames = self.parent.parent.parent.rawContent
        content = self.rawContent
        if column == 0:
            return content.name
        else:
            if isinstance(content, ParameterRule):
                if column == 1:
                    return content.parameter
            else:
                if not hasattr(content.score, str(column)):
                    column = colNames[column]
                return getattr(content.score, column)
        return ''

    def tooltip(self):
        """tooltip for rule: just the name of the ruleset"""
        ruleset = self.ruleset()
        if self.rawContent.description:
            return '<b>' + i18n(ruleset.name) + '</b><br><br>' + \
                i18n(self.rawContent.description)
        else:
            return i18n(ruleset.name)


class RuleModel(TreeModel):

    """a model for our rule table"""

    def __init__(self, rulesets, title, parent=None):
        super(RuleModel, self).__init__(parent)
        self.rulesets = rulesets
        self.loaded = False
        unitNames = list()
        for ruleset in rulesets:
            ruleset.load()
            for rule in ruleset.allRules:
                unitNames.extend(rule.score.unitNames.items())
        unitNames = sorted(unitNames, key=lambda x: x[1])
        unitNames = uniqueList(x[0] for x in unitNames)
        rootData = [title]
        rootData.extend(unitNames)
        self.rootItem = RuleRootItem(rootData)

    def canFetchMore(self, dummyParent=None):
        """did we already load the rules? We only want to do that
        when the config tab with rulesets is actually shown"""
        return not self.loaded

    def fetchMore(self, dummyParent=None):
        """load the rules"""
        for ruleset in self.rulesets:
            self.appendRuleset(ruleset)
        self.loaded = True

    def data(self, index, role):  # pylint: disable=no-self-use
        """get data fom model"""
        # pylint: disable=too-many-branches,redefined-variable-type
        # too many branches
        result = None
        if index.isValid():
            item = index.internalPointer()
            if role in (Qt.DisplayRole, Qt.EditRole):
                if index.column() == 1:
                    if isinstance(item, RuleItem) and isinstance(item.rawContent, BoolRule):
                        return ''
                showValue = item.content(index.column())
                if isinstance(showValue, str) and showValue.endswith('.0'):
                    try:
                        showValue = str(int(float(showValue)))
                    except ValueError:
                        pass
                if showValue == '0':
                    showValue = ''
                result = showValue
            elif role == Qt.CheckStateRole:
                if self.isCheckboxCell(index):
                    bData = item.content(index.column())
                    result = Qt.Checked if bData else Qt.Unchecked
            elif role == Qt.TextAlignmentRole:
                result = int(Qt.AlignLeft | Qt.AlignVCenter)
                if index.column() > 0:
                    result = int(Qt.AlignRight | Qt.AlignVCenter)
            elif role == Qt.FontRole and index.column() == 0:
                ruleset = item.ruleset()
                if isinstance(ruleset, PredefinedRuleset):
                    font = QFont()
                    font.setItalic(True)
                    result = font
            elif role == Qt.ToolTipRole:
                tip = '<b></b>%s<b></b>' % i18n(
                    item.tooltip()) if item else ''
                result = tip
        return result

    @staticmethod
    def isCheckboxCell(index):
        """are we dealing with a checkbox?"""
        if index.column() != 1:
            return False
        item = index.internalPointer()
        return isinstance(item, RuleItem) and isinstance(item.rawContent, BoolRule)

    def headerData(self, section, orientation, role):
        """tell the view about the wanted headers"""
        if Qt is None:
            # happens when kajongg exits unexpectedly
            return
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            if section >= self.rootItem.columnCount():
                return
            result = self.rootItem.content(section)
            if result == 'doubles':
                return 'x2'
            else:
                return i18nc('kajongg', result)
        elif role == Qt.TextAlignmentRole:
            leftRight = Qt.AlignLeft if section == 0 else Qt.AlignRight
            return int(leftRight | Qt.AlignVCenter)

    def appendRuleset(self, ruleset):
        """append ruleset to the model"""
        if not ruleset:
            return
        ruleset.load()
        parent = QModelIndex()
        row = self.rootItem.childCount()
        rulesetItems = list([RulesetItem(ruleset)])
        self.insertRows(row, rulesetItems, parent)
        rulesetIndex = self.index(row, 0, parent)
        ruleLists = list(x for x in ruleset.ruleLists if len(x))
        ruleListItems = list([RuleListItem(x) for x in ruleLists])
        for item in ruleListItems:
            item.colCount = self.rootItem.columnCount()
        self.insertRows(0, ruleListItems, rulesetIndex)
        for ridx, ruleList in enumerate(ruleLists):
            listIndex = self.index(ridx, 0, rulesetIndex)
            ruleItems = list([RuleItem(x)
                              for x in ruleList if 'internal' not in x.options])
            self.insertRows(0, ruleItems, listIndex)


class EditableRuleModel(RuleModel):

    """add methods needed for editing"""

    def __init__(self, rulesets, title, parent=None):
        RuleModel.__init__(self, rulesets, title, parent)

    def __setRuleData(self, column, content, value):
        """change rule data in the model"""
        dirty, message = False, None
        if column == 0:
            if content.name != english(value):
                dirty = True
                content.name = english(value)
        elif column == 1 and isinstance(content, ParameterRule):
            oldParameter = content.parameter
            if isinstance(content, BoolRule):
                return False, ''
            else:
                if content.parameter != value:
                    dirty = True
                    content.parameter = value
            message = content.validate()
            if message:
                content.parameter = oldParameter
                dirty = False
        else:
            unitName = self.rootItem.content(column)
            dirty, message = content.score.change(unitName, value)
        return dirty, message

    def setData(self, index, value, role=Qt.EditRole):
        """change data in the model"""
        # pylint:  disable=too-many-branches
        if not index.isValid():
            return False
        try:
            dirty = False
            column = index.column()
            item = index.internalPointer()
            ruleset = item.ruleset()
            content = item.rawContent
            if role == Qt.EditRole:
                if isinstance(content, Ruleset) and column == 0:
                    oldName = content.name
                    content.rename(english(value))
                    dirty = oldName != content.name
                elif isinstance(content, RuleBase):
                    dirty, message = self.__setRuleData(column, content, value)
                    if message:
                        Sorry(message)
                        return False
                else:
                    return False
            elif role == Qt.CheckStateRole:
                if isinstance(content, BoolRule) and column == 1:
                    if not isinstance(ruleset, PredefinedRuleset):
                        newValue = value == Qt.Checked
                        if content.parameter != newValue:
                            dirty = True
                            content.parameter = newValue
                else:
                    return False
            if dirty:
                if isinstance(content, RuleBase):
                    ruleset.updateRule(content)
                self.dataChanged.emit(index, index)
            return True
        except BaseException:
            return False

    def flags(self, index):  # pylint: disable=no-self-use
        """tell the view what it can do with this item"""
        if not index.isValid():
            return Qt.ItemIsEnabled
        column = index.column()
        item = index.internalPointer()
        content = item.rawContent
        checkable = False
        if isinstance(content, Ruleset) and column == 0:
            mayEdit = True
        elif isinstance(content, RuleBase):
            checkable = column == 1 and isinstance(content, BoolRule)
            mayEdit = bool(column)
        else:
            mayEdit = False
        mayEdit = mayEdit and not isinstance(item.ruleset(), PredefinedRuleset)
        result = Qt.ItemIsEnabled | Qt.ItemIsSelectable
        if mayEdit:
            result |= Qt.ItemIsEditable
        if checkable:
            result |= Qt.ItemIsUserCheckable
        return result


class RuleTreeView(QTreeView):

    """Tree view for our rulesets"""

    def __init__(self, name, btnCopy=None,
                 btnRemove=None, btnCompare=None, parent=None):
        QTreeView.__init__(self, parent)
        self.name = name
        self.setObjectName('RuleTreeView')
        self.btnCopy = btnCopy
        self.btnRemove = btnRemove
        self.btnCompare = btnCompare
        for button in [self.btnCopy, self.btnRemove, self.btnCompare]:
            if button:
                button.setEnabled(False)
        self.header().setObjectName('RuleTreeViewHeader')
        self.setSelectionMode(QAbstractItemView.SingleSelection)
        self.ruleModel = None
        self.ruleModelTest = None
        self.rulesets = []  # nasty: this generates self.ruleModel
        self.differs = []

    def dataChanged(self, dummyIndex1, dummyIndex2, dummyRoles=None):
        """gets called if the model has changed: Update all differs"""
        for differ in self.differs:
            differ.rulesetChanged()

    @property
    def rulesets(self):
        """a list of rulesets made available by this model"""
        return self.ruleModel.rulesets

    @rulesets.setter
    def rulesets(self, rulesets):
        """a new list: update display"""
        if not self.ruleModel or self.ruleModel.rulesets != rulesets:
            if self.btnRemove and self.btnCopy:
                self.ruleModel = EditableRuleModel(rulesets, self.name)
            else:
                self.ruleModel = RuleModel(rulesets, self.name) # pylint: disable=redefined-variable-type
            self.setItemDelegateForColumn(
                1,
                RightAlignedCheckboxDelegate(
                    self,
                    self.ruleModel.isCheckboxCell))
            for  col in (1, 2, 3):
                self.setItemDelegateForColumn(col, ZeroEmptyColumnDelegate(self))
            self.setModel(self.ruleModel)
            if Debug.modelTest:
                self.ruleModelTest = ModelTest(self.ruleModel, self)
            self.show()

    def selectionChanged(self, selected, dummyDeselected=None):
        """update editing buttons"""
        enableCopy = enableRemove = enableCompare = False
        if selected.indexes():
            item = selected.indexes()[0].internalPointer()
            isPredefined = isinstance(item.ruleset(), PredefinedRuleset)
            if isinstance(item, RulesetItem):
                enableCompare = True
                enableCopy = sum(
                    x.hash == item.ruleset(
                    ).hash for x in self.ruleModel.rulesets) == 1
                enableRemove = not isPredefined
        if self.btnCopy:
            self.btnCopy.setEnabled(enableCopy)
        if self.btnRemove:
            self.btnRemove.setEnabled(enableRemove)
        if self.btnCompare:
            self.btnCompare.setEnabled(enableCompare)

    def showEvent(self, dummyEvent):
        """reload the models when the view comes into sight"""
        # default: make sure the name column is wide enough
        if self.ruleModel.canFetchMore():
            # we want to load all before adjusting column width
            self.ruleModel.fetchMore()
        header = self.header()
        header.setStretchLastSection(False)
        header.setMinimumSectionSize(-1)
        for col in range(1, header.count()):
            header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        for col in range(header.count()):
            self.resizeColumnToContents(col)

    def selectedRow(self):
        """returns the currently selected row index (with column 0)"""
        rows = self.selectionModel().selectedRows()
        if len(rows) == 1:
            return rows[0]

    def copyRow(self):
        """copy a ruleset"""
        row = self.selectedRow()
        if row:
            item = row.internalPointer()
            assert isinstance(item, RulesetItem)
            ruleset = item.rawContent.copyTemplate()
            self.model().appendRuleset(ruleset)
            self.rulesets.append(ruleset)
            self.selectionChanged(self.selectionModel().selection())

    def removeRow(self):
        """removes a ruleset or a rule"""
        row = self.selectedRow()
        if row:
            item = row.internalPointer()
            assert not isinstance(item.ruleset(), PredefinedRuleset)
            assert isinstance(item, RulesetItem)
            ruleset = item.ruleset()
            self.model().removeRows(row.row(), parent=row.parent())
            self.rulesets.remove(ruleset)
            self.selectionChanged(self.selectionModel().selection())

    def compareRow(self):
        """shows the difference between two rulesets"""
        rows = self.selectionModel().selectedRows()
        ruleset = rows[0].internalPointer().rawContent
        assert isinstance(ruleset, Ruleset)
        differ = RulesetDiffer(ruleset, self.rulesets)
        differ.show()
        self.differs.append(differ)


class RulesetSelector(QWidget):

    """presents all available rulesets with previews"""

    def __init__(self, parent=None):
        super(RulesetSelector, self).__init__(parent)
        self.setContentsMargins(0, 0, 0, 0)
        self.setupUi()

    def setupUi(self):
        """layout the window"""
        decorateWindow(self, i18n('Customize rulesets'))
        self.setObjectName('Rulesets')
        hlayout = QHBoxLayout(self)
        v1layout = QVBoxLayout()
        self.v1widget = QWidget()
        v1layout = QVBoxLayout(self.v1widget)
        v2layout = QVBoxLayout()
        hlayout.addWidget(self.v1widget)
        hlayout.addLayout(v2layout)
        for widget in [self.v1widget, hlayout, v1layout, v2layout]:
            widget.setContentsMargins(0, 0, 0, 0)
        hlayout.setStretchFactor(self.v1widget, 10)
        self.btnCopy = QPushButton()
        self.btnRemove = QPushButton()
        self.btnCompare = QPushButton()
        self.btnClose = QPushButton()
        self.rulesetView = RuleTreeView(
            i18ncE('kajongg',
                   'Rule'),
            self.btnCopy,
            self.btnRemove,
            self.btnCompare)
        v1layout.addWidget(self.rulesetView)
        self.rulesetView.setWordWrap(True)
        self.rulesetView.setMouseTracking(True)
        spacerItem = QSpacerItem(
            20,
            20,
            QSizePolicy.Minimum,
            QSizePolicy.Expanding)
        v2layout.addWidget(self.btnCopy)
        v2layout.addWidget(self.btnRemove)
        v2layout.addWidget(self.btnCompare)
        self.btnCopy.clicked.connect(self.rulesetView.copyRow)
        self.btnRemove.clicked.connect(self.rulesetView.removeRow)
        self.btnCompare.clicked.connect(self.rulesetView.compareRow)
        self.btnClose.clicked.connect(self.hide)
        v2layout.addItem(spacerItem)
        v2layout.addWidget(self.btnClose)
        self.retranslateUi()
        StateSaver(self)
        self.show()

    def sizeHint(self):
        """we never want a horizontal scrollbar for player names,
        we always want to see them in full"""
        result = QWidget.sizeHint(self)
        available = Internal.app.desktop().availableGeometry()
        height = max(result.height(), available.height() * 2 // 3)
        width = max(result.width(), available.width() // 2)
        return QSize(width, height)

    def minimumSizeHint(self):
        """we never want a horizontal scrollbar for player names,
        we always want to see them in full"""
        return self.sizeHint()

    def showEvent(self, dummyEvent):
        """reload the rulesets"""
        self.refresh()

    def refresh(self):
        """retranslate and reload rulesets"""
        self.retranslateUi()
        self.rulesetView.rulesets = Ruleset.availableRulesets()

    def hideEvent(self, event):
        """close all differ dialogs"""
        for differ in self.rulesetView.differs:
            differ.hide()
            del differ
        QWidget.hideEvent(self, event)

    def retranslateUi(self):
        """translate to current language"""
        self.btnCopy.setText(i18n("C&opy"))
        self.btnCompare.setText(i18nc('Kajongg ruleset comparer', 'Co&mpare'))
        self.btnRemove.setText(i18n('&Remove'))
        self.btnClose.setText(i18n('&Close'))
