import Foundation public struct ItemCollectionViewEntryIndex: Comparable { public let collectionIndex: Int32 public let collectionId: ItemCollectionId public let itemIndex: ItemCollectionItemIndex public init(collectionIndex: Int32, collectionId: ItemCollectionId, itemIndex: ItemCollectionItemIndex) { self.collectionIndex = collectionIndex self.collectionId = collectionId self.itemIndex = itemIndex } public static func ==(lhs: ItemCollectionViewEntryIndex, rhs: ItemCollectionViewEntryIndex) -> Bool { return lhs.collectionIndex == rhs.collectionIndex && lhs.collectionId == rhs.collectionId && lhs.itemIndex == rhs.itemIndex } public static func <(lhs: ItemCollectionViewEntryIndex, rhs: ItemCollectionViewEntryIndex) -> Bool { if lhs.collectionIndex == rhs.collectionIndex { if lhs.itemIndex == rhs.itemIndex { return lhs.collectionId < rhs.collectionId } else { return lhs.itemIndex < rhs.itemIndex } } else { return lhs.collectionIndex < rhs.collectionIndex } } public static func lowerBound(collectionIndex: Int32, collectionId: ItemCollectionId) -> ItemCollectionViewEntryIndex { return ItemCollectionViewEntryIndex(collectionIndex: collectionIndex, collectionId: collectionId, itemIndex: ItemCollectionItemIndex(index: 0, id: 0)) } } public struct ItemCollectionViewEntry { public let index: ItemCollectionViewEntryIndex public let item: ItemCollectionItem public init(index: ItemCollectionViewEntryIndex, item: ItemCollectionItem) { self.index = index self.item = item } } private func fetchLowerEntries(namespaces: [ItemCollectionId.Namespace], collectionId: ItemCollectionId, collectionIndex: Int32, itemIndex: ItemCollectionItemIndex, count: Int, lowerCollectionId: (_ namespaceList: [ItemCollectionId.Namespace], _ collectionId: ItemCollectionId, _ collectionIndex: Int32) -> (ItemCollectionId, Int32)?, lowerItems: (_ collectionId: ItemCollectionId, _ itemIndex: ItemCollectionItemIndex, _ count: Int) -> [ItemCollectionItem]) -> [ItemCollectionViewEntry] { var entries: [ItemCollectionViewEntry] = [] var currentCollectionIndex = collectionIndex var currentCollectionId = collectionId var currentItemIndex = itemIndex while true { let remainingCount = count - entries.count assert(remainingCount > 0) let collectionItems = lowerItems(currentCollectionId, currentItemIndex, remainingCount) for item in collectionItems { entries.append(ItemCollectionViewEntry(index: ItemCollectionViewEntryIndex(collectionIndex: currentCollectionIndex, collectionId: currentCollectionId, itemIndex: item.index), item: item)) } if entries.count >= count { break } else { assert(collectionItems.count < remainingCount) if let (previousCollectionId, previousCollectionIndex) = lowerCollectionId(namespaces, currentCollectionId, currentCollectionIndex) { currentCollectionIndex = previousCollectionIndex currentCollectionId = previousCollectionId currentItemIndex = ItemCollectionItemIndex.upperBound } else { break } } } return entries } private func fetchHigherEntries(namespaces: [ItemCollectionId.Namespace], collectionId: ItemCollectionId, collectionIndex: Int32, itemIndex: ItemCollectionItemIndex, count: Int, higherCollectionId: (_ namespaceList: [ItemCollectionId.Namespace], _ collectionId: ItemCollectionId, _ collectionIndex: Int32) -> (ItemCollectionId, Int32)?, higherItems: (_ collectionId: ItemCollectionId, _ itemIndex: ItemCollectionItemIndex, _ count: Int) -> [ItemCollectionItem]) -> [ItemCollectionViewEntry] { var entries: [ItemCollectionViewEntry] = [] var currentCollectionIndex = collectionIndex var currentCollectionId = collectionId var currentItemIndex = itemIndex while true { let remainingCount = count - entries.count assert(remainingCount > 0) let collectionItems = higherItems(currentCollectionId, currentItemIndex, remainingCount) for item in collectionItems { entries.append(ItemCollectionViewEntry(index: ItemCollectionViewEntryIndex(collectionIndex: currentCollectionIndex, collectionId: currentCollectionId, itemIndex: item.index), item: item)) } if entries.count >= count { break } else { assert(collectionItems.count < remainingCount) if let (nextCollectionId, nextCollectionIndex) = higherCollectionId(namespaces, currentCollectionId, currentCollectionIndex) { currentCollectionIndex = nextCollectionIndex currentCollectionId = nextCollectionId currentItemIndex = ItemCollectionItemIndex.lowerBound } else { break } } } return entries } private func aroundEntries(namespaces: [ItemCollectionId.Namespace], aroundIndex: ItemCollectionViewEntryIndex?, count: Int, collectionIndexById: (ItemCollectionId) -> Int32?, lowerCollectionId: (_ namespaceList: [ItemCollectionId.Namespace], _ collectionId: ItemCollectionId, _ collectionIndex: Int32) -> (ItemCollectionId, Int32)?, fetchLowerItems: (_ collectionId: ItemCollectionId, _ itemIndex: ItemCollectionItemIndex, _ count: Int) -> [ItemCollectionItem], higherCollectionId: (_ namespaceList: [ItemCollectionId.Namespace], _ collectionId: ItemCollectionId, _ collectionIndex: Int32) -> (ItemCollectionId, Int32)?, fetchHigherItems: (_ collectionId: ItemCollectionId, _ itemIndex: ItemCollectionItemIndex, _ count: Int) -> [ItemCollectionItem]) -> ([ItemCollectionViewEntry], ItemCollectionViewEntry?, ItemCollectionViewEntry?) { var lowerEntries: [ItemCollectionViewEntry] = [] var upperEntries: [ItemCollectionViewEntry] = [] var lower: ItemCollectionViewEntry? var upper: ItemCollectionViewEntry? let selectedAroundIndex: ItemCollectionViewEntryIndex if let aroundIndex = aroundIndex, let aroundCollectionIndex = collectionIndexById(aroundIndex.collectionId) { selectedAroundIndex = ItemCollectionViewEntryIndex(collectionIndex: aroundCollectionIndex, collectionId: aroundIndex.collectionId, itemIndex: aroundIndex.itemIndex) } else { selectedAroundIndex = ItemCollectionViewEntryIndex(collectionIndex: 0, collectionId: ItemCollectionId(namespace: namespaces[0], id: 0), itemIndex: ItemCollectionItemIndex.lowerBound) } let collectionId: ItemCollectionId = selectedAroundIndex.collectionId let collectionIndex: Int32 = selectedAroundIndex.collectionIndex let itemIndex: ItemCollectionItemIndex = selectedAroundIndex.itemIndex lowerEntries.append(contentsOf: fetchLowerEntries(namespaces: namespaces, collectionId: collectionId, collectionIndex: collectionIndex, itemIndex: itemIndex, count: count / 2 + 1, lowerCollectionId: lowerCollectionId, lowerItems: fetchLowerItems)) let lowerIndices = lowerEntries.map { $0.index } assert(lowerIndices.sorted() == lowerIndices.reversed()) if lowerEntries.count >= count / 2 + 1 { lower = lowerEntries.last lowerEntries.removeLast() } upperEntries.append(contentsOf: fetchHigherEntries(namespaces: namespaces, collectionId: collectionId, collectionIndex: collectionIndex, itemIndex: ItemCollectionItemIndex(index: itemIndex.index, id: max(0, itemIndex.id - 1)), count: count - lowerEntries.count + 1, higherCollectionId: higherCollectionId, higherItems: fetchHigherItems)) let upperIndices = upperEntries.map { $0.index } assert(upperIndices.sorted() == upperIndices) if upperEntries.count >= count - lowerEntries.count + 1 { upper = upperEntries.last upperEntries.removeLast() } if lowerEntries.count != 0 && lowerEntries.count + upperEntries.count < count { var additionalLowerEntries: [ItemCollectionViewEntry] = fetchLowerEntries(namespaces: namespaces, collectionId: lowerEntries.last!.index.collectionId, collectionIndex: lowerEntries.last!.index.collectionIndex, itemIndex: lowerEntries.last!.index.itemIndex, count: count - lowerEntries.count - upperEntries.count + 1, lowerCollectionId: lowerCollectionId, lowerItems: fetchLowerItems) if additionalLowerEntries.count >= count - lowerEntries.count + upperEntries.count + 1 { lower = additionalLowerEntries.last additionalLowerEntries.removeLast() } lowerEntries.append(contentsOf: additionalLowerEntries) } var entries: [ItemCollectionViewEntry] = [] entries.append(contentsOf: lowerEntries.reversed()) entries.append(contentsOf: upperEntries) return (entries: entries, lower: lower, upper: upper) } final class MutableItemCollectionsView { let orderedItemListsViews: [MutableOrderedItemListView] let namespaces: [ItemCollectionId.Namespace] let requestedAroundIndex: ItemCollectionViewEntryIndex? let requestedCount: Int var collectionInfos: [(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?)] var entries: [ItemCollectionViewEntry] var lower: ItemCollectionViewEntry? var higher: ItemCollectionViewEntry? init(postbox: PostboxImpl, orderedItemListsViews: [MutableOrderedItemListView], namespaces: [ItemCollectionId.Namespace], aroundIndex: ItemCollectionViewEntryIndex?, count: Int) { self.orderedItemListsViews = orderedItemListsViews self.namespaces = namespaces self.requestedAroundIndex = aroundIndex self.requestedCount = count self.collectionInfos = [] self.entries = [] self.lower = nil self.higher = nil self.reload(postbox: postbox, aroundIndex: aroundIndex, count: count) } private func lowerItems(postbox: PostboxImpl, collectionId: ItemCollectionId, itemIndex: ItemCollectionItemIndex, count: Int) -> [ItemCollectionItem] { return postbox.itemCollectionItemTable.lowerItems(collectionId: collectionId, itemIndex: itemIndex, count: count) } private func higherItems(postbox: PostboxImpl, collectionId: ItemCollectionId, itemIndex: ItemCollectionItemIndex, count: Int) -> [ItemCollectionItem] { return postbox.itemCollectionItemTable.higherItems(collectionId: collectionId, itemIndex: itemIndex, count: count) } private func lowerCollectionId(postbox: PostboxImpl, namespaceList: [ItemCollectionId.Namespace], collectionId: ItemCollectionId, collectionIndex: Int32) -> (ItemCollectionId, Int32)? { return postbox.itemCollectionInfoTable.lowerCollectionId(namespaceList: namespaceList, collectionId: collectionId, index: collectionIndex) } private func higherCollectionId(postbox: PostboxImpl, namespaceList: [ItemCollectionId.Namespace], collectionId: ItemCollectionId, collectionIndex: Int32) -> (ItemCollectionId, Int32)? { return postbox.itemCollectionInfoTable.higherCollectionId(namespaceList: namespaceList, collectionId: collectionId, index: collectionIndex) } private func reload(postbox: PostboxImpl, aroundIndex: ItemCollectionViewEntryIndex?, count: Int) { self.collectionInfos = [] for namespace in namespaces { for (_, id, info) in postbox.itemCollectionInfoTable.getInfos(namespace: namespace) { let item = self.higherItems(postbox: postbox, collectionId: id, itemIndex: ItemCollectionItemIndex.lowerBound, count: 1).first self.collectionInfos.append((id, info, item)) } } let (entries, lower, higher) = aroundEntries(namespaces: namespaces, aroundIndex: aroundIndex, count: count, collectionIndexById: { id in return postbox.itemCollectionInfoTable.getIndex(id: id) }, lowerCollectionId: { namespaceList, collectionId, collectionIndex in return self.lowerCollectionId(postbox: postbox, namespaceList: namespaceList, collectionId: collectionId, collectionIndex: collectionIndex) }, fetchLowerItems: { collectionId, itemIndex, count in return self.lowerItems(postbox: postbox, collectionId: collectionId, itemIndex: itemIndex, count: count) }, higherCollectionId: { namespaceList, collectionId, collectionIndex in return self.higherCollectionId(postbox: postbox, namespaceList: namespaceList, collectionId: collectionId, collectionIndex: collectionIndex) }, fetchHigherItems: { collectionId, itemIndex, count in return self.higherItems(postbox: postbox, collectionId: collectionId, itemIndex: itemIndex, count: count) }) self.entries = entries self.lower = lower self.higher = higher } func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { var updated = false if !transaction.currentOrderedItemListOperations.isEmpty { for view in self.orderedItemListsViews { if view.replay(postbox: postbox, transaction: transaction) { updated = true } } } var reloadNamespaces = Set() for operation in transaction.currentItemCollectionInfosOperations { switch operation { case let .replaceInfos(namespace): reloadNamespaces.insert(namespace) } } for (id, operations) in transaction.currentItemCollectionItemsOperations { for operation in operations { switch operation { case .replaceItems: reloadNamespaces.insert(id.namespace) } } } var shouldReloadEntries = false if !reloadNamespaces.isEmpty { for namespace in self.namespaces { if reloadNamespaces.contains(namespace) { shouldReloadEntries = true break } } } if shouldReloadEntries { self.reload(postbox: postbox, aroundIndex: self.requestedAroundIndex, count: self.requestedCount) updated = true } return updated } } public final class ItemCollectionsView { public let orderedItemListsViews: [OrderedItemListView] public let collectionInfos: [(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?)] public let entries: [ItemCollectionViewEntry] public let lower: ItemCollectionViewEntry? public let higher: ItemCollectionViewEntry? init(_ mutableView: MutableItemCollectionsView) { self.orderedItemListsViews = mutableView.orderedItemListsViews.map { OrderedItemListView($0) } self.collectionInfos = mutableView.collectionInfos self.entries = mutableView.entries self.lower = mutableView.lower self.higher = mutableView.higher } }