import Foundation
import UIKit
import Display
import AsyncDisplayKit
import AvatarNode
import SwiftSignalKit
import TelegramCore
import AccountContext
import AudioBlob

public final class AnimatedAvatarSetContext {
    public final class Content {
        fileprivate final class Item {
            fileprivate enum Key: Hashable {
                case peer(EnginePeer.Id)
                case placeholder(Int)
            }
            
            fileprivate let peer: EnginePeer?
            fileprivate let placeholderColor: UIColor
            
            fileprivate init(peer: EnginePeer?, placeholderColor: UIColor) {
                self.peer = peer
                self.placeholderColor = placeholderColor
            }
        }
        
        fileprivate var items: [(Item.Key, Item)]
        
        fileprivate init(items: [(Item.Key, Item)]) {
            self.items = items
        }
    }
    
    private final class ItemState {
        let peer: EnginePeer
        
        init(peer: EnginePeer) {
            self.peer = peer
        }
    }
    
    private var peers: [EnginePeer] = []
    private var itemStates: [EnginePeer.Id: ItemState] = [:]
    
    public init() {
    }
    
    public func update(peers: [EnginePeer], animated: Bool) -> Content {
        var items: [(Content.Item.Key, Content.Item)] = []
        for peer in peers {
            items.append((.peer(peer.id), Content.Item(peer: peer, placeholderColor: .white)))
        }
        return Content(items: items)
    }

    public func updatePlaceholder(color: UIColor, count: Int, animated: Bool) -> Content {
        var items: [(Content.Item.Key, Content.Item)] = []
        for i in 0 ..< count {
            items.append((.placeholder(i), Content.Item(peer: nil, placeholderColor: color)))
        }
        return Content(items: items)
    }
}

private let avatarFont = avatarPlaceholderFont(size: 12.0)

private final class ContentNode: ASDisplayNode {
    private let context: AccountContext
    private var audioLevelView: VoiceBlobView?
    private var audioLevelBlobOverlay: UIImageView?
    private let unclippedNode: ASImageNode
    private let clippedNode: ASImageNode

    private var size: CGSize
    private var spacing: CGFloat
    
    private var disposable: Disposable?
    
    init(context: AccountContext, peer: EnginePeer?, placeholderColor: UIColor, synchronousLoad: Bool, size: CGSize, spacing: CGFloat) {
        self.context = context
        self.size = size
        self.spacing = spacing

        self.unclippedNode = ASImageNode()
        self.clippedNode = ASImageNode()
        
        super.init()
        
        self.addSubnode(self.unclippedNode)
        self.addSubnode(self.clippedNode)

        if let peer = peer {
            if let representation = peer.smallProfileImage, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: synchronousLoad) {
                let image = generateImage(size, rotatedContext: { size, context in
                    context.clear(CGRect(origin: CGPoint(), size: size))
                    context.setFillColor(UIColor.lightGray.cgColor)
                    context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
                })!
                self.updateImage(image: image, size: size, spacing: spacing)

                let disposable = (signal
                |> deliverOnMainQueue).startStrict(next: { [weak self] imageVersions in
                    guard let strongSelf = self else {
                        return
                    }
                    let image = imageVersions?.0
                    if let image = image {
                        strongSelf.updateImage(image: image, size: size, spacing: spacing)
                    }
                })
                self.disposable = disposable
            } else {
                let image = generateImage(size, rotatedContext: { size, context in
                    context.clear(CGRect(origin: CGPoint(), size: size))
                    drawPeerAvatarLetters(context: context, size: size, font: avatarFont, letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor)
                })!
                self.updateImage(image: image, size: size, spacing: spacing)
            }
        } else {
            let image = generateImage(size, rotatedContext: { size, context in
                context.clear(CGRect(origin: CGPoint(), size: size))
                context.setFillColor(placeholderColor.cgColor)
                context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
            })!
            self.updateImage(image: image, size: size, spacing: spacing)
        }
    }
    
    private func updateImage(image: UIImage, size: CGSize, spacing: CGFloat) {
        self.unclippedNode.image = image
        self.clippedNode.image = generateImage(size, rotatedContext: { size, context in
            context.clear(CGRect(origin: CGPoint(), size: size))
            context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
            context.scaleBy(x: 1.0, y: -1.0)
            context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
            context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size))
            context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
            context.scaleBy(x: 1.0, y: -1.0)
            context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
            
            context.setBlendMode(.copy)
            context.setFillColor(UIColor.clear.cgColor)
            context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.5, dy: -1.5).offsetBy(dx: spacing - size.width, dy: 0.0))
        })
    }
    
    deinit {
        self.disposable?.dispose()
    }
    
    func updateLayout(size: CGSize, isClipped: Bool, animated: Bool) {
        self.unclippedNode.frame = CGRect(origin: CGPoint(), size: size)
        self.clippedNode.frame = CGRect(origin: CGPoint(), size: size)
        
        if animated && self.unclippedNode.alpha.isZero != self.clippedNode.alpha.isZero {
            let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
            transition.updateAlpha(node: self.unclippedNode, alpha: isClipped ? 0.0 : 1.0)
            transition.updateAlpha(node: self.clippedNode, alpha: isClipped ? 1.0 : 0.0)
        } else {
            self.unclippedNode.alpha = isClipped ? 0.0 : 1.0
            self.clippedNode.alpha = isClipped ? 1.0 : 0.0
        }
    }
    
    func updateAudioLevel(color: UIColor, backgroundColor: UIColor, value: Float) {
        if self.audioLevelView == nil, value > 0.0, self.context.sharedContext.energyUsageSettings.fullTranslucency {
            let blobFrame = self.unclippedNode.bounds.insetBy(dx: -8.0, dy: -8.0)
            
            let audioLevelView = VoiceBlobView(
                frame: blobFrame,
                maxLevel: 0.3,
                smallBlobRange: (0, 0),
                mediumBlobRange: (0.7, 0.8),
                bigBlobRange: (0.8, 0.9)
            )
            
            let maskRect = CGRect(origin: .zero, size: blobFrame.size)
            let playbackMaskLayer = CAShapeLayer()
            playbackMaskLayer.frame = maskRect
            playbackMaskLayer.fillRule = .evenOdd
            let maskPath = UIBezierPath()
            maskPath.append(UIBezierPath(roundedRect: self.unclippedNode.bounds.offsetBy(dx: 8, dy: 8), cornerRadius: maskRect.width / 2.0))
            maskPath.append(UIBezierPath(rect: maskRect))
            playbackMaskLayer.path = maskPath.cgPath
            //audioLevelView.layer.mask = playbackMaskLayer
            
            audioLevelView.setColor(color)
            self.audioLevelView = audioLevelView
            self.view.insertSubview(audioLevelView, at: 0)
        }
        
        let level = min(1.0, max(0.0, CGFloat(value)))
        if let audioLevelView = self.audioLevelView {
            audioLevelView.updateLevel(CGFloat(value) * 2.0)
            
            let avatarScale: CGFloat
            let audioLevelScale: CGFloat
            if value > 0.0 {
                audioLevelView.startAnimating()
                avatarScale = 1.03 + level * 0.07
                audioLevelScale = 1.0
            } else {
                audioLevelView.stopAnimating(duration: 0.5)
                avatarScale = 1.0
                audioLevelScale = 0.01
            }
            
            let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
            transition.updateSublayerTransformScale(node: self, scale: CGPoint(x: avatarScale, y: avatarScale), beginWithCurrentState: true)
            transition.updateSublayerTransformScale(layer: audioLevelView.layer, scale: CGPoint(x: audioLevelScale, y: audioLevelScale), beginWithCurrentState: true)
        }
    }
}

public final class AnimatedAvatarSetNode: ASDisplayNode {
    private var contentNodes: [AnimatedAvatarSetContext.Content.Item.Key: ContentNode] = [:]
    
    override public init() {
        super.init()
    }
    
    public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, itemSize: CGSize = CGSize(width: 30.0, height: 30.0), customSpacing: CGFloat? = nil, animated: Bool, synchronousLoad: Bool) -> CGSize {
        var contentWidth: CGFloat = 0.0
        let contentHeight: CGFloat = itemSize.height

        let spacing: CGFloat
        if let customSpacing = customSpacing {
            spacing = customSpacing
        } else {
            spacing = 10.0
        }
        
        let transition: ContainedViewLayoutTransition
        if animated {
            transition = .animated(duration: 0.2, curve: .easeInOut)
        } else {
            transition = .immediate
        }
        
        var validKeys: [AnimatedAvatarSetContext.Content.Item.Key] = []
        var index = 0
        for i in 0 ..< content.items.count {
            let (key, item) = content.items[i]
            
            validKeys.append(key)
            
            let itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: 0.0), size: itemSize)
            
            let itemNode: ContentNode
            if let current = self.contentNodes[key] {
                itemNode = current
                itemNode.updateLayout(size: itemSize, isClipped: index != 0, animated: animated)
                transition.updateFrame(node: itemNode, frame: itemFrame)
            } else {
                itemNode = ContentNode(context: context, peer: item.peer, placeholderColor: item.placeholderColor, synchronousLoad: synchronousLoad, size: itemSize, spacing: spacing)
                self.addSubnode(itemNode)
                self.contentNodes[key] = itemNode
                itemNode.updateLayout(size: itemSize, isClipped: index != 0, animated: false)
                itemNode.frame = itemFrame
                if animated {
                    itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                    itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
                }
            }
            itemNode.zPosition = CGFloat(100 - i)
            contentWidth += itemSize.width - spacing
            index += 1
        }
        var removeKeys: [AnimatedAvatarSetContext.Content.Item.Key] = []
        for key in self.contentNodes.keys {
            if !validKeys.contains(key) {
                removeKeys.append(key)
            }
        }
        for key in removeKeys {
            guard let itemNode = self.contentNodes.removeValue(forKey: key) else {
                continue
            }
            if animated {
                itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak itemNode] _ in
                    itemNode?.removeFromSupernode()
                })
                itemNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false)
            } else {
                itemNode.removeFromSupernode()
            }
        }
        
        return CGSize(width: contentWidth, height: contentHeight)
    }
    
    public func updateAudioLevels(color: UIColor, backgroundColor: UIColor, levels: [EnginePeer.Id: Float]) {
        for (key, itemNode) in self.contentNodes {
            if case let .peer(peerId) = key, let value = levels[peerId] {
                itemNode.updateAudioLevel(color: color, backgroundColor: backgroundColor, value: value)
            } else {
                itemNode.updateAudioLevel(color: color, backgroundColor: backgroundColor, value: 0.0)
            }
        }
    }
}

public final class AnimatedAvatarSetView: UIView {
    private final class ContentView: UIView {
        private let unclippedView: UIImageView
        private let clippedView: UIImageView

        private var size: CGSize
        private var spacing: CGFloat
        
        private var disposable: Disposable?
        
        init(context: AccountContext, peer: EnginePeer?, placeholderColor: UIColor, synchronousLoad: Bool, size: CGSize, spacing: CGFloat) {
            self.size = size
            self.spacing = spacing

            self.unclippedView = UIImageView()
            self.clippedView = UIImageView()
            
            super.init(frame: CGRect())
            
            self.addSubview(self.unclippedView)
            self.addSubview(self.clippedView)

            if let peer = peer {
                if let representation = peer.smallProfileImage, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: synchronousLoad) {
                    let image = generateImage(size, rotatedContext: { size, context in
                        context.clear(CGRect(origin: CGPoint(), size: size))
                        context.setFillColor(UIColor.lightGray.cgColor)
                        context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
                    })!
                    self.updateImage(image: image, size: size, spacing: spacing)

                    let disposable = (signal
                    |> deliverOnMainQueue).startStrict(next: { [weak self] imageVersions in
                        guard let strongSelf = self else {
                            return
                        }
                        let image = imageVersions?.0
                        if let image = image {
                            strongSelf.updateImage(image: image, size: size, spacing: spacing)
                        }
                    })
                    self.disposable = disposable
                } else {
                    let image = generateImage(size, rotatedContext: { size, context in
                        context.clear(CGRect(origin: CGPoint(), size: size))
                        drawPeerAvatarLetters(context: context, size: size, font: avatarFont, letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor)
                    })!
                    self.updateImage(image: image, size: size, spacing: spacing)
                }
            } else {
                let image = generateImage(size, rotatedContext: { size, context in
                    context.clear(CGRect(origin: CGPoint(), size: size))
                    context.setFillColor(placeholderColor.cgColor)
                    context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
                })!
                self.updateImage(image: image, size: size, spacing: spacing)
            }
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        private func updateImage(image: UIImage, size: CGSize, spacing: CGFloat) {
            self.unclippedView.image = image
            self.clippedView.image = generateImage(size, rotatedContext: { size, context in
                context.clear(CGRect(origin: CGPoint(), size: size))
                context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
                context.scaleBy(x: 1.0, y: -1.0)
                context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
                context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size))
                context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
                context.scaleBy(x: 1.0, y: -1.0)
                context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
                
                context.setBlendMode(.copy)
                context.setFillColor(UIColor.clear.cgColor)
                context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.5, dy: -1.5).offsetBy(dx: spacing - size.width, dy: 0.0))
            })
        }
        
        deinit {
            self.disposable?.dispose()
        }
        
        func updateLayout(size: CGSize, isClipped: Bool, animated: Bool) {
            self.unclippedView.frame = CGRect(origin: CGPoint(), size: size)
            self.clippedView.frame = CGRect(origin: CGPoint(), size: size)
            
            if animated && self.unclippedView.alpha.isZero != self.clippedView.alpha.isZero {
                let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
                transition.updateAlpha(layer: self.unclippedView.layer, alpha: isClipped ? 0.0 : 1.0)
                transition.updateAlpha(layer: self.clippedView.layer, alpha: isClipped ? 1.0 : 0.0)
            } else {
                self.unclippedView.alpha = isClipped ? 0.0 : 1.0
                self.clippedView.alpha = isClipped ? 1.0 : 0.0
            }
        }
    }
    
    private var contentViews: [AnimatedAvatarSetContext.Content.Item.Key: ContentView] = [:]
    
    public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, itemSize: CGSize = CGSize(width: 30.0, height: 30.0), customSpacing: CGFloat? = nil, animation: ListViewItemUpdateAnimation, synchronousLoad: Bool) -> CGSize {
        var contentWidth: CGFloat = 0.0
        let contentHeight: CGFloat = itemSize.height

        let spacing: CGFloat
        if let customSpacing = customSpacing {
            spacing = customSpacing
        } else {
            spacing = 10.0
        }
        
        var validKeys: [AnimatedAvatarSetContext.Content.Item.Key] = []
        var index = 0
        for i in 0 ..< content.items.count {
            let (key, item) = content.items[i]
            
            validKeys.append(key)
            
            let itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: 0.0), size: itemSize)
            
            let itemView: ContentView
            if let current = self.contentViews[key] {
                itemView = current
                itemView.updateLayout(size: itemSize, isClipped: index != 0, animated: animation.isAnimated)
                animation.animator.updateFrame(layer: itemView.layer, frame: itemFrame, completion: nil)
            } else {
                itemView = ContentView(context: context, peer: item.peer, placeholderColor: item.placeholderColor, synchronousLoad: synchronousLoad, size: itemSize, spacing: spacing)
                self.addSubview(itemView)
                self.contentViews[key] = itemView
                itemView.updateLayout(size: itemSize, isClipped: index != 0, animated: false)
                itemView.frame = itemFrame
                if animation.isAnimated {
                    itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                    itemView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
                }
            }
            itemView.layer.zPosition = CGFloat(100 - i)
            contentWidth += itemSize.width - spacing
            index += 1
        }
        var removeKeys: [AnimatedAvatarSetContext.Content.Item.Key] = []
        for key in self.contentViews.keys {
            if !validKeys.contains(key) {
                removeKeys.append(key)
            }
        }
        for key in removeKeys {
            guard let itemView = self.contentViews.removeValue(forKey: key) else {
                continue
            }
            if animation.isAnimated {
                itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak itemView] _ in
                    itemView?.removeFromSuperview()
                })
                itemView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false)
            } else {
                itemView.removeFromSuperview()
            }
        }
        
        return CGSize(width: contentWidth, height: contentHeight)
    }
}