/* js.protos // 20080807 */

/// Shortcuts
var d = document;
var w = window;


/** Common objects **/

/// Cache
function cache(opts) {

    // Init
    this.opts = opts || { };
    // TODO: max size

    // Set cache options
    this.setOptions = function(opts) {

	this.opts.getMerge(opts);

    }

    // Push
    this.push = function(params) {

	var now = new Date();
	params.content.cached = now.getTime();
	this[params.key] = params.content;
	p.dbg('js', 'verbose', 'cache', "+ " + params.key);
	if (!this.opts.keep && this.opts.offline && !p.offline)
	    p.timers.register({ obj: this,
			        func: 'flush',
			        params: { cached: params.content.cached,
			                  key: params.key },
			        ms: parseInt(params.content.expire) * 1000 || 60000 });
	// TODO: core prefs

    }

    // Flush item
    this.flush = function(params) {

	if (!this[params.key]
	    || this[params.key].cached != params.cached)
	    return false;
	if (!this.opts.keep && this.opts.offline && !p.offline) {
	    p.dbg('js', 'verbose', 'cache',
		  "- " + params.key);
	    delete(this[params.key]);
	}

	return true;

    }

    // Clear cache
    this.clear = function() {

	for (var i in this)
	    if (typeof this[i] != 'function' && i != 'opts')
		delete(this[i]);

	// Cleared!
	p.dbg('js', 'info', 'cache', "cleared!");

    }

}


/// Pile
function pile() {

    // Add to pile
    this.add = function(value) {

	this.remove(value);
	this.push(value);
	return true;

    }

    // Remove instances of item
    this.remove = function(value) {

	do { this.except(this.indexOf(value)) }
	while (this.indexOf(value) != -1);
	return true;

    }

    // Remove one
    this.except = function(id) {

	if (id == -1) return false;
	for (var i = id; i < this.length - 1; i++)
	    this[i] = this[i + 1];
	this.pop();
	return true;

    }
    
    // Fall back to previous item
    this.back = function(action) {

	// Empty?
	if (!this.length)
	    return p.dbg('js', 'verbose', 'pile',
			 "already empty...");

	// Find next
	var value = false;
	var done = false;
	do {
	    value = this.pop();
	    done = action.obj[action.func](value);
	    if (!done) p.dbg('js', 'verbose', 'pile',
			     "skipping " + value);
	} while (!done && this.length);

	// Return value
	if (done) {
	    p.dbg('js', 'verbose', 'pile',
		  "back to " + value);
	    this.push(value);
	} else {
	    p.dbg('js', 'verbose', 'pile',
		  "pile empty...");
	}
	return done;

    }
    
}
pile.prototype = new Array();


/// History
function history() {
    
    // Init
    this.pos = -1;
    this.values = [ ];

    // Go back & forth
    this.go = function(i) {

	if (this.pos + i < 0
	    || this.pos + i >= this.values.length)
	    return false;

	this.pos += i;
	p.dbg('js', 'verbose', 'history',
	      this.pos + "/" + this.values.length + "/" + this.values[this.pos]);

	// Send boundaries & value
	return { pos: this.pos,
		 max: this.values.length,
		 value: this.values[this.pos] };

    }
    
    // Slice & append value
    this.push = function(value) {

	this.pos++;
	while (this.values.length > this.pos)
	    this.values.pop();
	this.values.push(value);

    }

    // Append value
    this.append = function(value) {

	this.values.push(value);
	this.pos = this.values.length;

    }
    
}


/// List
function list(name) {

    // Init
    this.n = 0;
    this.name = name;
    this.items = new Array();

    // Find available space
    this.next = function() {

	for (var i = 0; i <= this.n; i++) {
	    if (String(i >>> 0) != i) continue;
	    if (this.items[i] == null) break;
	}
	return i;

    }
    
    // Put value
    this.put = function(i, e, override) {

	if (this.items[i] != null && !override)
	    return false;
	if (this.items[i] == null) this.n++;
	this.items[i] = e;
	return true;

    }

    // Get value
    this.get = function(i) {

	return this.items[i];

    }
    
    // Add value
    this.add = function(e) {

	// Find space
	var i = this.next();
	return (this.put(i, e))? i: false;

    }

    // Find value
    this.find = function(v) {

	for (var i = 0; i < this.items.length; i++) {
 	    if (String(i >>> 0) != i) continue;
	    if (this.items[i] == v)
		return i;
	}
	return false;

    }

    // Get first value
    this.first = function(v) {

	for (var i = 0; i < this.items.length; i++) {
 	    if (String(i >>> 0) != i) continue;
	    if (this.items[i]) return i;
	}
	return false;

    }

    // Remove value
    this.remove = function(i) {

	if (!isKey(i)) return false;
	if (this.items[i] != null && delete(this.items[i])) this.n--;
	else p.dbg('js', 'warning', 'list',
		   "cannot delete element #" + i + " from " + this.name);
	// TODO: should slice items to last element to free memory
	return true;

    }
    
    // Empty list
    this.empty = function() {

	for (var i in this.items) {
	    if (!isKey(i)) continue;
	    this.remove(i);
	}
	return !this.n;

    }

    // Batch process
    this.exec = function(action) {

	var done = true;
	for (var i in this.items) {
	    if (!isKey(i)) continue;
	    var one = action.obj[action.func](action.key? i: this.items[i], action.params);
	    done &= (action.reverse)? !one: one;
	}
	return done;

    }

}


/// DOM function
function DOM(tag, atts, nodes) {
    var elm = d.createElement(tag);
    for (var i in atts) {
	if (i.substring(0, 3) == "get") continue;
	if (typeof atts[i] == 'function') elm[i] = atts[i];
	else {
	    if (i == "c") elm.className = atts[i];
	    else elm.setAttribute(i, atts[i]);
	}

    }
    if (nodes) {
	if (typeof nodes == 'string') elm.appendChild(d.createTextNode(nodes));
	else if (nodes.nodeName) elm.appendChild(nodes);
	else {
	    for (var i in nodes) {
		if (i.substring(0, 3) == "get") continue;
		if (typeof nodes[i] == 'string') elm.appendChild(d.createTextNode(nodes[i]));
		else if (typeof nodes[i] == 'object') elm.appendChild(nodes[i]);
	    }
	}
    }
    return elm;
}

/// DOM shorcut
function dom(tag, atts, nodes) { return DOM(tag, atts, nodes); }


/** Prototypes **/

// Is key?
function isKey(key) { return String(key >>> 0) == key; }

/// Object prototypes

/*
Object.prototype.getById = function(id) {
    return this.getElementById(id);
}
*/
d.getById = function(id) { // IE
    return d.getElementById(id);
}
/*    
Object.prototype.getByTag = function(tag) {
    return this.getElementsByTagName(tag);
}
*/
d.getByTag = function(tag) { // IE
    return (d.all)? d.all.tags(tag): d.getElementsByTagName(tag);
}
/*
Object.prototype.getByClass = function(cls) {
    var e = this.all || this.getElementsByTagName('*');
    var a = new Array();
    for (var i = 0; i < e.length; i++)
	if (e[i].className
	    && e[i].className.split(' ').inArray(cls))
	    a.push(e[i]);
    return a;
}

Object.prototype.getStyle = function(values) {
    var key;
    for (var i in values) {
	if (typeof values[i] == 'function') continue;
	if (i.indexOf('-') != -1) {
	    var keys = i.split('-');
	    key = keys[0] + keys[1].ucFirst();
	    if (keys[2]) key += keys[2].ucFirst();
	} else key = i;
	this.style[key] = values[i];
    }
}

Object.prototype.getSwitch = function(sw) {
    var ul = this.parentNode;
    var before = null;
    switch(sw) {
    case 'first':
	before = ul.firstChild;
	break;
    case 'previous':
	before = this.previousSibling || this.nextSibling;
	break;
    case 'next':
	if (this.nextSibling)
	    before = this.nextSibling.nextSibling;
	break;
    }
    ul.removeChild(this);
    ul.insertBefore(this, before);
}

Object.prototype.getMask = function(e) {
    this.getStyle({ width: e.offsetWidth + 'px',
		    height: e.offsetHeight + 'px' });
}

Object.prototype.getCoords = function() {
    return { x: this.getX(), y: this.getY(),
	     w: this.offsetWidth, h: this.offsetHeight };
}

Object.prototype.getSize = function() {
    return { w: parseInt(this.style.width), h: parseInt(this.style.height) };
}

Object.prototype.getX = function() {
    var x = (this.offsetLeft || 0) + (this.scrollLeft || 0);
    if (this.parentNode) return x + this.parentNode.getX();
    else return x;
}

Object.prototype.getY = function() {
    var y = (this.offsetTop || 0) + (this.scrollTop || 0);
    if (this.parentNode) return y + this.parentNode.getY();
    else return y;
}

Object.prototype.getData = function() {
    var elms = new Array();
    for (var i in this) {
	if (i.substring(0, 3) == 'get') continue;
	if (typeof this[i] == 'string')
	    elms.push(i + ":'" + escape(this[i]) + "'");
	else if (typeof this[i] == 'array'
		 || typeof this[i] == 'object')
	    elms.push(i + ':' + this[i].getData());
	else elms.push(i + ':' + this[i]);
    }
    return '{' + elms.join(',') + '}';
}

Object.prototype.getFromXML = function() {
    var obj = new Object();
    if (!this || !this.childNodes) return false;
    for (var i = 0; i < this.childNodes.length; i++) {
	if (this.childNodes[i].nodeName && this.childNodes[i].nodeName[0] != "#") {
	    if (this.childNodes[i].getAttribute("type") == "list") {
		obj[this.childNodes[i].nodeName] = new Array();
		for (var j = 0; j < this.childNodes[i].childNodes.length; j++)
		    if (this.childNodes[i].childNodes[j].nodeName
			&& this.childNodes[i].childNodes[j].nodeName[0] != "#")
			obj[this.childNodes[i].nodeName].push(this.childNodes[i].childNodes[j].getFromXML());
	    } else if (this.childNodes[i].childNodes.length > 1) {
		obj[this.childNodes[i].nodeName] = this.childNodes[i].getFromXML();
	    } else if (this.childNodes[i].firstChild) {
		var value = this.childNodes[i].firstChild.nodeValue;
		if (value == 'false') value  = false;
		else if (value == 'true') value = true;
		obj[this.childNodes[i].nodeName] = value;
	    } else obj[this.childNodes[i].nodeName] = "";
	}
    }
    return obj;
}
*/
function getFromXML(e) {
    var obj = new Object();
    if (!e || !e.childNodes) return false;
    for (var i = 0; i < e.childNodes.length; i++) {
	if (e.childNodes[i].nodeName && e.childNodes[i].nodeName[0] != "#") {
	    if (e.childNodes[i].getAttribute("type") == "list") {
		obj[e.childNodes[i].nodeName] = new Array();
		for (var j = 0; j < e.childNodes[i].childNodes.length; j++)
		    if (e.childNodes[i].childNodes[j].nodeName
			&& e.childNodes[i].childNodes[j].nodeName[0] != "#")
			obj[e.childNodes[i].nodeName].push(getFromXML(e.childNodes[i].childNodes[j]));
	    } else if (e.childNodes[i].childNodes.length > 1) {
		obj[e.childNodes[i].nodeName] = getFromXML(e.childNodes[i]);
	    } else if (e.childNodes[i].firstChild) {
		var value = e.childNodes[i].firstChild.nodeValue;
		if (value == 'false') value  = false;
		else if (value == 'true') value = true;
		obj[e.childNodes[i].nodeName] = value;
	    } else obj[e.childNodes[i].nodeName] = "";
	}
    }
    return obj;
}
/*
Object.prototype.getSelectOption = function(val) {
    for (var i = 0; i < this.options.length; i++)
	this.options[i].selected = (this.options[i].value == val)? 'selected': '';
}

Object.prototype.getSelected = function(att, val) {
    for (var i = 0; i < this.childNodes.length; i++) {
	var node = this.childNodes[i].firstChild;
	if (!node) continue;
	node.className = (node.getAttribute(att) == val)? 'selected': '';
    }
}

Object.prototype.getXML = function(title, sub) {
    var str = (sub)? "\n": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
    var arr = (this[0] != null);
    var type = arr? " type=\"list\"": "";
    str += "<" + title + type + ">";
    for (var i in this) {
	if (typeof this[i] == 'function') continue;
	var t = arr? 'element': i;
	if (typeof this[i] == 'object') str += this[i].getXML(t, true);
	else {
	    var v = this[i];
	    if (typeof this[i] == 'bool') v = v? "true": "false";
	    else if (typeof this[i] == 'string'
		     && this[i].indexOf("\n") != -1)
		v = "<![CDATA[" + encodeURIComponent(v) + "]]>";
	    else v = encodeURIComponent(v);
	    str += "\n<" + t + ">" + v + "</" + t + ">";
	}
    }
    str += "</" + title + ">";
    return str;
}

Object.prototype.getMerge = function(obj) {
    for (var i in obj)
	if (i.substring(0, 3) != 'get') this[i] = obj[i];
}

Object.prototype.getProto = function(parent) {
    if (arguments.length > 1) parent.apply(this, Array.prototype.slice.call(arguments, 1));
    else parent.call(this);
}

Object.prototype.getClear = function() {
    if (!this.childNodes) return;
    while (this.childNodes.length) this.removeChild(this.firstChild);
}

// Adapted from www.faqts.com/knowledge_base/view.phtml/aid/6231
Object.prototype.getClone = function() {
    var clone = new this.constructor();
    for (var i in this)
	if (typeof this[i] == 'object') clone[i] = this[i].getClone();
	else clone[i] = this[i];
    return clone;
}
*/
/// Array prototypes

Array.prototype.inArray = function(val) {
    for (var i = 0; i < this.length; i++)
	if (this[i] == val)
	    return true;
    return false;
}

Array.prototype.indexOf = function(val) {
    for (var i = 0; i < this.length; i++) {
	if (String(i >>> 0) != i) continue;
	if (this[i] == val) return i;
    }
    return -1;
}

Array.prototype.getData = function() {
    var elms = new Array();
    for (var i = 0; i < this.length; i++) {
	if (typeof this[i] == 'string') elms.push(i + ":'" + escape(this[i]) + "'");
	else if (typeof this[i] == 'array' || typeof this[i] == 'object') elms.push(i + ':' + this[i].getData());
	else elms.push(i + ':' + this[i]);
    }
    return '{' + elms.join(',') + '}';
}

Array.prototype.setFirst = function(value) {
    var i = this.indexOf(value);
    if (i == -1) this.unshift(value);
    else {
	for (var j = i; j > 0; j--) this[j] = this[j - 1];
	this[0] = value;
    }
}


/// String prototypes

String.prototype.ucFirst = function() {
    if (!this) return "";
    return this.substring(0, 1).toUpperCase() + this.substring(1);
}

String.prototype.lcFirst = function() {
    if (!this) return "";
    return this.substring(0, 1).toLowerCase() + this.substring(1);
}

String.prototype.firstUpper = function() {
    if (!this) return -1;
    for (var i = 0; i < this.length; i++)
	if (this.substring(i, i + 1).toUpperCase() == this.substring(i, i + 1))
	    return i;
    return -1;
}

String.prototype.firstChar = function() {
    if (!this) return "";
    return this.substring(0, 1);
}

String.prototype.lastChar = function() {
    if (!this) return "";
    return this.substring(this.length - 1);
}

String.prototype.lastWord = function() {
    return this.substring(Math.max(this.lastIndexOf('/'), this.lastIndexOf(' ')) + 1);
}

String.prototype.filename = function() {
    var str = this;
    if (str.indexOf('&') != -1) str = str.substring(0, str.indexOf('&'));
    if (str.lastChar() == '/') str = str.substring(0, str.length - 1);
    return str.substring(str.lastIndexOf('/') + 1);
}

String.prototype.folder = function() {
    if (this.lastIndexOf('/') == this.length - 1) return this;
    return this.substring(0, this.lastIndexOf('/') + 1);
}

String.prototype.parentFolder = function() {
    if (this.lastIndexOf('/') == this.length - 1) return this.substring(0, this.length - 1).folder();
    return this.folder();
}

String.prototype.encode = function() {
    return encodeURIComponent(this).replace(/%2F/g, '/').replace(/%3A/g, ':');
}

String.prototype.ext = function() {
    if (this.lastIndexOf('/') == this.length - 1) return 'folder';
    if (this.lastIndexOf('/') >= this.lastIndexOf('.')) return false;
    return this.substring(this.lastIndexOf('.') + 1).toLowerCase();
}

String.prototype.isFolder = function() {
    return (this.substring(0, 1) == '/' && this.substring(this.length - 1) == '/');
}

String.prototype.toObject = function() { // TODO: delete // obsolete
    var obj = new Object();
    var params = this.split('&');
    for (var i = 0; i < params.length; i++) {
	var idx = params[i].indexOf('/');
	if (idx) obj[params[i].substring(0, idx)] = unescape(params[i].substring(idx + 1));
	else obj[params[i]] = true;
    }
    return obj;
}


/// Date prototype

Date.now = function(sec, t) {
    var dt = (t)? new Date(t): new Date();
    this.z = function(num) { return (num < 10)? "0" + num: num; }
    return this.z(dt.getHours()) + ":" + this.z(dt.getMinutes()) + ((sec)? ":" + this.z(dt.getSeconds()): "");
}


/// Function prototype
// Adapted from www.coolpage.com/developer/javascript/Correct%20OOP%20for%20Javascript.html
Function.prototype.getProto = function(parent) {
    this.prototype = new parent();
    this.prototype.constructor = this;
}


environment.getProto(list);
function environment(app) {

    this.getProto(list, app);

    this.update = function(id, values, override) {
	for (var i in values) {
	    if (typeof values[i] == 'function') continue;
	    var type = typeof this.get(id)[i];
	    if (type != 'undefined' && !override) continue;
	    if (type == 'int') values[i] = parseInt(values[i]);
	    if (type == 'float') values[i] = parseFloat(values[i]);
	    this.get(id)[i] = values[i];
	}
    }

    this.set = function(id, key, value) {
	if (!this.get(id)) return false;
	this.get(id)[key] = value;
	return true;
    }

}
