//
//  Internals.DiffableDataUIDispatcher.Changeset.swift
//  CoreStore
//
//  Copyright © 2018 John Rommel Estropia
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.
//

#if canImport(UIKit) || canImport(AppKit)

import Foundation


// MARK: - Internals.DiffableDataUIDispatcher

extension Internals.DiffableDataUIDispatcher {
    
    // MARK: - ChangeSet
    
    // Implementation based on https://github.com/ra1028/DifferenceKit
    internal struct Changeset<C: Collection & Sendable>: Equatable, Sendable
    where C: Equatable {
        
        var data: C
        var sectionDeleted: [Int]
        var sectionInserted: [Int]
        var sectionUpdated: [Int]
        var sectionMoved: [(source: Int, target: Int)]

        var elementDeleted: [ElementPath]
        var elementInserted: [ElementPath]
        var elementUpdated: [ElementPath]
        var elementMoved: [(source: ElementPath, target: ElementPath)]

        @usableFromInline
        init(
            data: C,
            sectionDeleted: [Int] = [],
            sectionInserted: [Int] = [],
            sectionUpdated: [Int] = [],
            sectionMoved: [(source: Int, target: Int)] = [],
            elementDeleted: [ElementPath] = [],
            elementInserted: [ElementPath] = [],
            elementUpdated: [ElementPath] = [],
            elementMoved: [(source: ElementPath, target: ElementPath)] = []
        ) {
            self.data = data
            self.sectionDeleted = sectionDeleted
            self.sectionInserted = sectionInserted
            self.sectionUpdated = sectionUpdated
            self.sectionMoved = sectionMoved
            self.elementDeleted = elementDeleted
            self.elementInserted = elementInserted
            self.elementUpdated = elementUpdated
            self.elementMoved = elementMoved
        }

        @inlinable
        var sectionChangeCount: Int {
            
            return self.sectionDeleted.count
                + self.sectionInserted.count
                + self.sectionUpdated.count
                + self.sectionMoved.count
        }

        @inlinable
        var elementChangeCount: Int {
            
            return self.elementDeleted.count
                + self.elementInserted.count
                + self.elementUpdated.count
                + self.elementMoved.count
        }

        @inlinable
        var changeCount: Int {
            
            return self.sectionChangeCount + self.elementChangeCount
        }

        @inlinable
        var hasSectionChanges: Bool {
            
            return self.sectionChangeCount > 0
        }

        @inlinable
        var hasElementChanges: Bool {
            
            return self.elementChangeCount > 0
        }

        @inlinable
        var hasChanges: Bool {
            
            return self.changeCount > 0
        }
        
        
        // MARK: Equatable
        
        static func == (lhs: Changeset, rhs: Changeset) -> Bool {
            return lhs.data == rhs.data
                && Set(lhs.sectionDeleted) == Set(rhs.sectionDeleted)
                && Set(lhs.sectionInserted) == Set(rhs.sectionInserted)
                && Set(lhs.sectionUpdated) == Set(rhs.sectionUpdated)
                && Set(lhs.sectionMoved.map(HashablePair.init)) == Set(rhs.sectionMoved.map(HashablePair.init))
                && Set(lhs.elementDeleted) == Set(rhs.elementDeleted)
                && Set(lhs.elementInserted) == Set(rhs.elementInserted)
                && Set(lhs.elementUpdated) == Set(rhs.elementUpdated)
                && Set(lhs.elementMoved.map(HashablePair.init)) == Set(rhs.elementMoved.map(HashablePair.init))
        }
        
        
        // MARK: - HashablePair
        
        private struct HashablePair<H: Hashable>: Hashable {
            
            let first: H
            let second: H
        }
    }
}

#endif
