import Foundation
import SwiftUI
import Metal
import MetalKit
import Adamantium

struct RealtimeRenderDemoView: UIViewRepresentable {
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> some UIView {
        let mtkView = MTKView()
        mtkView.delegate = context.coordinator
        mtkView.preferredFramesPerSecond = 60
        mtkView.enableSetNeedsDisplay = true
        mtkView.device = Adamantium.sharedDevice
        
        mtkView.framebufferOnly = false
        mtkView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
        mtkView.drawableSize = mtkView.frame.size
        mtkView.enableSetNeedsDisplay = true
        mtkView.isPaused = false
        return mtkView
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    
    class Coordinator : NSObject, MTKViewDelegate {
        
        var uOffset: Float = 2.0
        var cycleValue: Float = 0.0
        var rotationValue: Float = 0.0
        var view: RealtimeRenderDemoView
        var metalDevice: MTLDevice!
        var metalCommandQueue: MTLCommandQueue!
        
        let skullTexture: MTLTexture
        let skullFillTexture: MTLTexture
        let circleTextTexture: MTLTexture
        
        init(_ view: RealtimeRenderDemoView) {
            self.view = view
            self.metalDevice = Adamantium.sharedDevice
            self.metalCommandQueue = Adamantium.sharedCommandQueue
            
            let outlineURL = Bundle
                .main.url(forResource: "white_skull_on_black", withExtension: "png")!
            self.skullTexture = try! Adamantium.sharedTextureLoader.newTexture(URL: outlineURL, options: [.SRGB: false])
            
            let fillURL = Bundle
                .main.url(forResource: "white_skull_on_black_fill", withExtension: "png")!
            self.skullFillTexture = try! Adamantium.sharedTextureLoader.newTexture(URL: fillURL, options: [.SRGB: false])
            
            let circleRingURL = Bundle
                .main.url(forResource: "background_ring_collection", withExtension: "png")!
            
            self.circleTextTexture = try! Adamantium.sharedTextureLoader.newTexture(URL: circleRingURL, options: [.SRGB: false])
            
            super.init()
        }
        
        func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
            
        }
        
        func draw(in view: MTKView) {
            
            var size: ADVector2 {
                return ADVector2(
                    x: Float(view.drawableSize.width),
                    y: Float(view.drawableSize.height)
                )
            }
            
            guard
                let commandBuffer = Adamantium.sharedCommandQueue?.makeCommandBuffer(),
                let currentDrawable = view.currentDrawable,
                let renderDescriptor = view.currentRenderPassDescriptor,
                let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderDescriptor)
            else { return }
            
            let backgroundFill = ADVector3(
                x: 0.07843137254902,
                y: 0.07843137254902,
                z: 0.07843137254902)
            
            let renderable = ColorFillRenderable()
            renderable.config = .init(color: backgroundFill)
            renderable.render(encoder: renderEncoder)
            
            let slideRenderable = RepeatedSlideRenderable()
            slideRenderable.map = skullTexture
            let colorScalar: Float = 0.12
            let color = ADVector3(x: colorScalar, y: colorScalar, z: colorScalar)
            
            if uOffset < 1.0 {
                uOffset += 1.0
            } else {
                uOffset -= 0.001
            }
            
            slideRenderable.config = .init(displaySize: size,
                                           globalScale: 5.0,
                                           rotation: 0.125,
                                           mappedColor: color,
                                           ratioSize: 0.8,
                                           uOffset: uOffset,
                                           vOffset: 0.0)
            slideRenderable.render(encoder: renderEncoder)
            
            if cycleValue > 1.0 {
                cycleValue -= 1.0
            } else {
                cycleValue += 0.005
            }
            
            if rotationValue > 1.0 {
                rotationValue -= 1.0
            } else {
                rotationValue += 0.0005
            }
            
            let backSize: Float = 0.55
            let forthSize: Float = 0.6
            
            let sinePulse = SinePulseRenderable()
            sinePulse.config = .init(displaySize: size,
                                     backSize: backSize,
                                     forthSize: forthSize,
                                     cycleAmount: cycleValue,
                                     mappedColor: backgroundFill)
            sinePulse.map = skullFillTexture
            sinePulse.render(encoder: renderEncoder)
            
            let sinePulseOutline = SinePulseRenderable()
            sinePulseOutline.config = .init(displaySize: size,
                                     backSize: backSize,
                                     forthSize: forthSize,
                                     cycleAmount: cycleValue,
                                     mappedColor: .ones)
            sinePulseOutline.map = skullTexture
            sinePulseOutline.render(encoder: renderEncoder)
            
            let rotateRenderable = RotationScaleRenderable()
            rotateRenderable.config = .init(displaySize: size,
                                            rotation: rotationValue,
                                            scale: 1.0,
                                            mappedColor: .ones)
            rotateRenderable.map = circleTextTexture
            rotateRenderable.render(encoder: renderEncoder)
            
            renderEncoder.endEncoding()
            commandBuffer.present(currentDrawable)
            commandBuffer.commit()
        }
    }
}
