# behavior.py
"""
Overview comment here
"""

from qtpy.QtCore import QAbstractItemModel, QAbstractTableModel, QModelIndex, QObject, Qt, Signal, Slot
from qtpy.QtGui import QColor
import os

class Behavior(QObject):
    """
    An annotation behavior, which is quite simple.  It comprises:
    name        The name of the behavior, which is displayed on various UI widgets
    hot_key     The case-sensitive key stroke used to start and stop instances of the behavior
    color       The color with which to display this behavior
    """

    def __init__(self, name: str, hot_key: str = '', color: QColor = QColor('gray'), active = False, visible = True):
        super().__init__()
        self._name = name
        self._hot_key = '' if hot_key == '_' else hot_key
        self._color = color
        self._visible = visible
        self._active = active
        self._get_functions = {
            'hot_key': self.get_hot_key,
            'name': self.get_name,
            'color': self.get_color,
            'active': self.is_active,
            'visible': self.is_visible
            }
        self._set_functions = {
            'hot_key': self.set_hot_key,
            'name': self.set_name,
            'color': self.set_color,
            'active': self.set_active,
            'visible': self.set_visible
            }

    def __repr__(self):
        return f"Behavior: name={self._name}, hot_key={self._hot_key}, color={self._color}, active={self._active}, visible={self._visible}"

    def get(self, key):
        # may raise KeyError
        return self._get_functions[key]()

    def set(self, key, value):
        # may raise KeyError
        self._set_functions[key](value)

    def get_hot_key(self):
        return self._hot_key

    def set_hot_key(self, hot_key: str):
        self._hot_key = hot_key

    def get_color(self):
        return self._color

    def set_color(self, color: QColor):
        self._color = color

    def is_active(self):
        return self._active

    @Slot(bool)
    def set_active(self, active):
        self._active = active

    def is_visible(self):
        return self._visible

    @Slot(bool)
    def set_visible(self, visible):
        self._visible = visible

    def get_name(self):
        return self._name

    def set_name(self, name):
        self._name = name

    def toDict(self):
        return {
            'hot_key': '_' if self._hot_key == '' else self._hot_key,
            'color': self._color,
            'name': self._name,
            'active': self._active,
            'visible': self._visible
            }

class Behaviors(QAbstractTableModel):
    """
    A set of behaviors, which represent "all" possible behaviors.
    The class supports reading from and writing to profile files that specify
    the default hot_key and color for each behavior, along with the name.

    Derives from QAbstractTableModel so that it can be viewed and edited
    directly in a QTableView widget.

    Use getattr(name) to get the Behavior instance for a given name.
    Use from_hot_key(key) to get the behavior(s) given the hot key.
        Returns None if the hot_key isn't defined.
    """

    behaviors_changed = Signal()
    layout_changed = Signal()

    def __init__(self):
        super().__init__()
        self._items = []
        self._by_name = {}
        self._by_hot_key = {}
        self._header = ['hot_key', 'color', 'name', 'active', 'visible']
        self._searchList = [self._by_hot_key, None, self._by_name, None, None]
        self._delete_behavior = Behavior('_delete', color = QColor('black'))
        self._immutableColumns = set()
        self._booleanColumns = set([self._header.index('active'), self._header.index('visible')])
        self._role_to_str = {
            Qt.DisplayRole: "DisplayRole",
            Qt.DecorationRole: "DecorationRole",
            Qt.EditRole: "EditRole",
            Qt.ToolTipRole: "ToolTipRole",
            Qt.StatusTipRole: "StatusTipRole",
            Qt.WhatsThisRole: "WhatsThisRole",
            Qt.SizeHintRole: "SizeHintRole",
            Qt.FontRole: "FontRole",
            Qt.TextAlignmentRole: "TextAlignmentRole",
            Qt.BackgroundRole: "BackgroundRole",
            Qt.ForegroundRole: "ForegroundRole",
            Qt.CheckStateRole: "CheckStateRole",
            Qt.InitialSortOrderRole: "InitialSortOrderRole",
            Qt.AccessibleTextRole: "AccessibleTextRole",
            Qt.UserRole: "UserRole"
        }

    def add(self, beh: Behavior, row=-1):
        if row < 0:
            row = self.rowCount()
        self.beginInsertRows(QModelIndex(), row, row)
        self._items.insert(row, beh)
        self._by_name[beh.get_name()] = beh
        hot_key = beh.get_hot_key()
        if hot_key:
            if hot_key not in self._by_hot_key.keys():
                self._by_hot_key[hot_key] = []
            assert(isinstance(self._by_hot_key[hot_key], list))
            self._by_hot_key[hot_key].append(beh)
        self.endInsertRows()
        self.dataChanged.emit(
            self.index(row, 0, QModelIndex()),
            self.index(row, self.columnCount()-1, QModelIndex()),
            [Qt.DisplayRole, Qt.EditRole])
        self.behaviors_changed.emit()

    def load(self, f):
        line = f.readline()
        while line:
            hot_key, name, r, g, b = line.strip().split(' ')
            if hot_key == '_':
                hot_key = ''
            self.add(Behavior(name, hot_key, QColor.fromRgbF(float(r), float(g), float(b))))
            line = f.readline()

    def save(self, f):
        for beh in self._items:
            h = beh.get_hot_key()
            if h == '':
                h = '_'
            color = beh.get_color()
            f.write(f"{h} {beh.get_name()} {color.redF()} {color.greenF()} {color.blueF()}" + os.linesep)

    def get(self, name):
        if name not in self._by_name.keys():
            return None
        return self._by_name[name]

    def from_hot_key(self, key):
        """
        Return the list of behaviors associated with this hot key, if any
        """
        try:
            return self._by_hot_key[key]
        except KeyError:
            return None

    def len(self):
        return len(self._items)

    def header(self):
        return self._header

    def colorColumns(self):
        return [self._header.index('color')]

    def __iter__(self):
        return iter(self._items)

    def getDeleteBehavior(self):
        return self._delete_behavior

    def addIfMissing(self, nameToAdd):
        if nameToAdd not in self._by_name:
            self.add(Behavior(nameToAdd, '', QColor('gray')))
            return True
        return False

    def isImmutable(self, index):
        return index.column() in self._immutableColumns

    def setImmutable(self, column):
        self._immutableColumns.add(column)

    # QAbstractTableModel API methods

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._header[col]
        return None

    def rowCount(self, parent=None):
        return len(self._items)

    def columnCount(self, parent=None):
        return len(self._header)

    def data(self, index, role=Qt.DisplayRole):
        datum = self._items[index.row()].get(self._header[index.column()])
        if isinstance(datum, bool):
            if role in [Qt.CheckStateRole, Qt.EditRole]:
                return Qt.Checked if datum else Qt.Unchecked
            return None
        if role in [Qt.DisplayRole, Qt.EditRole]:
            return self._items[index.row()].get(self._header[index.column()])
        return None

    def setData(self, index, value, role=Qt.EditRole):
        if not role in [Qt.CheckStateRole, Qt.EditRole]:
            return False
        if role == Qt.CheckStateRole:
            value = bool(value)
        beh = self._items[index.row()]
        key = self._header[index.column()]
        name = beh.get_name()
        hot_key = beh.get_hot_key()
        beh.set(key, value)
        if key == 'hot_key' and value != hot_key:
            # disassociate this behavior from the hot_key
            # and associate with the new hot_key if not ''
            if hot_key != '':
                del(self._by_hot_key[hot_key])
            if value != '':
                if value not in self._by_hot_key.keys():
                    self._by_hot_key[value] = []
                assert(isinstance(self._by_hot_key[value], list))
                self._by_hot_key[value].append(beh)
        elif key == 'name' and value != name:
            if name in self._by_name.keys():
                del(self._by_name[name])
            self._by_name[value] = beh
        self.behaviors_changed.emit()
        self.dataChanged.emit(index, index, [role])
        return True

    def insertRows(self, row, count, parent):
        if count < 1 or row < 0 or row > self.rowCount():
            return False
        self.beginInsertRows(QModelIndex(), row, row)
        for r in range(count):
            self._items.insert(row, Behavior('', active=True))
        self.endInsertRows()
        return True

    def removeRows(self, row, count, parent=QModelIndex()):
        if count <= 0 or row < 0 or row + count > self.rowCount(parent):
            return False
        self.beginRemoveRows(parent, row, row + count - 1)
        for item in self._items[row:row+count-1]:
            self._by_name.pop(item.name)
            self._by_hot_key.pop(item.hot_key)
        for i in range(count):
            self._items.pop(row)
        self.endRemoveRows()

    def flags(self, index):
        f = super().flags(index)
        if index.column() not in self._immutableColumns:
            f |= Qt.ItemIsEditable
        if index.column() in self._booleanColumns:
            f = (f & ~(Qt.ItemIsSelectable | Qt.ItemIsEditable)) | Qt.ItemIsUserCheckable
        return f