//
//  PreviewGameViewController.swift
//  Delta
//
//  Created by Riley Testut on 8/11/16.
//  Copyright © 2016 Riley Testut. All rights reserved.
//

import UIKit

import DeltaCore

private var kvoContext = 0

class PreviewGameViewController: DeltaCore.GameViewController
{
    // If non-nil, will override the default preview action items returned in previewActionItems()
    var overridePreviewActionItems: [UIPreviewActionItem]?
    
    // Save state to be loaded upon preview
    var previewSaveState: SaveStateProtocol?
    
    // Initial image to be shown while loading
    var previewImage: UIImage? {
        didSet {
            self.updatePreviewImage()
        }
    }
    
    private var emulatorCoreQueue = DispatchQueue(label: "com.rileytestut.Delta.PreviewGameViewController.emulatorCoreQueue", qos: .userInitiated)
    private var copiedSaveFiles = [(originalURL: URL, copyURL: URL)]()
    
    private lazy var temporaryDirectoryURL: URL = {
        let directoryURL = FileManager.default.temporaryDirectory.appendingPathComponent("preview-" + UUID().uuidString)
        try? FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
        return directoryURL
    }()
    
    override var game: GameProtocol? {
        willSet {
            self.emulatorCore?.removeObserver(self, forKeyPath: #keyPath(EmulatorCore.state), context: &kvoContext)
        }
        didSet {
            guard let emulatorCore = self.emulatorCore else {
                self.preferredContentSize = CGSize.zero
                return
            }
            
            emulatorCore.addObserver(self, forKeyPath: #keyPath(EmulatorCore.state), options: [.old], context: &kvoContext)
            
            let size = CGSize(width: emulatorCore.preferredRenderingSize.width * 2.0, height: emulatorCore.preferredRenderingSize.height * 2.0)
            self.preferredContentSize = size
        }
    }
    
    override var previewActionItems: [UIPreviewActionItem] {
        guard let previewActionItems = self.overridePreviewActionItems else { return [] }
        return previewActionItems
    }
    
    deinit
    {
        // Explicitly stop emulatorCore _before_ we remove ourselves as observer
        // so we can wait until stopped before restoring save files (again).
        self.emulatorCore?.stop()
        
        self.emulatorCore?.removeObserver(self, forKeyPath: #keyPath(EmulatorCore.state), context: &kvoContext)
    }
}

//MARK: - UIViewController -
/// UIViewController
extension PreviewGameViewController
{
    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        self.controllerView.isHidden = true
        
        // Temporarily prevent emulatorCore from updating gameView to prevent flicker of black, or other visual glitches
        self.emulatorCore?.remove(self.gameView)
    }
    
    override func viewWillAppear(_ animated: Bool)
    {
        super.viewWillAppear(animated)
        
        self.copySaveFiles()
    }

    override func viewDidAppear(_ animated: Bool)
    {
        super.viewDidAppear(animated)
        
        self.emulatorCoreQueue.async {
            self.emulatorCore?.start()
        }
    }
    
    override func viewWillDisappear(_ animated: Bool)
    {
        super.viewWillDisappear(animated)
        
        // Pause in viewWillDisappear and not viewDidDisappear like DeltaCore.GameViewController so the audio cuts off earlier if being dismissed
        self.emulatorCore?.pause()
    }
    
    override func viewDidDisappear(_ animated: Bool)
    {
        super.viewDidDisappear(animated)
        
        // Already stopped = we've already restored save files and removed directory.
        if self.emulatorCore?.state != .stopped
        {
            // Pre-emptively restore save files in case something goes wrong while stopping emulation.
            // This also ensures if the core is never stopped (for some reason), saves are still restored.
            self.restoreSaveFiles(removeCopyDirectory: false)
        }
    }
    
    override func viewDidLayoutSubviews()
    {
        super.viewDidLayoutSubviews()
        
        // Need to update in viewDidLayoutSubviews() to ensure bounds of gameView are not CGRect.zero
        self.updatePreviewImage()
    }

    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
    {
        guard context == &kvoContext else { return super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) }
        
        guard
            let rawValue = change?[.oldKey] as? Int,
            let previousState = EmulatorCore.State(rawValue: rawValue),
            let state = self.emulatorCore?.state
        else { return }
        
        switch state
        {
        case .running where previousState == .stopped:
            self.emulatorCoreQueue.async {
                // Pause to prevent it from starting before visible (in case user peeked slowly)
                self.emulatorCore?.pause()
                self.preparePreview()
            }
            
        case .stopped:
            // Emulation has stopped, so we can safely restore save files,
            // and also remove the directory they were copied to.
            self.restoreSaveFiles(removeCopyDirectory: true)
            
        default: break
        }
    }
}

//MARK: - Private -
private extension PreviewGameViewController
{
    func updatePreviewImage()
    {        
        if let previewImage = self.previewImage
        {
            self.gameView?.inputImage = CIImage(image: previewImage)
        }
        else
        {
            self.gameView?.inputImage = nil
        }
    }
    
    func preparePreview()
    {
        var previewSaveState = self.previewSaveState
        
        if let saveState = self.previewSaveState as? SaveState
        {
            saveState.managedObjectContext?.performAndWait {
                previewSaveState = DeltaCore.SaveState(fileURL: saveState.fileURL, gameType: saveState.gameType)
            }
        }
        
        if let saveState = previewSaveState
        {
            do
            {
                try self.emulatorCore?.load(saveState)
            }
            catch EmulatorCore.SaveStateError.doesNotExist
            {
                print("Save State does not exist.")
            }
            catch
            {
                print(error)
            }
        }
        
        self.emulatorCore?.updateCheats()
        
        // Re-enable emulatorCore to update gameView again
        self.emulatorCore?.add(self.gameView)
        
        self.emulatorCore?.resume()
    }
    
    func copySaveFiles()
    {
        guard let game = self.game as? Game, let gameSave = game.gameSave else { return }
        
        self.copiedSaveFiles.removeAll()
        
        let fileURLs = gameSave.syncableFiles.lazy.map { $0.fileURL }
        for fileURL in fileURLs
        {
            do
            {
                let destinationURL = self.temporaryDirectoryURL.appendingPathComponent(fileURL.lastPathComponent)
                try FileManager.default.copyItem(at: fileURL, to: destinationURL, shouldReplace: true)
                
                self.copiedSaveFiles.append((fileURL, destinationURL))
                print("Copied save file:", fileURL.lastPathComponent)
            }
            catch
            {
                print("Failed to back up save file \(fileURL.lastPathComponent).", error)
            }
        }
    }
    
    func restoreSaveFiles(removeCopyDirectory: Bool)
    {
        for (originalURL, copyURL) in self.copiedSaveFiles
        {
            do
            {
                try FileManager.default.copyItem(at: copyURL, to: originalURL, shouldReplace: true)
                print("Restored save file:", originalURL.lastPathComponent)
            }
            catch
            {
                print("Failed to restore copied save file \(copyURL.lastPathComponent).", error)
            }
        }
        
        if removeCopyDirectory
        {
            do
            {
                try FileManager.default.removeItem(at: self.temporaryDirectoryURL)
            }
            catch
            {
                print("Failed to remove preview temporary directory.", error)
            }
        }
    }
}
