import Metal
import MetalKit

public protocol OfflineMultiLayerRenderDelegate: AnyObject {
    func performPreRenderActionOnEncoder(_ encoder: MTLRenderCommandEncoder, renderable: Renderable)
    func finishedRenderingOffline(_ render: MTLTexture)
}

public class OfflineMultiLayerRender {

    public var delegate: OfflineMultiLayerRenderDelegate?
    public var render: MTLTexture?
    
    public var renderPassDescriptor: MTLRenderPassDescriptor?
    private var commandBuffer: MTLCommandBuffer?
    private var renderEncoder: MTLRenderCommandEncoder?
    private var temporaryTexture: MTLTexture?
    
    public struct Config {
        let pixelFormat: MTLPixelFormat
        let clearColor: MTLClearColor
        let width: Int
        let height: Int
        
        public init(pixelFormat: MTLPixelFormat,
                    clearColor: MTLClearColor,
                    width: Int,
                    height: Int) {
            self.pixelFormat = pixelFormat
            self.clearColor = clearColor
            self.width = width
            self.height = height
        }
    }
    
    public init() { }
    
    public func begin(config: Config) {
        let descriptor = MTLTextureDescriptor()
        descriptor.textureType = .type2D
        descriptor.pixelFormat = config.pixelFormat
        descriptor.width = config.width
        descriptor.height = config.height
        descriptor.usage = [.shaderRead, .renderTarget]
        temporaryTexture = Adamantium.sharedDevice?.makeTexture(descriptor: descriptor)
        
        // For use in MacOS Catalyst and simulator
        let newRenderPassDescriptor = MTLRenderPassDescriptor()
        newRenderPassDescriptor.colorAttachments[0].texture = temporaryTexture
        newRenderPassDescriptor.colorAttachments[0].loadAction = .clear
        newRenderPassDescriptor.colorAttachments[0].clearColor = config.clearColor
        newRenderPassDescriptor.colorAttachments[0].storeAction = .store
        renderPassDescriptor = newRenderPassDescriptor
        
        guard
            let newCommandBuffer = Adamantium.sharedCommandQueue?.makeCommandBuffer(),
            let newRenderEncoder = newCommandBuffer.makeRenderCommandEncoder(descriptor: newRenderPassDescriptor)
        else { return }
        
        renderEncoder = newRenderEncoder
        commandBuffer = newCommandBuffer
    }
    
    public func commit() {
        renderEncoder?.endEncoding()
        commandBuffer?.commit()
        render = temporaryTexture
        if let delegate = delegate, let render = render {
            delegate.finishedRenderingOffline(render)
        }
    }
        
    public func draw(renderable: Renderable?) {
        guard let renderable = renderable, let encoder = renderEncoder else { return }
        delegate?.performPreRenderActionOnEncoder(encoder, renderable: renderable)
        renderable.render(encoder: renderEncoder)
    }
}
