// Documentation for TI SensorTag CC2541:
// http://processors.wiki.ti.com/index.php/SensorTag_User_Guide
// http://processors.wiki.ti.com/index.php/File:BLE_SensorTag_GATT_Server.pdf

;(function()
{
	evothings.tisensortag.ble.CC2541 = {}

	/**
	 * @namespace
	 * @description Internal implementation of JavaScript library for the TI SensorTag CC2541.
	 * @alias evothings.tisensortag.ble.CC2541
	 */
	var sensortag = {}

	evothings.tisensortag.ble.CC2541 = sensortag

	/**
	 * Create a SensorTag CC2541 instance.
	 * @returns {@link evothings.tisensortag.SensorTagInstance}
	 * @private
	 */
	sensortag.addInstanceMethods = function(anInstance)
	{
		/**
		 * @namespace
		 * @alias evothings.tisensortag.SensorTagInstanceBLE_CC2541
		 * @description SensorTag CC2541 instance object.
		 * @public
		 */
		var instance = anInstance

		// Add generic BLE instance methods.
		evothings.tisensortag.ble.addInstanceMethods(instance)

		/**
		 * The device model.
		 * @instance
		 * @public
		 */
		instance.deviceModel = 'CC2541'

		/**
		 * Determine if a BLE device is a SensorTag CC2541.
		 * Checks for the CC2541 using the advertised name.
		 * @instance
		 * @public
		 */
		instance.deviceIsSensorTag = function(device)
		{
			return (device != null) &&
				(device.advertisementData != null) &&
				(device.advertisementData.kCBAdvDataLocalName ==
					'SensorTag')
		}

		/**
		 * Public. Set the accelerometer notification callback.
		 * @param fun - success callback called repeatedly: fun(data)
		 * @param interval - accelerometer rate in milliseconds.
		 * @instance
		 * @public
		 */
		instance.accelerometerCallback = function(fun, interval)
		{
			instance.accelerometerFun = fun
			instance.accelerometerConfig = [1] // on
			instance.accelerometerInterval = interval
			instance.requiredServices.push(instance.ACCELEROMETER.SERVICE)

			return instance
		}

		/**
		 * Public. Set the gyroscope notification callback.
		 * @param fun - success callback called repeatedly: fun(data)
		 * @param interval - gyroscope rate in milliseconds.
		 * @param axes - the axes to enable ((z << 2) | (y << 1) | x)
		 * Axis parameter values are:
		 * 1 = X only, 2 = Y only,
		 * 3 = X and Y, 4 = Z only,
		 * 5 = X and Z, 6 = Y and Z,
		 * 7 = X, Y and Z.
		 * @instance
		 * @public
		 */
		instance.gyroscopeCallback = function(fun, interval, axes)
		{
			if ('undefined' == typeof axes)
			{
				axes = 7 // 7 = enable all axes
			}
			instance.gyroscopeFun = fun
			instance.gyroscopeConfig = [axes]
			instance.gyroscopeInterval = Math.max(100, interval)
			instance.requiredServices.push(instance.GYROSCOPE.SERVICE)

			return instance
		}

		/**
		 * Public. Set the magnetometer notification callback.
		 * @param fun - success callback called repeatedly: fun(data)
		 * @param interval - magnetometer rate in milliseconds.
		 * @instance
		 * @public
		 */
		instance.magnetometerCallback = function(fun, interval)
		{
			instance.magnetometerFun = fun
			instance.magnetometerConfig = [1] // on
			instance.magnetometerInterval = interval
			instance.requiredServices.push(instance.MAGNETOMETER.SERVICE)

			return instance
		}

		/**
		 * Internal.
		 * @instance
		 * @private
		 */
		instance.activateSensorsImpl = function()
		{
			// Debug logging.
			//console.log('-------------------- SERVICES --------------------')
			//sensortag.logServices(instance.device)
			//console.log('---------------------- END -----------------------')

			instance.temperatureOn()
			instance.humidityOn()
			instance.barometerOn()
			instance.accelerometerOn()
			instance.magnetometerOn()
			instance.gyroscopeOn()
			instance.keypressOn()
		}

		/**
		 * SensorTag CC2541.
		 * Public. Turn on accelerometer notification.
		 * @instance
		 * @public
		 */
		instance.accelerometerOn = function()
		{
			instance.sensorOn(
				instance.ACCELEROMETER,
				instance.accelerometerConfig,
				instance.accelerometerInterval,
				instance.accelerometerFun
			)

			return instance
		}

		/**
		 * SensorTag CC2541.
		 * Public. Turn off accelerometer notification.
		 * @instance
		 * @public
		 */
		instance.accelerometerOff = function()
		{
			instance.sensorOff(instance.ACCELEROMETER)

			return instance
		}

		/**
		 * SensorTag CC2541.
		 * Public. Turn on gyroscope notification.
		 * @instance
		 * @public
		 */
		instance.gyroscopeOn = function()
		{
			instance.sensorOn(
				instance.GYROSCOPE,
				instance.gyroscopeConfig,
				instance.gyroscopeInterval,
				instance.gyroscopeFun
			)

			return instance
		}

		/**
		 * Public. Turn off gyroscope notification (SensorTag CC2541).
		 * @instance
		 * @public
		 */
		instance.gyroscopeOff = function()
		{
			instance.sensorOff(instance.GYROSCOPE)

			return instance
		}

		/**
		 * Public. Turn on magnetometer notification (SensorTag CC2541).
		 * @instance
		 * @public
		 */
		instance.magnetometerOn = function()
		{
			instance.sensorOn(
				instance.MAGNETOMETER,
				instance.magnetometerConfig,
				instance.magnetometerInterval,
				instance.magnetometerFun
			)

			return instance
		}

		/**
		 * Public. Turn off magnetometer notification (SensorTag CC2541).
		 * @instance
		 * @public
		 */
		instance.magnetometerOff = function()
		{
			instance.sensorOff(instance.MAGNETOMETER)

			return instance
		}

		/**
		 * SensorTag CC2541.
		 * Public. Turn on barometer notification.
		 * @instance
		 * @public
		 */
		instance.barometerOn = function()
		{
			// First fetch barometer calibration data,
			// then enable the barometer.
			instance.barometerCalibrate(function()
			{
				instance.sensorOn(
					instance.BAROMETER,
					instance.barometerConfig,
					instance.barometerInterval,
					instance.barometerFun
				)
			})

			return instance
		}

		/**
		 * SensorTag CC2541.
		 * Private. Enable barometer calibration mode.
		 * @instance
		 * @private
		 */
		instance.barometerCalibrate = function(callback)
		{
			instance.device.writeCharacteristic(
				instance.BAROMETER.CONFIG,
				new Uint8Array([2]),
				function()
				{
					instance.device.readCharacteristic(
						instance.BAROMETER.CALIBRATION,
						function(data)
						{
							data = new Uint8Array(data)
							instance.barometerCalibrationData =
							[
								evothings.util.littleEndianToUint16(data, 0),
								evothings.util.littleEndianToUint16(data, 2),
								evothings.util.littleEndianToUint16(data, 4),
								evothings.util.littleEndianToUint16(data, 6),
								evothings.util.littleEndianToInt16(data, 8),
								evothings.util.littleEndianToInt16(data, 10),
								evothings.util.littleEndianToInt16(data, 12),
								evothings.util.littleEndianToInt16(data, 14)
							]
							callback()
						},
						function(error)
						{
							console.log('CC2541 Barometer calibration failed: ' + error)
						})
				},
				instance.errorFun)

			return instance
		}

		/**
		 * SensorTag CC2541.
		 * Calculate accelerometer values from raw data.
		 * @param data - an Uint8Array.
		 * @return Object with fields: x, y, z.
		 * @instance
		 * @public
		 */
		instance.getAccelerometerValues = function(data)
		{
			// Set divisor based on firmware version.
			var divisors = {x: 16.0, y: -16.0, z: 16.0}

			// Calculate accelerometer values.
			var ax = evothings.util.littleEndianToInt8(data, 0) / divisors.x
			var ay = evothings.util.littleEndianToInt8(data, 1) / divisors.y
			var az = evothings.util.littleEndianToInt8(data, 2) / divisors.z

			// Return result.
			return { x: ax, y: ay, z: az }
		}

		/**
		 * SensorTag CC2541.
		 * Calculate gyroscope values from raw data.
		 * @param data - an Uint8Array.
		 * @return Object with fields: x, y, z.
		 * @instance
		 * @public
		 */
		instance.getGyroscopeValues = function(data)
		{
			// Calculate gyroscope values. NB: x,y,z has a weird order.
			var gy = -evothings.util.littleEndianToInt16(data, 0) * 500.0 / 65536.0
			var gx =  evothings.util.littleEndianToInt16(data, 2) * 500.0 / 65536.0
			var gz =  evothings.util.littleEndianToInt16(data, 4) * 500.0 / 65536.0

			// Return result.
			return { x: gx, y: gy, z: gz }
		}

		/**
		 * SensorTag CC2541.
		 * Calculate magnetometer values from raw data.
		 * @param data - an Uint8Array.
		 * @return Object with fields: x, y, z.
		 * @instance
		 * @public
		 */
		instance.getMagnetometerValues = function(data)
		{
			// Magnetometer values (Micro Tesla).
			var mx = evothings.util.littleEndianToInt16(data, 0) * (2000.0 / 65536.0) * -1
			var my = evothings.util.littleEndianToInt16(data, 2) * (2000.0 / 65536.0) * -1
			var mz = evothings.util.littleEndianToInt16(data, 4) * (2000.0 / 65536.0)

			// Return result.
			return { x: mx, y: my, z: mz }
		}

		/**
		 * SensorTag CC2541.
		 * Calculate barometer values from raw data.
		 * @instance
		 * @public
		 */
		instance.getBarometerValues = function(data)
		{
			var t = evothings.util.littleEndianToInt16(data, 0)
			var p = evothings.util.littleEndianToUint16(data, 2)
			var c = instance.barometerCalibrationData

			var S = c[2] + ((c[3] * t) / 131072) + ((c[4] * (t * t)) / 17179869184.0)
			var O = (c[5] * 16384.0) + (((c[6] * t) / 8)) + ((c[7] * (t * t)) / 524288.0)
			var Pa = (((S * p) + O) / 16384.0)
			var pInterpreted = Pa / 100.0

			return { pressure: pInterpreted }
		}

		/**
		 * Calculate temperature values from raw data.
		 * @param data - an Uint8Array.
		 * @return Object with fields: ambientTemperature, targetTemperature.
		 * @instance
		 * @public
		 */
		instance.getTemperatureValues = function(data)
		{
			// Calculate ambient temperature (Celsius).
			var ac = evothings.util.littleEndianToUint16(data, 2) / 128.0

			// Calculate target temperature (Celsius, based on ambient).
			var Vobj2 = evothings.util.littleEndianToInt16(data, 0) * 0.00000015625
			var Tdie = ac + 273.15
			var S0 =  6.4E-14	// calibration factor
			var a1 =  1.750E-3
			var a2 = -1.678E-5
			var b0 = -2.940E-5
			var b1 = -5.700E-7
			var b2 =  4.630E-9
			var c2 = 13.4
			var Tref = 298.15
			var S = S0 * (1 + a1 * (Tdie - Tref) + a2 * Math.pow((Tdie - Tref), 2))
			var Vos = b0 + b1 * (Tdie - Tref) + b2 * Math.pow((Tdie - Tref), 2)
			var fObj = (Vobj2 - Vos) + c2 * Math.pow((Vobj2 - Vos), 2)
			var tObj = Math.pow(Math.pow(Tdie, 4 ) + (fObj / S), 0.25)
			var tc = tObj - 273.15

			// Return result.
			return { ambientTemperature: ac, targetTemperature: tc }
		}

		/**
		 * Public. Checks if the Temperature sensor is available.
		 * @preturn true if available, false if not.
		 * @instance
		 * @public
		 */
		instance.isTemperatureAvailable = function()
		{
			return true
		}

		/**
		 * Public. Checks if the accelerometer sensor is available.
		 * @preturn true if available, false if not.
		 * @instance
		 * @public
		 */
		instance.isAccelerometerAvailable = function()
		{
			return true
		}

		/**
		 * Public. Checks if the humidity sensor is available.
		 * @preturn true if available, false if not.
		 * @instance
		 * @public
		 */
		instance.isHumidityAvailable = function()
		{
			return true
		}

		/**
		 * Public. Checks if the magnetometer sensor is available.
		 * @preturn true if available, false if not.
		 * @instance
		 * @public
		 */
		instance.isMagnetometerAvailable = function()
		{
			return true
		}

		/**
		 * Public. Checks if the barometer sensor is available.
		 * @preturn true if available, false if not.
		 * @instance
		 * @public
		 */
		instance.isBarometerAvailable = function()
		{
			return true
		}

		/**
		 * Public. Checks if the gyroscope sensor is available.
		 * @preturn true if available, false if not.
		 * @instance
		 * @public
		 */
		instance.isGyroscopeAvailable = function()
		{
			return true
		}

		/**
		 * Public. Checks if the keypress sensor is available.
		 * @preturn true if available, false if not.
		 * @instance
		 * @public
		 */
		instance.isKeypressAvailable = function()
		{
			return true
		}

		// Finally, return the SensorTag instance object.
		return instance
	}
})()