//
//  NotificationGroup.swift
//  Pachyderm
//
//  Created by Shadowfacts on 9/5/19.
//  Copyright © 2019 Shadowfacts. All rights reserved.
//

import Foundation

public struct NotificationGroup: Identifiable, Hashable, Sendable {
    public private(set) var notifications: [Notification]
    public let id: String
    public let kind: Notification.Kind

    public init?(notifications: [Notification]) {
        guard !notifications.isEmpty else { return nil }
        self.notifications = notifications
        self.id = notifications.first!.id
        self.kind = notifications.first!.kind
    }
    
    public static func ==(lhs: NotificationGroup, rhs: NotificationGroup) -> Bool {
        guard lhs.notifications.count == rhs.notifications.count else {
            return false
        }
        for (a, b) in zip(lhs.notifications, rhs.notifications) where a.id != b.id {
            return false
        }
        return true
    }
    
    public func hash(into hasher: inout Hasher) {
        for notification in notifications {
            hasher.combine(notification.id)
        }
    }
    
    private mutating func append(_ notification: Notification) {
        notifications.append(notification)
    }
    
    private mutating func append(group: NotificationGroup) {
        notifications.append(contentsOf: group.notifications)
    }

    @MainActor
    public static func createGroups(notifications: [Notification], only allowedTypes: [Notification.Kind]) -> [NotificationGroup] {
        var groups = [NotificationGroup]()
        for notification in notifications {
            if allowedTypes.contains(notification.kind) {
                if let lastGroup = groups.last, canMerge(notification: notification, into: lastGroup) {
                    groups[groups.count - 1].append(notification)
                    continue
                } else if groups.count >= 2 {
                    let secondToLastGroup = groups[groups.count - 2]
                    if allowedTypes.contains(groups[groups.count - 1].kind), canMerge(notification: notification, into: secondToLastGroup) {
                        groups[groups.count - 2].append(notification)
                        continue
                    }
                }
            }
            
            groups.append(NotificationGroup(notifications: [notification])!)
        }
        return groups
    }
    
    private static func canMerge(notification: Notification, into group: NotificationGroup) -> Bool {
        return notification.kind == group.kind && notification.status?.id == group.notifications.first!.status?.id
    }
    
    public static func mergeGroups(first: [NotificationGroup], second: [NotificationGroup], only allowedTypes: [Notification.Kind]) -> [NotificationGroup] {
        guard !first.isEmpty else {
            return second
        }
        guard !second.isEmpty else {
            return first
        }

        var merged = first
        var second = second
        merged.reserveCapacity(second.count)
        while let firstGroupFromSecond = second.first,
              allowedTypes.contains(firstGroupFromSecond.kind) {
            
            second.removeFirst()
            
            guard let lastGroup = merged.last,
                  allowedTypes.contains(lastGroup.kind) else {
                merged.append(firstGroupFromSecond)
                break
            }
            
            if canMerge(notification: firstGroupFromSecond.notifications.first!, into: lastGroup) {
                merged[merged.count - 1].append(group: firstGroupFromSecond)
            } else if merged.count >= 2 {
                let secondToLastGroup = merged[merged.count - 2]
                if allowedTypes.contains(secondToLastGroup.kind), canMerge(notification: firstGroupFromSecond.notifications.first!, into: secondToLastGroup) {
                    merged[merged.count - 2].append(group: firstGroupFromSecond)
                } else {
                    merged.append(firstGroupFromSecond)
                }
            } else {
                merged.append(firstGroupFromSecond)
            }
        }
        merged.append(contentsOf: second)
        return merged
    }
    
}
