//
//  EnvironmentTexturingViewController.swift
//  HeiseARKit2
//
//  Created by Gero Gerber on 11.09.18.
//  Copyright © 2018 Gero Gerber. All rights reserved.
//

import ARKit
import Metal
import UIKit

class EnvironmentTexturingViewController: UIViewController {

    @IBOutlet var transparencySlider: UISlider!
    @IBOutlet var cameraTrackingState: UILabel!
    @IBOutlet var boxSideSwitch: [UISwitch]!
    @IBOutlet var cubeMapScene: SCNView!
    @IBOutlet var sceneView: ARSCNView!
    var currentAnchorIdentifier: UUID!
    var currentNode: SCNNode!
    var cubeMapSidePlanes = [SCNNode]()
    
    @IBAction func toggleBoxSideVisibility(_ sender: UISwitch) {
        guard currentNode != nil else { return }
        guard currentAnchorIdentifier != nil else { return }
        
        if let anchor = getAnchorByIdentifier(identifier: currentAnchorIdentifier) as? AREnvironmentProbeAnchor {
            if let index = boxSideSwitch.firstIndex(of: sender) {
                updateBoxSideNode(node: currentNode, anchor: anchor, side: index, showTexture: sender.isOn)
            }
        }
    }
    
    @IBAction func transparencyChanged(_ sender: UISlider) {
        guard currentNode != nil else { return }
        guard currentAnchorIdentifier != nil else { return }
        
        if let anchor = getAnchorByIdentifier(identifier: currentAnchorIdentifier) as? AREnvironmentProbeAnchor {
            for index in 0 ..< boxSideSwitch.count {
                updateBoxSideNode(node: currentNode, anchor: anchor, side: index, showTexture: boxSideSwitch[index].isOn)
            }
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        createCubeMapDisplay()
        startSession()
    }
    
    func startSession() {
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = .horizontal
        configuration.environmentTexturing = .manual
        
        sceneView.session.delegate = self
        sceneView.automaticallyUpdatesLighting = true
        sceneView.session.run(configuration)
    }
    
    func getAnchorByIdentifier(identifier: UUID) -> ARAnchor? {
        if let frame = sceneView.session.currentFrame {
            for currentAnchor in frame.anchors {
                if currentAnchor.identifier == identifier {
                    return currentAnchor
                }
            }
        }
        return nil
    }
    
    func updateBoxSideNode(node: SCNNode, anchor: AREnvironmentProbeAnchor, side: Int, showTexture: Bool) {
        if let cubeMap = anchor.environmentTexture {
            let textureView = cubeMap.__newTextureView(with: cubeMap.pixelFormat, textureType: .type2D, levels: NSRange(location: 0, length: cubeMap.mipmapLevelCount), slices: NSRange(location: side, length: 1))
            
            if let child = node.childNode(withName: "plane_\(side)", recursively: true) {
                child.isHidden = !showTexture
                if let material = child.geometry?.firstMaterial {
                    material.isDoubleSided = true
                    material.diffuse.contents = UIColor.white

                    if showTexture == true {
                        material.diffuse.contents = textureView
                        material.transparency = CGFloat(transparencySlider.value)
                    }
                }
            }
            
            cubeMapSidePlanes[side].geometry?.firstMaterial?.diffuse.contents = textureView
        }
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        
        guard currentAnchorIdentifier == nil else { return }
        guard let touch = touches.first else { return }
        
        let touchLocation = touch.location(in: sceneView)
        let hits = sceneView.hitTest(touchLocation, types: .existingPlaneUsingExtent)
        guard let firstHit = hits.first else { return }
        
        var transform = firstHit.worldTransform
        transform.columns.3.y += 0.5
        
        let anchor = AREnvironmentProbeAnchor(name: "probe", transform: transform, extent: simd_float3(2, 2, 2))
        currentAnchorIdentifier = anchor.identifier
        sceneView.session.add(anchor: anchor)
    }
    
    func addChildNodes(rootNode: SCNNode, anchor: AREnvironmentProbeAnchor) {
        let extent = anchor.extent
        
        rootNode.addChildNode(createPlaneNode(side: 0, width: CGFloat(extent.z), height: CGFloat(extent.y), position: SCNVector3(extent.x / 2, 0, 0), rotation: SCNVector4(0, 1, 0, -Float.pi / 2)))
        rootNode.addChildNode(createPlaneNode(side: 1, width: CGFloat(extent.z), height: CGFloat(extent.y), position: SCNVector3(-extent.x / 2, 0, 0), rotation: SCNVector4(0, 1, 0, Float.pi / 2)))
        rootNode.addChildNode(createPlaneNode(side: 2, width: CGFloat(extent.x), height: CGFloat(extent.z), position: SCNVector3(0, extent.y / 2, 0), rotation: SCNVector4(1, 0, 0, Float.pi / 2)))
        rootNode.addChildNode(createPlaneNode(side: 3, width: CGFloat(extent.x), height: CGFloat(extent.z), position: SCNVector3(0, -extent.y / 2, 0), rotation: SCNVector4(1, 0, 0, -Float.pi / 2)))
        rootNode.addChildNode(createPlaneNode(side: 4, width: CGFloat(extent.x), height: CGFloat(extent.y), position: SCNVector3(0, 0, -extent.z / 2), rotation: SCNVector4(0, 0, 1, 0)))
        rootNode.addChildNode(createPlaneNode(side: 5, width: CGFloat(extent.x), height: CGFloat(extent.y), position: SCNVector3(0, 0, extent.z / 2), rotation: SCNVector4(0, 1, 0, Float.pi)))
        
        let sphere = SCNSphere(radius: 0.15)
        sphere.isGeodesic = true
        let reflectiveMaterial = SCNMaterial()
        reflectiveMaterial.lightingModel = .physicallyBased
        reflectiveMaterial.metalness.contents = 1.0
        reflectiveMaterial.roughness.contents = 0
        sphere.firstMaterial = reflectiveMaterial
        let sphereNode = SCNNode(geometry: sphere)
        
        let moveLeft = SCNAction.moveBy(x: 0.04, y: 0, z: 0, duration: 1)
        moveLeft.timingMode = .easeInEaseOut;
        let moveRight = SCNAction.moveBy(x: -0.04, y: 0, z: 0, duration: 1)
        moveRight.timingMode = .easeInEaseOut;
        
        let moveSequence = SCNAction.sequence([moveLeft, moveRight])
        let moveLoop = SCNAction.repeatForever(moveSequence)
        sphereNode.runAction(moveLoop)
        
        rootNode.addChildNode(sphereNode)
    }
    
    func createPlaneNode(side: Int, width: CGFloat, height: CGFloat, position: SCNVector3, rotation: SCNVector4) -> SCNNode {
        let planeGeometry = SCNPlane(width: width, height: height)
        let planeNode = SCNNode(geometry: planeGeometry)
        planeNode.name = "plane_\(side)"
        planeNode.position = position
        planeNode.rotation = rotation
        return planeNode
    }
    
    func createCubeMapDisplay() {
        cubeMapScene.scene = SCNScene()
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.camera?.usesOrthographicProjection = true
        cameraNode.camera?.projectionDirection = .horizontal
        
        cubeMapScene.scene?.rootNode.addChildNode(cameraNode)
        cubeMapScene.pointOfView = cameraNode
        cubeMapScene.backgroundColor = .white
        
        cubeMapSidePlanes.append(addCubeMapDisplayPlane(x: 0.25, y: 0, color: UIColor.red))
        cubeMapSidePlanes.append(addCubeMapDisplayPlane(x: -0.75, y: 0, color: UIColor.green))
        cubeMapSidePlanes.append(addCubeMapDisplayPlane(x: -0.25, y: 0.5, color: UIColor.blue))
        cubeMapSidePlanes.append(addCubeMapDisplayPlane(x: -0.25, y: -0.5, color: UIColor.yellow))
        cubeMapSidePlanes.append(addCubeMapDisplayPlane(x: -0.25, y: 0, color: UIColor.gray))
        cubeMapSidePlanes.append(addCubeMapDisplayPlane(x: 0.75, y: 0, color: UIColor.orange))
    }
    
    func addCubeMapDisplayPlane(x: Float, y: Float, color: UIColor) -> SCNNode {
        let planeNode = SCNNode(geometry: SCNPlane(width: 0.5, height: 0.5))
        planeNode.position.z = -10
        planeNode.geometry?.firstMaterial?.diffuse.contents = color
        planeNode.geometry?.firstMaterial?.isDoubleSided = false
        planeNode.position.x = x
        planeNode.position.y = y
        cubeMapScene.scene?.rootNode.addChildNode(planeNode)
        return planeNode
    }
}

extension EnvironmentTexturingViewController: ARSessionDelegate {
    func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
        cameraTrackingState.backgroundColor = StateMapper.trackingStateColor(trackingState: camera.trackingState)
        cameraTrackingState.text = "Tracking State: \(StateMapper.trackingStateDescription(trackingState: camera.trackingState))"
    }
    
    func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
        // For some reason ARKit adds SCNNode duplicates itself. We only want to show the SCNNode
        // that is currently attachted to the specific AREnvironmentProbeAnchor instance
        if currentAnchorIdentifier != nil {
            if let currentAnchor = anchors.first(where: { $0.identifier == currentAnchorIdentifier }) as? AREnvironmentProbeAnchor {
                if currentNode == nil {
                    currentNode = SCNNode()
                    addChildNodes(rootNode: currentNode, anchor: currentAnchor)
                    sceneView.scene.rootNode.addChildNode(currentNode)
                }
                
                currentNode.simdTransform = currentAnchor.transform
                
                for index in 0 ..< boxSideSwitch.count {
                    updateBoxSideNode(node: currentNode, anchor: currentAnchor, side: index, showTexture: boxSideSwitch[index].isOn)
                }
            }
        }
    }
}
