#
# Copyright 2016 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://openusd.org/license.
#

# pylint: disable=map-builtin-not-iterating

from .qt import QtCore, QtGui, QtWidgets


def _GetLengthOfRange(start, stop, step):
    if step == 0:
        return 0

    k = 1 if step > 0 else -1
    num = 1 + (stop - k - start) // step

    # note, sign(stop - start) != sign(step) when we have an empty range.  In those
    # cases, "num" ends up non-positive, so we can just take the max(num, 0)
    return max(0, num)


class _ArrayAttributeModel(QtCore.QAbstractListModel):
    '''This is a data model that represents a slice into some array data.
    '''

    RawDataRole = QtCore.Qt.ItemDataRole.UserRole + 0

    def __init__(self):
        super(_ArrayAttributeModel, self).__init__()
        self._arrayData = None
        self._scalarTypeName = ""
        self._slice = slice(None)
        self._fetchMoreTimer = QtCore.QTimer()
        self._fetchMoreTimer.setInterval(1000)
        self._fetchMoreTimer.timeout.connect(self.TryToFetchMore)
        self._fetchMoreTimer.start()

        self._rowCount = 0
        self._publishedRows = 0

    def SetArrayDataAndTypeName(self, arrayData, scalarTypeName):
        self._arrayData = arrayData
        self._scalarTypeName = scalarTypeName
        self._Reset()

    def SetSlice(self, slice_):
        if self._slice == slice_:
            return
        self._slice = slice_
        self._Reset()

    def _Reset(self):
        self._publishedRows = 0
        self._rowCount = 0
        if self._arrayData is not None:
            self._rowCount = _GetLengthOfRange(*self._slice.indices(len(self._arrayData)))
        self.modelReset.emit()

    def GetArrayData(self):
        return self._arrayData

    def GetScalarTypeName(self):
        return self._scalarTypeName

    def index(self, row, col, parent=QtCore.QModelIndex()):
        return self.createIndex(row, col)

    def parent(self, index):
        return QtCore.QModelIndex()

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

    def columnCount(self, parent=QtCore.QModelIndex()):
        return 1

    def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
        start, _, step = self._slice.indices(len(self._arrayData))
        idx = start + index.row() * step
        dataVal = self._arrayData[idx]

        if role == QtCore.Qt.ItemDataRole.DisplayRole:
            from .scalarTypes import ToString
            return str(idx) + ": " + ToString(
                dataVal, self._scalarTypeName)

        elif role == _ArrayAttributeModel.RawDataRole:
            return dataVal

        return None

    def fetchMore(self, index):
        left = self._rowCount - self._publishedRows
        toFetch = min(5000 if self._publishedRows == 0 else 100, left)
        self.beginInsertRows(
            index, self._publishedRows, self._publishedRows + toFetch - 1)
        self._publishedRows += toFetch
        self.endInsertRows()

    def canFetchMore(self, index):
        return self._publishedRows < self._rowCount

    def TryToFetchMore(self):
        rootIndex = QtCore.QModelIndex()
        if self.canFetchMore(rootIndex):
            self.fetchMore(rootIndex)


class ArrayAttributeView(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(ArrayAttributeView, self).__init__(parent)

        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)

        sliceLayout = QtWidgets.QHBoxLayout()
        layout.addLayout(sliceLayout)

        sliceLayout.addWidget(QtWidgets.QLabel("Slice:"))
        self._lineEdit = _SliceLineEdit()
        self._lineEdit.setPlaceholderText('e.g.: "0:5000", "::-1"')
        sliceLayout.addWidget(self._lineEdit)

        self._arrayAttrModel = _ArrayAttributeModel()
        self._listView = QtWidgets.QListView()
        self._listView.setUniformItemSizes(True)
        self._listView.setViewMode(QtWidgets.QListView.ViewMode.ListMode)
        self._listView.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
        self._listView.setModel(self._arrayAttrModel)
        layout.addWidget(self._listView)

        self._lineEdit.SliceChanged.connect(self._arrayAttrModel.SetSlice)

        self._SetupContextMenu()

    def SetAttribute(self, attr, frame):
        from .scalarTypes import GetScalarTypeFromAttr

        arrayData = attr.Get(frame)
        scalarTypeName, _ = GetScalarTypeFromAttr(attr)
        self._arrayAttrModel.SetArrayDataAndTypeName(arrayData, scalarTypeName)

    def CanView(self, attr):
        return attr.GetTypeName().isArray

    def Clear(self):
        self._arrayAttrModel.SetArrayDataAndTypeName(None, "")
        self._lineEdit.setText("")

    def keyPressEvent(self, e):
        # XXX note, this is extremely finicky.  it does not really
        # have keyboard focus.
        if e.matches(QtGui.QKeySequence.StandardKey.Copy):
            self.Copy()
        else:
            return super(ArrayAttributeView, self).keyPressEvent(e)

    # context menu stuff
    def _SetupContextMenu(self):
        self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
        self.customContextMenuRequested.connect(self._ShowContextMenu)

    def _ShowContextMenu(self, point):
        menu = QtWidgets.QMenu(self)
        if self._listView.selectedIndexes():
            menu.addAction("Copy Selected", self.CopySelected)
        else:
            menu.addAction("Copy All", self.CopyAll)
        menu.addAction("Select All", self.SelectAll)
        menu.exec_(QtGui.QCursor.pos())

    def CopyAll(self):
        self._CopyValsToClipboard(self._arrayAttrModel.GetArrayData())

    def CopySelected(self):
        selectedIndexes = self._listView.selectedIndexes()
        self._CopyValsToClipboard([
            self._listView.model().data(idx, _ArrayAttributeModel.RawDataRole)
            for idx in selectedIndexes])

    def _CopyValsToClipboard(self, vals):
        from .scalarTypes import ToClipboard
        scalarTypeName = self._arrayAttrModel.GetScalarTypeName()
        copyText = "[ %s ]" % (", ".join(
            ToClipboard(val, scalarTypeName)
            for val in vals))
        QtWidgets.QApplication.clipboard().setText(copyText)

    def SelectAll(self):
        self._listView.selectAll()


def _IntOrNone(s):
    if not s:
        return None
    return int(s)


def _GetSliceFromString(s):
    s = slice(*map(_IntOrNone, s.split(':')))
    if s.step == 0:
        raise ValueError("Slice cannot be 0")
    return s


class _SliceLineEdit(QtWidgets.QLineEdit):
    """LineEdit for inputing strings that represent slices"""

    SliceChanged = QtCore.Signal(object)

    def __init__(self, parent=None):
        super(_SliceLineEdit, self).__init__(parent)
        self.setValidator(_SliceLineEdit.Validator())
        self.editingFinished.connect(self._OnEditingFinished)

    class Validator(QtGui.QValidator):
        def validate(self, s, pos):
            s = s.strip()
            try:
                _GetSliceFromString(s)
                return (QtGui.QValidator.State.Acceptable, s, pos)
            except:
                return (QtGui.QValidator.State.Intermediate, s, pos)

    def setText(self, t):
        super(_SliceLineEdit, self).setText(t)
        self._OnEditingFinished()

    def _OnEditingFinished(self):
        self.SliceChanged.emit(_GetSliceFromString(self.text()))
