/**
* The Kobalt JavaScript Library v0.1
* Copyright Denis Wernert <denis@wernert.info>
* 
* This software is a computer program whose purpose is to ease JavaScript development by providing functionnalities to enforce type contracts, debug code, and extends the base types with convenience functions.
* 
* This software is governed by the CeCILL  license under French law and
* abiding by the rules of distribution of free software.  You can  use, 
* modify and/ or redistribute the software under the terms of the CeCILL
* license as circulated by CEA, CNRS and INRIA at the following URL
* "http://www.cecill.info". 
* 
* As a counterpart to the access to the source code and  rights to copy,
* modify and redistribute granted by the license, users are provided only
* with a limited warranty  and the software's author,  the holder of the
* economic rights,  and the successive licensors  have only  limited
* liability. 
* 
* In this respect, the user's attention is drawn to the risks associated
* with loading,  using,  modifying and/or developing or reproducing the
* software by the user in light of its specific status of free software,
* that may mean  that it is complicated to manipulate,  and  that  also
* therefore means  that it is reserved for developers  and  experienced
* professionals having in-depth computer knowledge. Users are therefore
* encouraged to load and test the software's suitability as regards their
* requirements in conditions enabling the security of their systems and/or 
* data to be ensured and,  more generally, to use and operate it in the 
* same conditions as regards security. 
* 
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL license and that you accept its terms.
**/

/**
 * Universal Element selector
 * @param element description
 * @returns A single element if the selector starts with '#', a group of elements matching a classname if the selector starts with '.', a group of elements whose tagname matches the selector in any other case.
 */
function $( selector ) {
	if ( Contract.is_string ( selector ) ) {
		switch ( selector[0] ) {
			case '#':
				return document.getElementById( selector.substr( 1 ) ) ;	
				break ;

			case '.':
				var elements = new Array ;
				var body = b() ;
				var classname = selector.substr( 1 ) ;
				if ( Contract.is_not_null( body ) ) {
					body.each_descendant( function( element ) {
						elements.push( element ) ;
					}, function( element ) {
						return ( element.className == classname ) ;
					}) ;
				}
				return elements ;
				break ;
		}
	}

	return document.getElementsByTagName( selector ) ;
}

/**
 * @returns the document's body element
 */
function b() {
	var body = document.getElementsByTagName( 'body' ) ;
	if ( body.length < 1 ) return null ;
	return body[0] ;
}

/**
 * Add each property of obj to the current object
 * @param obj An object that should extend the current object
 * @returns the current object
 */
Object.prototype.extend = function( obj )
{
	for ( i in obj ) {
		this[i] = obj[i] ;
	}
	return this ;
}

Contract = new Object() ;
Contract.extend({
	assert: function( truth, description ) {
		if ( ! truth ) {
			throw new ContractViolation( description ) ;
		}
	},

	enforce: function( obj, contract ) {
		Contract.enforce( contract, Contract.is_function ) ;
		Contract.assert( contract( obj ) ) ;
		return this ;
	},

	_satisfy_template: function( obj, contracts, truth_evaluation, start_from ) {
		Contract.enforce( contracts, Contract.is_array ).enforce( truth_evaluation, Contract.is_function ).enforce( start_from, Contract.is_boolean ) ;
		var satisfied = start_from ;
		contracts.each( function( contract ) {
			satisfied = truth_evaluation( satisfied, contract( obj ) ) ;
		}, Contract.is_function ) ;
		return statisfied ;
	},

	satisfy_all: function( obj, contracts ) {
		return Contract._satisfy_template( obj, contracts, function( a, b ) { return a && b }, true ) ;
	},

	satisfy_one: function( obj, contracts ) {
		return Contract._satisfy_template( obj, contracts, function( a, b ) { return a || b }, false ) ;
	},

	is_a: function ( obj, cons ) {
		return ( Contract.is_not_null( obj ) && ( obj.constructor == cons ) ) ;
	},

	is_string: function( obj ) {
		return Contract.is_a( obj, String ) ;
	},

	is_array: function( obj ) {
		return Contract.is_a( obj, Array ) ;
	},

	is_boolean: function( obj ) {
		return Contract.is_a( obj, Boolean ) ;
	},

	is_object: function( obj ) {
		return Contract.is_a( obj, Object ) ;
	},

	is_not_null: function( obj ) {
		return !( Contract.is_null( obj ) ) ;
	},

	is_null: function( obj ) {
		return ( null == obj ) ;
	},

	is_function: function( obj ) {
		return Contract.is_a( obj, Function ) ;
	},

	has_property: function( obj, property, contract ) {
		if ( Contract.is_not_null( obj ) ) {
			contract = Contract.is_function( contract ) ? contract : Contract.is_not_null ;
			return contract( obj[property] ) ;
		} else {
			return false ;
		}
	},

	respond_to: function( obj, property ) {
		return Contract.has_property( obj, property, Contract.is_function ) ;
	},

	combinator: function() {
		var contracts = arguments ;
		return function( e ) {
			return Contract.satisfy_all( e, contracts ) ;
		}
	},

	brace: function( do_block, final_block ) {
		if ( !( Contract.is_function( do_block ) && Contract.is_function( final_block ) ) ) return null ;
		var retval = null ;
		try {
			retval = do_block() ;
		} catch ( e ) {
			if ( e.constructor != ContractViolation ) {
				throw e ;
			}
			retval = e ;
		}
		final_block() ;
		return retval ;
	}
}) ;

function ContractViolation( description ) {
	this.description = "Contract violation" ;
	if ( Contract.is_not_null( description ) ) {
		this.description += ": " + description ;
	}

	this.shout_out = function() {
		alert( this.description ) ;
	}
}

Array.prototype.extend({
	each: function( f, contract ) {
		if ( Contract.is_function( f ) ) {
			var enforce_contract = Contract.is_function( contract ) ;
			for ( var i = 0 ; i < this.length ; i++ ) {
				if ( ( !enforce_contract ) || ( contract( this[i] ) ) ) {
					f( this[i], i ) ;
				}
			}
		}
		return this ;
	},

	map: function( f, contract ) {
		if ( Contract.is_function( f ) ) {
			var me = this ;
			this.each( function( value, key ) {
				me[key] = f( value ) ;
			}, contract ) ;
		}
		return this ;
	},

	filter: function( contract ) {
		if ( Contract.is_function( contract ) ) {
			var new_array = new Array ;
			this.each( function( e ) {
				new_array.push( e ) ;
			}, contract ) ;
		} else {
			return [] ;
		}
	},

	remove: function( index ) {
		if ( ( this.length - 1 ) >= index )
		{
			return this.splice( index, 1 ) ;
		} else {
			return null ;
		}
	}
}) ;

Object.prototype.extend({
	each: function( f, contract ) {
		if ( Contract.is_function( f ) ) {
			var enforce_contract = Contract.is_function( contract ) ;
			for ( member in this ) {
				if ( ( !enforce_contract ) || ( contract( this[member] ) ) )   {
					f( this[member], member ) ;
				}				
			}
		}
		return this ;
	},

	map: function( f, contract ) {
		if ( Contract.is_function( f ) ) {
			var me = this ;
			this.each( function( value, key ) {
				me[key] = f( value ) ;
			}, contract ) ;
		}
		return this ;
	}
}) ;

Element.prototype.extend({
	each_child: function( f, contract ) {
		this.childNodes.each( f, contract ) ;
		return this ;
	},

	each_descendant: function( f, contract ) {
		if ( Contract.is_function( f ) ) {
			var enforce_contract = Contract.is_function( contract ) ;
			this.each_child( function( item ) {
				if ( ( !enforce_contract) || contract( item ) ) {
					f( item ) ;
				}

				if ( Contract.respond_to( item, "each_descendant" ) ) {
					item.each_descendant( f, contract ) ;
				}
			}) ;
		}
		return this ;
	}
}) ;



Debug = new Object ;
Debug.extend({
	consoles: new Array,
	register_console: function( console ) {
		if ( -1 == Debug.consoles.indexOf( console ) ) {
			Debug.consoles.push( console ) ;
			return console ;
		}
	},

	unregister_console: function( console ) {
		if ( ( idx = Debug.consoles.indexOf( console ) ) >= 0 ) {
			return Debug.consoles.remove( idx ) ;
		}
	},

	print: function( text ) {
		Debug.consoles.each( function( console ) {
			console.print( text ) ;
		}, function( e ) {
			return Contract.respond_to( e, "print" ) ;
		}) ;
	}
}) ;

DebugConsole = function() {
	this.container = document.createElement( 'div' ) ;
	Debug.register_console( this ) ;
	this.container.style.position = "relative" ;
	this.container.style.width = "100%" ;
	this.container.style.top = "0px" ;
	this.container.style.left = "0px" ;
	this.container.style.border = "thin red solid" ;
	this.container.style.height = "150px" ;
	this.container.style.overflow = "scroll" ;
	this.container.style.backgroundColor = "#000000" ;
	this.container.style.color = "#FFFFFF" ;

	this.print = function( text ) {
		this.container.innerHTML += text + "<br/>" ;
	}

	var body = b() ;
	if ( Contract.is_not_null( body ) ) {
		if ( 0 != body.childNodes.length ) {
			body.insertBefore( this.container, body.childNodes[0] ) ;
		} else {
			body.appendChild( this.container ) ;
		}
	}
}

