/*
   Copyright 2012-2013 Eric Ptak - trouch.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

var CONTEXT = "/";

var _gaq = _gaq || [];
var _webiopi;

function w() {
	if (_webiopi == undefined) {
		_webiopi = new WebIOPi();
	}
	
	return _webiopi;
}

function webiopi() {
	return w();
}

/*
function isMobileUserAgent(a) {
	if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))
			return true
}

var _isMobile = undefined;
function isMobile() {
	if (_isMobile == undefined) {
		_isMobile = ((navigator.userAgent != undefined && isMobileUserAgent(navigator.userAgent))
				|| (navigator.vendor != undefined && isMobileUserAgent(navigator.vendor)) 
				|| (window.opera != undefined && isMobileUserAgent(window.opera)))
	}
	return _isMobile
}
*/

var isTouchDevice = "ontouchstart" in document.documentElement ? true : false;
var BUTTON_DOWN   = isTouchDevice ? "touchstart" : "mousedown";
var BUTTON_UP     = isTouchDevice ? "touchend"   : "mouseup";

/*
var deviceAgent = navigator.userAgent.toLowerCase();
if (deviceAgent.match(/(iphone|ipod|ipad)/) ||
		deviceAgent.match(/(android)/)  || 
		deviceAgent.match(/(iemobile)/) ||
		deviceAgent.match(/iphone/i) ||
		deviceAgent.match(/ipad/i) || 
		deviceAgent.match(/ipod/i) || 
		deviceAgent.match(/blackberry/i) ||
		deviceAgent.match(/bada/i) ||
	"ontouchstart" in document.documentElement
	) {
        isTouchDevice = true;
		BUTTON_DOWN = "touchstart";
		BUTTON_UP = "touchend";
}
*/


function WebIOPi() {
	this.readyCallback = null;
	this.context = CONTEXT;
	this.GPIO = Array(54);
	this.PINS = Array(27);

	this.TYPE = {
			DNC: {value: 0, style: "DNC", label: "--"},
			GND: {value: 1, style: "GND", label: "GROUND"},
			V33: {value: 2, style: "V33", label: "3.3V"},
			V50: {value: 3, style: "V50", label: "5.0V"},
			GPIO: {value: 4, style: "GPIO", label: "GPIO"}
	};
	
	this.ALT = {
			I2C: {name: "I2C", enabled: false, gpios: []},
			SPI: {name: "SPI", enabled: false, gpios: []},
			UART: {name: "UART", enabled: false, gpios: []},
			ONEWIRE: {name: "ONEWIRE", enabled: false, gpios: []}
		};
		
	// init GPIOs
	for (var i=0; i<this.GPIO.length; i++) {
		var gpio = Object();
		gpio.value = 0;
		gpio.func = "IN";
		gpio.mapped = false;
		this.GPIO[i] = gpio;
	}

	var head = document.getElementsByTagName('head')[0];

	var jquery = document.createElement('script');
	jquery.type = 'text/javascript';
	jquery.src = '../../jquery.js';
//	if (!isMobile()) {
		jquery.onload = function() {
			w().init();
		};
//	}
	head.appendChild(jquery);

/*
	if (isMobile()) {
		console.log("load jquery mobile");
		var mobile = document.createElement('script');
		mobile.type = 'text/javascript';
		mobile.src = '/jquery-mobile.js';
		mobile.onload = function() {
			w().initMobile()
		};
		head.appendChild(mobile);
	}
*/

	// GA
	_gaq.push(['_setAccount', 'UA-33979593-2']);
	_gaq.push(['_trackPageview']);
		
	var ga = document.createElement('script');
	ga.type = 'text/javascript';
	ga.async = false;
	ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
	head.appendChild(ga);
	
	var style = document.createElement('link');
	style.rel = "stylesheet";
	style.type = 'text/css';
	style.href = '../../webiopi.css';
	head.appendChild(style);

/*
	if (isMobile()) {
		var style = document.createElement('link');
		style.rel = "stylesheet";
		style.type = 'text/css';
		style.href = '/jquery-mobile.css';
		head.appendChild(style);
	}
*/
	
	// init ALTs
	this.addALT(this.ALT.I2C, 0, "SDA");
	this.addALT(this.ALT.I2C, 1, "SCL");
	this.addALT(this.ALT.I2C, 2, "SDA");
	this.addALT(this.ALT.I2C, 3, "SCL");

	this.addALT(this.ALT.SPI,  7, "CE1");
	this.addALT(this.ALT.SPI,  8, "CE0");
	this.addALT(this.ALT.SPI,  9, "MISO");
	this.addALT(this.ALT.SPI, 10, "MOSI");
	this.addALT(this.ALT.SPI, 11, "SCLK");
	
	this.addALT(this.ALT.UART, 14, "TX");
	this.addALT(this.ALT.UART, 15, "RX");
	
	this.addALT(this.ALT.ONEWIRE, 4, "");
}

WebIOPi.prototype.init = function() {
	$.getJSON(w().context + "map", function(data) {
		var count = w().PINS.length;
		for (i = 0; i<count-1; i++) {
			var type = w().TYPE.GPIO;
			var label = data[i];
			
			if (label == "DNC") {
				type = w().TYPE.DNC;
			}
			else if (label == "GND") {
				type = w().TYPE.GND;
			}
			else if (label == "V33") {
				type = w().TYPE.V33;
			}
			else if (label == "V50") {
				type = w().TYPE.V50;
			}
			
			if (type.value != w().TYPE.GPIO.value) {
				label = type.label;
			}
			
			w().map(i+1, type, label);
		}
		if (w().readyCallback != null) {
			w().readyCallback();
		}

		w().checkVersion();
	});
}

/*
WebIOPi.prototype.initMobile = function() {
	webiopi().init();
}
*/

WebIOPi.prototype.ready = function (cb) {
	w().readyCallback = cb;
}

WebIOPi.prototype.map = function (pin, type, value) {
	w().PINS[pin] = Object();
	w().PINS[pin].type = type
	w().PINS[pin].value = value;
	
	if (type.value == w().TYPE.GPIO.value) {
		w().GPIO[value].mapped = true;
	}
}

WebIOPi.prototype.addALT = function (alt, gpio, name) {
	var o = Object();
	o.gpio = gpio;
	o.name = name;
	alt.gpios.push(o);
}

WebIOPi.prototype.updateValue = function (gpio, value) {
	w().GPIO[gpio].value = value;
	var style = (value == 1) ? "HIGH" : "LOW";
	$("#gpio"+gpio).attr("class", style);
}

WebIOPi.prototype.updateFunction = function (gpio, func) {
	w().GPIO[gpio].func = func;
	$("#function"+gpio).val(func);
	$("#function"+gpio).text(func);
}

WebIOPi.prototype.updateSlider = function (gpio, slider, value) {
	$("#"+slider+gpio).val(value);
}

WebIOPi.prototype.updateALT = function (alt, enable) {
	for (var p in alt.gpios) {
		gpio = alt.gpios[p].gpio;
		$("#description"+gpio).empty();
		if (enable) {
			$("#description"+gpio).append(alt.name + " " + alt.gpios[p].name);
			$("#gpio"+gpio).attr("class", alt.name);
			$("#function"+gpio).attr("class", "FunctionSpecial");
		}
		else {
			$("#description"+gpio).append("GPIO " + gpio);
			$("#gpio"+gpio).attr("class", "");
			$("#function"+gpio).attr("class", "FunctionBasic");
		}
	}
	alt.enabled = enable;
}

WebIOPi.prototype.refreshGPIO = function (repeat) {
	$.getJSON(w().context + "*", function(data) {
		w().updateALT(w().ALT.I2C, data["I2C"]);
		w().updateALT(w().ALT.SPI, data["SPI"]);
		w().updateALT(w().ALT.UART, data["UART"]);
		w().updateALT(w().ALT.ONEWIRE, data["ONEWIRE"]);
		
		$.each(data["GPIO"], function(gpio, data) {
	    	w().updateFunction(gpio, data["function"]);
	    	if ( ((gpio != 4) && ((data["function"] == "IN") || (data["function"] == "OUT"))
	    		|| ((gpio == 4) && (w().ALT.ONEWIRE["enabled"] == false)))){
	    		w().updateValue(gpio, data["value"]);
	    	}
	    	else if (data["function"] == "PWM") {
	    		w().updateSlider(gpio, "ratio", data["ratio"]);
	    		w().updateSlider(gpio, "angle", data["angle"]);
	    	}
	    	
		});
	});
	if (repeat === true) {
		setTimeout(function(){w().refreshGPIO(repeat)}, 1000);
	}
}


WebIOPi.prototype.checkVersion = function () {
	var version;
	
	$.get(w().context + "version", function(data) {
		_gaq.push(['_trackEvent', 'version', data]);
//		version = data.split("/")[2];
//
//		$.get("http://webiopi.trouch.com/version.php", function(data) {
//			var lines = data.split("\n");
//			var c = version.split(".");
//			var n = lines[0].split(".");
//			var updated = false;
//			for (i=0; i<Math.min(c.length, n.length); i++) {
//				if (n[i]>c[i]) {
//					updated = true;
//				}
//			}
//			if (updated || (n.length > c.length)) {
//				var div = $('<div id="update"><a href="' + lines[1] + '">Update available</a></div>');
//				$("body").append(div);
//			}
//		});
	});
}

WebIOPi.prototype.digitalRead = function (gpio, callback) {
	if (callback != undefined) {
		$.get(w().context + 'GPIO/' + gpio + "/value", function(data) {
			w().updateValue(gpio, data);
			callback(gpio, data);
		});
	}
	return w().GPIO[gpio].value;
}

WebIOPi.prototype.digitalWrite = function (gpio, value, callback) {
	if (w().GPIO[gpio].func.toUpperCase()=="OUT") {
		$.post(w().context + 'GPIO/' + gpio + "/value/" + value, function(data) {
			w().updateValue(gpio, data);
			if (callback != undefined) {
				callback(gpio, data);
			}
		});
	}
	else {
		//console.log(w().GPIO[gpio].func);
	}
}

WebIOPi.prototype.getFunction = function (gpio, callback) {
	if (callback != undefined) {
		$.get(w().context + 'GPIO/' + gpio + "/function", function(data) {
			w().updateFunction(gpio, data);
			callback(gpio, data);
		});
	}
	return w().GPIO[gpio].func;
}
WebIOPi.prototype.setFunction = function (gpio, func, callback) {
	$.post(w().context + 'GPIO/' + gpio + "/function/" + func, function(data) {
		w().updateFunction(gpio, data);
		if (callback != undefined) {
			callback(gpio, data);
		}
	});
}

WebIOPi.prototype.toggleValue = function (gpio) {
	var value = (w().GPIO[gpio].value == 1) ? 0 : 1;
	w().digitalWrite(gpio, value);
}

WebIOPi.prototype.toggleFunction = function (gpio) {
	var value = (w().GPIO[gpio].func == "IN") ? "OUT" : "IN";
	w().setFunction(gpio, value)
}

WebIOPi.prototype.outputSequence = function (gpio, period, sequence, callback) {
	$.post(w().context + 'GPIO/' + gpio + "/sequence/" + period + "," + sequence, function(data) {
		w().updateValue(gpio, data);
		if (callback != undefined) {
			callback(gpio, data);
		}
	});
}

WebIOPi.prototype.callMacro = function (macro, args, callback) {
	if (args == undefined) {
		args = "";
	}
	$.post(w().context + 'macros/' + macro + "/" + args, function(data) {
		if (callback != undefined) {
			callback(macro, args, data);
		}
	});
}

WebIOPi.prototype.enablePWM = function(gpio, callback) {
	$.post(w().context + 'GPIO/' + gpio + "/pwm/enable", function(data) {
		if (callback != undefined) {
			callback(gpio, data);
		}
	});
}

WebIOPi.prototype.disablePWM = function(gpio, callback) {
	$.post(w().context + 'GPIO/' + gpio + "/pwm/disable", function(data) {
		if (callback != undefined) {
			callback(gpio, data);
		}
	});
}

WebIOPi.prototype.pulse = function(gpio, callback) {
	$.post(w().context + 'GPIO/' + gpio + "/pulse/", function(data) {
		if (callback != undefined) {
			callback(gpio, data);
		}
	});
}

WebIOPi.prototype.pulseRatio = function(gpio, ratio, callback) {
	$.post(w().context + 'GPIO/' + gpio + "/pulseRatio/" + ratio, function(data) {
		if (callback != undefined) {
			callback(gpio, data);
		}
	});
}

WebIOPi.prototype.pulseAngle = function(gpio, angle, callback) {
	$.post(w().context + 'GPIO/' + gpio + "/pulseAngle/" + angle, function(data) {
		if (callback != undefined) {
			callback(gpio, data);
		}
	});
}

WebIOPi.prototype.setLabel = function (id, label) {
	$("#" + id).val(label);
	$("#" + id).text(label);
}

WebIOPi.prototype.setClass = function (id, cssClass) {
	$("#" + id).attr("class", cssClass);
}

WebIOPi.prototype.createButton = function (id, label, callback, callbackUp) {
	var button = $('<button type="button" class="Default">');
	button.attr("id", id);
	button.text(label);
	if (callback != undefined) {
		button.bind(BUTTON_DOWN, callback);
	}
	if (callbackUp != undefined) {
		button.bind(BUTTON_UP, callbackUp);
	}
	return button;
}

WebIOPi.prototype.createGPIOButton = function (gpio, label) {
	var button = w().createButton("gpio" + gpio, label);
	button.bind(BUTTON_DOWN, function(event) {
		w().toggleValue(gpio);
	});
	return button;
}

WebIOPi.prototype.createFunctionButton = function (gpio) {
	var button = w().createButton("function" + gpio, " ");
	button.attr("class", "FunctionBasic");
	button.bind(BUTTON_DOWN, function(event) {
		w().toggleFunction(gpio);
	});
	return button;
}

WebIOPi.prototype.createPulseButton = function (id, label, gpio) {
    var button = webiopi().createButton(id, label);
    button.bind(BUTTON_DOWN, function(event) {
        webiopi().pulse(gpio);
    });
    return button;
}

WebIOPi.prototype.createMacroButton = function (id, label, macro, args) {
    var button = webiopi().createButton(id, label);
    button.bind(BUTTON_DOWN, function(event) {
        webiopi().callMacro(macro, args);
    });
    return button;
}

WebIOPi.prototype.createSequenceButton = function (id, label, gpio, period, sequence) {
    var button = webiopi().createButton(id, label);
    button.bind(BUTTON_DOWN, function(event) {
        webiopi().outputSequence(gpio, period, sequence);
    });
    return button;
}

WebIOPi.prototype.createRatioSlider = function(gpio) {
	var slider = $('<input type="range" min="0.0" max="1.0" step="0.01">');
	slider.attr("id", "ratio"+gpio);
	slider.bind("change", function() {
		w().pulseRatio(gpio, slider.val());
	});
	return slider;
}

WebIOPi.prototype.createAngleSlider = function(gpio) {
	var slider = $('<input type="range" min="-45" max="45" step="1">');
	slider.attr("id", "angle"+gpio);
	slider.bind("change", function() {
		w().pulseAngle(gpio, slider.val());
	});
	return slider;
}

WebIOPi.prototype.RPiHeader = function () {
	if (w()._header == undefined) {
		w()._header = new RPiHeader();
	}
	return w()._header;
}

function RPiHeader() {

}

RPiHeader.prototype.getPinCell = function (pin) {
	var cell = $('<td align="center">');
	var button;
	if (w().PINS[pin].type.value == w().TYPE.GPIO.value) {
		button = w().createGPIOButton(w().PINS[pin].value, pin);
	}
	else {
		var button = $('<button type="button">');
		button.val(pin);
		button.text(pin);
		button.attr("class", w().PINS[pin].type.style);
	}
	cell.append(button);
	return cell;
}

RPiHeader.prototype.getDescriptionCell = function (pin, align) {
	var cell = $('<td>');
	cell.attr("align", align);
	
	var div = $('<div>');
	div.attr("class", "Description");
	if (w().PINS[pin].type.value != w().TYPE.GPIO.value) {
		div.append(w().PINS[pin].value);
	}
	else {
		div.attr("id", "description"+w().PINS[pin].value);
		div.append("GPIO " + w().PINS[pin].value);
	}
	
	cell.append(div);

	return cell;
}

RPiHeader.prototype.getFunctionCell = function (pin) {
	var cell = $('<td align="center">');
	if (w().PINS[pin].type.value == w().TYPE.GPIO.value) {
		var button = w().createFunctionButton(w().PINS[pin].value);
		cell.append(button);
	}
	return cell;
}

RPiHeader.prototype.createTable = function (containerId) {
	var table = $("<table>");
	table.attr("id", "RPiHeader")
	for (var pin=1; pin<=26; pin++) {
		var line = 	$('<tr>');
		line.append(this.getFunctionCell(pin))
		line.append(this.getDescriptionCell(pin, "right"))
		line.append(this.getPinCell(pin));

		pin++;
		line.append(this.getPinCell(pin));
		line.append(this.getDescriptionCell(pin, "left"))
		line.append(this.getFunctionCell(pin))

		table.append(line);
	}
	
	if (containerId != undefined) {
		$("#"+containerId).append(table);
	}
	
	return table;
}

WebIOPi.prototype.Expert = function () {
	if (w()._expert == undefined) {
		w()._expert = new Expert();
	}
	return w()._expert;
}

function Expert() {
	
}

Expert.prototype.createGPIO = function (gpio) {
	var box = $("<div>");
	box.append(w().createFunctionButton(gpio));
	box.append(w().createGPIOButton(gpio, gpio));

	div = $('<div>');
	div.attr("id", "description"+gpio);
	div.attr("class", "Description");
	div.append("GPIO " + gpio);
	box.append(div);

	return box;
}

Expert.prototype.createList = function (containerId) {
	var box = $('<div>');
	
	for (i = 0; i<w().GPIO.length; i++) {
		if (w().GPIO[i].mapped == true) {
			var gpio = w().Expert().createGPIO(i);
			box.append(gpio);
		}
	}
		
	if (containerId != undefined) {
		$("#"+containerId).append(box);
	}
	
	return box;
} 

WebIOPi.prototype.Serial = function(device) {
	return new Serial(device);
}

function Serial(device) {
	this.device = device;
	this.url = "/devices/" + device
}

Serial.prototype.write = function(data) {
	$.post(this.url, data);
} 

Serial.prototype.read = function(callback) {
	$.get(this.url, callback);
}

WebIOPi.prototype.newDevice = function(type, name) {
	if (type == "ADC") {
		return new ADC(name);
	}
	
	if (type == "DAC") {
		return new DAC(name);
	}

	if (type == "PWM") {
		return new PWM(name);
	}

	if (type == "GPIOPort") {
		return new GPIOPort(name);
	}

	if (type == "Temperature") {
		return new Temperature(name);
	}

	if (type == "Pressure") {
		return new Pressure(name);
	}

	if (type == "Luminosity") {
		return new Luminosity(name);
	}

	if (type == "Distance") {
		return new Distance(name);
	}
	
	if (type == "PiFaceDigital") {
		return new PiFaceDigital(name);
	}

	if (type == "Humidity") {
		return new Humidity(name);
	}

	return undefined;
}

function GPIOPort(name) {
	this.name = name;
	this.url = "/devices/" + name;
	this.onready = null;
	this.channelCount = 0;
	this.refreshTime = 1000;

	var port = this;
	$.get(this.url + "/count", function(data) {
		port.channelCount = parseInt(data);
	});

}

GPIOPort.prototype.isReady = function() {
	return (this.channelCount > 0);
}

GPIOPort.prototype.toString = function() {
	if (this.channelCount > 0)
		return this.name + ": GPIO Port (" + this.channelCount + "-bits)";
	return this.name + ": GPIO Port";
}

GPIOPort.prototype.digitalRead = function(channel, callback) {
	var name = this.name;
	$.get(this.url + "/" + channel + "/value", function(data) {
		callback(name, channel, data);
	});
}

GPIOPort.prototype.digitalWrite = function(channel, value, callback) {
	var name = this.name;
	$.post(this.url + "/" + channel + "/value/" + value, function(data) {
		callback(name, channel, data);
	});
}

GPIOPort.prototype.setFunction = function(channel, func, callback) {
	var name = this.name;
	$.post(this.url + "/" + channel + "/function/" + func, function(data) {
		callback(name, channel, data);
	});
}

GPIOPort.prototype.readAll = function(callback) {
	var name = this.name;
	$.get(this.url+ "/*", function(data) {
		callback(name, data);
	});
}

GPIOPort.prototype.refreshUI = function() {
	var port = this;
	var element = this.element;
	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	if ((element != undefined) && (element.table == undefined) && this.isReady()) {
		element.header.text(this)
		element.table = $("<table>");
		element.append(element.table);

		var line = $("<tr>");
		for (var i = this.channelCount-1; i>=0; i--) {
			var cell = $("<td>");
			cell.text(1<<i);
			line.append(cell);
		}
		element.table.append(line);
		
		line = $("<tr>");
		for (var i = this.channelCount-1; i>=0; i--) {
			var cell = $("<td>");
			var button = webiopi().createButton(this.name + "_" + i + "_value", i, function() {
				if ($("#" + port.name + "_" + $(this).attr("channel") + "_value").attr("class") == "LOW") {
					value = 1;
				}
				else {
					value = 0;
				}
				port.digitalWrite($(this).attr("channel"), value, function(name, channel, data) {
					if (data == "1") {
						$("#" + name + "_" + channel + "_value").attr("class", "HIGH")
					}
					else {
						$("#" + name + "_" + channel + "_value").attr("class", "LOW")
					}
				});
			});
			button.attr("channel", i);
			button.attr("class", "LOW");
			cell.append(button);
			line.append(cell);
		}
		element.table.append(line);
		
		line = $("<tr>");
		for (var i = this.channelCount-1; i>=0; i--) {
			var cell = $("<td>");
			var button = webiopi().createButton(port.name + "_" + i + "_func", "IN", function() {
				var func = $(this).text();
				console.log(func);
				if (func == "IN") {
					func = "OUT";
				}
				else {
					func = "IN";
				}
				port.setFunction($(this).attr("channel"), func, function(name, channel, func) {
					$("#" + port.name + "_" + channel + "_func").text(func);
				});
			});
			button.attr("class", "FunctionBasic");
			button.attr("channel", i);
			cell.append(button);
			line.append(cell);
		}
		element.table.append(line);
	}
	
	this.readAll(function(name, data) {
		for (i in data) {
			$("#" + name + "_" + i + "_value").attr("class", data[i]["value"] == "1" ? "HIGH" : "LOW");
			$("#" + name + "_" + i + "_func").text(data[i]["function"]);
		}
		setTimeout(function(){port.refreshUI()}, port.refreshTime);
	});
}

function ADC(name) {
	this.name = name;
	this.url = "/devices/" + name + "/analog";
	this.channelCount = 0;
	this.maxInteger = 0;
	this.resolution = 0;
	this.refreshTime = 1000;
	
	var adc = this;
	$.get(this.url + "/count", function(data) {
		adc.channelCount = parseInt(data);
	});

	$.get(this.url + "/max", function(data) {
		adc.maxInteger = parseInt(data);
	});

	$.get(this.url + "/resolution", function(data) {
		adc.resolution = parseInt(data);
	});
}

ADC.prototype.isReady = function() {
	return (this.channelCount > 0 && this.maxInteger > 0 && this.resolution > 0 );
}

ADC.prototype.toString = function() {
	if (this.channelCount > 0 && this.resolution> 0)
		return this.name + ": ADC (" + this.resolution + "-bits, " + this.channelCount  + "-channels)";
	return this.name + ": ADC";
}

ADC.prototype.readInteger = function(channel, callback) {
	var name = this.name;
	$.get(this.url + "/" + channel + "/integer", function(data) {
		callback(name, channel, data);
	});
}

ADC.prototype.readFloat = function(channel, callback) {
	var name = this.name;
	$.get(this.url + "/" + channel + "/float", function(data) {
		callback(name, channel, data);
	});
}

ADC.prototype.readVolt = function(channel, callback) {
	var name = this.name;
	$.get(this.url + "/" + channel + "/volt", function(data) {
		callback(name, channel, data);
	});
}

ADC.prototype.readAllInteger = function(callback) {
	var name = this.name;
	$.get(this.url + "/*/integer", function(data) {
		callback(name, data);
	});
}

ADC.prototype.readAllFloat = function(callback) {
	var name = this.name;
	$.get(this.url + "/*/float", function(data) {
		callback(name, data);
	});
}

ADC.prototype.readAllVolt = function(callback) {
	var name = this.name;
	$.get(this.url + "/*/volt", function(data) {
		callback(name, data);
	});
}

ADC.prototype.refreshUI = function () {
	var adc = this;
	var element = this.element;
	
	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	if ((element != undefined) && (element.channels == undefined) && this.isReady()) {
		element.header.text(this);
		element.channels = Array();
		for (i = 0; i<this.channelCount; i++) {
			var div = $("<div>");
			div.text("Channel-" + i);
			element.append(div);
			element.channels[i] = div;
			
		}
	}
	this.readAllVolt(function(name, data) {
		for (i in data) {
			if ((element != undefined) && (element.channels != undefined)) {
				var div = element.channels[i];
				div.text("Channel-" + i + ": " + parseFloat(data[i]).toFixed(2) + "V")
			}
		}
		setTimeout(function(){adc.refreshUI()}, adc.refreshTime);
	});
}


function DAC(name) {
	this.name = name;
	this.url = "/devices/" + name + "/analog";
	this.channelCount = 0;
	this.maxInteger = 0;
	this.resolution = 0;
	this.vref = 0;
	
	var dac = this;
	$.get(this.url + "/count", function(data) {
		dac.channelCount = parseInt(data);
	});

	$.get(this.url + "/max", function(data) {
		dac.maxInteger = parseInt(data);
	});

	$.get(this.url + "/resolution", function(data) {
		dac.resolution = parseInt(data);
	});

	$.get(this.url + "/vref", function(data) {
		dac.vref = parseFloat(data);
	});
}

DAC.prototype.isReady = function() {
	return (this.channelCount > 0 && this.maxInteger > 0 && this.resolution > 0 && this.vref > 0);
}

DAC.prototype.toString = function() {
	if (this.channelCount > 0 && this.resolution> 0)
		return this.name + ": DAC (" + this.resolution + "-bits, " + this.channelCount  + "-channels)";
	return this.name + ": DAC";
}

DAC.prototype.writeInteger = function(channel, value, callback) {
	var name = this.name;
	$.post(this.url + "/" + channel + "/integer/" + value, function(data) {
		callback(name, channel, data);
	});
}

DAC.prototype.writeFloat = function(channel, value, callback) {
	var name = this.name;
	$.post(this.url + "/" + channel + "/float/" + value, function(data) {
		callback(name, channel, data);
	});
}

DAC.prototype.readAllInteger = function(callback) {
	var name = this.name;
	$.get(this.url + "/*/integer", function(data) {
		callback(name, data);
	});
}

DAC.prototype.readAllFloat = function(callback) {
	var name = this.name;
	$.get(this.url + "/*/float", function(data) {
		callback(name, data);
	});
}

DAC.prototype.refreshUI = function() {
	var dac = this;
	var element = this.element;

	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	if ((element != undefined) && (element.table == undefined) && this.isReady()) {
		element.header.text(this);
		element.table = $("<table>");
		element.append(element.table);
		for (var i = 0; i<this.channelCount; i++) {
			var line = $("<tr>");
			var cell
			cell = $("<td>");
			cell.text("Channel-" + i);
			line.append(cell);
			
			cell = $("<td>");
			var slider = $('<input type="range" min="0" max="100" step="1" value="0">')
			slider.attr("channel", i);
			slider.attr("id", "slider_" + this.name + "_" + i);
			cell.append(slider);
			line.append(cell);

			cell = $("<td>");
			var span = $('<span>');
			span.attr("id", "span_" + this.name + "_" + i);
			cell.append(span);
			line.append(cell);

			slider.bind("change", function() {
				dac.writeFloat($(this).attr("channel"), $(this).val()/100, function(name, channel, data) {
					var val = (data*100).toFixed(0);
					var volts = (data*dac.vref).toFixed(2);
					$("#span_" + name + "_" + channel).text(volts + "V - " + val + "%");
					$("#slider_" + name + "_" + channel).val(val);
				});
			});

			element.table.append(line);
		}
		this.readAllFloat(function(name, data) {
			for (i in data) {
				var val = (data[i]*100).toFixed(0);
				var volts = (data[i]*dac.vref).toFixed(2);
				$("#span_" + name + "_" + i).text(volts + "V - " + val + "%");
				$("#slider_" + name + "_" + i).val(val);
			}
		});
	}
	else {
		setTimeout(function(){dac.refreshUI()}, 1000);
	}

}

function PWM(name) {
	this.name = name;
	this.url = "/devices/" + name + "/pwm";
	this.channelCount = 0;
	this.maxInteger = 0;
	this.resolution = 0;
	this.refreshTime = 1000;
	
	var pwm = this;
	$.get(this.url + "/count", function(data) {
		pwm.channelCount = parseInt(data);
	});

	$.get(this.url + "/max", function(data) {
		pwm.maxInteger = parseInt(data);
	});

	$.get(this.url + "/resolution", function(data) {
		pwm.resolution = parseInt(data);
	});
}

PWM.prototype.isReady = function() {
	return (this.channelCount > 0 && this.maxInteger > 0 && this.resolution > 0 );
}

PWM.prototype.toString = function() {
	if (this.channelCount > 0 && this.resolution> 0)
		return this.name + ": PWM (" + this.resolution + "-bits, " + this.channelCount  + "-channels)";
	return this.name + ": PWM";
}

PWM.prototype.writeInteger = function(channel, value, callback) {
	var name = this.name;
	$.post(this.url + "/" + channel + "/integer/" + value, function(data) {
		callback(name, channel, data);
	});
}

PWM.prototype.writeFloat = function(channel, value, callback) {
	var name = this.name;
	$.post(this.url + "/" + channel + "/float/" + value, function(data) {
		callback(name, channel, data);
	});
}

PWM.prototype.writeAngle = function(channel, value, callback) {
	var name = this.name;
	$.post(this.url + "/" + channel + "/angle/" + value, function(data) {
		callback(name, channel, data);
	});
}

PWM.prototype.readAllInteger = function(callback) {
	var name = this.name;
	$.get(this.url + "/*/integer", function(data) {
		callback(name, data);
	});
}

PWM.prototype.readAllFloat = function(callback) {
	var name = this.name;
	$.get(this.url + "/*/float", function(data) {
		callback(name, data);
	});
}

PWM.prototype.readAll = function(callback) {
	var name = this.name;
	$.get(this.url + "/*", function(data) {
		callback(name, data);
	});
}

PWM.prototype.refreshUI = function() {
	var pwm = this;
	var element = this.element;
	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	if ((element != undefined) && (element.table == undefined) && this.isReady()) {
		element.header.text(this);
		element.table = $("<table>");
		element.append(element.table);

		for (var i = 0; i<this.channelCount; i++) {
			var line = $("<tr>");
			var cell
			cell = $("<td>");
			cell.text("Channel-" + i);
			line.append(cell);
			
			cell = $("<td>");
			var checkbox = $('<input type="checkbox">');
			checkbox.attr("id", "checkbox_" + this.name + "_" + i);
			checkbox.attr("channel", i);
			
			var cblabel = $('<label>');
			cblabel.append(checkbox);
			cblabel.append("Servo");
			cell.append(cblabel);
			line.append(cell);
			
			cell = $("<td>");
			var slider = $('<input type="range" min="0" max="100" step="1" value="0">')
			slider.attr("channel", i);
			slider.attr("id", "slider_" + this.name + "_" + i);
			cell.append(slider);
			line.append(cell);

			cell = $("<td>");
			var span = $('<span>');
			span.attr("id", "span_" + this.name + "_" + i);
			cell.append(span);
			line.append(cell);

			checkbox.bind("change", function() {
				var slider = $("#slider_" + pwm.name + "_" + $(this).attr("channel"))
				slider.attr("servo", $(this).is(":checked"));
			});

			slider.bind("change", function() {
				if ($(this).attr("servo") == "true") {
					pwm.writeAngle($(this).attr("channel"), $(this).val(), function(name, channel, data) {
						var val = data;
						$("#span_" + name + "_" + channel).text(val + "°");
						$("#slider_" + name + "_" + channel).val(val);
					});
				}
				else {
					pwm.writeFloat($(this).attr("channel"), $(this).val()/100, function(name, channel, data) {
						var val = (data*100).toFixed(0);
						$("#span_" + name + "_" + channel).text(val + "%");
						$("#slider_" + name + "_" + channel).val(val);
					});
				}
			});

			element.table.append(line);
		}
	}

	this.readAll(function(name, data) {
		for (i in data) {
			var slider = $("#slider_" + name + "_" + i);
			var span = $("#span_" + name + "_" + i);
			var val = 0;

			if (slider.attr("servo") == "true") {
				slider.attr("min", -45);
				slider.attr("max", 45);
				val = data[i]["angle"];
				span.text(val + "°");
			}
			else {
				slider.attr("min", 0);
				slider.attr("max", 100);
				val = (data[i]["float"]*100).toFixed(0);
				span.text(val + "%");
			}
			slider.val(val);
			
		}
		setTimeout(function(){pwm.refreshUI()}, pwm.refreshTime);
	});
}

function Temperature(name) {
	this.name = name;
	this.url = "/devices/" + name + "/sensor";
	this.refreshTime = 5000;
}

Temperature.prototype.toString = function() {
	return this.name + ": Temperature";
}

Temperature.prototype.getKelvin = function(callback) {
	$.get(this.url + "/temperature/k", function(data) {
		callback(this.name, data);
	});
}

Temperature.prototype.getCelsius = function(callback) {
	$.get(this.url + "/temperature/c", function(data) {
		callback(this.name, data);
	});
}

Temperature.prototype.getFahrenheit = function(callback) {
	$.get(this.url + "/temperature/f", function(data) {
		callback(this.name, data);
	});
}

Temperature.prototype.refreshUI = function() {
	var temp = this;
	var element = this.element;
	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	this.getCelsius(function(name, data){
		if (element != undefined) {
			element.header.text(temp + ": " + data + "°C");
		}
		setTimeout(function(){temp.refreshUI()}, temp.refreshTime);
	});
}

function Pressure(name) {
	this.name = name;
	this.url = "/devices/" + name + "/sensor";
	this.refreshTime = 5000;
}

Pressure.prototype.toString = function() {
	return this.name + ": Pressure";
}

Pressure.prototype.getPascal = function(callback) {
	$.get(this.url + "/pressure/pa", function(data) {
		callback(this.name, data);
	});
}

Pressure.prototype.getHectoPascal = function(callback) {
	$.get(this.url + "/pressure/hpa", function(data) {
		callback(this.name, data);
	});
}

Pressure.prototype.refreshUI = function() {
	var pressure = this;
	var element = this.element;
	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	pressure.getHectoPascal(function(name, data){
		if (element != undefined) {
			element.header.text(pressure + ": " + data + "hPa");
		}
		setTimeout(function(){pressure.refreshUI()}, pressure.refreshTime);
	});
}
	

function Luminosity(name) {
	this.name = name;
	this.url = "/devices/" + name + "/sensor";
	this.refreshTime = 1000;
}

Luminosity.prototype.toString = function() {
	return this.name + ": Luminosity";
}

Luminosity.prototype.getLux = function(callback) {
	$.get(this.url + "/luminosity/lux", function(data) {
		callback(this.name, data);
	});
}

Luminosity.prototype.refreshUI = function() {
	var lum = this;
	var element = this.element;
	
	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	this.getLux(function(name, data){
		if (element != undefined) {
			element.header.text(lum + ": " + data + "lux");
		}
		setTimeout(function(){lum.refreshUI()}, lum.refreshTime);
	});
}

function Distance(name) {
	this.name = name;
	this.url = "/devices/" + name + "/sensor";
	this.refreshTime = 1000;
}

Distance.prototype.toString = function() {
	return this.name + ": Distance";
}

Distance.prototype.getMillimeter = function(callback) {
	$.get(this.url + "/distance/mm", function(data) {
		callback(this.name, data);
	});
}

Distance.prototype.refreshUI = function() {
	var dist = this;
	var element = this.element;
	
	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	this.getMillimeter(function(name, data){
		if (element != undefined) {
			element.header.text(dist + ": " + data + "mm");
		}
		setTimeout(function(){dist.refreshUI()}, dist.refreshTime);
	});
}

function PiFaceDigital(name) {
	this.name = name;
	this.url = "/devices/" + name + "/digital";
	this.onready = null;
	this.refreshTime = 1000;
}

PiFaceDigital.prototype.toString = function() {
	return "PiFaceDigital";
}

PiFaceDigital.prototype.input = function(channel, callback) {
	var name = this.name;
	$.get(this.url + "/input/" + channel, function(data) {
		callback(name, channel, data);
	});
}

PiFaceDigital.prototype.output = function(channel, value, callback) {
	var name = this.name;
	$.post(this.url + "/output/" + channel + "/" + value, function(data) {
		callback(name, channel, data);
	});
}

PiFaceDigital.prototype.readAll = function(callback) {
	var name = this.name;
	$.get(this.url+ "/*", function(data) {
		callback(name, data);
	});
}

PiFaceDigital.prototype.refreshUI = function() {
	var port = this;
	var element = this.element;
	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	if ((element != undefined) && (element.table == undefined)) {
		element.header.text(this)
		element.table = $("<table>");
		element.append(element.table);

		var line = $("<tr>");
		line.append($("<td><b>Outputs</b></td>"))
		for (var i = 7; i>=0; i--) {
			var cell = $("<td>");
			var button = webiopi().createButton(this.name + "_output_" + i, i, function() {
				if ($("#" + port.name + "_output_" + $(this).attr("channel")).attr("class") == "LOW") {
					value = 1;
				}
				else {
					value = 0;
				}
				port.output($(this).attr("channel"), value, function(name, channel, data) {
					var button = $("#" + name + "_output_" + channel);
					if (data == "1") {
						button.attr("class", "HIGH")
					}
					else {
						button.attr("class", "LOW")
					}
				});
			});
			button.attr("channel", i);
			button.attr("class", "LOW");
			cell.append(button);
			line.append(cell);
		}
		element.table.append(line);

		line = $("<tr>");
		line.append($("<td><b>Inputs</b></td>"))
		for (var i = 7; i>=0; i--) {
			var cell = $("<td>");
			var button = webiopi().createButton(this.name + "_input_" + i, i, function() {
			});
			button.attr("channel", i);
			button.attr("class", "LOW");
			cell.append(button);
			line.append(cell);
		}
		element.table.append(line);

	}
	
	this.readAll(function(name, data) {
		for (i in data["input"]) {
			$("#" + name + "_input_" + i).attr("class", data["input"][i] == "1" ? "HIGH" : "LOW");
		}
		for (i in data["output"]) {
			$("#" + name + "_output_" + i).attr("class", data["output"][i] == "1" ? "HIGH" : "LOW");
		}
		setTimeout(function(){port.refreshUI()}, port.refreshTime);
	});
}

function Humidity(name) {
	this.name = name;
	this.url = "/devices/" + name + "/sensor";
	this.refreshTime = 5000;
}

Humidity.prototype.toString = function() {
	return this.name + ": Humidity";
}

Humidity.prototype.getHumidity = function(callback) {
	$.get(this.url + "/humidity/float", function(data) {
		callback(this.name, data);
	});
}

Humidity.prototype.getHumidityPercent = function(callback) {
	$.get(this.url + "/humidity/percent", function(data) {
		callback(this.name, data);
	});
}

Humidity.prototype.refreshUI = function() {
	var temp = this;
	var element = this.element;
	if ((element != undefined) && (element.header == undefined)) {
		element.header = $("<h3>" + this + "</h3>");
		element.append(element.header);
	}
	
	this.getHumidityPercent(function(name, data){
		if (element != undefined) {
			element.header.text(temp + ": " + data + "%");
		}
		setTimeout(function(){temp.refreshUI()}, temp.refreshTime);
	});
}