import Foundation
import TelegramApi
import Postbox

enum UpdateGroup {
    case withPts(updates: [Api.Update], users: [Api.User], chats: [Api.Chat])
    case withQts(updates: [Api.Update], users: [Api.User], chats: [Api.Chat])
    case withSeq(updates: [Api.Update], seqRange: (Int32, Int32), date: Int32, users: [Api.User], chats: [Api.Chat])
    case withDate(updates: [Api.Update], date: Int32, users: [Api.User], chats: [Api.Chat])
    case reset
    case updatePts(pts: Int32, ptsCount: Int32)
    case updateChannelPts(channelId: Int64, pts: Int32, ptsCount: Int32)
    case ensurePeerHasLocalState(id: PeerId)
    
    var updates: [Api.Update] {
        switch self {
            case let .withPts(updates, _, _):
                return updates
            case let .withDate(updates, _, _, _):
                return updates
            case let .withQts(updates, _, _):
                return updates
            case let .withSeq(updates, _, _, _, _):
                return updates
            case .reset, .updatePts, .updateChannelPts, .ensurePeerHasLocalState:
                return []
        }
    }
    
    var users: [Api.User] {
        switch self {
            case let .withPts(_, users, _):
                return users
            case let .withDate(_, _, users, _):
                return users
            case let .withQts(_, users, _):
                return users
            case let .withSeq(_, _, _, users, _):
                return users
            case .reset, .updatePts, .updateChannelPts, .ensurePeerHasLocalState:
                return []
        }
    }
    
    var chats: [Api.Chat] {
        switch self {
            case let .withPts(_, _, chats):
                return chats
            case let .withDate(_, _, _, chats):
                return chats
            case let .withQts(_, _, chats):
                return chats
            case let .withSeq(_, _, _, _, chats):
                return chats
            case .reset, .updatePts, .updateChannelPts, .ensurePeerHasLocalState:
                return []
        }
    }
}

func apiUpdatePtsRange(_ update: Api.Update) -> (Int32, Int32)? {
    switch update {
        case let .updateDeleteMessages(_, pts, ptsCount):
            return (pts, ptsCount)
        case let .updateNewMessage(_, pts, ptsCount):
            return (pts, ptsCount)
        case let .updateReadHistoryInbox(_, _, _, _, _, pts, ptsCount):
            return (pts, ptsCount)
        case let .updateReadHistoryOutbox(_, _, pts, ptsCount):
            return (pts, ptsCount)
        case let .updateEditMessage(_, pts, ptsCount):
            return (pts, ptsCount)
        case let .updateReadMessagesContents(_, pts, ptsCount):
            return (pts, ptsCount)
        case let .updateWebPage(_, pts, ptsCount):
            return (pts, ptsCount)
        case let .updateFolderPeers(_, pts, ptsCount):
            if ptsCount != 0 {
                return (pts, ptsCount)
            } else {
                return nil
            }
        case let .updatePinnedMessages(_, _, _, pts, ptsCount):
            return (pts, ptsCount)
        default:
            return nil
    }
}

func apiUpdateQtsRange(_ update: Api.Update) -> (Int32, Int32)? {
    switch update {
        case let .updateNewEncryptedMessage(_, qts):
            return (qts, 1)
        case _:
            return nil
    }
}

struct PtsUpdate {
    let update: Api.Update?
    let ptsRange: (Int32, Int32)
    let users: [Api.User]
    let chats: [Api.Chat]
}

struct QtsUpdate {
    let update: Api.Update
    let qtsRange: (Int32, Int32)
    let users: [Api.User]
    let chats: [Api.Chat]
}

struct SeqUpdates {
    let updates: [Api.Update]
    let seqRange: (Int32, Int32)
    let date: Int32
    let users: [Api.User]
    let chats: [Api.Chat]
}

func ptsUpdates(_ groups: [UpdateGroup]) -> [PtsUpdate] {
    var result: [PtsUpdate] = []
    
    for group in groups {
        switch group {
        case let .withPts(updates, users, chats):
            for update in updates {
                if let ptsRange = apiUpdatePtsRange(update) {
                    result.append(PtsUpdate(update: update, ptsRange: ptsRange, users: users, chats: chats))
                }
            }
        case let .updatePts(pts, ptsCount):
                result.append(PtsUpdate(update: nil, ptsRange: (pts, ptsCount), users: [], chats: []))
        case _:
            break
        }
    }
    
    result.sort(by: { $0.ptsRange.0 < $1.ptsRange.0 })
    
    return result
}

func qtsUpdates(_ groups: [UpdateGroup]) -> [QtsUpdate] {
    var result: [QtsUpdate] = []
    
    for group in groups {
        switch group {
            case let .withQts(updates, users, chats):
                for update in updates {
                    if let qtsRange = apiUpdateQtsRange(update) {
                        result.append(QtsUpdate(update: update, qtsRange: qtsRange, users: users, chats: chats))
                    }
                }
                break
            case _:
                break
        }
    }
    
    result.sort(by: { $0.qtsRange.0 < $1.qtsRange.0 })
    
    return result
}

func seqGroups(_ groups: [UpdateGroup]) -> [SeqUpdates] {
    var result: [SeqUpdates] = []
    
    for group in groups {
        switch group {
            case let .withSeq(updates, seqRange, date, users, chats):
                result.append(SeqUpdates(updates: updates, seqRange: seqRange, date: date, users: users, chats: chats))
            case _:
                break
        }
    }
    
    return result
}

func dateGroups(_ groups: [UpdateGroup]) -> [UpdateGroup] {
    var result: [UpdateGroup] = []
    
    for group in groups {
        switch group {
            case .withDate:
                result.append(group)
            case _:
                break
        }
    }
    
    return result
}

func groupUpdates(_ updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seqRange: (Int32, Int32)?) -> [UpdateGroup] {
    var updatesWithPts: [Api.Update] = []
    var updatesWithQts: [Api.Update] = []
    var otherUpdates: [Api.Update] = []
    
    for update in updates {
        if let _ = apiUpdatePtsRange(update) {
            updatesWithPts.append(update)
        } else if let _ = apiUpdateQtsRange(update) {
            updatesWithQts.append(update)
        } else {
            otherUpdates.append(update)
        }
    }
    
    var groups: [UpdateGroup] = []
    if updatesWithPts.count != 0 {
        groups.append(.withPts(updates: updatesWithPts, users: users, chats: chats))
    }
    if updatesWithQts.count != 0 {
        groups.append(.withQts(updates: updatesWithQts, users: users, chats: chats))
    }
    
    if let seqRange = seqRange {
        groups.append(.withSeq(updates: otherUpdates, seqRange: seqRange, date: date, users: users, chats: chats))
    } else {
        groups.append(.withDate(updates: otherUpdates, date: date, users: users, chats: chats))
    }
    
    return groups
}