import Foundation
import Postbox
import TelegramApi

public extension MessageFlags {
    var isSending: Bool {
        return (self.contains(.Unsent) || self.contains(.Sending)) && !self.contains(.Failed)
    }
}

public extension Message {
    var visibleButtonKeyboardMarkup: ReplyMarkupMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? ReplyMarkupMessageAttribute {
                if !attribute.flags.contains(.inline) && !attribute.rows.isEmpty {
                    if attribute.flags.contains(.personal) {
                        if !personal {
                            return nil
                        }
                    }
                    return attribute
                }
            }
        }
        return nil
    }

    var visibleReplyMarkupPlaceholder: String? {
        for attribute in self.attributes {
            if let attribute = attribute as? ReplyMarkupMessageAttribute {
                if !attribute.flags.contains(.inline) {
                    if attribute.flags.contains(.personal) {
                        if !personal {
                            return nil
                        }
                    }
                    return attribute.placeholder
                }
            }
        }
        return nil
    }
    
    var muted: Bool {
        for attribute in self.attributes {
            if let attribute = attribute as? NotificationInfoMessageAttribute {
                return attribute.flags.contains(.muted)
            }
        }
        return false
    }
    
    var personal: Bool {
        for attribute in self.attributes {
            if let attribute = attribute as? NotificationInfoMessageAttribute {
                return attribute.flags.contains(.personal)
            }
        }
        return false
    }
    
    var requestsSetupReply: Bool {
        for attribute in self.attributes {
            if let attribute = attribute as? ReplyMarkupMessageAttribute {
                if !attribute.flags.contains(.inline) {
                    if attribute.flags.contains(.personal) {
                        if !personal {
                            return false
                        }
                    }
                    return attribute.flags.contains(.setupReply)
                }
            }
        }
        return false
    }
    
    var isScam: Bool {
        if let author = self.author, author.isScam {
            return true
        }
        if let forwardAuthor = self.forwardInfo?.author, forwardAuthor.isScam {
            return true
        }
        for attribute in self.attributes {
            if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId, let bot = self.peers[peerId] as? TelegramUser, bot.isScam {
               return true
            }
        }
        return false
    }
    
    var isFake: Bool {
        if let author = self.author, author.isFake {
            return true
        }
        if let forwardAuthor = self.forwardInfo?.author, forwardAuthor.isFake {
            return true
        }
        for attribute in self.attributes {
            if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId, let bot = self.peers[peerId] as? TelegramUser, bot.isFake {
               return true
            }
        }
        return false
    }
    
    var sourceReference: SourceReferenceMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? SourceReferenceMessageAttribute {
                return attribute
            }
        }
        return nil
    }
    
    var sourceAuthorInfo: SourceAuthorInfoMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? SourceAuthorInfoMessageAttribute {
                return attribute
            }
        }
        return nil
    }
    
    var effectiveAuthor: Peer? {
        if let sourceAuthorInfo = self.sourceAuthorInfo {
            if let sourceAuthorId = sourceAuthorInfo.originalAuthor, let peer = self.peers[sourceAuthorId] {
                return peer
            }
        }
        if let forwardInfo = self.forwardInfo, let sourceReference = self.sourceReference, forwardInfo.author?.id == sourceReference.messageId.peerId {
            if let peer = self.peers[sourceReference.messageId.peerId] {
                return peer
            }
        } else if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported), let author = forwardInfo.author {
            return author
        }
        return self.author
    }
}

func messagesIdsGroupedByPeerId(_ ids: Set<MessageId>) -> [PeerId: [MessageId]] {
    var dict: [PeerId: [MessageId]] = [:]
    
    for id in ids {
        let peerId = id.peerId
        if dict[peerId] == nil {
            dict[peerId] = [id]
        } else {
            dict[peerId]!.append(id)
        }
    }
    
    return dict
}

func messagesIdsGroupedByPeerId(_ ids: [MessageId]) -> [PeerId: [MessageId]] {
    var dict: [PeerId: [MessageId]] = [:]
    
    for id in ids {
        let peerId = id.peerId
        if dict[peerId] == nil {
            dict[peerId] = [id]
        } else {
            dict[peerId]!.append(id)
        }
    }
    
    return dict
}

func messagesIdsGroupedByPeerId(_ ids: ReferencedReplyMessageIds) -> [PeerId: ReferencedReplyMessageIds] {
    var dict: [PeerId: ReferencedReplyMessageIds] = [:]
    
    for (targetId, sourceId) in ids.targetIdsBySourceId {
        let peerId = sourceId.peerId
        dict[peerId, default: ReferencedReplyMessageIds()].add(sourceId: sourceId, targetId: targetId)
    }
    
    return dict
}

func messagesIdsGroupedByPeerId(_ ids: Set<MessageAndThreadId>) -> [PeerAndThreadId: [MessageId]] {
    var dict: [PeerAndThreadId: [MessageId]] = [:]
    
    for id in ids {
        let peerAndThreadId = PeerAndThreadId(peerId: id.messageId.peerId, threadId: id.threadId)
        if dict[peerAndThreadId] == nil {
            dict[peerAndThreadId] = [id.messageId]
        } else {
            dict[peerAndThreadId]!.append(id.messageId)
        }
    }
    
    return dict
}

func messagesIdsGroupedByPeerId(_ ids: [MessageAndThreadId]) -> [PeerAndThreadId: [MessageId]] {
    var dict: [PeerAndThreadId: [MessageId]] = [:]
    
    for id in ids {
        let peerAndThreadId = PeerAndThreadId(peerId: id.messageId.peerId, threadId: id.threadId)
        if dict[peerAndThreadId] == nil {
            dict[peerAndThreadId] = [id.messageId]
        } else {
            dict[peerAndThreadId]!.append(id.messageId)
        }
    }
    
    return dict
}

func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer], associatedThreadInfo: Message.AssociatedThreadInfo? = nil, associatedMessages: SimpleDictionary<MessageId, Message> = SimpleDictionary()) -> Message? {
    guard case let .Id(id) = message.id else {
        return nil
    }
    
    var messagePeers = SimpleDictionary<PeerId, Peer>()
    
    var author: Peer?
    if let authorId = message.authorId {
        author = peers[authorId]
        if let author = author {
            messagePeers[author.id] = author
        }
    }
    
    if let peer = peers[id.peerId] {
        messagePeers[peer.id] = peer
        
        if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference {
            if let channelPeer = peers[migrationReference.peerId] {
                messagePeers[channelPeer.id] = channelPeer
            }
        }
    }
    
    for media in message.media {
        for peerId in media.peerIds {
            if let peer = peers[peerId] {
                messagePeers[peer.id] = peer
            }
        }
    }
    
    var forwardInfo: MessageForwardInfo?
    if let info = message.forwardInfo {
        forwardInfo = MessageForwardInfo(author: info.authorId.flatMap({ peers[$0] }), source: info.sourceId.flatMap({ peers[$0] }), sourceMessageId: info.sourceMessageId, date: info.date, authorSignature: info.authorSignature, psaType: info.psaType, flags: info.flags)
        if let author = forwardInfo?.author {
            messagePeers[author.id] = author
        }
        if let source = forwardInfo?.source {
            messagePeers[source.id] = source
        }
    }

    var hasher = Hasher()
    hasher.combine(id.id)
    hasher.combine(id.peerId)
    
    let hashValue = Int64(hasher.finalize())
    let first = UInt32((hashValue >> 32) & 0xffffffff)
    let second = UInt32(hashValue & 0xffffffff)
    let stableId = first &+ second
        
    return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, customTags: [], forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: associatedMessages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: associatedThreadInfo, associatedStories: [:])
}

func locallyRenderedMessage(message: StoreMessage, peers: AccumulatedPeers, associatedThreadInfo: Message.AssociatedThreadInfo? = nil) -> Message? {
    guard case let .Id(id) = message.id else {
        return nil
    }
    
    var messagePeers = SimpleDictionary<PeerId, Peer>()
    
    var author: Peer?
    if let authorId = message.authorId {
        author = peers.get(authorId)
        if let author = author {
            messagePeers[author.id] = author
        }
    }
    
    if let peer = peers.get(id.peerId) {
        messagePeers[peer.id] = peer
        
        if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference {
            if let channelPeer = peers.get(migrationReference.peerId) {
                messagePeers[channelPeer.id] = channelPeer
            }
        }
    }
    
    for media in message.media {
        for peerId in media.peerIds {
            if let peer = peers.get(peerId) {
                messagePeers[peer.id] = peer
            }
        }
    }
    
    var forwardInfo: MessageForwardInfo?
    if let info = message.forwardInfo {
        forwardInfo = MessageForwardInfo(author: info.authorId.flatMap({ peers.get($0) }), source: info.sourceId.flatMap({ peers.get($0) }), sourceMessageId: info.sourceMessageId, date: info.date, authorSignature: info.authorSignature, psaType: info.psaType, flags: info.flags)
        if let author = forwardInfo?.author {
            messagePeers[author.id] = author
        }
        if let source = forwardInfo?.source {
            messagePeers[source.id] = source
        }
    }

    var hasher = Hasher()
    hasher.combine(id.id)
    hasher.combine(id.peerId)
    
    let hashValue = Int64(hasher.finalize())
    let first = UInt32((hashValue >> 32) & 0xffffffff)
    let second = UInt32(hashValue & 0xffffffff)
    let stableId = first &+ second
        
    return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, customTags: [], forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: associatedThreadInfo, associatedStories: [:])
}

public extension Message {
    func effectivelyIncoming(_ accountPeerId: PeerId) -> Bool {
        if self.id.peerId == accountPeerId {
            if let sourceAuthorInfo = self.sourceAuthorInfo {
                if sourceAuthorInfo.originalOutgoing {
                    return false
                } else if let originalAuthor = sourceAuthorInfo.originalAuthor, originalAuthor == accountPeerId {
                    return false
                }
            } else if let forwardInfo = self.forwardInfo {
                if let author = forwardInfo.author, author.id == accountPeerId {
                    return false
                }
            }
            
            if self.forwardInfo != nil {
                return true
            } else {
                return false
            }
        } else if self.author?.id == accountPeerId {
            return false
        } else if self.flags.contains(.Incoming) {
            return true
        } else if let channel = self.peers[self.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
            return true
        } else {
            return false
        }
    }
    
    func effectivelyFailed(timestamp: Int32) -> Bool {
        if self.flags.contains(.Failed) {
            return true
        } else if self.id.namespace == Namespaces.Message.ScheduledCloud && self.timestamp != scheduleWhenOnlineTimestamp {
            return timestamp > self.timestamp + 60
        } else {
            return false
        }
    }
    
    func isCopyProtected() -> Bool {
        if self.flags.contains(.CopyProtected) {
            return true
        } else if let group = self.peers[self.id.peerId] as? TelegramGroup, group.flags.contains(.copyProtectionEnabled) {
            return true
        } else if let channel = self.peers[self.id.peerId] as? TelegramChannel, channel.flags.contains(.copyProtectionEnabled) {
            return true
        } else {
            return false
        }
    }
    
    func isAgeRestricted() -> Bool {
        return false
    }
}

public extension Message {
    var secretMediaDuration: Double? {
        var found = false
        for attribute in self.attributes {
            if let _ = attribute as? AutoremoveTimeoutMessageAttribute {
                found = true
                break
            } else if let _ = attribute as? AutoclearTimeoutMessageAttribute {
                found = true
                break
            }
        }
        
        if !found {
            return nil
        }
        
        for media in self.media {
            switch media {
            case _ as TelegramMediaImage:
                return nil
            case let file as TelegramMediaFile:
                return file.duration
            default:
                break
            }
        }
        
        return nil
    }
}

public extension Message {
    var isSentOrAcknowledged: Bool {
        if self.flags.contains(.Failed) {
            return false
        } else if self.flags.isSending {
            for attribute in self.attributes {
                if let attribute = attribute as? OutgoingMessageInfoAttribute {
                    if attribute.acknowledged {
                        return true
                    }
                }
            }
            return false
        } else {
            return true
        }
    }
}

public extension Message {
    var adAttribute: AdMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? AdMessageAttribute {
                return attribute
            }
        }
        return nil
    }
    
    var factCheckAttribute: FactCheckMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? FactCheckMessageAttribute {
                return attribute
            }
        }
        return nil
    }
    
    var inlineBotAttribute: InlineBusinessBotMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? InlineBusinessBotMessageAttribute {
                return attribute
            }
        }
        return nil
    }
}
public extension Message {
    var reactionsAttribute: ReactionsMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? ReactionsMessageAttribute {
                return attribute
            }
        }
        return nil
    }
    func effectiveReactionsAttribute(isTags: Bool) -> ReactionsMessageAttribute? {
        if !self.hasReactions {
            return nil
        }
        
        if let result = mergedMessageReactions(attributes: self.attributes, isTags: isTags) {
            return result
        } else {
            return nil
        }
    }
    func effectiveReactions(isTags: Bool) -> [MessageReaction]? {
        if !self.hasReactions {
            return nil
        }
        
        if let result = mergedMessageReactions(attributes: self.attributes, isTags: isTags) {
            return result.reactions
        } else {
            return nil
        }
    }
    var hasReactions: Bool {
        for attribute in self.attributes {
            if let attribute = attribute as? ReactionsMessageAttribute {
                if !attribute.reactions.isEmpty {
                    return true
                }
            }
        }
        for attribute in self.attributes {
            if let attribute = attribute as? PendingReactionsMessageAttribute {
                if !attribute.reactions.isEmpty {
                    return true
                }
            }
        }
        return false
    }
    
    var textEntitiesAttribute: TextEntitiesMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? TextEntitiesMessageAttribute {
                return attribute
            }
        }
        return nil
    }
    
    var restrictedContentAttribute: RestrictedContentMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? RestrictedContentMessageAttribute {
                return attribute
            }
        }
        return nil
    }
}

public extension Message {
    var webpagePreviewAttribute: WebpagePreviewMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? WebpagePreviewMessageAttribute {
                return attribute
            }
        }
        return nil
    }
    var invertMedia: Bool {
        for attribute in self.attributes {
            if let _ = attribute as? InvertMediaMessageAttribute {
                return true
            }
        }
        return false
    }
    var invertMediaAttribute: InvertMediaMessageAttribute? {
        for attribute in self.attributes {
            if let attribute = attribute as? InvertMediaMessageAttribute {
                return attribute
            }
        }
        return nil
    }
}

public extension Message {
    func areReactionsTags(accountPeerId: PeerId) -> Bool {
        if self.id.peerId == accountPeerId {
            if let reactionsAttribute = self.reactionsAttribute, !reactionsAttribute.reactions.isEmpty {
                return reactionsAttribute.isTags
            } else {
                return true
            }
        }
        return false
    }
}

public func _internal_parseMediaAttachment(data: Data) -> Media? {
    guard let object = Api.parse(Buffer(buffer: MemoryBuffer(data: data))) else {
        return nil
    }
    if let photo = object as? Api.Photo {
        return telegramMediaImageFromApiPhoto(photo)
    } else if let file = object as? Api.Document {
        return telegramMediaFileFromApiDocument(file)
    } else {
        return nil
    }
}