//
//  TextureMaskedAuraView.swift
//  Adamantium
//
//  Created by Sunny Uppal on 11/25/24.
//

import SwiftUI
import UIKit
import Metal
import MetalKit

public struct TextureMaskedAuraView: UIViewRepresentable {
    
    @ObservedObject var coordinator: Coordinator
    
    let colorSet: SunoTriColorSet
    let morphSpeed: ADFloat
    let scale: ADFloat
    let seed: ADFloat
    let frameRate: Int
    
    public init(_ coordinator: Coordinator,
        colorSet: SunoTriColorSet,
        morphSpeed: ADFloat = 0.1,
        scale: ADFloat = 1.0,
        seed: ADFloat = .zero,
        frameRate: Int = 60) {
        
        self.coordinator = coordinator
        coordinator.setGradientSpeed(morphSpeed)
        coordinator.setScale(scale)
        coordinator.setGradientSeed(seed)
        coordinator.setColorSet(colorSet, deltaTime: 1.0)
        
        self.colorSet = colorSet
        self.morphSpeed = morphSpeed
        self.scale = scale
        self.seed = seed
        self.frameRate = frameRate
    }
    
    public func makeCoordinator() -> Coordinator {
        return coordinator
    }
    
    public func makeUIView(context: Context) -> some UIView {
        let mtkView = MTKView()
        mtkView.delegate = context.coordinator
        mtkView.preferredFramesPerSecond = frameRate
        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
    }
    
    public func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    
    public class Coordinator: NSObject, ObservableObject, MTKViewDelegate {
        
        var metalDevice: MTLDevice?
        var metalCommandQueue: MTLCommandQueue?
        public weak var delegate: MetalAuraCoordinatorDelegate?
        private let noiseRenderable = TextureMaskedSimplex3DAuraRenderable()
        private var maskImage: MTLTexture?
        
        private var lastAColor: SIMD3<Float> = .zero
        private var lastBColor: SIMD3<Float> = .zero
        private var lastCColor: SIMD3<Float> = .zero
        
        private var gradientSpeed: ADFloat = 0.1
        private var gradientScale: ADFloat = 1.0
        private var gradientSeed: ADFloat = .zero
        
        private let startingTime: Date = .now
        private var lastTime: Date?
        private var speakingIntensity: Float = .zero
        private var mixCircleShape: Float = .zero
        private var isLerpDirectionForward: Bool = true
        
        public override init() {
            self.metalDevice = Adamantium.sharedDevice
            self.metalCommandQueue = Adamantium.sharedCommandQueue
            super.init()
        }
        
        public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
            
        }
        
        func setGradientSpeed(_ speedScalar: ADFloat) {
            gradientSpeed = speedScalar
        }
        
        func setScale(_ scale: ADFloat) {
            gradientScale = scale
        }
        
        func setGradientSeed(_ seed: ADFloat = .zero) {
            gradientSeed = seed
        }
        
        public func setMaskImage(_ data: Data) {
            do {
                maskImage = try Adamantium.sharedTextureLoader.newTexture(data: data)
            } catch {
                print(error)
            }
        }
        
        func setColorSet(_ triColorSet: SunoTriColorSet, deltaTime: TimeInterval) {
            let floatDeltaTime = Float(deltaTime)
            let targetColorA = triColorSet.floatColorA
            let targetColorB = triColorSet.floatColorB
            let targetColorC = triColorSet.floatColorC
            
            let aR = simd_mix(lastAColor.x, targetColorA.x, floatDeltaTime)
            let aG = simd_mix(lastAColor.y, targetColorA.y, floatDeltaTime)
            let aB = simd_mix(lastAColor.z, targetColorA.z, floatDeltaTime)
            lastAColor = simd_float3(x: aR, y: aG, z: aB)
            
            let bR = simd_mix(lastBColor.x, targetColorB.x, floatDeltaTime)
            let bG = simd_mix(lastBColor.y, targetColorB.y, floatDeltaTime)
            let bB = simd_mix(lastBColor.z, targetColorB.z, floatDeltaTime)
            lastBColor = simd_float3(x: bR, y: bG, z: bB)
            
            let cR = simd_mix(lastCColor.x, targetColorC.x, floatDeltaTime)
            let cG = simd_mix(lastCColor.y, targetColorC.y, floatDeltaTime)
            let cB = simd_mix(lastCColor.z, targetColorC.z, floatDeltaTime)
            lastCColor = simd_float3(x: cR, y: cG, z: cB)
        }
        
        public func draw(in view: MTKView) {
            var size: ADVector2 {
                return ADVector2(
                    x: Float(view.drawableSize.width),
                    y: Float(view.drawableSize.height)
                )
            }
            
            let deltaTime: TimeInterval
            if let lastTime = lastTime {
                deltaTime = Date.now.timeIntervalSince(lastTime)
            } else {
                deltaTime = .zero
            }
            
            lastTime = .now
            
            guard
                let commandBuffer = Adamantium.sharedCommandQueue?.makeCommandBuffer(),
                let currentDrawable = view.currentDrawable,
                let renderDescriptor = view.currentRenderPassDescriptor,
                let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderDescriptor)
            else { return }

            delegate?.didUpdateAuraFrame(deltaTime)
            renderAura(size, renderEncoder: renderEncoder)
            
            renderEncoder.endEncoding()
            commandBuffer.present(currentDrawable)
            commandBuffer.commit()
        }
    }
}

private extension TextureMaskedAuraView.Coordinator {
    
    func renderAura(_ size: ADVector2, renderEncoder: MTLRenderCommandEncoder) {
        let timeElapsed = ADFloat(Date.now.timeIntervalSince(startingTime))
        let fractalElapsedTime = timeElapsed * gradientSpeed
        let grainElapsedTime = timeElapsed * 0.01
        
        let mixPointStart: ADFloat = -0.2
        let mixPointBackground: ADFloat = -0.1
        let mixPointBackgroundA: ADFloat = 0.0
        let mixPointAB: ADFloat = 0.2
        let mixPointBC: ADFloat = 0.4
        let mixPointEnd: ADFloat = 2.0
        
        if let auraGeometryImage = maskImage {
            noiseRenderable.inputTexture = auraGeometryImage
        }
        
        noiseRenderable.config = .init(
            displaySize: size,
            fractalNoiseX: .zero,
            fractalNoiseY: .zero,
            fractalNoiseZ: fractalElapsedTime + gradientSeed,
            fractalUniformScale: 0.5 * gradientScale,
            grainNoiseZ: grainElapsedTime,
            grainNoiseScale: 5.0 * gradientScale,
            circleMaskRadius: 100.0,
            backgroundColor: SunoColorPalette.backgroundDotCom.floatThree,
            colorA: lastAColor,
            colorB: lastBColor,
            colorC: lastCColor,
            mixPointStart: mixPointStart,
            mixPointBackground: mixPointBackground,
            mixPointBackgroundA: mixPointBackgroundA,
            mixPointAB: mixPointAB,
            mixPointBC: mixPointBC,
            mixPointEnd: mixPointEnd
        )
        
        noiseRenderable.render(encoder: renderEncoder)
    }
}
