import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import TelegramUIPreferences
import TelegramPresentationData
import AvatarNode

private let backgroundCornerRadius: CGFloat = 11.0
private let borderLineWidth: CGFloat = 2.0

private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)

final class VoiceChatTileItem: Equatable {
    enum Icon: Equatable {
        case none
        case microphone(Bool)
        case presentation
    }
    
    let account: Account
    let peer: Peer
    let videoEndpointId: String
    let videoReady: Bool
    let videoTimeouted: Bool
    let isVideoLimit: Bool
    let videoLimit: Int32
    let isPaused: Bool
    let isOwnScreencast: Bool
    let strings: PresentationStrings
    let nameDisplayOrder: PresentationPersonNameOrder
    let icon: Icon
    let text: VoiceChatParticipantItem.ParticipantText
    let additionalText: VoiceChatParticipantItem.ParticipantText?
    let speaking: Bool
    let secondary: Bool
    let isTablet: Bool
    let action: () -> Void
    let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
    let getVideo: (GroupVideoNode.Position) -> GroupVideoNode?
    let getAudioLevel: (() -> Signal<Float, NoError>)?
    
    var id: String {
        return self.videoEndpointId
    }
    
    init(account: Account, peer: Peer, videoEndpointId: String, videoReady: Bool, videoTimeouted: Bool, isVideoLimit: Bool, videoLimit: Int32, isPaused: Bool, isOwnScreencast: Bool, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, secondary: Bool, isTablet: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, additionalText: VoiceChatParticipantItem.ParticipantText?, action:  @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping (GroupVideoNode.Position) -> GroupVideoNode?, getAudioLevel: (() -> Signal<Float, NoError>)?) {
        self.account = account
        self.peer = peer
        self.videoEndpointId = videoEndpointId
        self.videoReady = videoReady
        self.videoTimeouted = videoTimeouted
        self.isVideoLimit = isVideoLimit
        self.videoLimit = videoLimit
        self.isPaused = isPaused
        self.isOwnScreencast = isOwnScreencast
        self.strings = strings
        self.nameDisplayOrder = nameDisplayOrder
        self.icon = icon
        self.text = text
        self.additionalText = additionalText
        self.speaking = speaking
        self.secondary = secondary
        self.isTablet = isTablet
        self.action = action
        self.contextAction = contextAction
        self.getVideo = getVideo
        self.getAudioLevel = getAudioLevel
    }
    
    static func == (lhs: VoiceChatTileItem, rhs: VoiceChatTileItem) -> Bool {
        if !arePeersEqual(lhs.peer, rhs.peer) {
            return false
        }
        if lhs.videoEndpointId != rhs.videoEndpointId {
            return false
        }
        if lhs.videoReady != rhs.videoReady {
            return false
        }
        if lhs.videoTimeouted != rhs.videoTimeouted {
            return false
        }
        if lhs.isPaused != rhs.isPaused {
            return false
        }
        if lhs.isOwnScreencast != rhs.isOwnScreencast {
            return false
        }
        if lhs.icon != rhs.icon {
            return false
        }
        if lhs.text != rhs.text {
            return false
        }
        if lhs.additionalText != rhs.additionalText {
            return false
        }
        if lhs.speaking != rhs.speaking {
            return false
        }
        if lhs.secondary != rhs.secondary {
            return false
        }
        if lhs.icon != rhs.icon {
            return false
        }
        return true
    }
}

private let fadeColor = UIColor(rgb: 0x000000, alpha: 0.5)

var tileFadeImage: UIImage? = {
    return generateImage(CGSize(width: fadeHeight, height: fadeHeight), rotatedContext: { size, context in
        let bounds = CGRect(origin: CGPoint(), size: size)
        context.clear(bounds)
        
        let colorsArray = [fadeColor.withAlphaComponent(0.0).cgColor, fadeColor.cgColor] as CFArray
        var locations: [CGFloat] = [1.0, 0.0]
        let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray, locations: &locations)!
        context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
    })
}()

final class VoiceChatTileItemNode: ASDisplayNode {
    private let context: AccountContext
  
    let contextSourceNode: ContextExtractedContentContainingNode
    private let containerNode: ContextControllerSourceNode
    let contentNode: ASDisplayNode
    let backgroundNode: ASDisplayNode
    var videoContainerNode: ASDisplayNode
    var videoNode: GroupVideoNode?
    let infoNode: ASDisplayNode
    let fadeNode: ASDisplayNode
    private var shimmerNode: VoiceChatTileShimmeringNode?
    private let titleNode: ImmediateTextNode
    private var iconNode: ASImageNode?
    private var animationNode: VoiceChatMicrophoneNode?
    var highlightNode: VoiceChatTileHighlightNode
    private let statusNode: VoiceChatParticipantStatusNode
    
    let placeholderTextNode: ImmediateTextNode
    let placeholderIconNode: ASImageNode
    
    private var profileNode: VoiceChatPeerProfileNode?
    private var extractedRect: CGRect?
    private var nonExtractedRect: CGRect?
    
    private var validLayout: (CGSize, CGFloat)?
    var item: VoiceChatTileItem?
    private var isExtracted = false
    
    private let audioLevelDisposable = MetaDisposable()

    private let hierarchyTrackingNode: HierarchyTrackingNode
    private var isCurrentlyInHierarchy = false
    
    init(context: AccountContext) {
        self.context = context
        
        self.contextSourceNode = ContextExtractedContentContainingNode()
        self.containerNode = ContextControllerSourceNode()
        
        self.contentNode = ASDisplayNode()
        self.contentNode.clipsToBounds = true
        self.contentNode.cornerRadius = backgroundCornerRadius

        self.backgroundNode = ASDisplayNode()
        self.backgroundNode.backgroundColor = panelBackgroundColor
        
        self.videoContainerNode = ASDisplayNode()
        self.videoContainerNode.clipsToBounds = true
        
        self.infoNode = ASDisplayNode()
        
        self.fadeNode = ASDisplayNode()
        self.fadeNode.displaysAsynchronously = false
        if let image = tileFadeImage {
            self.fadeNode.backgroundColor = UIColor(patternImage: image)
        }
        
        self.titleNode = ImmediateTextNode()
        self.titleNode.displaysAsynchronously = false
        
        self.statusNode = VoiceChatParticipantStatusNode()
        
        self.highlightNode = VoiceChatTileHighlightNode()
        self.highlightNode.alpha = 0.0
        self.highlightNode.updateGlowAndGradientAnimations(type: .speaking)
        
        self.placeholderTextNode = ImmediateTextNode()
        self.placeholderTextNode.alpha = 0.0
        self.placeholderTextNode.maximumNumberOfLines = 2
        self.placeholderTextNode.textAlignment = .center
        
        self.placeholderIconNode = ASImageNode()
        self.placeholderIconNode.alpha = 0.0
        self.placeholderIconNode.contentMode = .scaleAspectFit
        self.placeholderIconNode.displaysAsynchronously = false
        
        var updateInHierarchy: ((Bool) -> Void)?
        self.hierarchyTrackingNode = HierarchyTrackingNode({ value in
            updateInHierarchy?(value)
        })

        super.init()

        self.addSubnode(self.hierarchyTrackingNode)
        
        self.containerNode.addSubnode(self.contextSourceNode)
        self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
        self.addSubnode(self.containerNode)
        
        self.contextSourceNode.contentNode.addSubnode(self.contentNode)
        self.contentNode.addSubnode(self.backgroundNode)
        self.contentNode.addSubnode(self.videoContainerNode)
        self.contentNode.addSubnode(self.fadeNode)
        self.contentNode.addSubnode(self.infoNode)
        self.infoNode.addSubnode(self.titleNode)
        self.contentNode.addSubnode(self.placeholderTextNode)
        self.contentNode.addSubnode(self.placeholderIconNode)
        self.contentNode.addSubnode(self.highlightNode)
        
        self.containerNode.shouldBegin = { [weak self] location in
            guard let strongSelf = self, let item = strongSelf.item, item.videoReady && !item.isVideoLimit else {
                return false
            }
            return true
        }
        self.containerNode.activated = { [weak self] gesture, _ in
            guard let strongSelf = self, let item = strongSelf.item, let contextAction = item.contextAction, !item.isVideoLimit else {
                gesture.cancel()
                return
            }
            contextAction(strongSelf.contextSourceNode, gesture)
        }
        self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
            guard let strongSelf = self, let _ = strongSelf.item else {
                return
            }
            strongSelf.updateIsExtracted(isExtracted, transition: transition)
        }

        updateInHierarchy = { [weak self] value in
            if let strongSelf = self {
                strongSelf.isCurrentlyInHierarchy = value
                strongSelf.highlightNode.isCurrentlyInHierarchy = value
            }
        }
    }
    
    deinit {
        self.audioLevelDisposable.dispose()
    }
    
    override func didLoad() {
        super.didLoad()
        
        if #available(iOS 13.0, *) {
            self.contentNode.layer.cornerCurve = .continuous
        }
        
        self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
    }
    
    @objc private func tap() {
        if let item = self.item {
            item.action()
        }
    }
    
    private func updateIsExtracted(_ isExtracted: Bool, transition: ContainedViewLayoutTransition) {
        guard self.isExtracted != isExtracted, let extractedRect = self.extractedRect, let nonExtractedRect = self.nonExtractedRect, let item = self.item else {
            return
        }
        self.isExtracted = isExtracted
        
        let springDuration: Double = 0.42
        let springDamping: CGFloat = 124.0
        if isExtracted {
            let profileNode = VoiceChatPeerProfileNode(context: self.context, size: extractedRect.size, sourceSize: nonExtractedRect.size, peer: item.peer, text: item.text, customNode: self.videoContainerNode, additionalEntry: .single(nil), requestDismiss: { [weak self] in
                self?.contextSourceNode.requestDismiss?()
            })
            profileNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
            self.profileNode = profileNode
            self.contextSourceNode.contentNode.addSubnode(profileNode)

            profileNode.animateIn(from: self, targetRect: extractedRect, transition: transition)
            var appearenceTransition = transition
            if transition.isAnimated {
                appearenceTransition = .animated(duration: springDuration, curve: .customSpring(damping: springDamping, initialVelocity: 0.0))
            }
            appearenceTransition.updateFrame(node: profileNode, frame: extractedRect)
            
            self.contextSourceNode.contentNode.customHitTest = { [weak self] point in
                if let strongSelf = self, let profileNode = strongSelf.profileNode {
                    if profileNode.avatarListWrapperNode.frame.contains(point) {
                        return profileNode.avatarListNode.view
                    }
                }
                return nil
            }
            
            self.backgroundNode.isHidden = true
            self.fadeNode.isHidden = true
            self.infoNode.isHidden = true
            self.highlightNode.isHidden = true
        } else if let profileNode = self.profileNode {
            self.profileNode = nil
            
            self.infoNode.isHidden = false
            profileNode.animateOut(to: self, targetRect: nonExtractedRect, transition: transition, completion: { [weak self] in
                if let strongSelf = self {
                    strongSelf.backgroundNode.isHidden = false
                    strongSelf.fadeNode.isHidden = false
                    strongSelf.highlightNode.isHidden = false
                }
            })
            
            var appearenceTransition = transition
            if transition.isAnimated {
                appearenceTransition = .animated(duration: 0.2, curve: .easeInOut)
            }
            appearenceTransition.updateFrame(node: profileNode, frame: nonExtractedRect)
            
            self.contextSourceNode.contentNode.customHitTest = nil
        }
    }
    
    private var absoluteLocation: (CGRect, CGSize)?
    func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
        self.absoluteLocation = (rect, containerSize)
        if let shimmerNode = self.shimmerNode {
            shimmerNode.updateAbsoluteRect(rect, within: containerSize)
        }
        self.updateIsEnabled()
    }
    
    var visibility = true {
        didSet {
            self.updateIsEnabled()
        }
    }
    
    func updateIsEnabled() {
        guard let (rect, containerSize) = self.absoluteLocation else {
            return
        }
        let isVisibleInContainer = rect.maxY >= 0.0 && rect.minY <= containerSize.height
        if let videoNode = self.videoNode, videoNode.supernode === self.videoContainerNode {
            videoNode.updateIsEnabled(self.visibility && isVisibleInContainer)
        }
    }
    
    func update(size: CGSize, availableWidth: CGFloat, item: VoiceChatTileItem, transition: ContainedViewLayoutTransition) {
        guard self.validLayout?.0 != size || self.validLayout?.1 != availableWidth || self.item != item else {
            return
        }
        
        self.validLayout = (size, availableWidth)
        
        if !item.videoReady || item.isOwnScreencast {
            let shimmerNode: VoiceChatTileShimmeringNode
            let shimmerTransition: ContainedViewLayoutTransition
            if let current = self.shimmerNode {
                shimmerNode = current
                shimmerTransition = transition
            } else {
                shimmerNode = VoiceChatTileShimmeringNode(account: item.account, peer: item.peer)
                self.contentNode.insertSubnode(shimmerNode, aboveSubnode: self.fadeNode)
                self.shimmerNode = shimmerNode
                
                if let (rect, containerSize) = self.absoluteLocation {
                    shimmerNode.updateAbsoluteRect(rect, within: containerSize)
                }
                shimmerTransition = .immediate
            }
            shimmerTransition.updateFrame(node: shimmerNode, frame: CGRect(origin: CGPoint(), size: size))
            shimmerNode.update(shimmeringColor: UIColor.white, shimmering: !item.isOwnScreencast && !item.videoTimeouted && !item.isPaused, size: size, transition: shimmerTransition)
        } else if let shimmerNode = self.shimmerNode {
            self.shimmerNode = nil
            shimmerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak shimmerNode] _ in
                shimmerNode?.removeFromSupernode()
            })
        }
        
        var nodeToAnimateIn: ASDisplayNode?
        var placeholderAppeared = false
        
        var itemTransition = transition
        if self.item != item {
            let previousItem = self.item
            self.item = item
            
            if let getAudioLevel = item.getAudioLevel {
                self.audioLevelDisposable.set((getAudioLevel()
                |> deliverOnMainQueue).start(next: { [weak self] value in
                    guard let strongSelf = self else {
                        return
                    }
                    strongSelf.highlightNode.updateLevel(CGFloat(value))
                }))
            }
            
            let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
            transition.updateAlpha(node: self.highlightNode, alpha: item.speaking ? 1.0 : 0.0)
            
            if previousItem?.videoEndpointId != item.videoEndpointId || self.videoNode == nil {
                if let current = self.videoNode {
                    self.videoNode = nil
                    current.removeFromSupernode()
                }
                
                if let videoNode = item.getVideo(item.secondary ? .list : .tile) {
                    itemTransition = .immediate
                    self.videoNode = videoNode
                    self.videoContainerNode.addSubnode(videoNode)
                    self.updateIsEnabled()
                }
            }
            
            self.videoNode?.updateIsBlurred(isBlurred: item.isPaused, light: true)
            
            var showPlaceholder = false
            if item.isVideoLimit {
                self.placeholderTextNode.attributedText = NSAttributedString(string: item.strings.VoiceChat_VideoParticipantsLimitExceeded(String(item.videoLimit)).string, font: Font.semibold(13.0), textColor: .white)
                self.placeholderIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/VideoUnavailable"), color: .white)
                showPlaceholder = true
            } else if item.isOwnScreencast {
                self.placeholderTextNode.attributedText = NSAttributedString(string: item.strings.VoiceChat_YouAreSharingScreen, font: Font.semibold(13.0), textColor: .white)
                self.placeholderIconNode.image = generateTintedImage(image: UIImage(bundleImageName: item.isTablet ? "Call/ScreenShareTablet" : "Call/ScreenSharePhone"), color: .white)
                showPlaceholder = true
            } else if item.isPaused {
                self.placeholderTextNode.attributedText = NSAttributedString(string: item.strings.VoiceChat_VideoPaused, font: Font.semibold(13.0), textColor: .white)
                self.placeholderIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Pause"), color: .white)
                showPlaceholder = true
            }
            
            placeholderAppeared = self.placeholderTextNode.alpha.isZero && showPlaceholder
            transition.updateAlpha(node: self.placeholderTextNode, alpha: showPlaceholder ? 1.0 : 0.0)
            transition.updateAlpha(node: self.placeholderIconNode, alpha: showPlaceholder ? 1.0 : 0.0)
            
            let titleFont = Font.semibold(13.0)
            let titleColor = UIColor.white
            var titleAttributedString: NSAttributedString?
            if item.isVideoLimit {
                titleAttributedString = nil
            } else if let user = item.peer as? TelegramUser {
                if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
                        let string = NSMutableAttributedString()
                        switch item.nameDisplayOrder {
                            case .firstLast:
                                string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor))
                                string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor))
                                string.append(NSAttributedString(string: lastName, font: titleFont, textColor: titleColor))
                            case .lastFirst:
                                string.append(NSAttributedString(string: lastName, font: titleFont, textColor: titleColor))
                                string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor))
                                string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor))
                        }
                        titleAttributedString = string
                } else if let firstName = user.firstName, !firstName.isEmpty {
                    titleAttributedString = NSAttributedString(string: firstName, font: titleFont, textColor: titleColor)
                } else if let lastName = user.lastName, !lastName.isEmpty {
                    titleAttributedString = NSAttributedString(string: lastName, font: titleFont, textColor: titleColor)
                } else {
                    titleAttributedString = NSAttributedString(string: item.strings.User_DeletedAccount, font: titleFont, textColor: titleColor)
                }
            } else if let group = item.peer as? TelegramGroup {
                titleAttributedString = NSAttributedString(string: group.title, font: titleFont, textColor: titleColor)
            } else if let channel = item.peer as? TelegramChannel {
                titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor)
            }
            
            var microphoneColor = UIColor.white
            if let additionalText = item.additionalText, case let .text(_, _, color) = additionalText {
                if case .destructive = color {
                    microphoneColor = destructiveColor
                }
            }
            self.titleNode.attributedText = titleAttributedString
            
            var hadMicrophoneNode = false
            var hadIconNode = false
            
            if case let .microphone(muted) = item.icon {
                let animationNode: VoiceChatMicrophoneNode
                if let current = self.animationNode {
                    animationNode = current
                } else {
                    animationNode = VoiceChatMicrophoneNode()
                    self.animationNode = animationNode
                    self.infoNode.addSubnode(animationNode)
                    
                    nodeToAnimateIn = animationNode
                }
                animationNode.alpha = 1.0
                animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: microphoneColor), animated: true)
            } else if let animationNode = self.animationNode {
                hadMicrophoneNode = true
                self.animationNode = nil
                animationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
                animationNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false, completion: { [weak animationNode] _ in
                    animationNode?.removeFromSupernode()
                })
            }
            
            if case .presentation = item.icon {
                let iconNode: ASImageNode
                if let current = self.iconNode {
                    iconNode = current
                } else {
                    iconNode = ASImageNode()
                    iconNode.displaysAsynchronously = false
                    iconNode.contentMode = .center
                    self.iconNode = iconNode
                    self.infoNode.addSubnode(iconNode)
                    
                    nodeToAnimateIn = iconNode
                }
                
                iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/StatusScreen"), color: .white)
            } else if let iconNode = self.iconNode {
                hadIconNode = true
                self.iconNode = nil
                iconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
                iconNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false, completion: { [weak iconNode] _ in
                    iconNode?.removeFromSupernode()
                })
            }
            
            if let node = nodeToAnimateIn, hadMicrophoneNode || hadIconNode {
                node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                node.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
            }
        }
        
        let bounds = CGRect(origin: CGPoint(), size: size)
        self.containerNode.frame = bounds
        self.contextSourceNode.frame = bounds
        self.contextSourceNode.contentNode.frame = bounds
        
        transition.updateFrame(node: self.contentNode, frame: bounds)
        
        let extractedWidth = availableWidth
        let makeStatusLayout = self.statusNode.asyncLayout()
        let (statusLayout, _) = makeStatusLayout(CGSize(width: availableWidth - 30.0, height: CGFloat.greatestFiniteMagnitude), item.text, true)
                
        let extractedRect = CGRect(x: 0.0, y: 0.0, width: extractedWidth, height: extractedWidth + statusLayout.height + 39.0)
        let nonExtractedRect = bounds
        self.extractedRect = extractedRect
        self.nonExtractedRect = nonExtractedRect
        
        self.contextSourceNode.contentRect = extractedRect
        
        if self.videoContainerNode.supernode === self.contentNode {
            if let videoNode = self.videoNode {
                itemTransition.updateFrame(node: videoNode, frame: bounds)
                if videoNode.supernode === self.videoContainerNode {
                    videoNode.updateLayout(size: size, layoutMode: .fillOrFitToSquare, transition: itemTransition)
                }
            }
            transition.updateFrame(node: self.videoContainerNode, frame: bounds)
        }
        
        transition.updateFrame(node: self.backgroundNode, frame: bounds)
        transition.updateFrame(node: self.highlightNode, frame: bounds)
        self.highlightNode.updateLayout(size: bounds.size, transition: transition)
        transition.updateFrame(node: self.infoNode, frame: bounds)
        transition.updateFrame(node: self.fadeNode, frame: CGRect(x: 0.0, y: size.height - fadeHeight, width: size.width, height: fadeHeight))
        
        let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 50.0, height: size.height))
        self.titleNode.frame = CGRect(origin: CGPoint(x: 30.0, y: size.height - titleSize.height - 8.0), size: titleSize)
        
        var transition = transition
        if nodeToAnimateIn != nil || placeholderAppeared {
            transition = .immediate
        }
        
        if let iconNode = self.iconNode, let image = iconNode.image {
            transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(16.0 - image.size.width / 2.0), y: floorToScreenPixels(size.height - 15.0 - image.size.height / 2.0)), size: image.size))
        }
        
        if let animationNode = self.animationNode {
            let animationSize = CGSize(width: 36.0, height: 36.0)
            animationNode.bounds = CGRect(origin: CGPoint(), size: animationSize)
            animationNode.transform = CATransform3DMakeScale(0.66667, 0.66667, 1.0)
            transition.updatePosition(node: animationNode, position: CGPoint(x: 16.0, y: size.height - 15.0))
        }
        
        let placeholderTextSize = self.placeholderTextNode.updateLayout(CGSize(width: size.width - 30.0, height: 100.0))
        transition.updateFrame(node: self.placeholderTextNode, frame: CGRect(origin: CGPoint(x: floor((size.width - placeholderTextSize.width) / 2.0), y: floorToScreenPixels(size.height / 2.0) + 10.0), size: placeholderTextSize))
        if let image = self.placeholderIconNode.image {
            let imageScale: CGFloat = item.isVideoLimit ? 1.0 : 0.5
            let imageSize = CGSize(width: image.size.width * imageScale, height: image.size.height * imageScale)
            transition.updateFrame(node: self.placeholderIconNode, frame: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floorToScreenPixels(size.height / 2.0) - imageSize.height - 4.0), size: imageSize))
        }
    }
    
    func transitionIn(from sourceNode: ASDisplayNode?) {
        guard let item = self.item else {
            return
        }
        var videoNode: GroupVideoNode?
        if let sourceNode = sourceNode as? VoiceChatFullscreenParticipantItemNode, let _ = sourceNode.item {
            if let sourceVideoNode = sourceNode.videoNode {
                sourceNode.videoNode = nil
                videoNode = sourceVideoNode
            }
        }
        
        if videoNode == nil {
            videoNode = item.getVideo(item.secondary ? .list : .tile)
        }
        
        if let videoNode = videoNode {
            videoNode.alpha = 1.0
            self.videoNode = videoNode
            self.videoContainerNode.addSubnode(videoNode)
            
            videoNode.updateLayout(size: self.bounds.size, layoutMode: .fillOrFitToSquare, transition: .immediate)
            videoNode.frame = self.bounds
            
            self.updateIsEnabled()
        }
    }
}

private let blue = UIColor(rgb: 0x007fff)
private let lightBlue = UIColor(rgb: 0x00affe)
private let green = UIColor(rgb: 0x33c659)
private let activeBlue = UIColor(rgb: 0x00a0b9)
private let purple = UIColor(rgb: 0x3252ef)
private let pink = UIColor(rgb: 0xef436c)

class VoiceChatTileHighlightNode: ASDisplayNode {
    enum Gradient {
        case speaking
        case active
        case mutedForYou
        case muted
    }
    
    private var maskView: UIView?
    private let maskLayer = CALayer()
    
    private let foregroundGradientLayer = CAGradientLayer()

    var isCurrentlyInHierarchy = false {
        didSet {
            if self.isCurrentlyInHierarchy != oldValue && self.isCurrentlyInHierarchy {
                self.updateAnimations()
            }
        }
    }
    
    private var audioLevel: CGFloat = 0.0
    private var presentationAudioLevel: CGFloat = 0.0
    
    private var displayLinkAnimator: ConstantDisplayLinkAnimator?
    
    override init() {
        self.foregroundGradientLayer.type = .radial
        self.foregroundGradientLayer.colors = [lightBlue.cgColor, blue.cgColor, blue.cgColor]
        self.foregroundGradientLayer.locations = [0.0, 0.85, 1.0]
        self.foregroundGradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0)
        self.foregroundGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
        
        super.init()
        
        self.displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in
            guard let strongSelf = self else { return }
            
            strongSelf.presentationAudioLevel = strongSelf.presentationAudioLevel * 0.9 + strongSelf.audioLevel * 0.1
        }
    }
    
    override func didLoad() {
        super.didLoad()
        
        self.layer.addSublayer(self.foregroundGradientLayer)
        
        let maskView = UIView()
        maskView.layer.addSublayer(self.maskLayer)
        self.maskView = maskView
        
        self.maskLayer.masksToBounds = true
        self.maskLayer.cornerRadius = backgroundCornerRadius - UIScreenPixel
        self.maskLayer.borderColor = UIColor.white.cgColor
        self.maskLayer.borderWidth = borderLineWidth
                
        self.view.mask = self.maskView
    }
    
    func updateAnimations() {
        if !self.isCurrentlyInHierarchy {
            self.foregroundGradientLayer.removeAllAnimations()
            return
        }
        self.setupGradientAnimations()
    }
    
    func updateLevel(_ level: CGFloat) {
        self.audioLevel = level
    }
    
    func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
        let bounds = CGRect(origin: CGPoint(), size: size)
        if let maskView = self.maskView {
            transition.updateFrame(view: maskView, frame: bounds)
        }
        transition.updateFrame(layer: self.maskLayer, frame: bounds)
        transition.updateFrame(layer: self.foregroundGradientLayer, frame: bounds)
    }
    
    private func setupGradientAnimations() {
        if let _ = self.foregroundGradientLayer.animation(forKey: "movement") {
        } else {
            let previousValue = self.foregroundGradientLayer.startPoint
            let newValue: CGPoint
            if self.presentationAudioLevel > 0.22 {
                newValue = CGPoint(x: CGFloat.random(in: 0.9 ..< 1.0), y: CGFloat.random(in: 0.15 ..< 0.35))
            } else if self.presentationAudioLevel > 0.01 {
                newValue = CGPoint(x: CGFloat.random(in: 0.57 ..< 0.85), y: CGFloat.random(in: 0.15 ..< 0.45))
            } else {
                newValue = CGPoint(x: CGFloat.random(in: 0.6 ..< 0.75), y: CGFloat.random(in: 0.25 ..< 0.45))
            }
            self.foregroundGradientLayer.startPoint = newValue
            
            CATransaction.begin()
            
            let animation = CABasicAnimation(keyPath: "startPoint")
            animation.duration = Double.random(in: 0.8 ..< 1.4)
            animation.fromValue = previousValue
            animation.toValue = newValue
            
            CATransaction.setCompletionBlock { [weak self] in
                guard let strongSelf = self else {
                    return
                }
                if strongSelf.isCurrentlyInHierarchy {
                    strongSelf.setupGradientAnimations()
                }
            }
            
            self.foregroundGradientLayer.add(animation, forKey: "movement")
            CATransaction.commit()
        }
    }
    
    private var gradient: Gradient?
    func updateGlowAndGradientAnimations(type: Gradient, animated: Bool = true) {
        guard self.gradient != type else {
            return
        }
        self.gradient = type
        let initialColors = self.foregroundGradientLayer.colors
        let targetColors: [CGColor]
        switch type {
            case .speaking:
                targetColors = [activeBlue.cgColor, green.cgColor, green.cgColor]
            case .active:
                targetColors = [lightBlue.cgColor, blue.cgColor, blue.cgColor]
            case .mutedForYou:
                targetColors = [pink.cgColor, destructiveColor.cgColor, destructiveColor.cgColor]
            case .muted:
                targetColors = [pink.cgColor, purple.cgColor, purple.cgColor]
        }
        self.foregroundGradientLayer.colors = targetColors
        if animated {
            self.foregroundGradientLayer.animate(from: initialColors as AnyObject, to: targetColors as AnyObject, keyPath: "colors", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
        }
        self.updateAnimations()
    }
}

final class ShimmerEffectForegroundNode: ASDisplayNode {
    private var currentForegroundColor: UIColor?
    private let imageNodeContainer: ASDisplayNode
    private let imageNode: ASDisplayNode
    
    private var absoluteLocation: (CGRect, CGSize)?
    private var isCurrentlyInHierarchy = false
    private var shouldBeAnimating = false
    
    private let size: CGFloat
    
    init(size: CGFloat) {
        self.size = size
        
        self.imageNodeContainer = ASDisplayNode()
        self.imageNodeContainer.isLayerBacked = true
        
        self.imageNode = ASDisplayNode()
        self.imageNode.isLayerBacked = true
        self.imageNode.displaysAsynchronously = false
        
        super.init()
        
        self.isLayerBacked = true
        self.clipsToBounds = true
        
        self.imageNodeContainer.addSubnode(self.imageNode)
        self.addSubnode(self.imageNodeContainer)
    }
    
    override func didEnterHierarchy() {
        super.didEnterHierarchy()
        
        self.isCurrentlyInHierarchy = true
        self.updateAnimation()
    }
    
    override func didExitHierarchy() {
        super.didExitHierarchy()
        
        self.isCurrentlyInHierarchy = false
        self.updateAnimation()
    }
    
    func update(foregroundColor: UIColor) {
        if let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
            return
        }
        self.currentForegroundColor = foregroundColor
        
        let image = generateImage(CGSize(width: self.size, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
            context.clear(CGRect(origin: CGPoint(), size: size))
            
            context.clip(to: CGRect(origin: CGPoint(), size: size))
            
            let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
            let peakColor = foregroundColor.cgColor
            
            var locations: [CGFloat] = [0.0, 0.5, 1.0]
            let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
            
            let colorSpace = CGColorSpaceCreateDeviceRGB()
            let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
            
            context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
        })
        if let image = image {
            self.imageNode.backgroundColor = UIColor(patternImage: image)
        }
    }
    
    func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
        if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
            return
        }
        let sizeUpdated = self.absoluteLocation?.1 != containerSize
        let frameUpdated = self.absoluteLocation?.0 != rect
        self.absoluteLocation = (rect, containerSize)
        
        if sizeUpdated {
            if self.shouldBeAnimating {
                self.imageNode.layer.removeAnimation(forKey: "shimmer")
                self.addImageAnimation()
            } else {
                self.updateAnimation()
            }
        }
        
        if frameUpdated {
            self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
        }
    }
    
    private func updateAnimation() {
        let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
        if shouldBeAnimating != self.shouldBeAnimating {
            self.shouldBeAnimating = shouldBeAnimating
            if shouldBeAnimating {
                self.addImageAnimation()
            } else {
                self.imageNode.layer.removeAnimation(forKey: "shimmer")
            }
        }
    }
    
    private func addImageAnimation() {
        guard let containerSize = self.absoluteLocation?.1 else {
            return
        }
        let gradientHeight: CGFloat = self.size
        self.imageNode.frame = CGRect(origin: CGPoint(x: -gradientHeight, y: 0.0), size: CGSize(width: gradientHeight, height: containerSize.height))
        let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.width + gradientHeight) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
        animation.repeatCount = Float.infinity
        animation.beginTime = 1.0
        self.imageNode.layer.add(animation, forKey: "shimmer")
    }
}

private class VoiceChatTileShimmeringNode: ASDisplayNode {
    private let backgroundNode: ImageNode
    private let effectNode: ShimmerEffectForegroundNode
    
    private let borderNode: ASDisplayNode
    private var borderMaskView: UIView?
    private let borderEffectNode: ShimmerEffectForegroundNode
    
    private var currentShimmeringColor: UIColor?
    private var currentShimmering: Bool?
    private var currentSize: CGSize?
    
    public init(account: Account, peer: Peer) {
        self.backgroundNode = ImageNode(enableHasImage: false, enableEmpty: false, enableAnimatedTransition: true)
        self.backgroundNode.displaysAsynchronously = false
        self.backgroundNode.contentMode = .scaleAspectFill
        
        self.effectNode = ShimmerEffectForegroundNode(size: 240.0)
        
        self.borderNode = ASDisplayNode()
        self.borderEffectNode = ShimmerEffectForegroundNode(size: 320.0)
        
        super.init()
        
        self.clipsToBounds = true
        self.cornerRadius = backgroundCornerRadius
        
        self.addSubnode(self.backgroundNode)
        self.addSubnode(self.effectNode)
        self.addSubnode(self.borderNode)
        self.borderNode.addSubnode(self.borderEffectNode)
        
        self.backgroundNode.setSignal(peerAvatarCompleteImage(account: account, peer: EnginePeer(peer), size: CGSize(width: 250.0, height: 250.0), round: false, font: Font.regular(16.0), drawLetters: false, fullSize: false, blurred: true))
    }
    
    public override func didLoad() {
        super.didLoad()
        
        if self.effectNode.supernode != nil {
            self.effectNode.layer.compositingFilter = "screenBlendMode"
            self.borderEffectNode.layer.compositingFilter = "screenBlendMode"
            
            let borderMaskView = UIView()
            borderMaskView.layer.borderWidth = 1.0
            borderMaskView.layer.borderColor = UIColor.white.cgColor
            borderMaskView.layer.cornerRadius = backgroundCornerRadius
            self.borderMaskView = borderMaskView
            
            if let size = self.currentSize {
                borderMaskView.frame = CGRect(origin: CGPoint(), size: size)
            }
            self.borderNode.view.mask = borderMaskView
            
            if #available(iOS 13.0, *) {
                borderMaskView.layer.cornerCurve = .continuous
            }
        }
        if #available(iOS 13.0, *) {
            self.layer.cornerCurve = .continuous
        }
    }
    
    public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
        self.effectNode.updateAbsoluteRect(rect, within: containerSize)
        self.borderEffectNode.updateAbsoluteRect(rect, within: containerSize)
    }
    
    public func update(shimmeringColor: UIColor, shimmering: Bool, size: CGSize, transition: ContainedViewLayoutTransition) {
        if let currentShimmeringColor = self.currentShimmeringColor, currentShimmeringColor.isEqual(shimmeringColor) && self.currentSize == size && self.currentShimmering == shimmering {
            return
        }
        
        let firstTime = self.currentShimmering == nil
        self.currentShimmeringColor = shimmeringColor
        self.currentShimmering = shimmering
        self.currentSize = size
        
        let transition: ContainedViewLayoutTransition = firstTime ? .immediate : (transition.isAnimated ? transition : .animated(duration: 0.45, curve: .easeInOut))
        transition.updateAlpha(node: self.effectNode, alpha: shimmering ? 1.0 : 0.0)
        transition.updateAlpha(node: self.borderNode, alpha: shimmering ? 1.0 : 0.0)
        
        let bounds = CGRect(origin: CGPoint(), size: size)
        
        self.effectNode.update(foregroundColor: shimmeringColor.withAlphaComponent(0.3))
        transition.updateFrame(node: self.effectNode, frame: bounds)
        
        self.borderEffectNode.update(foregroundColor: shimmeringColor.withAlphaComponent(0.45))
        transition.updateFrame(node: self.borderEffectNode, frame: bounds)
        
        transition.updateFrame(node: self.backgroundNode, frame: bounds)
        transition.updateFrame(node: self.borderNode, frame: bounds)
        if let borderMaskView = self.borderMaskView {
            transition.updateFrame(view: borderMaskView, frame: bounds)
        }
    }
}