/**
 *	@project bcslib
 *	@author Brent Rahn <brent.rahn@gmail.com>
 */

/*jshint -W065 */
/**
 * @namespace
 */
var BCS = {
	version: '0.2.0'
};

BCS.Helpers = (function () {
	/**
	 * Creates a `BCS.Helpers` Object
	 *
	 * Helper methods for accessing common values from the BCS.
	 * Added to BCS.Device by it's constructor
	 * @constructs BCS.Helpers
	 * @param {BCS.Device} device
	 */
	var Helpers = function (device) {
		this.device = device;
	};

	/**
	 * Get Temperature Probe objects
	 * @returns {Promise.Object[]} Promise of a list of temperature probes
	 */
	Helpers.prototype.getProbes = function () {
		var promises = [];
		for(var i = 0; i < this.device.probeCount; i++) {
			promises.push(this.device.read('temp/' + i));
		}
		return Promise.all(promises);
	};

	/**
	 * Get current temperature values
	 * @returns {Promise.number[]} Promise of a list of temperatures
	 */
	Helpers.prototype.getTempValues = function () {
		return this.device.read('temp').then(function (response) {
			return Promise.all(response.map(function (temp) { return temp / 10.0; }));
		});
	};

	/**
	 * Get Discrete Input objects
	 * @returns {Promise.Object[]} Promise of a list of discrete inputs
	 */
	Helpers.prototype.getDins = function () {
		var promises = [];
		for(var i = 0; i < this.device.inputCount; i++) {
			promises.push(this.device.read('din/' + i));
		}
		return Promise.all(promises);
	};

	/**
	 * Get current input values
	 * @returns {Promise.number[]} Promise of a list of on/off values for Dins
	 */
	Helpers.prototype.getDinValues = function () {
		return this.device.read('din');
	};

	/**
	 * Get Outputs objects
	 * @returns {Promise.Object[]} Promise of a list of outputs
	 */
	Helpers.prototype.getOutputs = function () {
		var promises = [];
		for(var i = 0; i < this.device.outputCount; i++) {
			promises.push(this.device.read('output/' + i));
		}
		return Promise.all(promises);
	};

	/**
	 * Get current output values
	 * @returns {Promise.number[]} Promise of a list of on/off values for outputs
	 */
	Helpers.prototype.getOutputValues = function () {
		return this.device.read('output');
	};

	/**
	 * Get current timer values
	 * @param {Number} Process number
	 * @returns {Promise.Array.<BCS.Time>} Promise of a list of `BCS.Time` objects
	 */
	Helpers.prototype.getTimerValues = function (process) {
		return this.device.read('process/' + process + '/timer')
			.then(function (response) {
				return Promise.all(response.map(function (timer) { return new BCS.Time(timer.value); }));
			});
	};

	/**
	 * Get current timer values as strings
	 * @param {Number} Process number
	 * @returns {Promise.string[]} Promise of a list of times as strings
	 */
	Helpers.prototype.getTimerStrings = function (process) {
		return this.getTimerValues(process)
			.then(function (timers) {
				return Promise.all(timers.map(function (timer) { return timer.toString(); }));
			});
	};

	/**
	 * Get running processes
	 * @returns {Promise.Object[]} Promise of a list of running processes with some runtime data
	 */
	Helpers.prototype.getRunningProcesses = function () {
		return this.device.read('poll')
		.then(function (poll) {
			return Promise.all(poll.process.map(function (p, i) {
					p.id = i;
					return p;
				})
				.filter(function (p) {return p.running;}));
		});
	};

	/**
	 * Get Processes objects
	 * @returns {Promise.Object[]} Promise of a list of processes
	 */
	Helpers.prototype.getProcesses = function () {
		var promises = [];
		for(var i = 0; i < 8; i++) {
			promises.push(this.device.read('process/' + i));
		}
		return Promise.all(promises);
	};


	return Helpers;
}());

BCS.Device = (function () {
	/**
	 * Handles communication with BCS.
	 * @constructs BCS.Device
	 * @param {string} address IP Address for BCS
	 * @param {options} options for BCS.  Currently only supports authentication. ex. `{auth: {username: 'x', password: 'y'}}`
	 * @property ready {boolean} Ready to communicate with BCS (other properties are not valid until ready is true)
	 * @property type {string} BCS type, eg `BCS-460`
	 * @property version {string} BCS firmware version, eg, `4.0.0`
	 * @property helpers {BCS.Helpers} Helpers object
	 * @property probeCount {number} The number of temp probes supported by the BCS hardware
	 * @property inputCount {number} The number of discrete inputs supported by the BCS hardware
	 * @property outputCount {number} The number of outputs supported by the BCS hardware
	 */
	var Device = function (address, options) {
		var obj = this;
		var parsedAuth;
		this.address = address;
		this.ready = false;
		this.type = null;
		this.version = null;
		this.helpers = new BCS.Helpers(this);
		this.url = (this.address.match(/^http/) ? '' : 'http://') + this.address + (this.address.match(/\/$/) ? '' : '/') + 'api/';
		this._callbacks = {};
		this.options = options || {};

		/* parse auth credentials from URL if present and auth is not set in options */
		if(!this.options.auth && this.url.match(/http:\/\/(.+):(.+)@/))
		{
			parsedAuth = this.url.match(/http:\/\/(.+):(.+)@/).slice(1);
			this.options.auth = {
				username: parsedAuth[0],
				password: parsedAuth[1]
			};
			this.url = this.url.replace(/(http:\/\/)(.+:.+@)/, '$1');
		}

		if(this.options.auth) {
			this.headers = { Authorization: 'Basic ' + btoa(this.options.auth.username + ":" + this.options.auth.password) };
		} else {
		  this.headers = {};
		}

		this.read('device')
			.then(function (body) {
				obj.ready = true;
				obj.version = body.version;
				obj.type = body.type;
				obj.trigger('ready');
			})
			.catch(function (error) {
				obj.trigger('notReady', [error]);
			});

		return this;
	};

	Device.prototype = {
		get probeCount() { return !this.ready ? null : (this.type === 'BCS-460' ? 4 : 8); },
		get inputCount() { return !this.ready ? null : (this.type === 'BCS-460' ? 4 : 8); },
		get outputCount() { return !this.ready ? null : (this.type === 'BCS-460' ? 6 : this.type === 'BCS-482' ? 16 : 18); }
	};

	/**
	 * Add an event listener
	 * @param {String} event The event to respond to.  ('ready', 'notReady')
	 * @param {callback} callback The function to be called when the event is triggered
	 */
	Device.prototype.on = function (event, callback) {
		if(this._callbacks[event] === undefined) {
			this._callbacks[event] = [callback];
		} else {
			this._callbacks[event].push(callback);
		}

		return this;
	};

	/**
	 * Trigger an event listener
	 * @private
	 * @param {String} event The event to trigger
	 * @param {Object} arg Argument to pass to the callback
	 */
	Device.prototype.trigger = function (event, arg) {
		var obj = this;
		if(this._callbacks[event] !== undefined) {
			this._callbacks[event].forEach(function (callback) {
				callback.apply(obj, arg);
			});
		}
	};

	/**
	 * Read from the BCS API
	 * @example
	 *    var bcs = BCS.Device("192.168.0.63");
	 *    bcs.read('device').then(function (response) {
	 *        alert("BCS Name:" + response.name);
	 *    });
	 * @param {String} resource The API endpoint to query
	 * @returns {Promise.Object} A Promise of the response from the API
	 */
	Device.prototype.read = function (resource) {
		return fetch(this.url + resource, {headers: this.headers})
			.then(function (body) {
				return body.json();
			});
	};

	/**
	 * Write to the BCS API
	 * @param {String} resource The API endpoint to update
	 * @param {Object} JSON object to POST to the API
	 * @returns {Promise.Object} A Promise of the response from the API
	 */
	Device.prototype.write = function (resource, data) {
		return fetch(this.url + resource, {
			method: 'POST',
			headers: this.headers,
			body: JSON.stringify(data)})
		.then(function (body) {
			return body.json();
		});
	};

	return Device;
}());

BCS.Time = (function () {
	/**
	 * Makes it easier to work with time values from the BCS
	 * @constructs BCS.Time
	 * @param {Number} time Time in tenths of a second
	 */
	var Time = function (time) {
		this.value = time / 10 || 0;
		return this;
	};

	var formatNumber = function (n) {
		if(n < 10) {
			return '0' + n;
		}

		return "" + n;
	};

	/**
	 * Return the string representation of the BCS.Time object
	 * @returns {String} The string representation of the Time
	 */
	Time.prototype.toString = function () {
		var hours = Math.floor(this.value / 3600);
		var minutes = Math.floor((this.value % 3600) / 60);
		var seconds = Math.floor(this.value % 60);
		return hours + ":" + formatNumber(minutes) + ":" + formatNumber(seconds);
	};

	/**
	 * Convert a string into a BCS.Time object
	 * @example
	 *    var timeobj = BCS.Time(1000);
	 *    BCS.Time.fromString(timeobj.toString()) == timeobj;
	 *
	 * @param {String} s The string to convert to BCS.Time in hh:mm:ss format
	 * @returns {BCS.Time} A BCS.Time object
	 */
	Time.fromString = function (s) {
		var parts = s.split(':').reverse();
		var value = 0;

		for(var i = 0; i < parts.length; i++) {
			// Don't process more than 3 :'s
			if(i > 2) { return new Time(value); }

			value += parseInt(parts[i]) * Math.pow(60, i);
		}

		return new Time(value * 10);
	};

	return Time;
}());

