//
//  SerializeWorldMapViewController.swift
//  HeiseARKit2
//
//  Created by Gero Gerber on 03.09.18.
//  Copyright © 2018 Gero Gerber. All rights reserved.
//

import ARKit
import UIKit

class SerializeWorldMapViewController: UIViewController {

    @IBOutlet var sceneView: ARSCNView!
    @IBOutlet var cameraTrackingState: UILabel!
    @IBOutlet var featurePoints: UISwitch!
    @IBOutlet var coordinateSystem: UISwitch!
    @IBOutlet var worldMappingStatus: UILabel!
    
    @IBAction func debugOptionChanged() {
        updateDebugOptions()
    }
    
    @IBAction func saveWorldMap() {
        retrieveWorldMap() { [weak self] (worldMapData) in
            if let strongSelf = self {
                let fileURL = strongSelf.createWorldMapFileURL()
                try! worldMapData.write(to: fileURL)
            }
        }
    }
    
    @IBAction func loadWorldMap() {
        let fileURL = self.createWorldMapFileURL()
        let fileManager = FileManager.default
        
        if fileManager.fileExists(atPath: fileURL.path) {
            let worldMapData = try! Data(contentsOf: fileURL)
            let worldMap = self.deserializeWorldMap(worldMapData: worldMapData)
            startSession(worldMap: worldMap)
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.title = "Serialize World-Map"
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        startSession()
        
        updateDebugOptions()
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        
        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 }
        
        let randomIndex = Int(arc4random() % UInt32(USDZItems.usdzItems.count))
        let anchorName = USDZItems.usdzItems[randomIndex]
        
        let anchor = ARAnchor(name: anchorName, transform: firstHit.worldTransform)
        sceneView.session.add(anchor: anchor)
    }
    
    func startSession(worldMap: ARWorldMap? = nil) {
        let configuration = ARWorldTrackingConfiguration()
        configuration.initialWorldMap = worldMap
        configuration.planeDetection = [.horizontal, .vertical]
        sceneView.session.run(configuration)
        
        sceneView.showsStatistics = true
        sceneView.delegate = self
        sceneView.session.delegate = self
        
        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        
        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        
        sceneView.scene.rootNode.addChildNode(lightNode)
        sceneView.scene.rootNode.addChildNode(ambientLightNode)
    }
    
    func updateDebugOptions() {
        var debugOptions = SCNDebugOptions()
        
        if featurePoints.isOn {
            debugOptions.insert(.showFeaturePoints)
        }
        
        if coordinateSystem.isOn {
            debugOptions.insert(.showWorldOrigin)
        }
        
        sceneView.debugOptions = debugOptions
    }
    
    func createWorldMapFileURL() -> URL {
        return FileManager.getDocumentsDirectoryURL().appendingPathComponent("HeiseARKit2WorldMap")
    }
    
    func retrieveWorldMap(execute action: @escaping (Data) -> ()) {
        sceneView.session.getCurrentWorldMap() { [weak self] (worldMap, error) in
            if let strongSelf = self {
                if error != nil {
                    let ac = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
                    ac.addAction(UIAlertAction(title: "OK", style: .default))
                    strongSelf.present(ac, animated: true)
                } else if worldMap == nil {
                    let ac = UIAlertController(title: "Error", message: "World-Map is nil!", preferredStyle: .alert)
                    ac.addAction(UIAlertAction(title: "OK", style: .default))
                    strongSelf.present(ac, animated: true)
                }
                else {
                    let worldMapData = strongSelf.serializeWorldMap(worldMap: worldMap!)
                    action(worldMapData)
                }
            }
        }
    }
    
    func serializeWorldMap(worldMap: ARWorldMap) -> Data {
        return try! NSKeyedArchiver.archivedData(withRootObject: worldMap, requiringSecureCoding: true)
    }
    
    func deserializeWorldMap(worldMapData: Data) -> ARWorldMap {
        return try! NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: worldMapData)!
    }
}

// MARK: ARSessionDelegate

extension SerializeWorldMapViewController: 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 frame: ARFrame) {
        worldMappingStatus.backgroundColor = StateMapper.worldMappingStatusColor(worldMappingStatus: frame.worldMappingStatus)
        worldMappingStatus.text = "World-Mapping Status: \(StateMapper.worldMappingStatusDescription(worldMappingStatus: frame.worldMappingStatus))"
    }
}

// MARK: ARSCNViewDelegate

extension SerializeWorldMapViewController: ARSCNViewDelegate {
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        if let anchorName = anchor.name {
            if let itemIndex = USDZItems.indexOf(item: anchorName) {
                let url = USDZItems.URLForItem(index: itemIndex)
                let anchorNode = SCNNode()
                
                if let refNode = SCNReferenceNode(url: url) {
                    DispatchQueue.global().async { [weak anchorNode] in
                        refNode.load()
                        
                        if let child = refNode.childNodes.first {
                            child.scale = SCNVector3(0.005, 0.005, 0.005)
                            child.castsShadow = true
                            
                            if let strongAnchorNode = anchorNode {
                                strongAnchorNode.addChildNode(child)
                            }
                        }
                    }
                    
                    return anchorNode
                }
            }
        }
        return nil
    }
}
