
import SwiftUI
import Metal
import MetalKit
import simd

public struct SpaceRocksView: UIViewRepresentable, Identifiable {
    public let id: String
    
    // Exposed uniforms:
    public var timeScale:  ADFloat
    
    // Frame control:
    public var frameRate: Int = 60
    public var isPaused:  Bool = false
    
    @ObservedObject private var coordinator: Coordinator
    
    public init(
        id: String,
        timeScale: ADFloat = 1.0,
        frameRate: Int = 60,
        isPaused: Bool = false
    ) {
        self.id          = id
        self.timeScale   = timeScale
        self.frameRate   = frameRate
        self.isPaused    = isPaused
        
        let c = Coordinator()
        self.coordinator = c
    }
    
    public func makeCoordinator() -> Coordinator { coordinator }
    
    public func makeUIView(context: Context) -> MTKView {
        let mtk = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
        mtk.delegate = context.coordinator
        mtk.preferredFramesPerSecond = frameRate
        mtk.framebufferOnly = false
        mtk.clearColor = MTLClearColorMake(0,0,0,0)
        mtk.enableSetNeedsDisplay = true
        mtk.isPaused = isPaused
        return mtk
    }
    
    public func updateUIView(_ uiView: MTKView, context: Context) {
        uiView.isPaused = isPaused
        // push updated uniforms down each update
        coordinator.updateTimeScale(timeScale)
    }
    
    public class Coordinator: NSObject, ObservableObject, MTKViewDelegate {
        private let device: MTLDevice?
        private let queue:  MTLCommandQueue?
        private let renderable: SpaceRocksRenderable
        
        // current uniform values
        private var uniforms = SpaceRocksRenderable.Config.defaultConfiguration
        
        // seed time
        private let start = CFAbsoluteTimeGetCurrent()
        private var lastTime: CFAbsoluteTime?
        private var timeScale: ADFloat = 1.0
        
        public override init() {
            device = Adamantium.sharedDevice
            queue = Adamantium.sharedCommandQueue
            renderable = SpaceRocksRenderable()
            super.init()
        }
        
        func updateTimeScale(_ newTimeScale: ADFloat) {
            timeScale = newTimeScale
        }
        
        public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
            // nothing special
        }
        
        public func draw(in view: MTKView) {
            guard
                let buf = queue?.makeCommandBuffer(),
                let desc = view.currentRenderPassDescriptor,
                let drw  = view.currentDrawable
            else { return }
            
            // compute delta time and absolute time
            let now = CFAbsoluteTimeGetCurrent()
            let delta = lastTime.map { now - $0 } ?? 0
            lastTime = now
            
            let elapsed = Float(now - start)
            // update per-frame uniforms:
            uniforms.resolution = .init(
                x: Float(view.drawableSize.width),
                y: Float(view.drawableSize.height)
            )
            uniforms.time = elapsed * timeScale
            
            // apply config and render
            renderable.config = uniforms
            
            let enc = buf.makeRenderCommandEncoder(descriptor: desc)!
            renderable.render(encoder: enc)
            enc.endEncoding()
            
            buf.present(drw)
            buf.commit()
        }
    }
}
