if ( typeof isArray == "undefined" )
	isArray = function ( obj ) {
		return Object.prototype.toString.call ( obj ) == "[object Array]";
	}
if ( typeof isFunction == "undefined" )
	isFunction = function ( obj ) {
		return Object.prototype.toString.call ( obj ) == "[object Function]";
	}
if ( typeof isObject == "undefined" )
	isObject = function ( obj ) {
		return Object.prototype.toString.call ( obj ) == "[object Object]";
	}


var Conversation = ( function ( ) {
	var makeChoice = function ( conversation, set_id, text, choice_id, actor, meta, scene, line ) {
		if ( isFunction ( text ) ) text = text.apply(conversation);
		if ( !conversation._responses ) conversation._responses = {}
		conversation._responses[choice_id] = function ( ) {
			conversation._responses = null;
			conversation._currentScene = scene;
			conversation._currentLine = line;
			if ( set_id ) {
				conversation._choices[set_id] = {
					id : choice_id,
					line : text,
					actor : conversation.getActor ( actor ),
					meta : meta || {},
					response : choice_id
				};
			}
		}
		return {
			actor : actor,
			line : formatContent ( text, conversation ),
			meta : meta || {},
			response : choice_id,
			choose : function ( ) { conversation.respond ( choice_id ); }
		}
	}
	var formatContent = function ( content, conversation, data ) {
		if ( isFunction ( content ) ) content = content.apply ( conversation );
		return content.replace ( /\${([^}]*)}/, function ( match, parameter ) {
			var context = data;
			var parts = parameter.split ( "." );
			while ( parts.length ) {
				var part = parts.shift();
				if ( context && context[part] ) {
					context = context[part];
				} else {
					if ( conversation.getActor ( part ) ) {
						context = conversation.getActor ( part );
					} else {
						context = parameter;
						break;
					}
				}
			}
			return context;
		} );
	}
	var $class = function ( script, data ) {
		this._script = script || {};
		this._data = data;
		this.reset();
	}
	$class.prototype.reset = function ( ) {
		this._choices = {};
		this._choices.toString = function ( ) {
			var qs = [];
			for ( var id in this ) {
				if ( this.hasOwnProperty(id) && id != "toString" ) 
					qs.push ( id + "=" + escape ( this[id].id ) );
			}
			return qs.join("&");
		}
		this._cast = this._script.cast || {};
		this._scenes = this._script.scenes || {};
		this._title = this._script.title;
		var start = this._script.start;
		if ( !start ) {
			if ( this._script.scenes[1] )
				start = 1;
			else
				for ( start in this._script.scenes )
					break;
		}
		this._currentScene = start;
		this._currentLine = 0;
	}
	$class.prototype.next = function ( data ) {
		var $scene = this._currentScene;
		if ( isFunction($scene) ) $scene = $scene.apply(this);
		var $line = this._currentLine;
		if ( isFunction($line) ) $line = $line.apply(this);

		var next = (this._scenes[$scene]||[])[$line];
		if ( !next ) return null;

		if ( next.jump ) {
			var jump = isFunction(next.jump) ? next.jump.apply(this) : next.jump;
			if ( jump ) {
				this._currentScene = jump;
				this._currentLine = 0;
			} else {
				this._currentLine += 1;
			}
			return this.next(data);
		}

		if ( next.line ) {
			this._currentLine += 1;
			return {
				actor : next.actor,
				line : formatContent ( next.line, this, $.extend ( {}, data, this._data ) ),
				meta : next.meta || {}
			}
			return line;
		} else if ( next.choices ) {
			var choices = [];
			var c = next.choices;
			if ( isFunction ( c ) ) c = c.apply(this);

			if ( isArray ( c ) ) {
				for ( var i = 0; i < c.length; i ++ ) {
					var text = formatContent ( c[i].line, this, $.extend ( {}, data, this._data ) )
					var next_scene = c[i].jump ? c[i].jump : $scene;
					var next_line = c[i].jump ? 0 : $line + 1;
					choices.push ( makeChoice ( this, next.id, text, i, c[i].actor || next.actor, c[i].meta, next_scene, next_line ) );
				}
			} else if ( isObject ( c ) ) {
				for ( var i in c ) {
					var text = formatContent ( c[i].line, this, $.extend ( {}, data, this._data ) )
					var next_scene = c[i].jump ? c[i].jump : $scene;
					var next_line = c[i].jump ? 0 : $line + 1;
					choices.push ( makeChoice ( this, next.id, text, i, c[i].actor || next.actor, c[i].meta, next_scene, next_line ) );
				}
			} else {
				var text = formatContent ( c, this, $.extend ( {}, data, this._data ) )
				choices.push ( makeChoice ( this, next.id, text, 0, next.actor, null, $scene, $line + 1 ) );
			}
			return {
				title : next.title,
				choices : choices
			}
		} else if ( next.wait ) {
			this._currentLine += 1;
			return { wait : next.wait, onWait : next.onWait || function ( cb ) { cb(); } };
		}
	}
	$class.prototype.respond = function ( choice ) {
		if ( isObject ( this._responses ) && isFunction ( this._responses[choice] ) )
			this._responses[choice]();
	}
	$class.prototype.getTitle = function ( ) {
		return this._title;
	}
	$class.prototype.getActor = function ( id ) {
		var actor = null;
		if ( actor = this._cast[id] )
			actor.id = id;
		return actor;
	}
	$class.prototype.getChoices = function ( ) {
		return this._choices;
	}
	$class.prototype.actors = function ( ) {
		return this._cast;
	}
	return $class;
} )();
