/*
 * Copyright [yyyy] [name of copyright owner]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


// Anonymous function start
//
(function( window, undefined )
{

/**
 * Config
 */
var Config =
{
	debug			: true,
	trace			: true,
	loopInterval	: 35,
	loadInterval	: 1000,
	actorValBase	: 10000,
	canvasId		: "screen",
	usageId			: "usage",
	infoId			: "info",
	dataJSName		: "stage.js"
};

/**
 * Res
 */
var Res =
{
	String :
	{
		LOADING_TEXT	: "Now Loading...",
		ERROR_TEXT		: "Error"
	},
	Color :
	{
		BG_READY		: "rgb(0, 0, 0)",
		FONT_READY		: "white"
	},
	Font :
	{
		LARGE		: "bold 18px 'ＭＳ Ｐゴシック'",
		SMALL		: "bold 14px 'ＭＳ Ｐゴシック'"
	}
};

/**
 * Debug
 */
var Debug = new function()
{
	/**
	 * メッセージボックスを表示する。
	 */
	this.alert = function( message )
	{
		if ( Config.debug )
		{
			window.alert( message );
		}
	};
	/**
	 * メッセージボックスを表示する。
	 */
	this.alertError = function( err )
	{
		var str =
			"name=" + err.name + "\n" +
			"message=" + err.message + "\n" +
			"lineNumber=" + err.lineNumber + "\n" +
			err.stack;

		if ( Config.debug )
		{
			window.alert( str );
		}
		if ( typeof console != "undefined" )
		{
			console.log( str );
		}
	};
	/**
	 * デバッグエリアに出力する。
	 */
	this.print = function( message )
	{
		if ( Config.debug )
		{
			var info	= window.document.getElementById( Config.infoId );
			if ( info )
			{
				info.innerHTML += "<p>" + message + "</p>";
			}
		}
	};
	/**
	 * トレース処理を追加する。（可変数引数）
	 * Application::loop 以下は呼ばれすぎるので実用性に難あり。
	 */
	this.intercept = function()
	{
		if ( !Config.trace || typeof console == "undefined" ) { return; }

		for ( var i = 0; i < arguments.length; i++ )
		{
			var proto	= arguments[i].prototype;
			for ( var property in proto )
			{
				this.setInterceptor( proto, property );	// 別関数にしないと method 変数が上書きされる。
			}
		}
	};
	/**
	 * トレース付き関数に入れ替える。
	 */
	this.setInterceptor = function( proto, property )
	{
		if ( typeof proto[ property ] == "function" )
		{
			var method = proto[ property ];
			proto[ property ] = function()
			{
				var startTime = new Date().getTime();
				console.log( "[" + property + "] has started" );

				var ret = method.apply( this, arguments );

				var endTime = new Date().getTime() - this.startTime;
				console.log( "[" + property + "] has finished at " + endTime + "ms" );
				return ret;
			};
		}
	};
}();

/**
 * Command
 */
var Command = function()
{
	this.tbl	= {};
};

(function( pt )
{
	/**
	 * 
	 */
	pt.clear = function()
	{
		for ( var key in this.tbl )
		{
			this.tbl[ key ] = 0;
		}
	};
})( Command.prototype );

/**
 * Element
 */
var Element = function()
{
	this.prev	= null;
	this.next	= null;
	this.child	= null;	// head of child list
};

(function( pt )
{
	/**
	 * 
	 */
	pt.append = function( element )
	{
		var tmp = this;

		while ( tmp )
		{
			if ( !tmp.next )
			{
				tmp.next		= element;
				element.prev	= tmp;
				return true;
			}
			tmp = tmp.next;
		}
		return false;
	};
	/**
	 * 
	 */
	pt.link = function( tmpPrev )
	{
		var tmpNext	= tmpPrev.next;
		tmpPrev.next= this;

		this.prev	= tmpPrev;
		this.next	= tmpNext;

		if ( tmpNext != null )
		{
			tmpNext.prev	= this;
		}
	};
	/**
	 * 
	 */
	pt.unlink = function()
	{
		var tmpPrev	= this.prev;
		var tmpNext	= this.next;
		if ( tmpPrev != null )
		{
			tmpPrev.next	= tmpNext;
		}
		if ( tmpNext != null )
		{
			tmpNext.prev	= tmpPrev;
		}
		this.prev	= null;
		this.next	= null;
	};
})( Element.prototype );

/**
 * Task
 */
var Task = function()
{
	this.command	= null;
	this.status		= 0;
	this.interfaces	= null;
};
Task.prototype = new Element();

(function( pt )
{
	/**
	 * 
	 */
	pt.setStatus = function( status )
	{
		this.status		= status;
		if ( this.interfaces )
		{
			var interface	= this.interfaces[ this.status ];
			// set interface properties
			for ( key in interface )
			{
				this[ key ] = interface[ key ];
			}
		}
	};
	/**
	 * 
	 */
	pt.update = function( scene )
	{
		return false;
	};
	/**
	 * 
	 */
	pt.draw = function( scene )
	{
	};
	/**
	 * 
	 */
	pt.updateChildren = function( scene )
	{
		var upd = false;
		for ( var task = this.child; task != null; task = task.next )
		{
			if ( task.update( scene ) )
			{
				upd = true;
			}
		}
		return upd;
	};
	/**
	 * 
	 */
	pt.drawChildren = function( scene )
	{
		for ( var task = this.child; task != null; task = task.next )
		{
			task.draw( scene );
		}
	};
})( Task.prototype );

/**
 * SoundTask
 */
var SoundTask = function( scene )
{
	this.audio		= null;
	this.play		= false;
	this.loop		= false;
};
SoundTask.prototype = new Task();

(function( pt )
{
	/**
	 * 
	 */
	pt.prepare = function( audio )
	{
		this.audio	= audio;
		this.play	= true;
		this.loop	= false;
	};
	/**
	 * 
	 */
	pt.prepareLoop = function( audio )
	{
		this.audio	= audio;
		this.play	= true;
		this.loop	= true;
	};
	/**
	 * 
	 */
	pt.cancel = function( audio )
	{
		this.audio	= audio;
		this.play	= false;
		this.loop	= false;
	};
	/**
	 * 
	 */
	pt.update = function( scene )
	{
		var upd = false;

		if ( this.audio != null )
		{
			var app			= scene.app;
			if ( this.play )
			{
				if ( this.loop )
				{
					//this.audio.loop	= true;
					//...Debug: ループ再生の応急処置
					var ad	= this.audio;
					if ( !ad.onended )
					{
						ad.onended = function( e ) { };
						app.addSysEventHandler( ad, "ended", function( e ) { ad.play(); } );
					}
				}
				this.audio.play();
			}
			else
			{
				this.audio.pause();	//...stop は無し？
			}
			// clear
			this.audio	= null;
			this.play	= false;
			this.loop	= false;
		}
		return upd;
	};
})( SoundTask.prototype );

/**
 * ReadyTask
 */
var ReadyTask = function( scene )
{
	this.text	= Res.String.LOADING_TEXT;
};
ReadyTask.prototype = new Task();

(function( pt )
{
	/**
	 * 
	 */
	pt.draw = function( scene )
	{
		var context		= scene.context;
		var canvas		= context.canvas;

		context.save();

		context.fillStyle	= Res.Color.BG_READY;
		context.fillRect( 0, 0, canvas.width, canvas.height );

		context.font		= Res.Font.LARGE;
		context.fillStyle	= Res.Color.FONT_READY;
		context.fillText( this.text, 10, canvas.height / 2 + 20 );

		context.restore();
	};
})( ReadyTask.prototype );

/**
 * ImageAnimator
 */
var ImageAnimator = function( image, anim )
{
	this.image	= image;
	this.anim	= anim;
	this.count	= 0;

	this.fx		= 0;
	this.fy		= 0;
	this.fw		= anim.fw;
	this.fh		= anim.fh;
};

(function( pt )
{
	/**
	 * 
	 */
	pt.start = function()
	{
		this.count = 0;
		this.proceed();
	};
	/**
	 * 
	 */
	pt.proceed = function()
	{
		var upd = false;

		var frame = this.anim.frames[ this.count ];
		if ( frame )
		{
			this.fx	= frame.x * this.anim.fw;
			this.fy	= frame.y * this.anim.fh;
			upd = true;
		}
		this.count++;

		return upd;
	};
	/**
	 * 
	 */
	pt.isActive = function()
	{
		return ( this.count < this.anim.frameCount );
	};
})( ImageAnimator.prototype );


/**
 * Message
 */
var Message = function( type )
{
	this.type	= type;
	this.data	= {};
};

/**
 * MessageHandler
 */
var MessageHandler = function( type, object, func )
{
	this.type		= type;
	this.object		= object;
	this.func		= func;	// function( scene, message )
};

/**
 * MessageManager
 */
var MessageManager = function()
{
	this.queue		= [];
	this.breakQueue	= [];
	this.handlers	= [];
};

(function( pt )
{
	/**
	 * 
	 */
	pt.addHandler = function( handler )
	{
		var type = handler.type;
		if ( !this.handlers[ type ] )
		{
			this.handlers[ type ]		= {};
			this.handlers[ type ].array	= [];
		}
		this.handlers[ type ].array.push( handler );
	};
	/**
	 * 
	 */
	pt.removeHandler = function( handler )
	{
		var array = this.handlers[ handler.type ].array;
		for ( var i = 0; i < array.length; i++ )
		{
			if ( array[i] == handler )
			{
				array.splice( i, 1 );
				break;
			}
		}
	};
	/**
	 * 
	 */
	pt.getHandlerArray = function( message )
	{
		var type = message.type;
		if ( this.handlers[ type ] )
		{
			return this.handlers[ type ].array;
		}
		return null;
	};

	/**
	 * 
	 */
	pt.postMessage = function( message )
	{
		this.queue.push( message );
	};
	/**
	 * 
	 */
	pt.postBreakMessage = function( message )
	{
		this.breakQueue.push( message );
	};
	/**
	 * 
	 */
	pt.handleMessages = function( scene )
	{
		var tmpQueue	= this.queue.concat( this.breakQueue );
		var ret			= ( this.breakQueue.length <= 0 );
		// clear queue
		this.queue.length		= 0;
		this.breakQueue.length	= 0;

		// for each queue
		for ( var i = 0; i < tmpQueue.length; i++ )
		{
			var message	= tmpQueue[i];
			var array	= this.getHandlerArray( message );
			if ( array )
			{
				// for each handler
				for ( var n = 0; n < array.length; n++ )
				{
					var handler = array[n];
					handler.func.call( handler.object, scene, message );
				}
			}
		}
		return ret;
	};
})( MessageManager.prototype );

/**
 * SceneStatus
 */
var SceneStatus =
{
	READY	: 0,
	RUNNING	: 1,
	ERROR	: 2
};

/**
 * Scene
 */
var Scene = function()
{
	this.app		= null;
	this.name		= null;

	this.context	= null;

	this.ticks		= 0;
	this.focus		= null;

	this.init		= null;
	this.loop		= null;

	this.readyTask	= new ReadyTask( this );
	this.interfaces	=
	[
		{ loop : this.loopReady,	draw : this.drawReady },
		{ loop : this.loopRunning,	draw : this.drawRunning },
		{ loop : this.loopReady,	draw : this.drawReady }
	];

	this.msgManager	= null;	// ここで new するとインスタンス間で実体が共有される。
};
Scene.prototype = new Task();

(function( pt )
{
	/**
	 * 
	 */
	pt.setFocus = function( task )
	{
		this.focus	= task;
	};
	/**
	 * 
	 */
	pt.getFocus = function()
	{
		return this.focus;
	};
	/**
	 * 
	 */
	pt.setUsage = function( html )
	{
		var usage	= this.app.getDomElement( Config.usageId );
		if ( usage )
		{
			usage.innerHTML = html;
		}
	};
	/**
	 * 
	 */
	pt.holdContext = function()
	{
		this.context	= this.app.getDomElement( Config.canvasId ).getContext("2d");
	};
	/**
	 * 
	 */
	pt.updateChildren = function( scene )
	{
		var upd = false;
		// check focus
		if ( this.focus == null )
		{
			for ( var task = this.child; task != null; task = task.next )
			{
				if ( task.update( scene ) ) { upd = true; }
			}
		}
		else
		{
			if ( this.focus.update( scene ) ) { upd = true; }
		}
		return upd;
	};

	/**
	 * 
	 */
	pt.loopReady = function()
	{
		this.ticks++;

		// update & draw
		var upd = this.readyTask.update( this );
		// draw
		if ( upd )
		{
			this.readyTask.draw( this );
		}
		return upd;
	};
	/**
	 * 
	 */
	pt.loopRunning = function()
	{
		this.ticks++;

		// update & draw
		var upd = this.updateChildren( this );
		// handle message
		if ( this.msgManager )
		{
			if ( !this.msgManager.handleMessages( this ) )
			{
				return false;
			}
		}
		// draw
		if ( upd )
		{
			this.drawChildren( this );
		}
		return upd;
	};
	/**
	 * 
	 */
	pt.drawReady = function()
	{
		this.readyTask.draw( this );
	};
	/**
	 * 
	 */
	pt.drawRunning = function()
	{
		this.drawChildren( this );
	};
	/**
	 * 
	 */
	pt.handleSysEvent = function( event )
	{
		this.command.handleSysEvent( event );
	};
})( Scene.prototype );

/**
 * SceneManager
 */
var SceneManager = function()
{
	this.current	= null;

	this.stack		= [];
	this.scenes		= {};
};

(function( pt )
{
	/**
	 * 
	 */
	pt.add = function( scene )
	{
		this.scenes[ scene.name ] = scene;
	};
	/**
	 * 
	 */
	pt.push = function( name )
	{
		var scene = this.scenes[ name ];
		if ( scene )
		{
			this.stack.push( scene );
			this.current = scene;
			return this.current;
		}
		else
		{
			return null;
		}
	};
	/**
	 * 
	 */
	pt.pop = function()
	{
		if ( this.stack.length > 1 )
		{
			var poped		= this.stack.pop();
			this.current	= this.stack[ this.stack.length - 1 ];
			return poped;
		}
		else
		{
			return null;
		}
	};

	/**
	 * 
	 */
	pt.init = function( name )
	{
		// window.onload のタイミング
		for ( key in this.scenes )
		{
			var scene = this.scenes[ key ];
			if ( scene.init )
			{
				scene.init();
			}
		}
		this.current.show();
	};
	/**
	 * 
	 */
	pt.loop = function()
	{
		this.current.loop();
	};
	/**
	 * 
	 */
	pt.handleSysEvent = function( event )
	{
		this.current.handleSysEvent( event );
	};
})( SceneManager.prototype );


/**
 * AppStatus
 */
var AppStatus =
{
	READY	: 0,
	RUNNING	: 1,
	ERROR	: 2
};

/**
 * Application
 */
var Application = function()
{
	this.status			= AppStatus.READY;
	this.loopInterval	= Config.loopInterval;
	this.sceneManager	= new SceneManager();
};

(function( pt )
{
	/**
	 * 
	 */
	pt.runLoop = function()
	{
		try
		{
			this.sceneManager.loop();
			if ( this.status != AppStatus.ERROR )
			{
				var self = this;
				window.setTimeout( function() { self.runLoop(); }, this.loopInterval );
			}
		}
		catch ( e )
		{
			this.kill();
			Debug.alertError( e );
		}
	};
	/**
	 * 
	 */
	pt.kill = function()
	{
		this.status	= AppStatus.ERROR;
	};
	/**
	 * 
	 */
	pt.loadScript = function( url )
	{
		var node	= window.document.createElement("script");
		node.type	= "text/javascript";
		//node.language	= "javascript";
		node.charset= "UTF-8";
		node.src	= url;

		window.document.getElementsByTagName("body")[0].appendChild( node );
	};
	/**
	 * 
	 */
	pt.getDomElement = function( id )
	{
		return window.document.getElementById( id );
	};
	/**
	 * 
	 */
	pt.createXMLHttpRequest = function()
	{
		if ( window.XMLHttpRequest )
		{
			return new XMLHttpRequest();
		}
		else if ( window.ActiveXObject )
		{
			try
			{
				return new ActiveXObject("Msxml2.XMLHTTP");
			}
			catch ( e )
			{
				try
				{
					return new ActiveXObject("Microsoft.XMLHTTP");
				}
				catch ( e2 )
				{
					return null;
				}
			}
		}
		else
		{
			return null;
		}
	};
	/**
	 * 
	 */
	pt.loadResource = function( baseUrl, resource )
	{
		var base = ( baseUrl.charAt( baseUrl.length - 1 ) == '/' ) ? baseUrl : baseUrl + '/';

		var arr = [ resource.image, resource.sound ];
		for ( var i = 0; i < arr.length; i++ )
		{
			var obj = arr[i];
			for ( var key in obj )
			{
				var prop = obj[ key ];
				if ( prop instanceof Array )
				{
					for ( var n = 0; n < prop.length; n++ )
					{
						prop[n].data = ( obj == resource.image ) ? new Image() : new Audio();
						prop[n].data.src = base + prop[n].name;
					}
				}
				else
				{
					prop.data = ( obj == resource.image ) ? new Image() : new Audio();
					prop.data.src = base + prop.name;
				}
			}
		}
	};
	/**
	 * 
	 */
	pt.handleSysEvent = function( event )
	{
		try
		{
			// check status
			if ( this.status != AppStatus.RUNNING ) { return; }

			var newEvent = {};
			// ここでブラウザ間の差異を吸収する。
			newEvent.orgEvent	= event;
			newEvent.prevent	= this.preventSysEvent;
			newEvent.target		= event.target;
			newEvent.type		= event.type;
			newEvent.keyCode	= event.keyCode;
			newEvent.layerX		= event.layerX;
			newEvent.layerY		= event.layerY;
			if ( !newEvent.layerX && !newEvent.layerY && event.offsetX && event.offsetY )
			{
				newEvent.layerX	= event.offsetX;
				newEvent.layerY	= event.offsetY;
			}
			this.sceneManager.handleSysEvent( newEvent );
		}
		catch ( e )
		{
			this.kill();
			Debug.alertError( e );
		}
	};
	/**
	 * 
	 */
	pt.preventSysEvent = function( event )
	{
		if ( event.preventDefault )
		{
			event.preventDefault();
		}
		else
		{
			event.returnValue	= false;
		}
		if ( event.stopPropagation )
		{
			event.stopPropagation();
		}
		else
		{
			event.cancelBubble	= true;
		}
	};
	/**
	 * 
	 */
	pt.addSysEventHandler = function( target, type, listener )
	{
		if ( !listener )
		{
			var self	= this;
			listener	= function( e ) { self.handleSysEvent( e ); };
		}

		if ( target.addEventListener )
		{
			target.addEventListener( type, listener, false );
		}
		else if ( target.attachEvent )
		{
			target.attachEvent( 'on' + type, function() { listener.call( target, window.event ); } );
		}
		else
		{
			target[ 'on' + type ]	= function( e ) { listener.call( target, e || window.event ); };
		}
	};
	/**
	 * 
	 */
	pt.init = function()
	{
		try
		{
			// check status
			if ( this.status == AppStatus.ERROR ) { return; }

			var self	= this;
			var canvas	= this.getDomElement( Config.canvasId );
			var handler	= function( e ) { self.handleSysEvent( e ); };

			// set handler
			this.addSysEventHandler( window.document,	"keydown",		handler );
			this.addSysEventHandler( window.document,	"keyup",		handler );
			this.addSysEventHandler( canvas,			"mousedown",	handler );
			this.addSysEventHandler( canvas,			"mouseup",		handler );
			this.addSysEventHandler( canvas,			"mousemove",	handler );
			this.addSysEventHandler( canvas,			"mouseout",		handler );

			this.sceneManager.init();

			this.status	= AppStatus.RUNNING;
			this.runLoop();
		}
		catch ( e )
		{
			this.kill();
			Debug.alertError( e );
		}
	};
})( Application.prototype );

// set interceptor
//Debug.setInterceptor( MessageManager, SceneManager, Application );

// Expose
if ( !window.h5glib ) { window.h5glib = {}; }

window.h5glib.Config		= Config;
window.h5glib.Debug			= Debug;
window.h5glib.Command		= Command;
window.h5glib.Task			= Task;
window.h5glib.SoundTask		= SoundTask;
window.h5glib.ReadyTask		= ReadyTask;
window.h5glib.ImageAnimator	= ImageAnimator;
window.h5glib.Message		= Message;
window.h5glib.MessageHandler= MessageHandler;
window.h5glib.MessageManager= MessageManager;
window.h5glib.SceneStatus	= SceneStatus;
window.h5glib.Scene			= Scene;
window.h5glib.Application	= Application;

// for unit test
if ( window.h5glib.UNIT_TEST )
{
	window.h5glib.Element		= Element;
	window.h5glib.SceneManager	= SceneManager;
	window.h5glib.AppStatus		= AppStatus;
	window.h5glib.Application	= Application;
}

// Anonymous function end
//
})( window );
