import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramUIPreferences
import ComponentFlow
import AudioTranscriptionButtonComponent
import ChatMessageDateAndStatusNode
import ChatMessageBubbleContentNode
import ChatMessageItemCommon
import ChatMessageInteractiveInstantVideoNode
import ChatMessageInteractiveFileNode

extension ChatMessageInteractiveInstantVideoNode.AnimateFileNodeDescription {
    convenience init(_ node: ChatMessageInteractiveFileNode) {
        self.init(
            node: node,
            textClippingNode: node.textClippingNode,
            dateAndStatusNode: node.dateAndStatusNode,
            fetchingTextNode: node.fetchingTextNode,
            waveformView: node.waveformView,
            statusNode: node.statusNode,
            audioTranscriptionButton: node.audioTranscriptionButton
        )
    }
}

public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode {
    public let interactiveFileNode: ChatMessageInteractiveFileNode
    public let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode
    
    private let maskLayer = SimpleLayer()
    private let maskForeground = SimpleLayer()
    
    private let backdropMaskLayer = SimpleLayer()
    private let backdropMaskForeground = BubbleMaskLayer()
    
    private var isExpanded = false
    
    private var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .collapsed
    
    public var hasExpandedAudioTranscription: Bool {
        if case .expanded = self.audioTranscriptionState {
            return true
        } else {
            return false
        }
    }
    
    override public var visibility: ListViewItemNodeVisibility {
        didSet {
            var wasVisible = false
            if case .visible = oldValue {
                wasVisible = true
            }
            let isVisible = self.isContentVisible
            if wasVisible != isVisible {
                if !isVisible {
                    Queue.mainQueue().after(0.05) {
                        if isVisible == self.isContentVisible {
                            self.interactiveVideoNode.visibility = isVisible
                        }
                    }
                } else {
                    self.interactiveVideoNode.visibility = isVisible
                }
            }
        }
    }
    
    private var isContentVisible: Bool  {
        var isVisible = false
        if case .visible = self.visibility {
            isVisible = true
        }
        return isVisible
    }
    
    required public init() {
        self.interactiveFileNode = ChatMessageInteractiveFileNode()
        self.interactiveVideoNode = ChatMessageInteractiveInstantVideoNode()
                
        super.init()
        
        self.maskForeground.backgroundColor = UIColor.white.cgColor
        self.maskForeground.masksToBounds = true
        self.maskLayer.addSublayer(self.maskForeground)
            
        self.addSubnode(self.interactiveVideoNode)
                
        self.interactiveVideoNode.requestUpdateLayout = { [weak self] _ in
            if let strongSelf = self, let item = strongSelf.item {
                let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false)
            }
        }
        self.interactiveVideoNode.updateTranscriptionExpanded = { [weak self] state in
            if let strongSelf = self, let item = strongSelf.item {
                let previous = strongSelf.audioTranscriptionState
                strongSelf.audioTranscriptionState = state
                strongSelf.interactiveFileNode.audioTranscriptionState = state
                let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, state != .inProgress && previous != state)
            }
        }
        self.interactiveVideoNode.updateTranscriptionText = { [weak self] text in
            if let strongSelf = self, let item = strongSelf.item {
                strongSelf.interactiveFileNode.forcedAudioTranscriptionText = text
                let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false)
            }
        }
        self.interactiveFileNode.updateTranscriptionExpanded = { [weak self] state in
            if let strongSelf = self, let item = strongSelf.item {
                let previous = strongSelf.audioTranscriptionState
                strongSelf.audioTranscriptionState = state
                strongSelf.interactiveVideoNode.audioTranscriptionState = state
                let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, previous != state)
            }
        }
        
        self.interactiveFileNode.toggleSelection = { [weak self] value in
            if let strongSelf = self, let item = strongSelf.item {
                item.controllerInteraction.toggleMessagesSelection([item.message.id], value)
            }
        }
        
        self.interactiveFileNode.activateLocalContent = { [weak self] in
            if let strongSelf = self, let item = strongSelf.item {
                let _ = item.controllerInteraction.openMessage(item.message, .default)
            }
        }
        
        self.interactiveFileNode.requestUpdateLayout = { [weak self] _ in
            if let strongSelf = self, let item = strongSelf.item {
                let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false)
            }
        }
        
        self.interactiveFileNode.displayImportedTooltip = { [weak self] sourceNode in
            if let strongSelf = self, let item = strongSelf.item {
                let _ = item.controllerInteraction.displayImportedMessageTooltip(sourceNode)
            }
        }
        
        self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] value in
            guard let strongSelf = self, let item = strongSelf.item else {
                return
            }
            item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
        }
        
        self.interactiveFileNode.dateAndStatusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in
            guard let strongSelf = self, let item = strongSelf.item else {
                gesture?.cancel()
                return
            }
            
            item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value)
        }
        
        self.interactiveFileNode.updateIsTextSelectionActive = { [weak self] value in
            self?.updateIsTextSelectionActive?(value)
        }
    }
    
    override public func accessibilityActivate() -> Bool {
        if let item = self.item {
            let _ = item.controllerInteraction.openMessage(item.message, .default)
        }
        return true
    }
    
    required public init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
        
    override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
        let interactiveVideoLayout = self.interactiveVideoNode.asyncLayout()
        let interactiveFileLayout = self.interactiveFileNode.asyncLayout()
        
        let currentExpanded = self.isExpanded
        let audioTranscriptionState = self.audioTranscriptionState
        let didSetupFileNode = self.item != nil
        
        return { item, layoutConstants, preparePosition, selection, constrainedSize, avatarInset in
            var selectedFile: TelegramMediaFile?
            for media in item.message.media {
                if let telegramFile = media as? TelegramMediaFile {
                    selectedFile = telegramFile
                }
            }
            
            var incoming = item.message.effectivelyIncoming(item.context.account.peerId)
            if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
                incoming = false
            }
            
            let statusType: ChatMessageDateAndStatusType?
            switch preparePosition {
            case .linear(_, .None), .linear(_, .Neighbour(true, _, _)):
                    if incoming {
                        statusType = .BubbleIncoming
                    } else {
                        if item.message.flags.contains(.Failed) {
                            statusType = .BubbleOutgoing(.Failed)
                        } else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
                            statusType = .BubbleOutgoing(.Sending)
                        } else {
                            statusType = .BubbleOutgoing(.Sent(read: item.read))
                        }
                    }
                default:
                    statusType = nil
            }
            
            let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
            
            let (initialWidth, refineLayout) = interactiveFileLayout(ChatMessageInteractiveFileNode.Arguments(
                context: item.context,
                presentationData: item.presentationData,
                message: item.message,
                topMessage: item.topMessage,
                associatedData: item.associatedData,
                chatLocation: item.chatLocation,
                attributes: item.attributes,
                isPinned: item.isItemPinned,
                forcedIsEdited: item.isItemEdited,
                file: selectedFile!,
                automaticDownload: automaticDownload,
                incoming: incoming,
                isRecentActions: item.associatedData.isRecentActions,
                forcedResourceStatus: item.associatedData.forcedResourceStatus,
                dateAndStatusType: statusType,
                displayReactions: false,
                messageSelection: item.message.groupingKey != nil ? selection : nil,
                layoutConstants: layoutConstants,
                constrainedSize: CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height),
                controllerInteraction: item.controllerInteraction
            ))
            
            var isReplyThread = false
            if case .replyThread = item.chatLocation {
                isReplyThread = true
            }
                        
            var isExpanded = false
            if case .expanded = audioTranscriptionState {
                isExpanded = true
            }
            
            var isPlaying = false
            let normalDisplaySize = layoutConstants.instantVideo.dimensions
            var displaySize = normalDisplaySize
            let maximumDisplaySize = CGSize(width: min(404, constrainedSize.width - 2.0), height: min(404, constrainedSize.width - 2.0))
            if item.associatedData.currentlyPlayingMessageId == item.message.index {
                isPlaying = true
                if !isExpanded {
                    displaySize = maximumDisplaySize
                }
            }
            
            let leftInset: CGFloat = 0.0
            let rightInset: CGFloat = 0.0
        
            let (videoLayout, videoApply) = interactiveVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, topMessage: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.attributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), constrainedSize.width - leftInset - rightInset - avatarInset, displaySize, maximumDisplaySize, isPlaying ? 1.0 : 0.0, .free, automaticDownload, avatarInset)
            
            let videoFrame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: videoLayout.contentSize)
            
            let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none, shareButtonOffset: isExpanded ? nil : CGPoint(x: avatarInset + displaySize.width + 4.0, y: -25.0), hidesHeaders: !isExpanded, avatarOffset: !isExpanded && isPlaying ? -100.0 : 0.0)
            
            let videoFrameWidth = videoFrame.width + 2.0
            
            return (contentProperties, nil, initialWidth, { constrainedSize, position in
                var refinedWidth = videoFrameWidth
                var finishLayout: ((CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void))?
                
                if isExpanded || !didSetupFileNode {
                    (refinedWidth, finishLayout) = refineLayout(CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right - 44.0, height: constrainedSize.height))
                    refinedWidth += layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right
                }
                
                if !isExpanded {
                    refinedWidth = videoFrameWidth
                }
                
                return (refinedWidth, { boundingWidth in
                    var finalSize: CGSize
                    var finalFileSize: CGSize?
                    var finalFileApply: ((Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void)?
                    if let finishLayout = finishLayout {
                        let (fileSize, fileApply) = finishLayout(boundingWidth - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right)
                        if isExpanded {
                            finalSize = CGSize(width: fileSize.width + layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right, height: fileSize.height + layoutConstants.file.bubbleInsets.top + layoutConstants.file.bubbleInsets.bottom)
                        } else {
                            finalSize = CGSize(width: boundingWidth, height: videoFrame.height + 2.0)
                        }
                        finalFileSize = fileSize
                        finalFileApply = fileApply
                    } else {
                        finalSize = CGSize(width: boundingWidth, height: videoFrame.height + 2.0)
                    }
                    
                    return (finalSize, { [weak self] animation, synchronousLoads, applyInfo in
                        if let strongSelf = self {
                            strongSelf.item = item
                            strongSelf.isExpanded = isExpanded
                            
                            strongSelf.bubbleBackgroundNode?.layer.mask = strongSelf.maskLayer
                            if let bubbleBackdropNode = strongSelf.bubbleBackdropNode, bubbleBackdropNode.hasImage && strongSelf.backdropMaskForeground.superlayer == nil {
                                strongSelf.bubbleBackdropNode?.overrideMask = true
                                strongSelf.bubbleBackdropNode?.maskView?.layer.addSublayer(strongSelf.backdropMaskForeground)
                            }
                            
                            strongSelf.maskLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 640.0, height: 640.0))
                            strongSelf.backdropMaskLayer.frame = strongSelf.maskLayer.frame
                                          
                            let bubbleSize = strongSelf.bubbleBackgroundNode?.backgroundFrame.size ?? finalSize
                            
                            let radius: CGFloat = displaySize.width / 2.0
                            let maskCornerRadius = isExpanded ? 1.0 : radius
                            let maskFrame = CGRect(origin: CGPoint(x: isExpanded ? 1.0 : (incoming ? 7.0 : 1.0), y: isExpanded ? 0.0 : 1.0), size: isExpanded ? bubbleSize : CGSize(width: radius * 2.0, height: radius * 2.0))
                            animation.animator.updateCornerRadius(layer: strongSelf.maskForeground, cornerRadius: maskCornerRadius, completion: nil)
                            animation.animator.updateFrame(layer: strongSelf.maskForeground, frame: maskFrame, completion: nil)
                            
                            let backdropMaskFrame = CGRect(origin: CGPoint(x: isExpanded ? (incoming ? 8.0 : 2.0) : (incoming ? 8.0 : 2.0), y: isExpanded ? 2.0 : 2.0), size: isExpanded ? CGSize(width: bubbleSize.width - 8.0, height: bubbleSize.height - 3.0) : CGSize(width: radius * 2.0, height: radius * 2.0))
                           
                            let topLeftCornerRadius: CGFloat
                            let topRightCornerRadius: CGFloat
                            let bottomLeftCornerRadius: CGFloat
                            let bottomRightCornerRadius: CGFloat
                            if let bubbleCorners = strongSelf.bubbleBackgroundNode?.currentCorners(bubbleCorners: item.presentationData.chatBubbleCorners) {
                                topLeftCornerRadius = isExpanded ? bubbleCorners.topLeftRadius : radius
                                topRightCornerRadius = isExpanded ? bubbleCorners.topRightRadius : radius
                                bottomLeftCornerRadius = isExpanded ? bubbleCorners.bottomLeftRadius : radius
                                bottomRightCornerRadius = isExpanded ? bubbleCorners.bottomRightRadius : radius
                            } else {
                                let backdropRadius = isExpanded ? item.presentationData.chatBubbleCorners.mainRadius : radius
                                topLeftCornerRadius = backdropRadius
                                topRightCornerRadius = backdropRadius
                                bottomLeftCornerRadius = backdropRadius
                                bottomRightCornerRadius = backdropRadius
                            }
                            
                            strongSelf.backdropMaskForeground.update(
                                size: backdropMaskFrame.size,
                                topLeftCornerRadius: topLeftCornerRadius,
                                topRightCornerRadius: topRightCornerRadius,
                                bottomLeftCornerRadius: bottomLeftCornerRadius,
                                bottomRightCornerRadius: bottomRightCornerRadius,
                                animator: animation.animator
                            )
                            animation.animator.updateFrame(layer: strongSelf.backdropMaskForeground, frame: backdropMaskFrame, completion: nil)
                                                        
                            let videoLayoutData: ChatMessageInstantVideoItemLayoutData = .constrained(left: 0.0, right: 0.0)
                            
                            var videoAnimation = animation
                            var fileAnimation = animation
                            if currentExpanded != isExpanded {
                                videoAnimation = .None
                                fileAnimation = .None
                            }

                            animation.animator.updateFrame(layer: strongSelf.interactiveVideoNode.layer, frame: videoFrame, completion: nil)
                            videoApply(videoLayoutData, videoAnimation)
                            
                            if let fileSize = finalFileSize {
                                strongSelf.interactiveFileNode.frame = CGRect(origin: CGPoint(x: layoutConstants.file.bubbleInsets.left, y: layoutConstants.file.bubbleInsets.top), size: fileSize)
                                finalFileApply?(synchronousLoads, fileAnimation, applyInfo)
                            }
                            
                            if currentExpanded != isExpanded {
                                if isExpanded {
                                    strongSelf.interactiveVideoNode.animateTo(ChatMessageInteractiveInstantVideoNode.AnimateFileNodeDescription(strongSelf.interactiveFileNode), animator: animation.animator)
                                } else {
                                    strongSelf.interactiveVideoNode.animateFrom(ChatMessageInteractiveInstantVideoNode.AnimateFileNodeDescription(strongSelf.interactiveFileNode), animator: animation.animator)
                                }
                            }
                        }
                    })
                })
            })
        }
    }
    
    override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
        return nil
    }
    
    override public func updateHiddenMedia(_ media: [Media]?) -> Bool {
        return false
    }
    
    override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
        self.interactiveVideoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
    }
    
    override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
        self.interactiveVideoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
    }
    
    override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
        self.interactiveVideoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
    }
    
    override public func willUpdateIsExtractedToContextPreview(_ value: Bool) {
        self.interactiveFileNode.willUpdateIsExtractedToContextPreview(value)
    }
    
    override public func updateIsExtractedToContextPreview(_ value: Bool) {
        self.interactiveFileNode.updateIsExtractedToContextPreview(value)
    }
    
    override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
        if !self.interactiveFileNode.isHidden {
            if self.interactiveFileNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveFileNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.dateAndStatusNode.view), with: nil) {
                return .ignore
            }
            if self.interactiveFileNode.hasTapAction(at: self.view.convert(point, to: self.interactiveFileNode.view)) {
                return .ignore
            }
        }
        if !self.interactiveVideoNode.isHidden {
            if self.interactiveVideoNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveVideoNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveVideoNode.dateAndStatusNode.view), with: nil) {
                return .ignore
            }
            if let audioTranscriptionButton = self.interactiveVideoNode.audioTranscriptionButton, let _ = audioTranscriptionButton.hitTest(self.view.convert(point, to: audioTranscriptionButton), with: nil) {
                return .ignore
            }
        }
        return super.tapActionAtPoint(point, gesture: gesture, isEstimating: isEstimating)
    }
    
    override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if self.isExpanded, let result = self.interactiveFileNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.view), with: event) {
            return result
        }
        if !self.isExpanded, let result = self.interactiveVideoNode.hitTest(self.view.convert(point, to: self.interactiveVideoNode.view), with: event) {
            return result
        }
        return super.hitTest(point, with: event)
    }
    
    override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
        if !self.interactiveVideoNode.dateAndStatusNode.isHidden {
            return self.interactiveVideoNode.dateAndStatusNode.reactionView(value: value)
        }
        return nil
    }
    
    override public func targetForStoryTransition(id: StoryId) -> UIView? {
        return self.interactiveVideoNode.targetForStoryTransition(id: id)
    }
    
    override public var disablesClipping: Bool {
        return true
    }
}

private class BubbleMaskLayer: SimpleLayer {
    private class CornerLayer: SimpleLayer {
        private let contentLayer = SimpleLayer()
        
        override init(layer: Any) {
            super.init(layer: layer)
        }
        
        init(cornerMask: CACornerMask) {
            super.init()
            self.masksToBounds = true
            
            self.contentLayer.backgroundColor = UIColor.white.cgColor
            self.contentLayer.masksToBounds = true
            self.contentLayer.maskedCorners = cornerMask
            self.addSublayer(self.contentLayer)
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func update(size: CGSize, cornerRadius: CGFloat, animator: ControlledTransitionAnimator) {
            animator.updateCornerRadius(layer: self.contentLayer, cornerRadius: cornerRadius, completion: nil)
            
            let mask = self.contentLayer.maskedCorners
            var origin = CGPoint()
            if mask == .layerMinXMinYCorner {
                origin = .zero
            } else if mask == .layerMaxXMinYCorner {
                origin = CGPoint(x: -size.width / 2.0, y: 0.0)
            } else if mask == .layerMinXMaxYCorner {
                origin = CGPoint(x: 0.0, y: -size.height / 2.0)
            } else if mask == .layerMaxXMaxYCorner {
                origin = CGPoint(x: -size.width / 2.0, y: -size.height / 2.0)
            }
            animator.updateFrame(layer: self.contentLayer, frame: CGRect(origin: origin, size: size), completion: nil)
        }
    }
    
    private let topLeft = CornerLayer(cornerMask: [.layerMinXMinYCorner])
    private let topRight = CornerLayer(cornerMask: [.layerMaxXMinYCorner])
    private let bottomLeft = CornerLayer(cornerMask: [.layerMinXMaxYCorner])
    private let bottomRight = CornerLayer(cornerMask: [.layerMaxXMaxYCorner])
    
    override init(layer: Any) {
        super.init(layer: layer)
    }
    
    override init() {
        super.init()
        
        self.addSublayer(self.topLeft)
        self.addSublayer(self.topRight)
        self.addSublayer(self.bottomLeft)
        self.addSublayer(self.bottomRight)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func update(
        size: CGSize,
        topLeftCornerRadius: CGFloat,
        topRightCornerRadius: CGFloat,
        bottomLeftCornerRadius: CGFloat,
        bottomRightCornerRadius: CGFloat,
        animator: ControlledTransitionAnimator
    ) {
        var size = CGSize(width: floor(size.width), height: floor(size.height))
        if Int(size.width) % 2 != 0 {
            size.width += 1.0
        }
        if Int(size.height) % 2 != 0 {
            size.height += 1.0
        }
        animator.updateFrame(layer: self.topLeft, frame: CGRect(origin: .zero, size: CGSize(width: size.width / 2.0, height: size.height / 2.0)), completion: nil)
        animator.updateFrame(layer: self.topRight, frame: CGRect(origin: CGPoint(x: size.width / 2.0, y: 0.0), size: CGSize(width: size.width / 2.0, height: size.height / 2.0)), completion: nil)
        animator.updateFrame(layer: self.bottomLeft, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height / 2.0), size: CGSize(width: size.width / 2.0, height: size.height / 2.0)), completion: nil)
        animator.updateFrame(layer: self.bottomRight, frame: CGRect(origin: CGPoint(x: size.width / 2.0, y: size.height / 2.0), size: CGSize(width: size.width / 2.0, height: size.height / 2.0)), completion: nil)
        
        self.topLeft.update(size: size, cornerRadius: topLeftCornerRadius, animator: animator)
        self.topRight.update(size: size, cornerRadius: topRightCornerRadius, animator: animator)
        self.bottomLeft.update(size: size, cornerRadius: bottomLeftCornerRadius, animator: animator)
        self.bottomRight.update(size: size, cornerRadius: bottomRightCornerRadius, animator: animator)
    }
}