                 return
             }
             
-            //  there is no reject event, we can just say this call has ended
-            statusTextView.text = VectorL10n.eventFormatterCallHasEnded
+            //  check events include an answer event
+            if !events.contains(where: { $0.eventType == .callAnswer }) {
+                configureForUnansweredCall()
+                return
+            }
+            
+            //  check events include a hangup event
+            if let hangupEvent = events.first(where: { $0.eventType == .callHangup }) {
+                configureForHangupCall(withEvent: hangupEvent)
+                return
+            }
+            
+            //  there is no reject or hangup event, we can just say this call has ended
+            viewState = .ended
+            statusText = VectorL10n.eventFormatterCallHasEnded(callDurationString)
             return
         }
         
         configure(withCall: call)
     }
     
-    override func prepareForReuse() {
-        statusTextView.isSelectable = false
-        statusTextView.text = nil
-        statusTextView.attributedText = nil
-        
-        super.prepareForReuse()
+    private func callDuration(from events: [MXEvent]) -> TimeInterval {
+        guard let startDate = events.first(where: { $0.eventType == .callAnswer })?.originServerTs else {
+            //  never started
+            return 0
+        }
+        guard let endDate = events.first(where: { $0.eventType == .callHangup })?.originServerTs
+                ?? events.first(where: { $0.eventType == .callReject })?.originServerTs else {
+            //  not ended yet, compute the diff from now
+            return (NSTimeIntervalSince1970 - TimeInterval(startDate))/MSEC_PER_SEC
+        }
+        
+        //  ended, compute the diff between two dates
+        return TimeInterval(endDate - startDate)/MSEC_PER_SEC
+    }
+    
+    private func readableCallDuration(from events: [MXEvent]) -> String {
+        let duration = callDuration(from: events)
+        
+        if duration <= 0 {
+            return ""
+        }
+        
+        return RoomDirectCallStatusBubbleCell.callDurationFormatter.string(from: duration) ?? ""
     }
     
 }
-
-//  MARK: - UITextViewDelegate
-
-extension RoomDirectCallStatusBubbleCell {
-    
-    override func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
-        if URL == Constants.statusCallBackURL && interaction == .invokeDefaultAction {
-            let userInfo: [AnyHashable: Any]?
-            
-            guard let bubbleCellData = bubbleData as? RoomBubbleCellData else {
-                return false
-            }
-            let events = bubbleCellData.allLinkedEvents()
-            if let callInviteEvent = events.first(where: { $0.eventType == .callInvite }) {
-                userInfo = [kMXKRoomBubbleCellEventKey: callInviteEvent]
-            } else {
-                userInfo = nil
-            }
-            
-            self.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellCallBackButtonPressed, userInfo: userInfo)
-            return true
-        }
-        return false
-    }
-    
-}


import UIKit

class RoomDirectCallStatusBubbleCell: RoomBaseCallBubbleCell {
    
    private enum Constants {
        static let statusTextFontSize: CGFloat = 14
        static let statusTextInsets: UIEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 12, right: 8)
        // swiftlint:disable force_unwrapping
        static let statusCallBackURL: URL = URL(string: "element://call")!
        // swiftlint:enable force_unwrapping
    }
    
    private lazy var statusTextView: UITextView = {
        let textView = UITextView()
        textView.font = .systemFont(ofSize: Constants.statusTextFontSize)
        textView.backgroundColor = .clear
        textView.textColor = ThemeService.shared().theme.noticeSecondaryColor
        textView.linkTextAttributes = [
            .font: UIFont.systemFont(ofSize: Constants.statusTextFontSize),
            .foregroundColor: ThemeService.shared().theme.tintColor
        ]
        textView.textAlignment = .center
        textView.contentInset = .zero
        textView.isEditable = false
        textView.isSelectable = false
        textView.isScrollEnabled = false
        textView.scrollsToTop = false
        textView.textContainerInset = Constants.statusTextInsets
        textView.textContainer.lineFragmentPadding = 0
        textView.delegate = self
        return textView
    }()
    
    override var bottomContentView: UIView? {
        return statusTextView
    }
    
    override func update(theme: Theme) {
        super.update(theme: theme)
        statusTextView.textColor = theme.noticeSecondaryColor
        statusTextView.linkTextAttributes = [
            .font: UIFont.systemFont(ofSize: Constants.statusTextFontSize),
            .foregroundColor: theme.tintColor
        ]
    }
    
    private func configure(withCall call: MXCall) {

        }
        
        if isMyReject {
            viewState = .declined
            statusText = VectorL10n.eventFormatterCallYouDeclined
        } else {
            viewState = .ended
            statusText = VectorL10n.eventFormatterCallHasEnded(callDurationString)
        }
    }
    
    private func configureForHangupCall(withEvent event: MXEvent) {
        guard let hangupEventContent = MXCallHangupEventContent(fromJSON: event.content) else {
            viewState = .ended
            statusText = VectorL10n.eventFormatterCallHasEnded(callDurationString)
            return
        }
        
        switch hangupEventContent.reasonType {
        case .userHangup:
            viewState = .ended
            statusText = VectorL10n.eventFormatterCallHasEnded(callDurationString)
        default:
            viewState = .failed
            statusText = VectorL10n.eventFormatterCallConnectionFailed
        }
    }
    
    private func configureForUnansweredCall() {
        if isIncoming {
            //  missed call
            viewState = .missed
            statusText = VectorL10n.eventFormatterCallYouMissed
        } else {
            //  outgoing unanswered call
            viewState = .ended
            statusText = VectorL10n.eventFormatterCallHasEnded(callDurationString)
        }
    }
    
    //  MARK: - Actions
    
    @objc
    private func callBackAction(_ sender: CallTileActionButton) {
        self.delegate?.cell(self,
                            didRecognizeAction: kMXKRoomBubbleCellCallBackButtonPressed,
                            userInfo: actionUserInfo)
    }
    
    @objc
    private func declineCallAction(_ sender: CallTileActionButton) {
        self.delegate?.cell(self,
                            didRecognizeAction: kMXKRoomBubbleCellCallDeclineButtonPressed,
                            userInfo: actionUserInfo)
    }
    
    @objc
    private func answerCallAction(_ sender: CallTileActionButton) {
        self.delegate?.cell(self,
                            didRecognizeAction: kMXKRoomBubbleCellCallAnswerButtonPressed,
                            userInfo: actionUserInfo)
    }
    
    //  MARK: - MXKCellRendering
