import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import WallpaperBackgroundNode

public enum ChatMessageBackgroundMergeType: Equatable {
    case None, Side, Top(side: Bool), Bottom, Both, Extracted
    
    public init(top: Bool, bottom: Bool, side: Bool) {
        if top && bottom {
            self = .Both
        } else if top {
            self = .Top(side: side)
        } else if bottom {
            if side {
                self = .Side
            } else {
                self = .Bottom
            }
        } else {
            if side {
                self = .Side
            } else {
                self = .None
            }
        }
    }
}

public enum ChatMessageBackgroundType: Equatable {
    case none
    case incoming(ChatMessageBackgroundMergeType)
    case outgoing(ChatMessageBackgroundMergeType)

    public static func ==(lhs: ChatMessageBackgroundType, rhs: ChatMessageBackgroundType) -> Bool {
        switch lhs {
            case .none:
                if case .none = rhs {
                    return true
                } else {
                    return false
                }
            case let .incoming(mergeType):
                if case .incoming(mergeType) = rhs {
                    return true
                } else {
                    return false
                }
            case let .outgoing(mergeType):
                if case .outgoing(mergeType) = rhs {
                    return true
                } else {
                    return false
                }
        }
    }
}

public class ChatMessageBackground: ASDisplayNode {
    public weak var backdropNode: ChatMessageBubbleBackdrop?
        
    public private(set) var type: ChatMessageBackgroundType?
    private var currentHighlighted: Bool?
    private var hasWallpaper: Bool?
    private var graphics: PrincipalThemeEssentialGraphics?
    private var maskMode: Bool?
    private let outlineImageNode: ASImageNode
    private weak var backgroundNode: WallpaperBackgroundNode?
    
    private var imageFrame: CGRect?
    private var imageView: UIImageView?
    private var imageViewImage: UIImage?
    
    public var customHighlightColor: UIColor? {
        didSet {
            self.imageView?.tintColor = self.customHighlightColor
        }
    }
    
    public var backgroundFrame: CGRect = .zero
    
    public var hasImage: Bool {
        self.imageViewImage != nil
    }
    
    public override init() {
        self.outlineImageNode = ASImageNode()
        self.outlineImageNode.displaysAsynchronously = false
        self.outlineImageNode.displayWithoutProcessing = true
        
        super.init()
                
        self.isUserInteractionEnabled = false
        self.addSubnode(self.outlineImageNode)
    }
    
    override public func didLoad() {
        super.didLoad()
        
        let imageView = UIImageView()
        self.imageView = imageView
        self.view.addSubview(imageView)
        
        imageView.image = self.imageViewImage
        imageView.tintColor = self.customHighlightColor
        
        if let imageFrame = self.imageFrame {
            imageView.frame = imageFrame
        }
    }
    
    public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
        let imageFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)
        self.imageFrame = imageFrame
        if let imageView = self.imageView {
            transition.updateFrame(view: imageView, frame: imageFrame)
        }
        transition.updateFrame(node: self.outlineImageNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0))
    }
    
    public func updateLayout(size: CGSize, transition: ListViewItemUpdateAnimation) {
        let imageFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)
        self.imageFrame = imageFrame
        if let imageView = self.imageView {
            transition.animator.updateFrame(layer: imageView.layer, frame: imageFrame, completion: nil)
        }
        
        transition.animator.updateFrame(layer: self.outlineImageNode.layer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0), completion: nil)
    }
    
    public func setMaskMode(_ maskMode: Bool) {
        if let type = self.type, let hasWallpaper = self.hasWallpaper, let highlighted = self.currentHighlighted, let graphics = self.graphics, let backgroundNode = self.backgroundNode {
            self.setType(type: type, highlighted: highlighted, graphics: graphics, maskMode: maskMode, hasWallpaper: hasWallpaper, transition: .immediate, backgroundNode: backgroundNode)
        }
    }
    
    public func currentCorners(bubbleCorners: PresentationChatBubbleCorners) -> (topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat, drawTail: Bool)? {
        guard let type = self.type else {
            return nil
        }
        
        let maxRadius = bubbleCorners.mainRadius
        let minRadius = bubbleCorners.auxiliaryRadius
        
        switch type {
        case .none:
            return nil
        case let .incoming(mergeType):
            switch mergeType {
            case .None:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .none)
            case let .Top(side):
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .top(side: side))
            case .Bottom:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .bottom)
            case .Both:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .both)
            case .Side:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .side)
            case .Extracted:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .extracted)
            }
        case let .outgoing(mergeType):
            switch mergeType {
            case .None:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .none)
            case let .Top(side):
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .top(side: side))
            case .Bottom:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .bottom)
            case .Both:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .both)
            case .Side:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .side)
            case .Extracted:
                return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .extracted)
            }
        }
    }
    
    public func setType(type: ChatMessageBackgroundType, highlighted: Bool, graphics: PrincipalThemeEssentialGraphics, maskMode: Bool, hasWallpaper: Bool, transition: ContainedViewLayoutTransition, backgroundNode: WallpaperBackgroundNode?) {
        let previousType = self.type
        if let currentType = previousType, currentType == type, self.currentHighlighted == highlighted, self.graphics === graphics, backgroundNode === self.backgroundNode, self.maskMode == maskMode, self.hasWallpaper == hasWallpaper {
            return
        }
        self.type = type
        self.currentHighlighted = highlighted
        self.graphics = graphics
        self.backgroundNode = backgroundNode
        self.hasWallpaper = hasWallpaper
        
        var image: UIImage?
        
        switch type {
        case .none:
            image = nil
        case let .incoming(mergeType):
            if maskMode, let backgroundNode = backgroundNode, backgroundNode.hasBubbleBackground(for: .incoming), !highlighted {
                image = nil
            } else {
                switch mergeType {
                case .None:
                    image = highlighted ? graphics.chatMessageBackgroundIncomingHighlightedImage : graphics.chatMessageBackgroundIncomingImage
                case let .Top(side):
                    if side {
                        image = highlighted ? graphics.chatMessageBackgroundIncomingMergedTopSideHighlightedImage : graphics.chatMessageBackgroundIncomingMergedTopSideImage
                    } else {
                        image = highlighted ? graphics.chatMessageBackgroundIncomingMergedTopHighlightedImage : graphics.chatMessageBackgroundIncomingMergedTopImage
                    }
                case .Bottom:
                    image = highlighted ? graphics.chatMessageBackgroundIncomingMergedBottomHighlightedImage : graphics.chatMessageBackgroundIncomingMergedBottomImage
                case .Both:
                    image = highlighted ? graphics.chatMessageBackgroundIncomingMergedBothHighlightedImage : graphics.chatMessageBackgroundIncomingMergedBothImage
                case .Side:
                    image = highlighted ? graphics.chatMessageBackgroundIncomingMergedSideHighlightedImage : graphics.chatMessageBackgroundIncomingMergedSideImage
                case .Extracted:
                    image = graphics.chatMessageBackgroundIncomingExtractedImage
                }
            }
        case let .outgoing(mergeType):
            if maskMode, let backgroundNode = backgroundNode, backgroundNode.hasBubbleBackground(for: .outgoing), !highlighted {
                image = nil
            } else {
                switch mergeType {
                case .None:
                    image = highlighted ? graphics.chatMessageBackgroundOutgoingHighlightedImage : graphics.chatMessageBackgroundOutgoingImage
                case let .Top(side):
                    if side {
                        image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedTopSideImage
                    } else {
                        image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedTopHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedTopImage
                    }
                case .Bottom:
                    image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedBottomHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedBottomImage
                case .Both:
                    image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedBothHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedBothImage
                case .Side:
                    image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedSideHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedSideImage
                case .Extracted:
                    image = graphics.chatMessageBackgroundOutgoingExtractedImage
                }
            }
        }
        
        let outlineImage: UIImage?
        if hasWallpaper {
            switch type {
            case .none:
                outlineImage = nil
            case let .incoming(mergeType):
                switch mergeType {
                case .None:
                    outlineImage = graphics.chatMessageBackgroundIncomingOutlineImage
                case let .Top(side):
                    if side {
                        outlineImage = graphics.chatMessageBackgroundIncomingMergedTopSideOutlineImage
                    } else {
                        outlineImage = graphics.chatMessageBackgroundIncomingMergedTopOutlineImage
                    }
                case .Bottom:
                    outlineImage = graphics.chatMessageBackgroundIncomingMergedBottomOutlineImage
                case .Both:
                    outlineImage = graphics.chatMessageBackgroundIncomingMergedBothOutlineImage
                case .Side:
                    outlineImage = graphics.chatMessageBackgroundIncomingMergedSideOutlineImage
                case .Extracted:
                    outlineImage = graphics.chatMessageBackgroundIncomingExtractedOutlineImage
                }
            case let .outgoing(mergeType):
                switch mergeType {
                case .None:
                    outlineImage = graphics.chatMessageBackgroundOutgoingOutlineImage
                case let .Top(side):
                    if side {
                        outlineImage = graphics.chatMessageBackgroundOutgoingMergedTopSideOutlineImage
                    } else {
                        outlineImage = graphics.chatMessageBackgroundOutgoingMergedTopOutlineImage
                    }
                case .Bottom:
                    outlineImage = graphics.chatMessageBackgroundOutgoingMergedBottomOutlineImage
                case .Both:
                    outlineImage = graphics.chatMessageBackgroundOutgoingMergedBothOutlineImage
                case .Side:
                    outlineImage = graphics.chatMessageBackgroundOutgoingMergedSideOutlineImage
                case .Extracted:
                    outlineImage = graphics.chatMessageBackgroundOutgoingExtractedOutlineImage
                }
            }
        } else {
            outlineImage = nil
        }
        
        if let previousType = previousType, previousType != .none, type == .none {
            if transition.isAnimated, let imageView = self.imageView {
                let tempLayer = CALayer()
                tempLayer.contents = imageView.layer.contents
                tempLayer.contentsScale = imageView.layer.contentsScale
                tempLayer.rasterizationScale = imageView.layer.rasterizationScale
                tempLayer.contentsGravity = imageView.layer.contentsGravity
                tempLayer.contentsCenter = imageView.layer.contentsCenter
                
                tempLayer.frame = imageView.frame
                self.layer.insertSublayer(tempLayer, above: imageView.layer)
                transition.updateAlpha(layer: tempLayer, alpha: 0.0, completion: { [weak tempLayer] _ in
                    tempLayer?.removeFromSuperlayer()
                })
            }
        } else if transition.isAnimated, let imageView = self.imageView {
            if let previousContents = imageView.layer.contents {
                if let image = image {
                    if (previousContents as AnyObject) !== image.cgImage {
                        imageView.layer.animate(from: previousContents as AnyObject, to: image.cgImage! as AnyObject, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.42)
                    }
                } else {
                    let tempLayer = CALayer()
                    tempLayer.contents = imageView.layer.contents
                    tempLayer.contentsScale = imageView.layer.contentsScale
                    tempLayer.rasterizationScale = imageView.layer.rasterizationScale
                    tempLayer.contentsGravity = imageView.layer.contentsGravity
                    tempLayer.contentsCenter = imageView.layer.contentsCenter
                    tempLayer.compositingFilter = imageView.layer.compositingFilter
                    
                    tempLayer.frame = imageView.frame
                    
                    imageView.superview?.layer.insertSublayer(tempLayer, above: imageView.layer)
                    transition.updateAlpha(layer: tempLayer, alpha: 0.0, completion: { [weak tempLayer] _ in
                        tempLayer?.removeFromSuperlayer()
                    })
                }
            }
        }
        
        self.imageViewImage = image
        if let imageView = self.imageView {
            imageView.image = image
        }
        
        self.outlineImageNode.image = outlineImage
    }

    public func animateFrom(sourceView: UIView, transition: CombinedTransition) {
        if transition.isAnimated {
            self.imageView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
            self.outlineImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)

            self.view.addSubview(sourceView)

            sourceView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak sourceView] _ in
                sourceView?.removeFromSuperview()
            })

            if let imageView = imageView {
                transition.animateFrame(layer: imageView.layer, from: sourceView.frame)
                transition.updateFrame(layer: sourceView.layer, frame: CGRect(origin: imageView.frame.origin, size: CGSize(width: imageView.frame.width - 7.0, height: imageView.frame.height)))
            }
            transition.animateFrame(layer: self.outlineImageNode.layer, from: sourceView.frame)
        }
    }
}

public final class ChatMessageShadowNode: ASDisplayNode {
    private let contentNode: ASImageNode
    private var graphics: PrincipalThemeEssentialGraphics?
    
    public override init() {
        self.contentNode = ASImageNode()
        self.contentNode.isLayerBacked = true
        self.contentNode.displaysAsynchronously = false
        self.contentNode.displayWithoutProcessing = true
        
        super.init()
        
        self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
        
        self.isLayerBacked = true
        
        self.addSubnode(self.contentNode)
    }
    
    public func setType(type: ChatMessageBackgroundType, hasWallpaper: Bool, graphics: PrincipalThemeEssentialGraphics) {
        let shadowImage: UIImage?
        
        if hasWallpaper {
            switch type {
            case .none:
                shadowImage = nil
            case let .incoming(mergeType):
                switch mergeType {
                case .None:
                    shadowImage = graphics.chatMessageBackgroundIncomingShadowImage
                case let .Top(side):
                    if side {
                        shadowImage = graphics.chatMessageBackgroundIncomingMergedTopSideShadowImage
                    } else {
                        shadowImage = graphics.chatMessageBackgroundIncomingMergedTopShadowImage
                    }
                case .Bottom:
                    shadowImage = graphics.chatMessageBackgroundIncomingMergedBottomShadowImage
                case .Both:
                    shadowImage = graphics.chatMessageBackgroundIncomingMergedBothShadowImage
                case .Side:
                    shadowImage = graphics.chatMessageBackgroundIncomingMergedSideShadowImage
                case .Extracted:
                    shadowImage = nil
                }
            case let .outgoing(mergeType):
                switch mergeType {
                case .None:
                    shadowImage = graphics.chatMessageBackgroundOutgoingShadowImage
                case let .Top(side):
                    if side {
                        shadowImage = graphics.chatMessageBackgroundOutgoingMergedTopSideShadowImage
                    } else {
                        shadowImage = graphics.chatMessageBackgroundOutgoingMergedTopShadowImage
                    }
                case .Bottom:
                    shadowImage = graphics.chatMessageBackgroundOutgoingMergedBottomShadowImage
                case .Both:
                    shadowImage = graphics.chatMessageBackgroundOutgoingMergedBothShadowImage
                case .Side:
                    shadowImage = graphics.chatMessageBackgroundOutgoingMergedSideShadowImage
                case .Extracted:
                    shadowImage = nil
                }
            }
        } else {
            shadowImage = nil
        }
        
        self.contentNode.image = shadowImage
    }
    
    public func updateLayout(backgroundFrame: CGRect, animator: ControlledTransitionAnimator) {
        animator.updateFrame(layer: self.contentNode.layer, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX - 10.0, y: backgroundFrame.minY - 10.0), size: CGSize(width: backgroundFrame.width + 20.0, height: backgroundFrame.height + 20.0)), completion: nil)
    }
    
    public func updateLayout(backgroundFrame: CGRect, transition: ContainedViewLayoutTransition) {
        transition.updateFrame(layer: self.contentNode.layer, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX - 10.0, y: backgroundFrame.minY - 10.0), size: CGSize(width: backgroundFrame.width + 20.0, height: backgroundFrame.height + 20.0)), completion: nil)
    }
}


private let maskInset: CGFloat = 1.0

public func bubbleMaskForType(_ type: ChatMessageBackgroundType, graphics: PrincipalThemeEssentialGraphics) -> UIImage? {
    let image: UIImage?
    switch type {
    case .none:
        image = nil
    case let .incoming(mergeType):
        switch mergeType {
        case .None:
            image = graphics.chatMessageBackgroundIncomingMaskImage
        case let .Top(side):
            if side {
                image = graphics.chatMessageBackgroundIncomingMergedTopSideMaskImage
            } else {
                image = graphics.chatMessageBackgroundIncomingMergedTopMaskImage
            }
        case .Bottom:
            image = graphics.chatMessageBackgroundIncomingMergedBottomMaskImage
        case .Both:
            image = graphics.chatMessageBackgroundIncomingMergedBothMaskImage
        case .Side:
            image = graphics.chatMessageBackgroundIncomingMergedSideMaskImage
        case .Extracted:
            image = graphics.chatMessageBackgroundIncomingExtractedMaskImage
        }
    case let .outgoing(mergeType):
        switch mergeType {
        case .None:
            image = graphics.chatMessageBackgroundOutgoingMaskImage
        case let .Top(side):
            if side {
                image = graphics.chatMessageBackgroundOutgoingMergedTopSideMaskImage
            } else {
                image = graphics.chatMessageBackgroundOutgoingMergedTopMaskImage
            }
        case .Bottom:
            image = graphics.chatMessageBackgroundOutgoingMergedBottomMaskImage
        case .Both:
            image = graphics.chatMessageBackgroundOutgoingMergedBothMaskImage
        case .Side:
            image = graphics.chatMessageBackgroundOutgoingMergedSideMaskImage
        case .Extracted:
            image = graphics.chatMessageBackgroundOutgoingExtractedMaskImage
        }
    }
    return image
}

public final class ChatMessageBubbleBackdrop: ASDisplayNode {
    public private(set) var backgroundContent: WallpaperBubbleBackgroundNode?
    
    private var currentType: ChatMessageBackgroundType?
    private var currentMaskMode: Bool?
    private var theme: ChatPresentationThemeData?
    private var essentialGraphics: PrincipalThemeEssentialGraphics?
    private weak var backgroundNode: WallpaperBackgroundNode?
    
    public var maskView: UIImageView?
    private var fixedMaskMode: Bool?

    private var absolutePosition: (CGRect, CGSize)?
    
    public var overrideMask: Bool = false {
        didSet {
            self.maskView?.image = nil
        }
    }
    
    public var hasImage: Bool {
        return self.backgroundContent != nil
    }
    
    public override var frame: CGRect {
        didSet {
            if let maskView = self.maskView {
                let maskFrame = self.bounds.insetBy(dx: -maskInset, dy: -maskInset)
                if maskView.frame != maskFrame {
                    maskView.frame = maskFrame
                }
            }
            if let backgroundContent = self.backgroundContent {
                backgroundContent.frame = self.bounds
                if let (rect, containerSize) = self.absolutePosition {
                    var backgroundFrame = backgroundContent.frame
                    backgroundFrame.origin.x += rect.minX
                    backgroundFrame.origin.y += rect.minY
                    backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
                }
            }
        }
    }
    
    public override init() {
        super.init()
        
        self.clipsToBounds = true
    }
    
    public func setMaskMode(_ maskMode: Bool) {
        if let currentType = self.currentType, let theme = self.theme, let essentialGraphics = self.essentialGraphics, let backgroundNode = self.backgroundNode {
            self.setType(type: currentType, theme: theme, essentialGraphics: essentialGraphics, maskMode: maskMode, backgroundNode: backgroundNode)
        }
    }
        
    public func setType(type: ChatMessageBackgroundType, theme: ChatPresentationThemeData, essentialGraphics: PrincipalThemeEssentialGraphics, maskMode inputMaskMode: Bool, backgroundNode: WallpaperBackgroundNode?) {
        let maskMode = self.fixedMaskMode ?? inputMaskMode

        if self.currentType != type || self.theme != theme || self.currentMaskMode != maskMode || self.essentialGraphics !== essentialGraphics || self.backgroundNode !== backgroundNode {
            let typeUpdated = self.currentType != type || self.theme != theme || self.currentMaskMode != maskMode || self.backgroundNode !== backgroundNode

            self.currentType = type
            self.theme = theme
            self.essentialGraphics = essentialGraphics
            self.backgroundNode = backgroundNode
            
            if maskMode != self.currentMaskMode {
                self.currentMaskMode = maskMode
                
                if maskMode {
                    let maskView: UIImageView
                    if let current = self.maskView {
                        maskView = current
                    } else {
                        maskView = UIImageView()
                        maskView.frame = self.bounds.insetBy(dx: -maskInset, dy: -maskInset)
                        self.maskView = maskView
                        self.view.mask = maskView
                    }
                } else {
                    if let _ = self.maskView {
                        self.view.mask = nil
                        self.maskView = nil
                    }
                }
            }

            if let backgroundContent = self.backgroundContent {
                backgroundContent.frame = self.bounds
                if let (rect, containerSize) = self.absolutePosition {
                    var backgroundFrame = backgroundContent.frame
                    backgroundFrame.origin.x += rect.minX
                    backgroundFrame.origin.y += rect.minY
                    backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
                }
            }

            if typeUpdated {
                if let backgroundContent = self.backgroundContent {
                    self.backgroundContent = nil
                    backgroundContent.removeFromSupernode()
                }

                switch type {
                case .none:
                    break
                case .incoming:
                    if let backgroundContent = backgroundNode?.makeBubbleBackground(for: .incoming) {
                        backgroundContent.frame = self.bounds
                        if let (rect, containerSize) = self.absolutePosition {
                            var backgroundFrame = backgroundContent.frame
                            backgroundFrame.origin.x += rect.minX
                            backgroundFrame.origin.y += rect.minY
                            backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
                        }
                        self.backgroundContent = backgroundContent
                        self.insertSubnode(backgroundContent, at: 0)
                    }
                case .outgoing:
                    if let backgroundContent = backgroundNode?.makeBubbleBackground(for: .outgoing) {
                        backgroundContent.frame = self.bounds
                        if let (rect, containerSize) = self.absolutePosition {
                            var backgroundFrame = backgroundContent.frame
                            backgroundFrame.origin.x += rect.minX
                            backgroundFrame.origin.y += rect.minY
                            backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
                        }
                        self.backgroundContent = backgroundContent
                        self.insertSubnode(backgroundContent, at: 0)
                    }
                }
            }
            
            if let maskView = self.maskView {
                maskView.image = self.overrideMask ? nil : bubbleMaskForType(type, graphics: essentialGraphics)
            }
        }
    }
        
    public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) {
        self.absolutePosition = (rect, containerSize)
        if let backgroundContent = self.backgroundContent {
            var backgroundFrame = backgroundContent.frame
            backgroundFrame.origin.x += rect.minX
            backgroundFrame.origin.y += rect.minY
            backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
        }
    }
    
    public func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
        self.backgroundContent?.offset(value: value, animationCurve: animationCurve, duration: duration)
    }
    
    public func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
        self.backgroundContent?.offsetSpring(value: value, duration: duration, damping: damping)
    }
    
    public func updateFrame(_ value: CGRect, animator: ControlledTransitionAnimator, completion: @escaping () -> Void = {}) {
        if let maskView = self.maskView {
            animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)).insetBy(dx: -maskInset, dy: -maskInset), completion: nil)
        }
        if let backgroundContent = self.backgroundContent {
            animator.updateFrame(layer: backgroundContent.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)), completion: nil)
            if let (rect, containerSize) = self.absolutePosition {
                var backgroundFrame = backgroundContent.frame
                backgroundFrame.origin.x += rect.minX
                backgroundFrame.origin.y += rect.minY
                backgroundContent.update(rect: backgroundFrame, within: containerSize, animator: animator)
            }
        }
        animator.updateFrame(layer: self.layer, frame: value, completion: { _ in
            completion()
        })
    }
    
    public func updateFrame(_ value: CGRect, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) {
        if let maskView = self.maskView {
            transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)).insetBy(dx: -maskInset, dy: -maskInset))
        }
        if let backgroundContent = self.backgroundContent {
            transition.updateFrame(layer: backgroundContent.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)))
            if let (rect, containerSize) = self.absolutePosition {
                var backgroundFrame = backgroundContent.frame
                backgroundFrame.origin.x += rect.minX
                backgroundFrame.origin.y += rect.minY
                backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
            }
        }
        transition.updateFrame(node: self, frame: value, completion: { _ in
            completion()
        })
    }

    public func updateFrame(_ value: CGRect, transition: CombinedTransition, completion: @escaping () -> Void = {}) {
        if let maskView = self.maskView {
            transition.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)).insetBy(dx: -maskInset, dy: -maskInset))
        }
        if let backgroundContent = self.backgroundContent {
            transition.updateFrame(layer: backgroundContent.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value.size.width, height: value.size.height)))
            if let (rect, containerSize) = self.absolutePosition {
                var backgroundFrame = backgroundContent.frame
                backgroundFrame.origin.x += rect.minX
                backgroundFrame.origin.y += rect.minY
                backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
            }
        }
        transition.updateFrame(layer: self.layer, frame: value, completion: { _ in
            completion()
        })
    }

    public func animateFrom(sourceView: UIView, transition: CombinedTransition) {
        if transition.isAnimated {
            let previousFrame = self.frame
            self.updateFrame(CGRect(origin: CGPoint(x: previousFrame.minX, y: sourceView.frame.minY), size: sourceView.frame.size), transition: .immediate)
            self.updateFrame(previousFrame, transition: transition)

            self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
        }
    }
}