import Foundation
import UIKit
import CoreVideo
import CoreImage
import QuartzCore
import Metal
import MetalKit
import simd

@objc public protocol LNHWDMetalViewDelegate: AnyObject {
    @objc(onMetalViewUnavailable)
    func onMetalViewUnavailable()
}

@objc public protocol LNVAPMetalViewDelegate: AnyObject {
    @objc(onMetalViewUnavailable)
    func onMetalViewUnavailable()
}

@objc public protocol LNHWDMP4OpenGLViewDelegate: AnyObject {
    @objc(onViewUnavailableStatus)
    func onViewUnavailableStatus()
}

private final class LNPixelBufferLayerRenderer {
    private let ciContext = CIContext(options: nil)

    func render(pixelBuffer: CVPixelBuffer, into layer: CALayer) {
        let image = CIImage(cvPixelBuffer: pixelBuffer)
        guard let cgImage = ciContext.createCGImage(image, from: image.extent) else { return }
        DispatchQueue.main.async {
            layer.contents = cgImage
        }
    }
}

@available(iOS 13.0, *)
private final class LNHWDMetalCoreRenderer {
    private struct LNColorParameters {
        var matrix: simd_float3x3
        var offset: simd_float2
    }

    private static let kVertexFunctionName = "hwd_vertexShader"
    private static let kFragmentFunctionName = "hwd_yuvFragmentShader"

    private static let verticesByBlendMode: [[[Float]]] = [
        // alpha left
        [
            [-1.0, -1.0, 0.0, 1.0, 0.5, 1.0, 0.0, 1.0],
            [-1.0,  1.0, 0.0, 1.0, 0.5, 0.0, 0.0, 0.0],
            [ 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.5, 1.0],
            [ 1.0,  1.0, 0.0, 1.0, 1.0, 0.0, 0.5, 0.0]
        ],
        // alpha right
        [
            [-1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.5, 1.0],
            [-1.0,  1.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.0],
            [ 1.0, -1.0, 0.0, 1.0, 0.5, 1.0, 1.0, 1.0],
            [ 1.0,  1.0, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0]
        ],
        // alpha top
        [
            [-1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.5],
            [-1.0,  1.0, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0],
            [ 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.5],
            [ 1.0,  1.0, 0.0, 1.0, 1.0, 0.5, 1.0, 0.0]
        ],
        // alpha bottom
        [
            [-1.0, -1.0, 0.0, 1.0, 0.0, 0.5, 0.0, 1.0],
            [-1.0,  1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.5],
            [ 1.0, -1.0, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0],
            [ 1.0,  1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.5]
        ]
    ]

    private static let matrix601Full = simd_float3x3(
        SIMD3<Float>(1.0, 1.0, 1.0),
        SIMD3<Float>(0.0, -0.34413, 1.772),
        SIMD3<Float>(1.402, -0.71414, 0.0)
    )

    private static let matrix709Full = simd_float3x3(
        SIMD3<Float>(1.0, 1.0, 1.0),
        SIMD3<Float>(0.0, -0.18732, 1.8556),
        SIMD3<Float>(1.57481, -0.46813, 0.0)
    )

    private let device: MTLDevice
    private let commandQueue: MTLCommandQueue
    private let pipelineState: MTLRenderPipelineState
    private var textureCache: CVMetalTextureCache?
    private var vertexBuffer: MTLBuffer?
    private var yuvMatrixBuffer: MTLBuffer?
    private var currentColorMatrix = matrix601Full

    init?(metalLayer: CAMetalLayer, blendMode: LNTextureBlendMode) {
        guard let device = MTLCreateSystemDefaultDevice(),
              let queue = device.makeCommandQueue() else {
            return nil
        }

        let shaderLoader = LNVAPMetalShaderFunctionLoader(device: device)
        guard let vertexFunc = shaderLoader.loadFunction(withName: Self.kVertexFunctionName),
              let fragmentFunc = shaderLoader.loadFunction(withName: Self.kFragmentFunctionName) else {
            return nil
        }

        let desc = MTLRenderPipelineDescriptor()
        desc.vertexFunction = vertexFunc
        desc.fragmentFunction = fragmentFunc
        desc.colorAttachments[0].pixelFormat = metalLayer.pixelFormat
        guard let pipelineState = try? device.makeRenderPipelineState(descriptor: desc) else {
            return nil
        }

        self.device = device
        self.commandQueue = queue
        self.pipelineState = pipelineState

        metalLayer.device = device
        metalLayer.framebufferOnly = true

        let cacheStatus = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &textureCache)
        guard cacheStatus == kCVReturnSuccess else { return nil }

        updateBlendMode(blendMode)
        updateYuvMatrixBuffer(matrix: Self.matrix601Full)
    }

    func updateBlendMode(_ blendMode: LNTextureBlendMode) {
        let modeIndex = max(0, min(Int(blendMode.rawValue), Self.verticesByBlendMode.count - 1))
        let flat = Self.verticesByBlendMode[modeIndex].flatMap { $0 }
        vertexBuffer = device.makeBuffer(bytes: flat, length: flat.count * MemoryLayout<Float>.size, options: .storageModeShared)
    }

    func render(pixelBuffer: CVPixelBuffer, metalLayer: CAMetalLayer) {
        guard metalLayer.bounds.width > 0,
              metalLayer.bounds.height > 0,
              let drawable = metalLayer.nextDrawable(),
              let commandBuffer = commandQueue.makeCommandBuffer(),
              let descriptor = currentRenderPassDescriptor(texture: drawable.texture),
              let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor),
              let vertexBuffer,
              let yuvMatrixBuffer else {
            return
        }

        updateYuvMatrixIfNeeded(pixelBuffer: pixelBuffer)
        guard let yTexture = makeTexture(pixelBuffer: pixelBuffer, plane: 0, format: .r8Unorm),
              let uvTexture = makeTexture(pixelBuffer: pixelBuffer, plane: 1, format: .rg8Unorm) else {
            return
        }

        encoder.setRenderPipelineState(pipelineState)
        encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
        encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
        encoder.setFragmentTexture(yTexture, index: 0)
        encoder.setFragmentTexture(uvTexture, index: 1)
        encoder.drawPrimitives(type: MTLPrimitiveType.triangleStrip, vertexStart: 0, vertexCount: 4)
        encoder.endEncoding()

        commandBuffer.present(drawable)
        commandBuffer.commit()
    }

    func dispose() {
        if let textureCache {
            CVMetalTextureCacheFlush(textureCache, 0)
        }
        textureCache = nil
        vertexBuffer = nil
        yuvMatrixBuffer = nil
    }

    private func makeTexture(pixelBuffer: CVPixelBuffer, plane: Int, format: MTLPixelFormat) -> MTLTexture? {
        guard let textureCache else { return nil }
        let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, plane)
        let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane)
        var cvTexture: CVMetalTexture?
        let status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                               textureCache,
                                                               pixelBuffer,
                                                               nil,
                                                               format,
                                                               width,
                                                               height,
                                                               plane,
                                                               &cvTexture)
        guard status == kCVReturnSuccess, let cvTexture else { return nil }
        return CVMetalTextureGetTexture(cvTexture)
    }

    private func currentRenderPassDescriptor(texture: MTLTexture) -> MTLRenderPassDescriptor? {
        let descriptor = MTLRenderPassDescriptor()
        descriptor.colorAttachments[0].texture = texture
        descriptor.colorAttachments[0].loadAction = .clear
        descriptor.colorAttachments[0].storeAction = .store
        descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
        return descriptor
    }

    private func updateYuvMatrixIfNeeded(pixelBuffer: CVPixelBuffer) {
        let matrixAttachment = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, nil)?.takeUnretainedValue()
        let matrix = (matrixAttachment != nil && CFEqual(matrixAttachment, kCVImageBufferYCbCrMatrix_ITU_R_709_2)) ? Self.matrix709Full : Self.matrix601Full
        guard matrix != currentColorMatrix else { return }
        currentColorMatrix = matrix
        updateYuvMatrixBuffer(matrix: matrix)
    }

    private func updateYuvMatrixBuffer(matrix: simd_float3x3) {
        let params = LNColorParameters(matrix: matrix, offset: simd_float2(0.5, 0.5))
        yuvMatrixBuffer = device.makeBuffer(bytes: [params], length: MemoryLayout<LNColorParameters>.stride, options: .storageModeShared)
    }
}

@available(iOS 13.0, *)
private final class LNVAPMetalCoreRenderer {
    private struct LNColorParameters {
        var matrix: simd_float3x3
        var offset: simd_float2
    }

    private struct LNMaskParameters {
        var weightMatrix: simd_float3x3
        var coreSize: Int32
        var texelOffset: Float
    }

    private static let matrix601Full = simd_float3x3(
        SIMD3<Float>(1.0, 1.0, 1.0),
        SIMD3<Float>(0.0, -0.34413, 1.772),
        SIMD3<Float>(1.402, -0.71414, 0.0)
    )

    private static let matrix709Full = simd_float3x3(
        SIMD3<Float>(1.0, 1.0, 1.0),
        SIMD3<Float>(0.0, -0.18732, 1.8556),
        SIMD3<Float>(1.57481, -0.46813, 0.0)
    )

    private let device: MTLDevice
    private let pixelFormat: MTLPixelFormat
    private let commandQueue: MTLCommandQueue
    private let shaderLoader: LNVAPMetalShaderFunctionLoader
    private var textureCache: CVMetalTextureCache?
    private var defaultMainPipelineState: MTLRenderPipelineState?
    private var mainPipelineStateForMask: MTLRenderPipelineState?
    private var mainPipelineStateForMaskBlur: MTLRenderPipelineState?
    private var attachmentPipelineState: MTLRenderPipelineState?

    private var vertexBuffer: MTLBuffer?
    private var yuvMatrixBuffer: MTLBuffer?
    private var maskBlurBuffer: MTLBuffer?
    private var maskTexture: MTLTexture?
    private var currentColorMatrix = matrix601Full

    var commonInfo: LNVAPCommonInfo? {
        didSet {
            updateMainVertexBuffer()
        }
    }

    var maskInfo: LNVAPMaskInfo? {
        didSet {
            updateMainVertexBuffer()
            updateMaskTexture()
        }
    }

    init?(metalLayer: CAMetalLayer) {
        guard let device = MTLCreateSystemDefaultDevice(),
              let queue = device.makeCommandQueue() else {
            return nil
        }

        self.device = device
        self.pixelFormat = metalLayer.pixelFormat
        self.commandQueue = queue
        self.shaderLoader = LNVAPMetalShaderFunctionLoader(device: device)

        metalLayer.device = device
        metalLayer.framebufferOnly = true

        let cacheStatus = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &textureCache)
        guard cacheStatus == kCVReturnSuccess else { return nil }

        updateYuvMatrixBuffer(matrix: Self.matrix601Full)
    }

    func render(pixelBuffer: CVPixelBuffer, metalLayer: CAMetalLayer, mergeInfos: [LNVAPMergedInfo]) {
        guard metalLayer.bounds.width > 0,
              metalLayer.bounds.height > 0,
              let drawable = metalLayer.nextDrawable(),
              let commandBuffer = commandQueue.makeCommandBuffer(),
              let descriptor = currentRenderPassDescriptor(texture: drawable.texture),
              let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
            return
        }

        updateYuvMatrixIfNeeded(pixelBuffer: pixelBuffer)
        guard let yTexture = makeTexture(pixelBuffer: pixelBuffer, plane: 0, format: .r8Unorm),
              let uvTexture = makeTexture(pixelBuffer: pixelBuffer, plane: 1, format: .rg8Unorm) else {
            return
        }

        drawBackground(encoder: encoder, yTexture: yTexture, uvTexture: uvTexture)
        drawMergedAttachments(encoder: encoder, yTexture: yTexture, uvTexture: uvTexture, mergeInfos: mergeInfos)

        encoder.endEncoding()
        commandBuffer.present(drawable)
        commandBuffer.commit()
    }

    func dispose() {
        if let textureCache {
            CVMetalTextureCacheFlush(textureCache, 0)
        }
        textureCache = nil
        vertexBuffer = nil
        yuvMatrixBuffer = nil
        maskBlurBuffer = nil
        maskTexture = nil
        defaultMainPipelineState = nil
        mainPipelineStateForMask = nil
        mainPipelineStateForMaskBlur = nil
        attachmentPipelineState = nil
    }

    private func drawBackground(encoder: MTLRenderCommandEncoder, yTexture: MTLTexture, uvTexture: MTLTexture) {
        guard let vertexBuffer,
              let yuvMatrixBuffer else {
            return
        }

        if let maskInfo,
           let maskTexture {
            if maskInfo.blurLength > 0 {
                guard let pipelineState = mainPipelineStateForMaskBlur ?? makeMaskBlurPipelineState(),
                      let maskBlurBuffer = maskBlurBuffer ?? makeMaskBlurBuffer() else {
                    return
                }
                encoder.setRenderPipelineState(pipelineState)
                encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
                encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
                encoder.setFragmentBuffer(maskBlurBuffer, offset: 0, index: 1)
                encoder.setFragmentTexture(yTexture, index: 0)
                encoder.setFragmentTexture(uvTexture, index: 1)
                encoder.setFragmentTexture(maskTexture, index: 2)
                encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
                return
            }

            guard let pipelineState = mainPipelineStateForMask ?? makeMaskPipelineState() else {
                return
            }
            encoder.setRenderPipelineState(pipelineState)
            encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
            encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
            encoder.setFragmentTexture(yTexture, index: 0)
            encoder.setFragmentTexture(uvTexture, index: 1)
            encoder.setFragmentTexture(maskTexture, index: 2)
            encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
            return
        }

        guard let pipelineState = defaultMainPipelineState ?? makeMainPipelineState() else {
            return
        }
        encoder.setRenderPipelineState(pipelineState)
        encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
        encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
        encoder.setFragmentTexture(yTexture, index: 0)
        encoder.setFragmentTexture(uvTexture, index: 1)
        encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
    }

    private func drawMergedAttachments(encoder: MTLRenderCommandEncoder, yTexture: MTLTexture, uvTexture: MTLTexture, mergeInfos: [LNVAPMergedInfo]) {
        guard !mergeInfos.isEmpty,
              let commonInfo,
              let pipelineState = attachmentPipelineState ?? makeAttachmentPipelineState(),
              let yuvMatrixBuffer else {
            return
        }

        for mergeInfo in mergeInfos {
            guard let sourceTexture = mergeInfo.source?.texture,
                  let colorParamsBuffer = mergeInfo.source?.colorParamsBuffer,
                  let mergeVertexBuffer = mergeInfo.vertexBuffer(containerSize: commonInfo.size,
                                                                 maskContianerSize: commonInfo.videoSize,
                                                                 device: device) else {
                continue
            }
            encoder.setRenderPipelineState(pipelineState)
            encoder.setVertexBuffer(mergeVertexBuffer, offset: 0, index: 0)
            encoder.setFragmentBuffer(yuvMatrixBuffer, offset: 0, index: 0)
            encoder.setFragmentBuffer(colorParamsBuffer, offset: 0, index: 1)
            encoder.setFragmentTexture(yTexture, index: 0)
            encoder.setFragmentTexture(uvTexture, index: 1)
            encoder.setFragmentTexture(sourceTexture, index: 2)
            encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
        }
    }

    private func makeMainPipelineState() -> MTLRenderPipelineState? {
        guard let state = createPipelineState(vertexFunction: LNVAPMetalUtil.vapVertexFunctionName,
                                              fragmentFunction: LNVAPMetalUtil.vapYUVFragmentFunctionName) else {
            return nil
        }
        defaultMainPipelineState = state
        return state
    }

    private func makeMaskPipelineState() -> MTLRenderPipelineState? {
        guard let state = createPipelineState(vertexFunction: LNVAPMetalUtil.vapVertexFunctionName,
                                              fragmentFunction: LNVAPMetalUtil.vapMaskFragmentFunctionName) else {
            return nil
        }
        mainPipelineStateForMask = state
        return state
    }

    private func makeMaskBlurPipelineState() -> MTLRenderPipelineState? {
        guard let state = createPipelineState(vertexFunction: LNVAPMetalUtil.vapVertexFunctionName,
                                              fragmentFunction: LNVAPMetalUtil.vapMaskBlurFragmentFunctionName) else {
            return nil
        }
        mainPipelineStateForMaskBlur = state
        return state
    }

    private func makeAttachmentPipelineState() -> MTLRenderPipelineState? {
        guard let state = createPipelineState(vertexFunction: LNVAPMetalUtil.vapAttachmentVertexFunctionName,
                                              fragmentFunction: LNVAPMetalUtil.vapAttachmentFragmentFunctionName) else {
            return nil
        }
        attachmentPipelineState = state
        return state
    }

    private func createPipelineState(vertexFunction: String, fragmentFunction: String) -> MTLRenderPipelineState? {
        guard let vertexProgram = shaderLoader.loadFunction(withName: vertexFunction),
              let fragmentProgram = shaderLoader.loadFunction(withName: fragmentFunction) else {
            return nil
        }

        let descriptor = MTLRenderPipelineDescriptor()
        descriptor.vertexFunction = vertexProgram
        descriptor.fragmentFunction = fragmentProgram
        descriptor.colorAttachments[0].pixelFormat = pixelFormat
        descriptor.colorAttachments[0].isBlendingEnabled = true
        descriptor.colorAttachments[0].rgbBlendOperation = .add
        descriptor.colorAttachments[0].alphaBlendOperation = .add
        descriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
        descriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
        descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
        descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
        return try? device.makeRenderPipelineState(descriptor: descriptor)
    }

    private func updateMainVertexBuffer() {
        guard let commonInfo else {
            vertexBuffer = nil
            return
        }

        let vertices = LNVAPMetalUtil.vapMTLVerticesIdentity
        let rgbCoordinates = LNVAPMetalUtil.genTextureCoordinates(rect: commonInfo.rgbAreaRect, containerSize: commonInfo.videoSize, reverse: false, degree: 0)
        let alphaCoordinates = LNVAPMetalUtil.genTextureCoordinates(rect: commonInfo.alphaAreaRect, containerSize: commonInfo.videoSize, reverse: false, degree: 0)
        let maskCoordinates: [Float]
        if let maskInfo {
            maskCoordinates = LNVAPMetalUtil.genTextureCoordinates(rect: maskInfo.sampleRect, containerSize: maskInfo.dataSize, reverse: false, degree: 0)
        } else {
            maskCoordinates = [Float](repeating: 0, count: 8)
        }

        var vertexData = [Float](repeating: 0, count: 40)
        var index = 0
        for i in 0..<16 {
            vertexData[index] = vertices[i]
            index += 1
            if i % 4 == 3 {
                let row = i / 4
                vertexData[index] = rgbCoordinates[row * 2]
                index += 1
                vertexData[index] = rgbCoordinates[row * 2 + 1]
                index += 1
                vertexData[index] = alphaCoordinates[row * 2]
                index += 1
                vertexData[index] = alphaCoordinates[row * 2 + 1]
                index += 1
                vertexData[index] = maskCoordinates[row * 2]
                index += 1
                vertexData[index] = maskCoordinates[row * 2 + 1]
                index += 1
            }
        }
        vertexBuffer = device.makeBuffer(bytes: vertexData, length: vertexData.count * MemoryLayout<Float>.size, options: .storageModeShared)
    }

    private func updateMaskTexture() {
        guard let maskInfo else {
            maskTexture = nil
            return
        }
        guard maskInfo.dataSize.width > 0,
              maskInfo.dataSize.height > 0,
              !maskInfo.data.isEmpty else {
            maskTexture = nil
            return
        }
        maskTexture = maskInfo.texture(with: device)
    }

    private func makeMaskBlurBuffer() -> MTLBuffer? {
        let weight = simd_float3x3(
            SIMD3<Float>(0.0625, 0.125, 0.0625),
            SIMD3<Float>(0.125, 0.25, 0.125),
            SIMD3<Float>(0.0625, 0.125, 0.0625)
        )
        let params = LNMaskParameters(weightMatrix: weight, coreSize: 3, texelOffset: 0.01)
        let buffer = device.makeBuffer(bytes: [params], length: MemoryLayout<LNMaskParameters>.stride, options: .storageModeShared)
        maskBlurBuffer = buffer
        return buffer
    }

    private func makeTexture(pixelBuffer: CVPixelBuffer, plane: Int, format: MTLPixelFormat) -> MTLTexture? {
        guard let textureCache else { return nil }
        let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, plane)
        let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, plane)
        var cvTexture: CVMetalTexture?
        let status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                               textureCache,
                                                               pixelBuffer,
                                                               nil,
                                                               format,
                                                               width,
                                                               height,
                                                               plane,
                                                               &cvTexture)
        guard status == kCVReturnSuccess, let cvTexture else { return nil }
        return CVMetalTextureGetTexture(cvTexture)
    }

    private func currentRenderPassDescriptor(texture: MTLTexture) -> MTLRenderPassDescriptor? {
        let descriptor = MTLRenderPassDescriptor()
        descriptor.colorAttachments[0].texture = texture
        descriptor.colorAttachments[0].loadAction = .clear
        descriptor.colorAttachments[0].storeAction = .store
        descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
        return descriptor
    }

    private func updateYuvMatrixIfNeeded(pixelBuffer: CVPixelBuffer) {
        let matrixAttachment = CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, nil)?.takeUnretainedValue()
        let matrix = (matrixAttachment != nil && CFEqual(matrixAttachment, kCVImageBufferYCbCrMatrix_ITU_R_709_2)) ? Self.matrix709Full : Self.matrix601Full
        guard matrix != currentColorMatrix else { return }
        currentColorMatrix = matrix
        updateYuvMatrixBuffer(matrix: matrix)
    }

    private func updateYuvMatrixBuffer(matrix: simd_float3x3) {
        let params = LNColorParameters(matrix: matrix, offset: simd_float2(0.5, 0.5))
        yuvMatrixBuffer = device.makeBuffer(bytes: [params], length: MemoryLayout<LNColorParameters>.stride, options: .storageModeShared)
    }
}

private final class LNMetalUnavailableBridge: NSObject {
    var callback: (() -> Void)?

    @objc func onMetalViewUnavailable() {
        callback?()
    }

    @objc func onViewUnavailableStatus() {
        callback?()
    }
}

@objcMembers
public final class LNHWDMetalRenderer: NSObject {
    public var blendMode: LNTextureBlendMode {
        didSet {
            if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNHWDMetalCoreRenderer {
                swiftRenderer.updateBlendMode(blendMode)
            }
        }
    }

    private let fallback = LNPixelBufferLayerRenderer()
    private let fallbackLayer = CALayer()
    private let swiftRenderer: AnyObject?

    @objc(initWithMetalLayer:blendMode:)
    public init(metalLayer: AnyObject, blendMode: LNTextureBlendMode) {
        self.blendMode = blendMode
        if #available(iOS 13.0, *), let metalLayer = metalLayer as? CAMetalLayer {
            self.swiftRenderer = LNHWDMetalCoreRenderer(metalLayer: metalLayer, blendMode: blendMode)
        } else {
            self.swiftRenderer = nil
        }
        super.init()
    }

    @objc(renderPixelBuffer:metalLayer:)
    public func renderPixelBuffer(_ pixelBuffer: CVPixelBuffer?, metalLayer: AnyObject) {
        if #available(iOS 13.0, *),
           let pixelBuffer,
           let metalLayer = metalLayer as? CAMetalLayer,
           let swiftRenderer = swiftRenderer as? LNHWDMetalCoreRenderer {
            swiftRenderer.render(pixelBuffer: pixelBuffer, metalLayer: metalLayer)
            return
        }
        guard let pixelBuffer else { return }
        let target = (metalLayer as? CALayer) ?? fallbackLayer
        fallback.render(pixelBuffer: pixelBuffer, into: target)
    }

    @objc(dispose)
    public func dispose() {
        if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNHWDMetalCoreRenderer {
            swiftRenderer.dispose()
        }
    }
}

@objcMembers
public final class LNHWDMetalView: UIView {
    public weak var delegate: LNHWDMetalViewDelegate?
    public weak var lnDelegate: LNHWDMetalViewDelegate? {
        get { delegate }
        set { delegate = newValue }
    }
    public var blendMode: LNTextureBlendMode {
        didSet {
            fallbackRenderer.blendMode = blendMode
        }
    }

    private let unavailableBridge = LNMetalUnavailableBridge()
    private var renderLayer: CALayer = CALayer()
    private lazy var fallbackRenderer = LNHWDMetalRenderer(metalLayer: renderLayer, blendMode: blendMode)

    @objc(initWithFrame:blendMode:)
    public init(frame: CGRect, blendMode: LNTextureBlendMode) {
        self.blendMode = blendMode
        super.init(frame: frame)
        setupRenderingLayer(frame: frame)
    }

    public required init?(coder: NSCoder) {
        self.blendMode = .alphaLeft
        super.init(coder: coder)
        setupRenderingLayer(frame: bounds)
    }

    private func setupRenderingLayer(frame: CGRect) {
        unavailableBridge.callback = { [weak self] in
            self?.delegate?.onMetalViewUnavailable()
        }

        if #available(iOS 13.0, *) {
            let metalLayer = CAMetalLayer()
            metalLayer.pixelFormat = .bgra8Unorm
            renderLayer = metalLayer
        } else {
            renderLayer = CALayer()
        }

        renderLayer.frame = frame
        renderLayer.contentsScale = UIScreen.main.scale
        renderLayer.contentsGravity = .resizeAspect
        layer.addSublayer(renderLayer)
    }

    public override func layoutSubviews() {
        super.layoutSubviews()
        renderLayer.frame = bounds
    }

    @objc(display:)
    public func display(_ pixelBuffer: CVPixelBuffer?) {
        fallbackRenderer.renderPixelBuffer(pixelBuffer, metalLayer: renderLayer)
    }

    @objc(dispose)
    public func dispose() {
        fallbackRenderer.dispose()
        renderLayer.contents = nil
    }
}

@objcMembers
public final class LNVAPMetalRenderer: NSObject {
    public var commonInfo: LNVAPCommonInfo? {
        didSet {
            if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNVAPMetalCoreRenderer {
                swiftRenderer.commonInfo = commonInfo
            }
        }
    }

    public var maskInfo: LNVAPMaskInfo? {
        didSet {
            if let maskInfo,
               (maskInfo.data.isEmpty || maskInfo.dataSize.width <= 0 || maskInfo.dataSize.height <= 0) {
                LNVAPLogger.log(level: 2,
                                file: #file,
                                line: #line,
                                func: #function,
                                module: "Render",
                                message: "setMaskInfo ignored: invalid mask data or size")
                return
            }
            if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNVAPMetalCoreRenderer {
                swiftRenderer.maskInfo = maskInfo
            }
        }
    }

    private let fallback = LNPixelBufferLayerRenderer()
    private let fallbackLayer = CALayer()
    private let swiftRenderer: AnyObject?

    @objc(initWithMetalLayer:)
    public init(metalLayer: AnyObject) {
        if #available(iOS 13.0, *), let metalLayer = metalLayer as? CAMetalLayer {
            self.swiftRenderer = LNVAPMetalCoreRenderer(metalLayer: metalLayer)
        } else {
            self.swiftRenderer = nil
        }
        super.init()
    }

    @objc(renderPixelBuffer:metalLayer:mergeInfos:)
    public func renderPixelBuffer(_ pixelBuffer: CVPixelBuffer?, metalLayer: AnyObject, mergeInfos: [LNVAPMergedInfo]) {
        if #available(iOS 13.0, *),
           let pixelBuffer,
           let metalLayer = metalLayer as? CAMetalLayer,
           let swiftRenderer = swiftRenderer as? LNVAPMetalCoreRenderer {
            swiftRenderer.render(pixelBuffer: pixelBuffer, metalLayer: metalLayer, mergeInfos: mergeInfos)
            return
        }

        guard let pixelBuffer else { return }
        let target = (metalLayer as? CALayer) ?? fallbackLayer
        fallback.render(pixelBuffer: pixelBuffer, into: target)
    }

    @objc(dispose)
    public func dispose() {
        if #available(iOS 13.0, *), let swiftRenderer = swiftRenderer as? LNVAPMetalCoreRenderer {
            swiftRenderer.dispose()
        }
    }
}

@objcMembers
public final class LNVAPMetalView: UIView {
    public weak var delegate: LNVAPMetalViewDelegate?
    public weak var lnDelegate: LNVAPMetalViewDelegate? {
        get { delegate }
        set { delegate = newValue }
    }
    public var commonInfo: LNVAPCommonInfo?
    public var maskInfo: LNVAPMaskInfo?
    private var drawableSizeShouldUpdate = true

    private let unavailableBridge = LNMetalUnavailableBridge()
    private var renderLayer: CALayer = CALayer()
    private lazy var fallbackRenderer = LNVAPMetalRenderer(metalLayer: renderLayer)

    public override init(frame: CGRect) {
        super.init(frame: frame)
        setupRenderingLayer(frame: frame)
    }

    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupRenderingLayer(frame: bounds)
    }

    private func setupRenderingLayer(frame: CGRect) {
        unavailableBridge.callback = { [weak self] in
            self?.delegate?.onMetalViewUnavailable()
        }

        if #available(iOS 13.0, *) {
            let metalLayer = CAMetalLayer()
            metalLayer.pixelFormat = .bgra8Unorm
            renderLayer = metalLayer
        } else {
            renderLayer = CALayer()
        }

        renderLayer.frame = frame
        renderLayer.contentsScale = UIScreen.main.scale
        renderLayer.contentsGravity = .resizeAspect
        layer.addSublayer(renderLayer)
    }

    public override func layoutSubviews() {
        super.layoutSubviews()
        renderLayer.frame = bounds
        drawableSizeShouldUpdate = true
    }

    public override func didMoveToWindow() {
        super.didMoveToWindow()
        drawableSizeShouldUpdate = true
    }

    @objc(display:mergeInfos:)
    public func display(_ pixelBuffer: CVPixelBuffer?, mergeInfos: [LNVAPMergedInfo]) {
        if window == nil {
            delegate?.onMetalViewUnavailable()
            return
        }
        if drawableSizeShouldUpdate {
            if #available(iOS 13.0, *), let metalLayer = renderLayer as? CAMetalLayer {
                let nativeScale = UIScreen.main.nativeScale
                metalLayer.drawableSize = CGSize(width: bounds.width * nativeScale, height: bounds.height * nativeScale)
            }
            drawableSizeShouldUpdate = false
        }
        fallbackRenderer.commonInfo = commonInfo
        fallbackRenderer.maskInfo = maskInfo
        fallbackRenderer.renderPixelBuffer(pixelBuffer, metalLayer: renderLayer, mergeInfos: mergeInfos)
    }

    @objc(dispose)
    public func dispose() {
        fallbackRenderer.dispose()
        renderLayer.contents = nil
    }
}

@objcMembers
public final class LNHWDMP4OpenGLView: UIView {
    public weak var displayDelegate: LNHWDMP4OpenGLViewDelegate?
    public var glContext: AnyObject?

    public var blendMode: LNTextureBlendMode = .alphaLeft {
        didSet {
            fallbackRenderer.blendMode = blendMode
        }
    }

    public var pause: Bool = false
    private var renderLayer: CALayer = CALayer()
    private lazy var fallbackRenderer = LNHWDMetalRenderer(metalLayer: renderLayer, blendMode: blendMode)

    public override init(frame: CGRect) {
        super.init(frame: frame)
        setupRenderingLayer(frame: frame)
    }

    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupRenderingLayer(frame: bounds)
    }

    private func setupRenderingLayer(frame: CGRect) {
        if #available(iOS 13.0, *) {
            let metalLayer = CAMetalLayer()
            metalLayer.pixelFormat = .bgra8Unorm
            renderLayer = metalLayer
        } else {
            renderLayer = CALayer()
        }

        renderLayer.frame = frame
        renderLayer.contentsScale = UIScreen.main.scale
        renderLayer.contentsGravity = .resizeAspect
        layer.addSublayer(renderLayer)
    }

    public override func layoutSubviews() {
        super.layoutSubviews()
        renderLayer.frame = bounds
    }

    @objc(setupGL)
    public func setupGL() {
        renderLayer.frame = bounds
    }

    @objc(displayPixelBuffer:)
    public func displayPixelBuffer(_ pixelBuffer: CVPixelBuffer?) {
        if window == nil {
            displayDelegate?.onViewUnavailableStatus()
            return
        }
        guard !pause, let pixelBuffer else { return }
        fallbackRenderer.blendMode = blendMode
        fallbackRenderer.renderPixelBuffer(pixelBuffer, metalLayer: renderLayer)
    }

    @objc(dispose)
    public func dispose() {
        fallbackRenderer.dispose()
        renderLayer.contents = nil
    }

    @objc(updateBackingSize)
    public func updateBackingSize() {
        renderLayer.frame = bounds
    }
}
