
// array.js
/* 
ARRAY EXTENSIONS
version 0.1
by Caio Chassot (http://v2studio.com/k/code/)
*/




if (!Array.prototype.push) Array.prototype.push = function() { 
	for (var i=0; i<arguments.length; i++) this[this.length] = arguments[i];
	return this.length;
}

Array.prototype.find = function(value, start) {
	start = start || 0;
	for (var i=start; i<this.length; i++) 
		if (this[i]==value)
			return i;
	return false; 
}

Array.prototype.has = function(value) {
	return this.find(value)!==false;
}

Array.prototype.count = function(value) {
	var pos, start = 0, count = 0;
	while ((pos = this.find(value, start))!==false) {
		start = pos + 1;
		count++;
	}
	return count;
}

Array.prototype.remove = function(value,all) {
	while (this.has(value)) {
		this.splice(this.find(value),1);
		if (!all) break
	}
	return this;
}

Array.prototype.last = function() {
	return this[this.length-1];
}

Array.prototype.sjoin = function() { return this.join(' ') }
Array.prototype.njoin = function() { return this.join('\n') }
Array.prototype.cjoin = function() { return this.join(', ') }


// functional.js
/*
FUNCTIONAL
version 0.1
by Caio Chassot (http://v2studio.com/k/code/)

uses: string.js

string.js is necessary for __strfn. if you pass only real functions (no strings)
to map, filter and reduce, string.js is not needed.
*/



function __strfn(args, fn) { 
	function quote(s) { return '"' + s.replace(/"/g,'\\"') + '"' }
	if (!/\breturn\b/.test(fn)) {
		fn = fn.replace(/;\s+$/, '');
		fn = fn.insert(fn.lastIndexOf(';')+1, ' return ');
	}
	return eval('new Function({0},{1})'.subArgs(
		map(args.split(/\s*,\s*/), quote).join(),
		quote(fn)));
}

function map(list, fn) {
	if (typeof(fn)=='string') return map(list, __strfn('item,idx,list', fn));

	var result = [];
	fn = fn || function(v) {return v};
	for (var i=0; i < list.length; i++) result.push(fn(list[i], i, list)); 
	return result;
}

function filter(list, fn) { 
	if (typeof(fn)=='string') return filter(list, __strfn('item,idx,list', fn));

	var result = [];
	fn = fn || function(v) {return v};
	map(list, function(item,idx,list) { if (fn(item,idx,list)) result.push(item) } );
	return result;
}

function reduce(list, fn, initial) { 
	if (typeof(fn)=='string') return reduce(list, __strfn('a,b', fn), initial);
	if (isdef(initial)) list.splice(0,0,initial);
	if (list.length===0) return false;
	if (list.length===1) return list[0];
	var result = list[0];
	var i = 1;
	while(i<list.length) result = fn(result,list[i++]);
	return result;
}




// string.js
/*
STRING EXTENSIONS
version 0.1
by Caio Chassot (http://v2studio.com/k/code/)

uses: utils.js
*/



String.prototype.trimLeft = function() { 
	return this.replace(/^\s+/,'');
}
String.prototype.trimRight = function() {	return this.replace(/\s+$/,'');
}
String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g,'');
}

String.prototype.insert = function(idx,value) { 
	return this.slice(0,idx) + value + this.slice(idx);
}

String.prototype.strip = function(idx1,idx2) { 
	if (arguments.length==1) idx2 = this.length;
	return this.slice(0,idx1) + this.slice(idx2);
}

String.prototype.splice = function(idx,count,value) { 
	return this.strip(idx, idx+count).insert(idx, value);
}

String.prototype.subArgs = function() {
	var args = arguments;
	return this.replace(/\{(\d+)\}/g, function(s,i){return args[i]} );
//	return this.replace(/(?:^|[^\\])\{(\d+)\}/g, function(s,i){return args[i]} ).replace(/\\\{/g, '{' );
//  does not replace when { is preceded by a \ [str notation: '\\{...'] . Doesn't sound necessary
}

String.prototype.subDict = function(dict) {
	return this.replace(/\{(\w+)\}/g, function(s,k) { return !isUndefined(dict[k]) ? dict[k] : s });
}

String.prototype.wrap = function(left,right) { 
	if (undef(left)) throw 'S.wrap takes 1 argument (none given)';
	if (undef(right)) right = left;
	return left + this + right;
}

String.prototype.quote = function() { 
	return this.wrap('"');
}

String.prototype.squote = function() { 
	return this.wrap("'");
}

String.prototype.pad = function(side, len, chr) { 
	if (undef(chr)) chr = ' ';
	var s = this;
	var left = side.toLowerCase()=='left';
	while (s.length<len) s = left? chr + s : s + chr;
	return s;
}

String.prototype.padLeft = function(len, chr) { 
	return this.pad('left',len,chr);
}

String.prototype.padRight = function(len, chr) { 
	return this.pad('right',len,chr);
}

String.prototype.zerofill = function(len) { 
	var s = this;
	var ix = /^[+-]/.test(s) ? 1 : 0;
	while (s.length<len) s = s.insert(ix, '0');
	return s;
}

String.prototype.isEmpty = function(donttrim) { 
	return !(donttrim? this : this.trim()).length;
}



// utils.js
/*
MISC UTILITIES
version 0.1
by Caio Chassot (http://v2studio.com/k/code/)

uses: functional.js (for maprange)
*/



// MISC CLEANING-AFTER-MICROSOFT STUFF

function isUndefined(v) { 
	var undef;
	return v===undef;
}


// LAZINESS

function undef(v) {  return  isUndefined(v) }
function isdef(v) {  return !isUndefined(v) }

function list(s, sep) { 
	if (typeof sep != 'string' && !(sep instanceof RegExp)) 
		sep = sep? ',' : /\s*,\s*/;
	return s.split(sep);
}


// PYTHONIC

function range(start,stop,step) { 
	if (isUndefined(stop)) return range(0,start,step);
	if (isUndefined(step)) step = 1;
	var ss = (step/Math.abs(step)); // step sign
	var r = [];
	for (i=start; i*ss<stop*ss; i=i+step) r.push(i);
	return r;
}

function maprange(start, stop, fn) { 
	if (arguments.length==2) return maprange(0, start, stop);
	if (arguments.length!=3) throw "maprange takes 2 or 3 arguments";
	return map(range(start,stop), fn);
}



// dom.js
/* 
DOM AND DOM EVENTS UTILITIES
version 0.1
by Caio Chassot (http://v2studio.com/k/code/)

uses: utils.js, array.js, functional.js
*/



// BASIC DOM

function getElem(elem) { 
	if (document.getElementById) {
		if (typeof elem == "string") {
			elem = document.getElementById(elem);
			if (elem===null) throw 'cannot get element: element does not exist';
		} else if (typeof elem != "object") {
			throw 'cannot get element: invalid datatype';
		}
	} else throw 'cannot get element: unsupported DOM';
	return elem;
}

function getElementsByClass(className, tagName, parentNode) { 
	parentNode = isdef(parentNode)? getElem(parentNode) : document;
	if (undef(tagName)) tagName = '*';
	return filter(parentNode.getElementsByTagName(tagName), 
		function(elem) { return hasClass(elem, className) });
}


// CLASS MANIPULATION

function hasClass(elem, className) { 
	return getElem(elem).className.split(' ').count(className);
}

function remClass(elem, className, all) { 
	elem = getElem(elem);
	elem.className = elem.className.split(' ').remove(className,all).join(' ');
}

function addClass(elem, className, allowDuplicates) { 
	elem = getElem(elem);
	if (!allowDuplicates && elem.className.split(' ').has(className)) return;
	elem.className += ' ' + className;
}


// SHORTHANDS

function getHtml() { 
	return document.getElementsByTagName('html')[0];
}

function getHead() { 
	return document.getElementsByTagName('head')[0];
}

function getBody() { 
	return document.getElementsByTagName('body')[0];
}

function getAll(tagName, parent) { 
	return (!isUndefined(parent)? getElem(parent) : document).
		getElementsByTagName(!isUndefined(tagName)? tagName : '*');
}


// DOM EVENTS

function listen(event, elem, func) { 
	elem = getElem(elem);
	if (elem.addEventListener)  // W3C DOM 
		elem.addEventListener(event,func,false);  
	else if (elem.attachEvent)  // IE DOM
		elem.attachEvent('on'+event, function(){ func(new W3CDOM_Event(elem)) } );
		// for IE we use a wrapper function that passes in a simplified faux Event object. 
	else throw 'cannot add event listener';
}

function mlisten(event, elem_list, func) { 
	map(elem_list, function(elem) { listen(event, elem, func) } );
}

function W3CDOM_Event(currentTarget) { 
	this.currentTarget   = currentTarget;
	this.preventDefault  = function() { window.event.returnValue  = false }
	this.stopPropagation = function() { window.event.cancelBubble = true }
	this.target  = window.event.srcElement;
	this.clientX = event.clientX;
	this.clientY = event.clientY;
	return this;
}



