//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalRingRTC
import SignalServiceKit
import WebRTC

class GroupCallRemoteVideoManager {
    private let callServiceState: CallServiceState

    init(callServiceState: CallServiceState) {
        self.callServiceState = callServiceState
    }

    private var currentRingRtcCall: SignalRingRTC.GroupCall? {
        switch callServiceState.currentCall?.mode {
        case nil:
            return nil
        case .individual:
            return nil
        case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
            return call.ringRtcCall
        }
    }

    // MARK: - Remote Video Views
    private var videoViews = [UInt32: [CallMemberVisualContext: GroupCallRemoteVideoView]]()

    func remoteVideoView(for device: RemoteDeviceState, context: CallMemberVisualContext) -> GroupCallRemoteVideoView {
        AssertIsOnMainThread()

        var currentVideoViewsDevice = videoViews[device.demuxId] ?? [:]

        if let current = currentVideoViewsDevice[context] { return current }

        let videoView = GroupCallRemoteVideoView(demuxId: device.demuxId)
        videoView.sizeDelegate = self
        videoView.isGroupCall = true

        if context == .speaker { videoView.isFullScreen = true }

        currentVideoViewsDevice[context] = videoView
        videoViews[device.demuxId] = currentVideoViewsDevice

        return videoView
    }

    private func destroyRemoteVideoView(for demuxId: UInt32) {
        AssertIsOnMainThread()

        videoViews[demuxId]?.forEach { $0.value.removeFromSuperview() }
        videoViews[demuxId] = nil
    }

    private var updateVideoRequestsDebounceTimer: Timer?
    private func updateVideoRequests() {
        updateVideoRequestsDebounceTimer?.invalidate()
        updateVideoRequestsDebounceTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false, block: { [weak self] _ in
            AssertIsOnMainThread()
            guard let self = self else { return }
            guard let groupCall = self.currentRingRtcCall else { return }

            var activeSpeakerHeight: UInt16 = 0

            let videoRequests: [VideoRequest] = groupCall.remoteDeviceStates.map { demuxId, _ in
                guard
                    let renderingVideoViews = self.videoViews[demuxId]?.filter({ $0.value.isRenderingVideo }),
                    !renderingVideoViews.isEmpty
                else {
                    return VideoRequest(demuxId: demuxId, width: 0, height: 0, framerate: nil)
                }

                if let activeSpeakerVideoView = renderingVideoViews[.speaker] {
                    activeSpeakerHeight = max(activeSpeakerHeight, UInt16(activeSpeakerVideoView.currentSize.height))
                }

                let size = renderingVideoViews.reduce(CGSize.zero) { partialResult, element in
                    partialResult.max(element.value.currentSize)
                }

                return VideoRequest(
                    demuxId: demuxId,
                    width: UInt16(size.width),
                    height: UInt16(size.height),
                    framerate: size.height <= GroupCallVideoOverflow.itemHeight ? 15 : 30
                )
            }

            groupCall.updateVideoRequests(resolutions: videoRequests, activeSpeakerHeight: activeSpeakerHeight)
        })
    }
}

extension GroupCallRemoteVideoManager: GroupCallRemoteVideoViewSizeDelegate {
    func groupCallRemoteVideoViewDidChangeSize(remoteVideoView: GroupCallRemoteVideoView) {
        AssertIsOnMainThread()
        updateVideoRequests()
    }

    func groupCallRemoteVideoViewDidChangeSuperview(remoteVideoView: GroupCallRemoteVideoView) {
        AssertIsOnMainThread()
        guard let device = currentRingRtcCall?.remoteDeviceStates[remoteVideoView.demuxId] else { return }
        remoteVideoView.configure(for: device)
        updateVideoRequests()
    }
}

extension GroupCallRemoteVideoManager: CallServiceStateObserver {
    func didUpdateCall(from oldValue: SignalCall?, to newValue: SignalCall?) {
        videoViews.forEach { self.destroyRemoteVideoView(for: $0.key) }
        switch oldValue?.mode {
        case nil, .individual:
            break
        case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
            call.removeObserver(self)
        }
        switch newValue?.mode {
        case nil, .individual:
            break
        case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
            call.addObserverAndSyncState(self)
        }
    }
}

extension GroupCallRemoteVideoManager: GroupCallObserver {
    func groupCallRemoteDeviceStatesChanged(_ call: GroupCall) {
        for (demuxId, videoViews) in videoViews {
            guard let device = call.ringRtcCall.remoteDeviceStates[demuxId] else {
                destroyRemoteVideoView(for: demuxId)
                continue
            }
            videoViews.values.forEach { $0.configure(for: device) }
        }
    }

    func groupCallEnded(_ call: GroupCall, reason: GroupCallEndReason) {
        videoViews.keys.forEach { destroyRemoteVideoView(for: $0) }
    }
}

private protocol GroupCallRemoteVideoViewSizeDelegate: AnyObject {
    func groupCallRemoteVideoViewDidChangeSize(remoteVideoView: GroupCallRemoteVideoView)
    func groupCallRemoteVideoViewDidChangeSuperview(remoteVideoView: GroupCallRemoteVideoView)
}

class GroupCallRemoteVideoView: UIView {
    fileprivate weak var sizeDelegate: GroupCallRemoteVideoViewSizeDelegate?

    fileprivate private(set) var currentSize: CGSize = .zero {
        didSet {
            guard oldValue != currentSize else { return }
            remoteVideoView.frame = CGRect(origin: .zero, size: currentSize)
            sizeDelegate?.groupCallRemoteVideoViewDidChangeSize(remoteVideoView: self)
        }
    }

    // We cannot subclass this, for some unknown reason WebRTC
    // will not render frames properly if we try to.
    private let remoteVideoView = RemoteVideoView()

    private weak var videoTrack: RTCVideoTrack? {
        didSet {
            guard oldValue != videoTrack else { return }
            oldValue?.remove(remoteVideoView)
            videoTrack?.add(remoteVideoView)
        }
    }

    override var frame: CGRect {
        didSet { currentSize = frame.size }
    }

    override var bounds: CGRect {
        didSet { currentSize = bounds.size }
    }

    override func didMoveToSuperview() {
        sizeDelegate?.groupCallRemoteVideoViewDidChangeSuperview(remoteVideoView: self)
    }

    var isGroupCall: Bool {
        get { remoteVideoView.isGroupCall }
        set { remoteVideoView.isGroupCall = newValue }
    }

    var isFullScreen: Bool {
        get { remoteVideoView.isFullScreen }
        set { remoteVideoView.isFullScreen = newValue }
    }

    var isScreenShare: Bool {
        get { remoteVideoView.isScreenShare }
        set { remoteVideoView.isScreenShare = newValue }
    }

    var isRenderingVideo: Bool { videoTrack != nil }

    fileprivate let demuxId: UInt32
    fileprivate init(demuxId: UInt32) {
        self.demuxId = demuxId
        super.init(frame: .zero)
        addSubview(remoteVideoView)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    deinit { videoTrack = nil }

    func configure(for device: RemoteDeviceState) {
        guard device.demuxId == demuxId else {
            return owsFailDebug("Tried to configure with incorrect device")
        }

        videoTrack = superview == nil ? nil : device.videoTrack
    }
}
