/*
 *  Copyright © 2016, 2017, Oracle and/or its affiliates. All rights reserved.
 *  This software is dual-licensed to you under the MIT License (MIT) and
 *  the Universal Permissive License (UPL). See the LICENSE file in the root
 *  directory for license terms. You may choose either license, or both.
 */

import UIKit
import EnterpriseLib

class MotionActivatedCameraView: UIViewController, UITextViewDelegate,
                                 UITextFieldDelegate {
    
    @objc let MOTION_ACTIVATED_CAMERA_MODEL_URN:String =
        "urn:com:oracle:iot:device:motion_activated_camera"
    
    var virtualDevice: VirtualDevice?
    @objc var cvs: [String] = []
    @objc var idx = 0
    
    private class MACVSyncCallback : SyncCallback {
        var block:((SyncEvent)->())!
        override init() {
            super.init()
        }
        convenience init(block: @escaping (SyncEvent)->()) {
            self.init()
            self.block = block
        }
        override public func onSync(event:SyncEvent) {
            block(event)
        }
    }
    private var onChangeSyncCallback:MACVSyncCallback?
    private var dataSyncCallback:MACVSyncCallback?
    private var alertSyncCallback:MACVSyncCallback?
    private var errorSyncCallback:MACVSyncCallback?
    
    @IBOutlet var motionActivatedViewOutlet: UIView!
    @IBOutlet weak var appNameLabelOutlet: UILabel!
    @IBOutlet weak var deviceNameLabelOutlet: UILabel!
    @IBOutlet var deviceIdLabelOutlet: UILabel!
    @IBOutlet weak var manufacturerLabelOutlet: UILabel!
    @IBOutlet weak var modelNumberLabelOutlet: UILabel!
    @IBOutlet weak var serialNumberLabelOutlet: UILabel!
    @IBOutlet weak var recordTimeLabelOutlet: UILabel!
    @IBOutlet weak var recordTimeTextFieldOutlet: UITextField!

    @IBOutlet weak var downloadDirectory: UILabel!
    @IBOutlet weak var imageMessageOutlet: UITextView!
    @IBOutlet weak var recordButtonOutlet: UIButton!
   
    override func viewDidLoad() {
        super.viewDidLoad()
        
        imageMessageOutlet.delegate = self
        recordTimeTextFieldOutlet.delegate = self

        for dev in activeDevices {
            if dev.getEndpointId() == monitoredDevice {
                virtualDevice = dev
            }
        }
        
        // Monitor the selected device for uploads and update the
        // UI view accordingly.
        if virtualDevice == nil {
            return
        }
        
        onChangeSyncCallback =
            MACVSyncCallback(block: { (event) in
                print("#### In onChange SyncCallback ####")
                self.syncEventMsg(cb:"onChange", event:event)
            })
        
        
        dataSyncCallback =
            MACVSyncCallback(block: { (event) in
                print("#### In Data SyncCallback ####")
                self.syncEventMsg(cb:"onData", event:event)
            })
        
        alertSyncCallback =
            MACVSyncCallback(block: { (event) in
                print("#### In Alert SyncCallback ####")
                self.syncEventMsg(cb:"onAlert", event:event)
            })
        
        
        errorSyncCallback =
            MACVSyncCallback(block: { (event) in
                print("#### In Error SyncCallback ####")
                self.syncEventMsg(cb:"onError", event:event)
            })

        registerCallbacks()
    }
    
    override func viewWillAppear (_ animated: Bool) {

        deviceNameLabelOutlet.text = "Motion Activated Camera"
        deviceIdLabelOutlet.text = monitoredDevice
        appNameLabelOutlet.font = .boldSystemFont(ofSize: 18.0)
        appNameLabelOutlet.text = kEnterpriseHeadLine
        manufacturerLabelOutlet.text = kManufacturer + monitoredDeviceManufacturer
        modelNumberLabelOutlet.text = kModelNumber + monitoredDeviceModelNumber
        serialNumberLabelOutlet.text = kSerialNumber + monitoredDeviceSerialNumber
    
        let id = getImageDir()
        let dd = String(id[getDocumentPath(file: "").endIndex..<id.endIndex])
        downloadDirectory.text = dd
        
        //self.navigationItem.setHidesBackButton(true, animated: false)
        
        for dev in activeDevices {
            if dev.getEndpointId() == monitoredDevice {
                virtualDevice = dev
            }
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // actions
    
    @objc var canRecord:Bool = false
    
    // Action to apply changes on humidity threshold.
    @IBAction func recordButtonAction(_ sender: UIButton) {
        defer {
            canRecord = false
            recordTimeTextFieldOutlet.text = ""
            recordButtonOutlet.isEnabled = false
        }
        if !canRecord {
            return
        }
        print ("#### Sending record action ####")
        
        guard let recordTimeString = recordTimeTextFieldOutlet.text,
              let recordTimeInt = Int(recordTimeString), recordTimeInt > 0 else {
            canRecord = false
            showError(controller: self, alertTitle: "Record Time Setting Alert",
                      message: "The value must be an integer greater than 0",
                        actionTitle: "OK",
                      handler: { (action:UIAlertAction?) -> () in
                self.recordTimeTextFieldOutlet.text = ""
            })
            return
        }

        do {
            let newValue = NSNumber(value: recordTimeInt)
            let _ = try self.virtualDevice?.call(actionName: "record",
                                                 argument: newValue as AnyObject)
            return
        } catch ClientError.argument(let error) {
            print(error)
            showError(controller: self, alertTitle: "Record Action Alert",
                      message: error, actionTitle: "OK",
                      handler: { (action:UIAlertAction?) -> () in
            })
        } catch  {
            print(error)
            showError(controller: self, alertTitle: "Record Action Alert",
                      message: "\(error)", actionTitle: "OK",
                      handler: { (action:UIAlertAction?) -> () in
            })
        }
    }
    
    func textFieldShouldReturn(_ userText: UITextField) -> Bool {
        userText.resignFirstResponder()
        userText.endEditing(true)
        return false;
    }
    
    /**
     * Check trusted asset store to determine if asset is already provisioned
     */
    func textFieldDidEndEditing(_ textField: UITextField) {
        if let seconds = recordTimeTextFieldOutlet.text {
            if seconds != "" {
                canRecord = true
                recordButtonOutlet.isEnabled = true
            }
        }
    }
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        canRecord = false
        recordButtonOutlet.isEnabled = false
    }

    private func update(text:String) {
        DispatchQueue.main.async(execute: {
            let outtext = self.imageMessageOutlet.text + "\n" + text
            self.imageMessageOutlet.text = outtext
            // Force text field to (invisibly) scroll until latest line is at bottom
            let bottom: NSRange = NSMakeRange(self.imageMessageOutlet.text.count - 10, 1)
            self.imageMessageOutlet.scrollRangeToVisible((bottom))
        })
    }
    
    @objc func registerCallbacks() {
        
        virtualDevice?.setOnChange(callback: { (event: ChangeEvent) in
            let msg = "onChange: "
            self.processNamedValue(prefix: msg, namedValue: event.getNamedValue(),
                                   soCB: self.onChangeSyncCallback!)
        })
        
        virtualDevice?.setOnAlert(callback: { (event: AlertEvent) in
            
            let longUrn = event.getURN()
            let etime = event.getEventTime()
            let index = longUrn.range(of: ":", options: .backwards)?.lowerBound
            let shortUrn = String(longUrn[index!..<longUrn.endIndex])
            
            let msgFormat = "onAlert: %@ : %@ : "
            let msg:String = String(format: msgFormat,
                                    ISO8601DateFormatter().string(from: etime),
                                    shortUrn)
            
            self.processNamedValue(prefix: msg, namedValue: event.getNamedValue(),
                                   soCB: self.alertSyncCallback!)
        })
        
        virtualDevice?.setOnError(callback: { (event: ErrorEvent) in
            let errMessage = event.getMessage()
            print("Error message: " + errMessage)
            
            if errMessage == "User Authentication failed!" {
                appDeviceModelsList.removeAll()
                dmArray.removeAll()
                urnArray.removeAll()
                
                activeDevices.removeAll()
                deviceManufacturers.removeAll()
                deviceModelNumbers.removeAll()
                deviceSerialNumbers.removeAll()
                
                do {
                    try ecl.close()
                } catch {
                    print("Error while closing enterprise client")
                }
                
                // Should post alert popup
                DispatchQueue.main.async(execute: {
                    let firstViewController = self.storyboard?.instantiateViewController(
                        withIdentifier: "ServerLoginView") as! ServerLoginView
                    self.navigationController?.pushViewController(firstViewController,
                                                                  animated: false)
                })
                return
            }
            let errorFormat = "onError: %@ : "
            let msg = String(format: errorFormat, errMessage)
            self.processNamedValue(prefix: msg, namedValue: event.getNamedValue(),
                                   soCB: self.errorSyncCallback!)
        })
        
        // this callback will  be invoked when data is received for the format
        // urn:com:oracle:iot:device:motion_activated_camera:recording
        virtualDevice?.setOnData(formatURN: MOTION_ACTIVATED_CAMERA_MODEL_URN + ":recording",
                                 callback: { event in
            let etime = event.getEventTime()
            // Just use recording the model urn is always the same.
            let urn = ":recording" // "event.getURN()
            
            let format:String = "onData: %@ : %@ : "
            let msg:String = String(format: format,
                                    ISO8601DateFormatter().string(from: etime),
                                    urn)
            self.processNamedValue(prefix: msg, namedValue: event.getNamedValue(),
                                   soCB: self.dataSyncCallback!)
        })
    }
    
    private func processNamedValue(prefix:String, namedValue:NamedValue,
                           soCB: MACVSyncCallback) {
        var msg = prefix + "\n"
        var _namedValue:NamedValue? = namedValue
        var sep = ""
        let format = "%@   \"%@\"=%@"
        while _namedValue != nil {
            let name:String = _namedValue!.getName()
            let value:AnyObject = _namedValue!.getValue() ?? "nil" as AnyObject
            if name == "imageTime" {
                msg += String(format: format, sep, name,
                    ISO8601DateFormatter().string(from: Date(
                        timeIntervalSince1970: TimeInterval(truncating: value as! NSNumber))))
            } else if value is Date {
                msg += String(format: format, sep, name,
                              ISO8601DateFormatter().string(from: value as! Date))
            } else if value is StorageObject {
                // Oracle Cloud Storage
                let so:StorageObject = value as! StorageObject
                self.getStorageObject(so: so, callback: soCB)
                msg += String(format: format, sep, name, so.getName())
            } else if value is ExternalObject {
                // No or unknown storage server
                let eo:ExternalObject = value as! ExternalObject
                msg += String(format: format, sep, name, eo.getURI())
            } else {
                msg += String(format: format, sep, name, "\(value)")
            }
            _namedValue = _namedValue!.next();
            sep = "\n"
        }
        self.update(text: msg)
    }

    @objc let syncEventFormat = "SyncEvent(%@)\n  %@ name: %@\n  syncStatus: %@\n  path: %@"
    
    private func syncEventMsg(cb:String, event:SyncEvent) {
        let so:StorageObject = event.getSource()
        // This is composed of the "getImageDir" and the output file name
        // Get just the path substring, last path components
        let op = so.getOutputPath()!
        let filepath = String(op[getImageDir().endIndex..<op.endIndex])
        let msg = String(format: syncEventFormat, cb,
                         event.getName()!, so.getName(), "\(so.getSyncStatus())", filepath)
        self.update(text: msg)
    }

    // TODO: Should probably be triggered by a download button.
    private func getStorageObject(so:StorageObject, callback: MACVSyncCallback) {
        // The client library gives control to the application to decide
        // whether and where to download content from the Storage Cloud Service.
        so.setOnSync(callback: callback);
        do {
            try so.setOutputPath(outputPath: getImageDir() + so.getName())
            // sync happens in the background and calls the syncCallback when done
            try so.sync()
        } catch  {
            print(error)
        }
    }
}
