/*
 *  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 Foundation

/**
 * A simulated temperature sensor for use in the samples. The sensor has
 * temperature, maximum temperature threshold, and minimum temperature
 * threshold attributes.
 */
public class TemperatureSensor {

    // Whether the sensor is powered on (true), or off (false)
    private var power:Bool = false
    // The time (measured in EPOCH) at which the system was powered ON or reset
    private var startTime:Int64 = 0
    // The minimum value measured by the sensor since power ON or reset
    
    private var minTemp:Double = .greatestFiniteMagnitude
    // The maximum value measured by the sensor since power ON or reset
    private var maxTemp:Double = .leastNormalMagnitude
    // The minimum threshold is in the range [-20,0]
    private var minThreshold:Int = 0
    // The maximum threshold is in the range [65,80]
    private var maxThreshold:Int = 70
    // A unique identifier for this sensor
    private var hardwareId:String = ""
    
    // The TemperatureSensor simulates temperature data by generating values that
    // fluctuate around the 'set point', arbitrarily set to 65 Celsius Degrees.
    private var setPoint: Double = 65.0
    // amplitude is the maximum deviation from the 'set point'

    private var amplitude:Double = 0.0
    // the simulated data
    private var temperatures:[Double]?
    // index into temperature data
    private var index:Int = 0
    
    /**
     * Create a TemperatureSensor
     * - parameter id: a unique identifier for this sensor
     */
    public init(id: String) {
        hardwareId = id
        
        let persistentData = UserDefaults.standard
        if let min = persistentData.object(forKey: hardwareId + "minThreshold") {
            minThreshold = min as! Int
        } else {
            minThreshold = 0
            persistentData.set(minThreshold, forKey: hardwareId + "minThreshold")
        }
        if let max = persistentData.object(forKey: hardwareId + "maxThreshold") {
            maxThreshold = max as! Int
        } else {
            maxThreshold = 70
            persistentData.set(maxThreshold, forKey: hardwareId + "maxThreshold")
        }
        // Add a small offset to the 0.5 as defined in JCL the radian conversion
        // is not the same and this ensures that there are temperatures greater
        // than the threshold
        amplitude = Double(maxThreshold) - setPoint + 0.525
        temperatures = createTemperatureData(setPoint:setPoint,
                                             amplitude:amplitude)
        power(on: true)
    }
    
    /**
     * Set the power on or off.
     * - parameter on: `true` to set power on, `false` to set power off
     */
    public func power(on: Bool) {
        if on {
            if self.power {
                return
            }
            self.startTime = Int64(Date().timeIntervalSince1970)
            self.power = true
            reset()
        } else {
            self.power = false
        }
    }
    
    /**
     * Get whether or not the sensor is powered on.
     * - returns: `true`, if the sensor is powered on.
     */
    public func isPoweredOn() -> Bool {
        return self.power
    }
    
    /**
     * An action to reset the miniumum and maximum measured temperature to
     * the current value.
     */
    public func reset() {
        let temp:Double = temperatures![index]
        self.minTemp = temp
        self.maxTemp = temp
    }
    
    /**
     * Get the current temperature value.
     * The simulated readings will fluctuate +/- 2 degrees.
     *
     * - returns: temperature value
     */
    public func getTemp() -> Double {
        
        let temp:Double = temperatures![index]
        index = (index + 1) % temperatures!.count
        
        if temp < minTemp {
            minTemp = temp
        } else if temp > maxTemp {
            maxTemp = temp
        }
        return temp
    }
    
    /**
     * Get the temperature units (scale)
     * - returns: the temperature units
     */
    public func getUnit() -> String {
        return "\u{00B0}C" //"Celsius"
    }
    
    /**
     * Get the minimum temperature since the last power on or reset.
     * - returns: the minimum temperature since the last power on or reset
     */
    public func getMinTemp() -> Double {
        return self.minTemp
    }
    
    /**
     * Get the maximum temperature since the last power on or reset.
     * - returns: the maximum temperature since the last power on or reset
     */
    public func getMaxTemp() -> Double {
        return self.maxTemp
    }
    
    /**
     * Get the minumum threshold value.
     * - returns: the minimum threshold
     */
    public func getMinThreshold() -> Int {
        return self.minThreshold
    }
    
    /**
     * Set the minimum degrees Celsius threshold for alerts. The value is
     * clamped to the range [-20..0].
     * - parameter threshold: a value between -20 and 0
     */
    public func setMinThreshold(threshold: Int) {
        if threshold < -20 {
            minThreshold = -20
        } else if threshold > 0 {
            minThreshold = 0
        } else {
            minThreshold = threshold
        }
        let persistentData = UserDefaults.standard
        persistentData.set(threshold, forKey: hardwareId + "minThreshold")
    }
    
    /**
     * Get the maximum threshold value.
     * - returns: the maximum threshold
     */
    public func getMaxThreshold() -> Int {
        return self.maxThreshold
    }
    
    /**
     * Set the maximum degrees Celsius threshold for alerts. The value is
     * clamped to the range [65..80].
     * - parameter threshold: a value between 65 and 80
     */
    public func setMaxThreshold(threshold: Int) {
        if threshold < 65 {
            maxThreshold = 65
        } else if threshold > 80 {
            maxThreshold = 80
        } else {
            maxThreshold = threshold
        }
        let persistentData = UserDefaults.standard
        persistentData.set(threshold, forKey: hardwareId + "maxThreshold")
    }
    
    /**
     * Get the time at which the device was powered on or reset.
     * - returns: the device start time
     */
    public func getStartTime() -> Int64 {
        return self.startTime
    }
    /**
     * Get the manufacturer name, which can be used as part of the
     * device meta-data.
     * - returns the manufacturer name
     */
    public func getManufacturer() -> String {
        return "Sample"
    }
    
    /**
     * Get the model number, which can be used as part of the
     * device meta-data.
     * - returns: the model number
     */
    public func getModelNumber() -> String {
        return "MN-" + self.hardwareId
    }
    
    /**
     * Get the serial number, which can be used as part of the
     * device meta-data.
     * - returns: the serial number
     */
    public func getSerialNumber() -> String {
        return "SN-" + self.hardwareId
    }
    
    /**
     * Get the hardware id, which can be used as part of the
     * device meta-data.
     * - returns: the hardware id
     */
    public func getHardwareId() -> String {
        return self.hardwareId
    }
    
    private func createTemperatureData(setPoint:Double,
                                       amplitude:Double) -> [Double]{
        let delta:Int = 10
        let dataPoints:Int = 360/delta
        var data:[Double] = [Double]()
        var angle:Int = 0
        
        for _ in 0..<dataPoints {
            data.append(calculateTemp(setPoint:setPoint, amplitude:amplitude,
                                      angle:angle))
            angle += delta
        }
    
        // introduce some out of spec values.
        // Data model has temp attribute range: [-20, 80]
        let badValues:[Double] = [ -25.0, -22.0, 83.0, 85.0 ]
        
        //for (int index = 0; index < badValues.length; index++) {
        for i in 0..<badValues.count {
            // range is 0 to data.length - 1
            let r:UInt32 = arc4random_uniform(UInt32(data.count))
            data[Int(r)] = badValues[i]
        }
        return data
    }
    
    private func calculateTemp(setPoint:Double, amplitude:Double, angle:Int) -> Double {
        let degrees = Measurement(value: Double(angle), unit: UnitAngle.degrees)
        let radians = degrees.converted(to: .radians)
        let delta:Double = amplitude * sin(radians.value)
        // normal temp sensors don't have readings such as 21.890890309842234
        let l:Int64 = Int64((setPoint + delta) * 100.0)
        return Double(l / 100)
    }
}
