/*

	objx core library
	
	Copyright (c) 2009 Mat Ryer
	
	http://objx.googlecode.com/

	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	"Software"), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
	
*/


// define root namespace
var objx = function(o){

	// if this is already an objx object -
	//  just return it
	if (o && o._objx) {
		return o;
	}
	
	// create new object
	return new objx.fn.init(o);
	
};


/*
 *  debug
 *  if 'true' (default), performs additional error checking
 *	to assist development.  This can be set to 'false' for production
 *  and will provide slight performance enhancements.
 */ 
var ODebug = true;


// current version
var OVersion = "2.1.1";


// the root object
objx.fn = objx.prototype = {

	// object marker
	_objx: true,


	// actual object
	_obj: null,


	// initialization function
	init: function(obj) {
	
		// save the object
		this.obj(obj);
		
	},
	
	
	/*
	 *  extend
	 *  Extends the selected object 
	 */
	extend: function() {
	
		var args = [];
		
		// add the first argument
		args.push(this._obj);
		
		// add the other arguments
		for (var i = 0; i < arguments.length; i++) {
			args.push(arguments[i]);
		}
	
		// call the extender
		OExtend.apply(objx, args);
	
		// return this for chaining
		return this;
	
	},
	
	
	/*
	 *  type
	 *  Gets the type of the object
	 */
	type: function() {
		return OType(this._obj);
	},
	
	
	/*
	 *  obj()
	 *  Gets or sets the object that is being enhanced
	 *
	 *		objx(obj).obj() == obj
	 *		objx(obj).obj(obj2)
	 *
	 */
	obj: function() {
		if (arguments.length === 0) {
			return this._obj;
		} else {
			this._obj = arguments[0];
		}
		return this;
	},
	
	
	/*
	 *  size()
	 *  Gets the size of the object.
	 */
	size: function() {
	
		if (typeof this._obj.length == "undefined") {
			OError("size", "Cannot get the size of " + this.type() + " objects.");
		};
		
		return this._obj.length;
	},
	
	
	/*
	 *  toString()
	 *  Gets a string representing this object - to make debugging easier
	 */
	toString: function() {
		
		var type = this.type();
		var val = this._obj.toString();
		
		switch (type) {
			case "string":
				val = '"' + val + '"';
				break;
			case "function":
				val = " ... "
				break;
			case "array":
			
				var size = this.size();
				
				if (size === 0) {
					val = "0";
				} else {
					val = " 0.." + (this.size() - 1) + " ";
				}
				
				break;
		}
		
		return "(" + type + "(" + val + "))";
	},
	

	/*
	 *  requires
	 *  Instance version of ORequires
	 *
	 *  Allows objx("Source").requires("item1", "item2").requires("item3");
	 *
	 */
	requires: function() {

		for (var i = 0, l = arguments.length; i<l; i++)
			ORequires(arguments[0], this.obj());

		return this;

	}
	
};


// setup easy construction
objx.fn.init.prototype = objx.fn;


// keep track of plugins
OPlugins = {};


/*
 *  OIsObjx
 *  checks whether an object is an objx object or not
 *
 *  OIsObjx( object-to-test )
 *
 */
var OIsObjx = function(o) {
	if (o && o._objx) {
		return true;
	}
	return false;
};


/*
 *  OExtend
 *  extends the first object with the others
 *
 *  js.extend(destination, source1 [, source2 [, source3]]);
 *  
 *  destination		-	The target object (everything will be copied
 *  										into this object)
 *  sourceX			-	These objects will have their properties, functions
 *  					etc. copied to 'destination'
 *  
 */
var OExtend = function() {
	
	if (ODebug) {
		if (arguments.length < 2) {
			OError("OExtend", "Must provide at least two arguments when using OExtend(). See http://code.google.com/p/objx/wiki/extend");
		}
	}
		
	for (var i = 1; i < arguments.length; i++) {
		for (var property in arguments[i]) {
			arguments[0][property] = arguments[i][property];
		}
	}
	
	return arguments[0];
	
};


/*
 *  OBind
 *  Binds context and arguments to a function
 *
 *  js.bind(function, context [, argument1 [, argument2]]);
 */
var OBind = function() {

	var _func = arguments[0] || null;
	var _obj = arguments[1] || this;
	var _args = [];
	
	// add arguments
	for (var i = 2, l = arguments.length; i<l; i++) {
		_args.push(arguments[i]);
	}
	
	// return a new function that wraps everything up
	var bound = function() {
		
		// start an array to get the args
		var theArgs = [];
		
		var i = 0;

		// add every argument from _args
		for (i = 0, l = _args.length; i < l; i++) {
			theArgs.push(_args[i]);
		}
		
		// add any real arguments passed
		for (i = 0, l = arguments.length; i < l; i++) {
			theArgs.push(arguments[i]);
		}

		// call the function with the specified context and arguments
		return _func.apply(_obj, theArgs);

	};
	
	bound.func = _func;
	bound.context = _obj;
	bound.args = _args;
	
	return bound;
	
};


/*
 *  OGet
 *
 *  Gets a value from an object by property name
 *
 */
var OGet = function(o, p) {

	var dotPos = p.indexOf(".");

	if (dotPos == -1) {
		return o[p];
	}

	return OGet(o[p.substring(0, dotPos)], p.substring(dotPos + 1));

};


/*
 *  ORequires
 *  Indicates that a plugin is required and throws an error if it is not included
 *
 *  ORequires( source, plugin )
 *
 *  plugin 	-	The name of the plugin that is required
 *  source 	-	(optional) A name describing the plugin that requires this other plugin
 *
 */
var ORequires = function(plugin, source) {

	// has this been provided?
	if (!OPlugins[plugin]) {
		OError("requires", "Plugin \"" + plugin + "\" is required by \"" + source + "\" but missing. Are you missing a <script /> tag?  Have you got your <script /> tags in the wrong order?");
	}
	
	return this;
		
};

/*
 *  OProvides
 *  Indicates that a plugin is being provided
 *
 *	OProvides( plugin )
 *
 *	plugin - A unique name describing the plugin
 *
 */
var OProvides = function(plugin) {

	// has this already been provided?
	if (OPlugins[plugin]) {
		OError("provides", "A plugin called \"" + plugin + "\" has already been provided. Have you duplicated a <script /> tag?");
	}
	
	// save the fact that this has been provided
	OPlugins[plugin] = true;
	
};

/*
 *  OIndex
 *  Gets the real index from magic index values (i.e can be negative)
 *  
 */
var OIndex = function(i, l) {
	return (i > -1) ? i : (l + (i));
};


/*
 *  OIndexRange
 *  Gets the real index range from magic index values (i.e. can be negative)
 */
var OIndexRange = function(s, e, len) {
	
	var range = {};
	
	// resolve any magic indexes (i.e. negative numbers)
	range.start = OIndex(s, len);
	if (e) {
		range.end = OIndex(e, len);
	} else if (s < 0) {
		range.end = len - 1;
	} else {
		range.end = range.start;
	}
	
	// make sure they're the right way around
	if (s && e) {
	
		s = Math.min(range.start, range.end);
		e = Math.max(range.start, range.end);
	
		range.start = s;
		range.end = e;
	
	}
	
	return range;
	
};


/*
 *  OType
 *  Gets the type of object
 */
var OType = function(o) {
	// return the type of the object
	if (o && typeof o == "object" && typeof o.length != "undefined") {
		return "array";
	} else {
		return typeof o;
	}
};


/*
 *  OArray
 *  Ensures that an object is an array
 *
 *	(array) OArray( object );
 *
 */
var OArray = function(o){
	
	if (OType(o) != "array") {
		return [o];
	} else {
		return o;
	}
	
};


/*
 *  OProvided
 *  Gets whether a plugin has been provided or not
 */
var OProvided = function(plugin) {
	return OPlugins[plugin] || false;
};


/*
 *  OError
 *  Just throws an error
 *
 *  OError( message )
 *  OError( tag, message )
 */
var OError = function() {

	// the last thing is the message
	var tag = arguments.length > 1 ? arguments[0] : "Error";
	var message = arguments[arguments.length - 1];
	throw tag + ": " + message;
	
};


/*
 *  OToString
 *  Gets a string representation of this object to make debugging easier.
 */
var OToString = function() {
	return "{objx engine}";
};
