 
 import Foundation
 
-public protocol Array2DProtocol: RangeReplaceableCollection {
+public protocol Array2DProtocol: RangeReplaceableCollection where Index == IndexPath, Element == Array2D<Value, Item>.Element {
     associatedtype Value
     associatedtype Item
     var array2DView: Array2D<Value, Item> { get set }
 }
 
-public struct Array2D<Value, Item>: Array2DProtocol {
-
-    public struct Section: MutableCollection {
+public struct Array2D<Value, Item>: Array2DProtocol, CustomDebugStringConvertible {
+
+    public struct Section: MutableCollection, CustomDebugStringConvertible {
 
         public var value: Value
         public var items: [Item]

    }

    public var startIndex: IndexPath {
        guard sections.count > 0 else { return IndexPath(item: 0, section: 0) }
        var section = 0
        while section < sections.count && sections[section].count == 0 {
            section += 1
        }
        return IndexPath(item: 0, section: section)
    }

    public var endIndex: IndexPath {
        return IndexPath(item: 0, section: sections.count)
    }

    public func index(after i: IndexPath) -> IndexPath {
        if i.section < sections.count {
            let section = sections[i.section]
            if i.item + 1 < section.items.count {
                return IndexPath(item: i.item + 1, section: i.section)
            } else {
                var section = i.section + 1
                while section < sections.count {
                    if sections[section].items.count > 0 {
                        return IndexPath(item: 0, section: section)
                    } else {
                        section += 1
                    }
                }
                return endIndex
            }
        } else {
            return endIndex
        }
    }

    public var isEmpty: Bool {
        return sections.reduce(true) { $0 && $1.items.isEmpty }
    }

    public var count: Int {
        return sections.reduce(0) { $0 + $1.items.count }
    }

    public subscript(indexPath: IndexPath) -> Item {
        get {
            return sections[indexPath.section].items[indexPath.item]
        }
        set {
            sections[indexPath.section].items[indexPath.item] = newValue
        }
    }


            self = newValue
        }
    }

    public var debugDescription: String {
        return "[" + sections.map { $0.debugDescription }.joined(separator: ", ") + "]"
    }
}

extension MutableObservableCollection where UnderlyingCollection: Array2DProtocol {

    public typealias Value = UnderlyingCollection.Value
    public typealias Item = UnderlyingCollection.Item
    public typealias Section = Array2D<Value, Item>.Section
    public typealias Element = Array2D<Value, Item>.Element

    public subscript(itemAt indexPath: IndexPath) -> Item {
        get {
            return collection.array2DView[indexPath].item!
        }
        set {
            descriptiveUpdate { (collection) -> [CollectionOperation<IndexPath>] in
                collection.array2DView[indexPath] = .item(newValue)
                return [.update(at: indexPath)]
            }
        }
    }

    public subscript(sectionAt index: Int) -> Section {
        get {
            return collection.array2DView.sections[index]
        }
        set {
            descriptiveUpdate { (collection) -> [CollectionOperation<IndexPath>] in
                collection.array2DView.sections[index] = newValue
                return [.update(at: [index])]
            }
        }
    }

    /// Append new section at the end of the 2D array.
    public func appendSection(_ section: Section) {
        append(.section(section))
    }

    /// Append `item` to the section `section` of the array.
    public func appendItem(_ item: Item, toSectionAt sectionIndex: Int) {
        let section = collection.array2DView.sections[sectionIndex]
        insert(item: item, at: IndexPath(item: section.count, section: sectionIndex))
    }

    /// Insert section at `index` with `items`.
    public func insert(section: Section, at index: Int)  {
        insert(.section(section), at: [index])
    }

    /// Insert `item` at `indexPath`.
    public func insert(item: Item, at indexPath: IndexPath)  {
        insert(.item(item), at: indexPath)
    }

    /// Insert `items` at index path `indexPath`.
    public func insert(contentsOf items: [Item], at indexPath: IndexPath) {
        insert(contentsOf: items.map { .item($0) }, at: indexPath)
    }

    /// Move the section at index `fromIndex` to index `toIndex`.
    public func moveSection(from fromIndex: Int, to toIndex: Int) {
        move(from: [fromIndex], to: [toIndex])
    }

    /// Move the item at `fromIndexPath` to `toIndexPath`.
    public func moveItem(from fromIndexPath: IndexPath, to toIndexPath: IndexPath) {
        move(from: fromIndexPath, to: toIndexPath)
    }

    /// Remove and return the section at `index`.
    @discardableResult
    public func removeSection(at index: Int) -> Section {
        return remove(at: [index]).section!
    }

    /// Remove and return the item at `indexPath`.
    @discardableResult
    public func removeItem(at indexPath: IndexPath) -> Item {
        return remove(at: indexPath).item!
    }

    /// Remove all items from the array. Keep empty sections.
    public func removeAllItems() {
        descriptiveUpdate { (collection) -> [CollectionOperation<IndexPath>] in
            let indices = collection.array2DView.indices.map { $0 }.filter { $0.count == 2 }
            for index in collection.array2DView.sections.indices {
                collection.array2DView.sections[index].items = []
            }
            return indices.map { .delete(at: $0) }
        }
    }

    /// Remove all items and sections from the array.
    public func removeAllItemsAndSections() {
        removeAll()
    }
}
