"""The DetailModel module defines a model to display the details of
StructBase, Array, and BasicBase instances."""

# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (c) 2007-2012, Python File Format Interface
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#
#    * Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials provided
#      with the distribution.
#
#    * Neither the name of the Python File Format Interface
#      project nor the names of its contributors may be used to endorse
#      or promote products derived from this software without specific
#      prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# ***** END LICENSE BLOCK *****

from PyQt4 import QtCore

from pyffi.utils.graph import EdgeFilter, GlobalNode
from pyffi.qskope.detail_tree import DetailTreeItem, DetailTreeItemData

# implementation references:
# http://doc.trolltech.com/4.3/model-view-programming.html
# http://doc.trolltech.com/4.3/model-view-model-subclassing.html
class DetailModel(QtCore.QAbstractItemModel):
    """General purpose model for QModelIndexed access to pyffi data structures
    such as StructBase, Array, and BasicBase instances."""
    # column definitions
    NUM_COLUMNS = 3
    COL_NAME  = 0
    COL_TYPE  = 1
    COL_VALUE = 2

#    def __init__(self, parent = None, block = None, refnumber_dict = None):
#        """Initialize the model to display the given block. The refnumber_dict
#        dictionary is used to handle references in the block."""
#        QtCore.QAbstractItemModel.__init__(self, parent)
#        # this list stores the blocks in the view
#        # is a list of NiObjects for the nif format, and a list of Chunks for
#        # the cgf format
#        self.block = block
#        self.refNumber = refnumber_dict if not refnumber_dict is None else {}

    def __init__(self, parent=None, globalnode=None, globalmodel=None,
                 edge_filter=EdgeFilter()):
        """Initialize the model to display the given global node in the
        detail tree. We also need a reference to the global model to
        resolve node references.
        """
        QtCore.QAbstractItemModel.__init__(self, parent)
        self.root_item = DetailTreeItem(
            data=DetailTreeItemData(node=globalnode),
            edge_filter=EdgeFilter())
        self.globalmodel = globalmodel

    def flags(self, index):
        """Return flags for the given index: all indices are enabled and
        selectable."""
        if not index.isValid():
            return QtCore.Qt.ItemFlags()
        # all items are enabled and selectable
        flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
        # determine whether item value can be set
        if index.column() == self.COL_VALUE:
            try:
                index.internalPointer().data.node.get_value()
                # TODO: find more clever system
            except AttributeError:
                pass
            except NotImplementedError:
                pass
            else:
                flags |= QtCore.Qt.ItemIsEditable
        return QtCore.Qt.ItemFlags(flags)

    def data(self, index, role):
        """Return the data of model index in a particular role. Only
        QtCore.Qt.DisplayRole is implemented.
        """
        # check if the index is valid
        # check if the role is supported
        if not index.isValid() or role != QtCore.Qt.DisplayRole:
            return None
        # get the data for display
        item = index.internalPointer()

        # the name column
        if index.column() == self.COL_NAME:
            return item.data.name

        # the type column
        elif index.column() == self.COL_TYPE:
            return item.data.typename

        # the value column
        elif index.column() == self.COL_VALUE:
            # get display element
            display = item.data.display
            if isinstance(display, GlobalNode):
                # reference
                blocknum = self.globalmodel.index_dict[display]
                if (not hasattr(display, "name") or not display.name):
                    return "%i [%s]" % (blocknum, display.__class__.__name__)
                else:
                    return "%i (%s)" % (blocknum, display.name)
            elif isinstance(display, str):
                # regular string
                if len(display) > 32:
                    display = display[:32] + "..."
                return display.replace("\n", " ").replace("\r", " ")
            else:
                raise TypeError("%s: do not know how to display %s"
                                % (item.data.name, display.__class__.__name__))

        # other colums: invalid
        else:
            return None

    def headerData(self, section, orientation, role):
        """Return header data."""
        if (orientation == QtCore.Qt.Horizontal
            and role == QtCore.Qt.DisplayRole):
            if section == self.COL_TYPE:
                return "Type"
            elif section == self.COL_NAME:
                return "Name"
            elif section == self.COL_VALUE:
                return "Value"
        return None

    def rowCount(self, parent=QtCore.QModelIndex()):
        """Calculate a row count for the given parent index."""
        if not parent.isValid():
            # top level: one row for each attribute
            return len(self.root_item.children)
        else:
            # get the parent child count
            return len(parent.internalPointer().children)

    def columnCount(self, parent=QtCore.QModelIndex()):
        """Return column count."""
        # column count is constant everywhere
        return self.NUM_COLUMNS

    def index(self, row, column, parent):
        """Create an index to item (row, column) of object parent."""
        # check if the parent is valid
        if not parent.isValid():
            # parent is not valid, so we need a top-level object
            # return the row'th attribute
            item = self.root_item.children[row]
        else:
            # parent is valid, so we need to go get the row'th attribute
            # get the parent pointer
            item = parent.internalPointer().children[row]
        return self.createIndex(row, column, item)

    def parent(self, index):
        """Calculate parent of a given index."""
        # get parent structure
        parent_item = index.internalPointer().parent
        # if parent's parent is None, then index must be a top
        # level object, so return invalid index
        if parent_item.parent is None:
            return QtCore.QModelIndex()
        # if parent's parent is not None, then it must be member of
        # some deeper nested structure, so calculate the row as usual
        else:
            return self.createIndex(parent_item.row, 0, parent_item)

    def setData(self, index, value, role):
        """Set data of a given index from given QVariant value. Only
        QtCore.Qt.EditRole is implemented.
        """
        if role == QtCore.Qt.EditRole:
            # fetch the current data, as a regular Python type
            node = index.internalPointer().data.node
            currentvalue = node.get_value()
            # transform the QVariant value into the right class
            if isinstance(value, QtCore.QVariant):
                if isinstance(currentvalue, int):
                    # use long type to work around QVariant(0xffffffff).toInt() bug
                    pyvalue, ok = value.toLongLong()
                elif isinstance(currentvalue, float):
                    pyvalue, ok = value.toDouble()
                elif isinstance(currentvalue, str):
                    pyvalue = str(value.toString())
                    ok = True
                elif isinstance(currentvalue, bool):
                    pyvalue, ok = value.toBool()
                else:
                    # type not supported
                    return False
            else:
                pyvalue = value
                ok = True
            # check if conversion worked
            if not ok:
                return False
            # set the value (EditRole, so use set_editor_value, not set_value)
            node.set_editor_value(pyvalue)
            # tell everyone that the data has changed
            self.emit(QtCore.SIGNAL('dataChanged(QModelIndex, QModelIndex)'),
                                    index, index)
            return True
        # all other cases: failed
        return False
