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

import ARKit
import UIKit

class ObjectScanningViewController: UIViewController {

    var lastScale: Float!
    var lastPanPosition: SCNVector3!
    let initialBoundingBoxSize: Float = 0.1
    var boundingBox: SCNNode!
    var panGestureRecognizer: UIPanGestureRecognizer!
    var rotationGestureRecognizer: UIRotationGestureRecognizer!
    var pinchGestureRecognizer: UIPinchGestureRecognizer!
    var referenceObject: ARReferenceObject? {
        didSet {
            DispatchQueue.main.async {
                self.saveObjectBtn.isEnabled = self.referenceObject != nil
            }
        }
    }
    
    @IBOutlet var cameraTrackingState: UILabel!
    @IBOutlet var scanState: UILabel!
    @IBOutlet var sceneView: ARSCNView!
    @IBOutlet var saveObjectBtn: UIButton!
    
    @IBAction func restartSession() {
        startSession()
    }
    
    @IBAction func saveObject() {
        if let referenceObject = referenceObject {
            if let frame = sceneView.session.currentFrame {
                if let screenshot = Screenshot.createScreenshot(frame: frame) {
                    let ac = UIAlertController(title: "Give object a name", message: "Please enter name:", preferredStyle: .alert)
                    ac.addTextField() { _ in
                        if ac.actions.count > 0 {
                            ac.actions[0].isEnabled = !ac.textFields![0].text!.isEmpty
                        }                        
                    }
                    ac.addAction(UIAlertAction(title: "OK", style: .default) { _ in
                            ARObjectSerializer.writeARObject(withName: ac.textFields![0].text!, object: referenceObject, screenshot: screenshot)
                        })
                    ac.addAction(UIAlertAction(title: "Cancel", style: .cancel))
                    present(ac, animated: true)
                }
            }
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.title = "3D Object Scanning"
        
        panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPanGesture(_:)))
        panGestureRecognizer.delegate = self
        sceneView.addGestureRecognizer(panGestureRecognizer)
        
        rotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(didRotateGesture(_:)))
        rotationGestureRecognizer.delegate = self
        sceneView.addGestureRecognizer(rotationGestureRecognizer)
        
        pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(didPinchGesture(_:)))
        pinchGestureRecognizer.delegate = self
        sceneView.addGestureRecognizer(pinchGestureRecognizer)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        startSession()
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        
        guard boundingBox == 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 }
        
        boundingBox = BoundingBox.createBoundingBoxNode(size: CGFloat(initialBoundingBoxSize), transparency: 0.3)
        boundingBox.position = SCNVector3Make(firstHit.worldTransform.columns.3.x, firstHit.worldTransform.columns.3.y, firstHit.worldTransform.columns.3.z)
        sceneView.scene.rootNode.addChildNode(boundingBox)
        
        scanState.text = "Adjust bounding-box to object and move device around object"
    }
    
    @objc func didPanGesture(_ sender: UIPanGestureRecognizer) {
        guard boundingBox != nil else { return }
        
        let gestureLocation = sender.location(in: sceneView)
        let hits = sceneView.hitTest(gestureLocation, types: .existingPlaneUsingExtent)
        guard let firstHit = hits.first else { return }

        if sender.state == .began {
            lastPanPosition = SCNVector3Make(firstHit.worldTransform.columns.3.x, firstHit.worldTransform.columns.3.y, firstHit.worldTransform.columns.3.z)
            return
        }
        else if sender.state == .changed {
             let currPanPosition = SCNVector3Make(firstHit.worldTransform.columns.3.x, firstHit.worldTransform.columns.3.y, firstHit.worldTransform.columns.3.z)
            boundingBox.position = SCNVector3Make(boundingBox.position.x + (currPanPosition.x - lastPanPosition.x), boundingBox.position.y + (currPanPosition.y - lastPanPosition.y), boundingBox.position.z + (currPanPosition.z - lastPanPosition.z))
            lastPanPosition = currPanPosition
        }
    }
    
    @objc func didRotateGesture(_ sender: UIRotationGestureRecognizer) {
        if let boundingBox = boundingBox {
            if sender.state == .changed {
                boundingBox.eulerAngles.y = -Float(sender.rotation)
            }
        }
    }
    
    @objc func didPinchGesture(_ sender: UIPinchGestureRecognizer) {
        if let boundingBox = boundingBox {
            let currScale = simd_clamp(Float(sender.scale), 0.1, 10.0)
            
            if sender.state == .changed {
                let deltaScale = currScale - lastScale
                let finalScale = simd_clamp(Float(boundingBox.scale.x + deltaScale), 0.1, 10.0)
                boundingBox.scale = SCNVector3(finalScale, finalScale, finalScale)
            }
            
            lastScale = currScale
        }
    }
    
    func startSession() {
        boundingBox = nil
        referenceObject = nil
        
        let configuration = ARObjectScanningConfiguration()
        configuration.planeDetection = .horizontal
        sceneView.scene = SCNScene()
        sceneView.session.run(configuration, options: .resetTracking)
        sceneView.session.delegate = self
        sceneView.debugOptions = [.showFeaturePoints]
        
        sceneView.delegate = self
        
        scanState.text = "Tap onto object to initialize scanning"
    }
}

// MARK: ARSessionDelegate

extension ObjectScanningViewController: ARSessionDelegate {
    func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
        cameraTrackingState.backgroundColor = StateMapper.trackingStateColor(trackingState: camera.trackingState)
        cameraTrackingState.text = "Tracking State: \(StateMapper.trackingStateDescription(trackingState: camera.trackingState))"
    }
}

// MARK: ARSCNViewDelegate

extension ObjectScanningViewController: ARSCNViewDelegate {
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        if let boundingBox = boundingBox {
            let extent = initialBoundingBoxSize * boundingBox.scale.x
            sceneView.session.createReferenceObject(transform: boundingBox.simdWorldTransform, center: simd_float3(), extent: simd_float3(extent, extent, extent)) { [weak self] (object, error) in
                if let strongSelf = self {
                    strongSelf.referenceObject = object
                    
                    if let error = error {
                        print(error)
                    }
                }
            }
        }
    }
}

// MARK: UIGestureRecognizerDelegate

extension ObjectScanningViewController: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        // Panning should always run on its own
        if gestureRecognizer == panGestureRecognizer || otherGestureRecognizer == panGestureRecognizer {
            return false
        }
        
        return true
    }
}
