         fatalError("init(coder:) has not been implemented")
     }
 
-    // MARK: - Methods
-
-    // MARK: - Layout Attributes
-
-    open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
-
-        guard let attributesArray = super.layoutAttributesForElements(in: rect) as? [MessagesCollectionViewLayoutAttributes] else { return nil }
-
-        attributesArray.forEach { attributes in
-            if attributes.representedElementCategory == UICollectionElementCategory.cell {
-                configure(attributes: attributes)
-            }
-        }
-
-        return attributesArray
-    }
-
-    open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
-
-        guard let attributes = super.layoutAttributesForItem(at: indexPath) as? MessagesCollectionViewLayoutAttributes else { return nil }
-
-        if attributes.representedElementCategory == UICollectionElementCategory.cell {
-            configure(attributes: attributes)
-        }
-
-        return attributes
-
-    }
-
-    private func configure(attributes: MessagesCollectionViewLayoutAttributes) {
-
-        let intermediateAttributes = messageIntermediateLayoutAttributes(for: attributes.indexPath)
-
-        let messageOrigin = messageContainerOrigin(for: intermediateAttributes, and: attributes.frame)
-        attributes.messageContainerFrame = CGRect(origin: messageOrigin, size: intermediateAttributes.messageContainerSize)
-
-        let avaOrigin = avatarOrigin(for: intermediateAttributes, and: attributes.frame)
-        attributes.avatarFrame = CGRect(origin: avaOrigin, size: intermediateAttributes.avatarSize)
-
-        let topLabelOrigin = cellTopLabelOrigin(for: intermediateAttributes, and: attributes.frame)
-        attributes.cellTopLabelFrame = CGRect(origin: topLabelOrigin, size: intermediateAttributes.cellTopLabelSize)
-
-        let bottomLabelOrigin = cellBottomLabelOrigin(for: intermediateAttributes, and: attributes.frame)
-        attributes.cellBottomLabelFrame = CGRect(origin: bottomLabelOrigin, size: intermediateAttributes.cellBottomLabelSize)
-
-        switch intermediateAttributes.message.data {
-        case .emoji:
-            attributes.messageLabelFont = emojiLabelFont
-        default:
-            attributes.messageLabelFont = messageLabelFont
-        }
-
-        attributes.avatarHorizontalAlignment = intermediateAttributes.avatarHorizontal
-        attributes.messagePadding = intermediateAttributes.messageContainerPadding
-        attributes.messageLabelInsets = intermediateAttributes.messageLabelInsets
-        attributes.cellTopLabelInsets = intermediateAttributes.cellTopLabelAlignment.insets
-        attributes.cellBottomLabelInsets = intermediateAttributes.cellBottomLabelAlignment.insets
-
-    }
-
-    // MARK: - Invalidation Context
-
+    // MARK: - Methods [Public]
+    
+    /// Removes the cached layout information for a given `MessageType` using the `messageId`.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` whose cached layout information is to be removed.
+    public func removeCachedAttributes(for message: MessageType) {
+        intermediateAttributesCache.removeValue(forKey: message.messageId)
+    }
+    
+    /// Removes the cached layout information for all `MessageType`s.
+    public func removeAllCachedAttributes() {
+        intermediateAttributesCache.removeAll()
+    }
+    
     open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
         if collectionView?.bounds.width != newBounds.width {
             intermediateAttributesCache = [:]

import UIKit
import AVFoundation

open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {

    // MARK: - Properties

    open var messageLabelFont: UIFont {
        didSet {
            emojiLabelFont = messageLabelFont.withSize(2 * messageLabelFont.pointSize)
        }
    }

    private var emojiLabelFont: UIFont

    open var avatarAlwaysLeading: Bool {
        willSet {
            if newValue {

            let height = layoutDelegate.heightForLocation(message: message, at: indexPath, with: maxWidth, in: messagesCollectionView)
            messageContainerSize = CGSize(width: width, height: height)
        }
        
        return messageContainerSize
        
    }
    
}

// MARK: - Cell Bottom Label Calculations  [ H - K ]

private extension MessagesCollectionViewFlowLayout {
    
    // H
    
    /// Returns the attributed text for the cell's bottom label.
    ///
    /// - Parameters:
    ///   - attributes: The `MessageIntermediateLayoutAttributes` containing the `MessageType` object.
    func cellBottomLabelText(for attributes: MessageIntermediateLayoutAttributes) -> NSAttributedString? {
        return messagesDataSource.cellBottomLabelAttributedText(for: attributes.message, at: attributes.indexPath)
    }
    
    // I
    
    /// Returns the alignment of the cell's bottom label.
    ///
    /// - Parameters:
    ///   - attributes: The `MessageIntermediateLayoutAttributes` containing the `MessageType` object.
    func cellBottomLabelAlignment(for attributes: MessageIntermediateLayoutAttributes) -> LabelAlignment {
        return messagesLayoutDelegate.cellBottomLabelAlignment(for: attributes.message, at: attributes.indexPath, in: messagesCollectionView)
    }
    
    // J
    
    /// Returns the max available width for the cell's bottom label considering the specified layout information.
    ///
    /// - Parameters:
    ///   - attributes: The `MessageIntermediateLayoutAttributes` to consider when calculating the max width.
    func cellBottomLabelMaxWidth(for attributes: MessageIntermediateLayoutAttributes) -> CGFloat {
        
        guard attributes.cellBottomLabelText != nil else { return 0 }
        
        let labelHorizontal = attributes.cellBottomLabelAlignment
        let avatarHorizontal = attributes.avatarHorizontal
        let avatarVertical = attributes.avatarVertical
        let avatarWidth = attributes.avatarSize.width
        
        switch (labelHorizontal, avatarHorizontal, avatarVertical) {
            
        case (.cellLeading, .cellTrailing, .cellBottom), (.cellTrailing, .cellLeading, .cellBottom):
            return itemWidth - avatarWidth - attributes.cellBottomLabelHorizontalInsets
            
        case (.cellLeading, _, _), (.cellTrailing, _, _):
            return itemWidth - attributes.cellBottomLabelHorizontalInsets
            
        case (.cellCenter, .cellLeading, .cellBottom), (.cellCenter, .cellTrailing, .cellBottom):
            return itemWidth - (avatarWidth * 2) - attributes.cellBottomLabelHorizontalInsets
            
        case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _):
            return itemWidth - attributes.cellBottomLabelHorizontalInsets
            
        case (.messageTrailing, .cellLeading, .cellBottom), (.messageLeading, .cellTrailing, .cellBottom):
            return attributes.messageContainerSize.width - attributes.cellBottomLabelHorizontalInsets
            
        case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _):
            return itemWidth - avatarWidth - attributes.cellBottomLabelHorizontalInsets
            
        case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _):
            return attributes.messageContainerSize.width + avatarWidth - attributes.cellBottomLabelHorizontalInsets
        }
        
    }
    
    // K
    
    /// Returns the size of the cell's bottom label considering the specified layout information.
    ///
    /// - Parameters:
    ///   - attributes: The `MessageIntermediateLayoutAttributes` to consider when calculating label's size.
    func cellBottomLabelSize(for attributes: MessageIntermediateLayoutAttributes) -> CGSize {
        guard let bottomLabelText = attributes.cellBottomLabelText else { return .zero }
        
        var bottomLabelSize = labelSize(for: bottomLabelText, considering: attributes.cellBottomLabelMaxWidth)
        bottomLabelSize.width += attributes.cellBottomLabelHorizontalInsets
        bottomLabelSize.height += attributes.cellBottomLabelVerticalInsets
        
        return bottomLabelSize
        
    }

}

// MARK: - Cell Top Label Size Calculations [ L - O ]

private extension MessagesCollectionViewFlowLayout {
    
    // L
    
    /// Returns the attributed text for the cell's top label.
    ///
    /// - Parameters:
    ///   - attributes: The `MessageIntermediateLayoutAttributes` containing the `MessageType` object.
    func cellTopLabelText(for attributes: MessageIntermediateLayoutAttributes) -> NSAttributedString? {
        return messagesDataSource.cellTopLabelAttributedText(for: attributes.message, at: attributes.indexPath)
    }
    
    // M
    
    /// Returns the alignment of the cell's top label.
    ///
    /// - Parameters:
    ///   - attributes: The `MessageIntermediateLayoutAttributes` containing the `MessageType` object.
    func cellTopLabelAlignment(for attributes: MessageIntermediateLayoutAttributes) -> LabelAlignment {
        return messagesLayoutDelegate.cellTopLabelAlignment(for: attributes.message, at: attributes.indexPath, in: messagesCollectionView)
    }
    
    // N
    
    /// Returns the max available width for the cell's top label considering the specified layout information.
    ///
    /// - Parameters:
    ///   - attributes: The `MessageIntermediateLayoutAttributes` to consider when calculating the max width.
    func cellTopLabelMaxWidth(for attributes: MessageIntermediateLayoutAttributes) -> CGFloat {
        
        guard attributes.cellTopLabelText != nil else { return 0 }
        
        let labelHorizontal = attributes.cellTopLabelAlignment
        let avatarHorizontal = attributes.avatarHorizontal
        let avatarVertical = attributes.avatarVertical
        let avatarWidth = attributes.avatarSize.width
        
        switch (labelHorizontal, avatarHorizontal, avatarVertical) {
            
        case (.cellLeading, .cellTrailing, .cellTop), (.cellTrailing, .cellLeading, .cellTop):
            return itemWidth - avatarWidth - attributes.cellTopLabelHorizontalInsets
            
        case (.cellLeading, _, _), (.cellTrailing, _, _):
            return itemWidth - attributes.cellTopLabelHorizontalInsets
            
        case (.cellCenter, .cellLeading, .cellTop), (.cellCenter, .cellTrailing, .cellTop):
            return itemWidth - (avatarWidth * 2) - attributes.cellTopLabelHorizontalInsets
            
        case (.cellCenter, .cellLeading, _), (.cellCenter, .cellTrailing, _):
            return itemWidth - attributes.cellTopLabelHorizontalInsets
            
        case (.messageTrailing, .cellLeading, .cellTop), (.messageLeading, .cellTrailing, .cellTop):
            return attributes.messageContainerSize.width - attributes.cellTopLabelHorizontalInsets
            
        case (.messageLeading, .cellLeading, _), (.messageTrailing, .cellTrailing, _):
            return itemWidth - avatarWidth - attributes.cellTopLabelHorizontalInsets
            
        case (.messageLeading, .cellTrailing, _), (.messageTrailing, .cellLeading, _):
            return attributes.messageContainerSize.width + avatarWidth - attributes.cellTopLabelHorizontalInsets
        }
        
    }
    
    // O
    
    /// Returns the size of the cell's top label considering the specified layout information.
    ///
    /// - Parameters:
    ///   - attributes: The `MessageIntermediateLayoutAttributes` to consider when calculating label's size.
    func cellTopLabelSize(for attributes: MessageIntermediateLayoutAttributes) -> CGSize {
        guard let topLabelText = attributes.cellTopLabelText else { return .zero }
        
        var topLabelSize = labelSize(for: topLabelText, considering: attributes.cellTopLabelMaxWidth)
        topLabelSize.width += attributes.cellTopLabelHorizontalInsets
        topLabelSize.height += attributes.cellTopLabelVerticalInsets
        
        return topLabelSize
    }
    
}

// MARK: - Cell Sizing

private extension MessagesCollectionViewFlowLayout {
    
    // P
    
    /// The height of a `MessageCollectionViewCell`.
    ///
    /// - Parameters:
    ///   - attributes: The `MessageIntermediateLayoutAttributes` to use to determine the height of the cell.
    private func cellHeight(for attributes: MessageIntermediateLayoutAttributes) -> CGFloat {
        
        var cellHeight: CGFloat = 0
        
        switch attributes.avatarVertical {
        case .cellTop:
            cellHeight += max(attributes.avatarSize.height, attributes.cellTopLabelSize.height)
