/*
 * pettanR system.js
 *   version 0.5.48
 *
 * gadgetOS
 *   author:
 *     itozyun
 *   licence:
 *     3-clause BSD
 */

( function( window, document, undefined ){
	
	var body           = document.getElementsByTagName( 'body' )[ 0 ]; //( document.compatMode || '' ) !== 'CSS1Compat' ? document.body : document.documentElement;// 
	var SERVICE_LIST   = [];
	var SUPER_USER_KEY = { getUID: function(){ return 0; }};
	var API_USER_LIST  = [ SUPER_USER_KEY ];
	var numApiUser     = 1;
	
	function EMPTY_FUNCTION(){};
	
	function isApiUser( _user ){
		return _user === SUPER_USER_KEY ||
			File.isDriver( _user ) === true ||
			Application.isApplicationInstance( _user ) === true;
	};
	
	var Const = {
		FILE: {
			TYPE: {
				UNKNOWN:	0,
				FOLDER:		1,
				IMAGE:		2,
				TEXT:		3,
				HTML:		4,
				CSV:		5,
				JSON:		6,
				XML:		7
			},
			STATE: {
				UNKNOWN:	0,
				OK:			1,
				LOADING:	2,
				ERROR:		3,
				BROKEN:		4
			},
			UPDATE_POLICY: {
				_____:		parseInt( '00000', 2 ),
				____C:		parseInt( '00001', 2 ), // hasCreateMenu
				___W_:		parseInt( '00010', 2 ), // isWritable
				___WC:		parseInt( '00011', 2 ), // isWritable
				__R__:		parseInt( '00100', 2 ), // isRenamable
				__R_C:		parseInt( '00101', 2 ), // hasCreateMenu
				__RW_:		parseInt( '00110', 2 ), // isWritable
				__RWC:		parseInt( '00111', 2 ), // isWritable
				_S___:		parseInt( '01000', 2 ), // childrenIsSortable
				_S__C:		parseInt( '01001', 2 ),
				_S_W_:		parseInt( '01010', 2 ),
				_S_WC:		parseInt( '01011', 2 ),
				_SR__:		parseInt( '01100', 2 ),
				_SR_C:		parseInt( '01101', 2 ),
				_SRW_:		parseInt( '01110', 2 ),
				_SRWC:		parseInt( '01111', 2 ),
				D____:		parseInt( '10000', 2 ),
				D___C:		parseInt( '10001', 2 ), // hasCreateMenu
				D__W_:		parseInt( '10010', 2 ), // isWritable
				D__WC:		parseInt( '10011', 2 ), // isWritable
				D_R__:		parseInt( '10100', 2 ), // isRenamable
				D_R_C:		parseInt( '10101', 2 ), // hasCreateMenu
				D_RW_:		parseInt( '10110', 2 ), // isWritable
				D_RWC:		parseInt( '10111', 2 ), // isWritable
				DS___:		parseInt( '11000', 2 ), // childrenIsSortable
				DS__C:		parseInt( '11001', 2 ),
				DS_W_:		parseInt( '11010', 2 ),
				DS_WC:		parseInt( '11011', 2 ),
				DSR__:		parseInt( '11100', 2 ),
				DSR_C:		parseInt( '11101', 2 ),
				DSRW_:		parseInt( '11110', 2 ),
				DSRWC:		parseInt( '11111', 2 ),
				CREATE:		1,
				WRAITE:		2,
				RENAME:		4,
				SORT:		8,
				DELETE:		16
			},
			EVENT: {
				UPDATE_ATTRIVUTE:	'onFileUpdate',
				GET_SEQENTIAL_FILES:'gotSeqentilFiles'
			},
			DATA_PROPERTY_RESERVED: [
				'children', 'driver', 'state', 'type'
			]			
		},
		TREE: {
			EVENT: {
				UPDATE:				'onTreeUpdate'
			}
		},
		KEY: {
			EVENT: {
				KEY_DOWN:			'keydown',
				KEY_UP:				'keyup',
				KEY_CHANGE:			'keychange',
				CURSOL:				'cursol'
			}
		},
		APP: {
			TYPE: {
				GENERAL : 0,
				OVERLAY : 1,
				PAGE    : 2
			}
		}
	};

/**
 * Class を定義し システムの管理下に置く．
 * 全てのクラスと pool が有効の場合インスタンスへの参照が保持される．
 *  1. Class.create( def, opt_final, opt_pool, opt_abstract ) でクラスを登録．
 *  2. コンストラクタ となるメソッドは、Constructor : function( arg ){ ... }, に書く．
 *  3. 通常通り new で インスタンス生成
 *  4. kill() でオブジェクトをクリーンして削除、pool が有効の場合は pool される．
 *  5. pool が有効の場合、new で pool されたインスタンスが返される．
 *  6. 
 * 
 */
var Class = ( function(){
	var CLASS_LIST         = [],
		DEF_LIST           = [],
		PRIVATE_CLASS_LIST = [],
		PRIVATE_DEF_LIST   = [],
		CONSTRUCTOR        = 'Constructor',
		GET_INDEX          = Util.getIndex,
		killPrivateFlag    = false,
		dataUser           = null,
		traits             = null,
		f                  = true,
		copyArray          = Util.copyArray;
	
	function getClass( instance ){
		var cList    = CLASS_LIST,
			i        = cList.length,
			klass;
		for( ; i; ){
			klass = cList[ --i ];
			if( instance instanceof klass ) return klass;
		};
		cList = PRIVATE_CLASS_LIST;
		i     = cList.length;
		for( ; i; ){
			klass = cList[ --i ];
			if( instance instanceof klass ) return klass;
		};
		
		if( GET_INDEX( cList, instance ) !== -1 ) return instance;
		if( GET_INDEX( CLASS_LIST, instance ) !== -1 ) return instance;
	};
	
	function getClassDef( KlassOrInstance ){
		var getIndex = GET_INDEX,
			i        = getIndex( CLASS_LIST, KlassOrInstance );
		if( i === -1 ) i = getIndex( CLASS_LIST, getClass( KlassOrInstance ) );
		if( i !== -1 ) return DEF_LIST[ i ];
		
		i = getIndex( PRIVATE_CLASS_LIST, KlassOrInstance );
		if( i === -1 ) i = getIndex( PRIVATE_CLASS_LIST, getClass( KlassOrInstance ) );
		if( i !== -1 ) return PRIVATE_DEF_LIST[ i ];
		
		if( GET_INDEX( DEF_LIST, KlassOrInstance ) !== -1 ) return KlassOrInstance;
		if( GET_INDEX( PRIVATE_DEF_LIST, KlassOrInstance ) !== -1 ) return KlassOrInstance;
	};
	
	/* over のプロパティを target にコピーする．ただし target の プロパティが優先, force で解除 */
	function override( target, over, force ){
		for( var p in over ){
			if( force === true || typeof target[ p ] === 'undefined' ){
				target[ p ] = over[ p ];
			};
		};
		return target;
	};
	
	/* サブクラスを作るメソッド  
	 * var subClass = superClass.inherits( ... ) 
	 * http://d.hatena.ne.jp/m-hiyama/20051018/1129605002
	 */
	function inherits( /* displayName, classSetting, opt_PrivateClass, props */ ){
		var args        = copyArray( arguments ),
			params      = [],
			Super       = this,
			superDef    = getClassDef( Super ),
			displayName = args[ 0 ],
			classSetting,
			opt_super,
			klass;
		if( superDef.Final === true ) throw new Error( 'Class is final!' );
		
		if( Type.isString( displayName ) === true ){
			args.shift();
		} else {
			displayName = 'SubClass of ' + superDef.displayName;
		};
		params.push( displayName );
		
		classSetting = args[ 0 ];
		if( Type.isNumber( classSetting ) === true ){
			if( superDef.isPrivate === true ) classSetting = classSetting | Class.PRIVATE_DATA;
			opt_super = !!( classSetting & Class.SUPER_ACCESS );
			params.push( classSetting );
			args.shift();
		};
		if( getClass( args[ 0 ] ) ){
			params.push( args.shift() );
		} else
		if( superDef.privateClass ){
			params.push( superDef.privateClass );
		};
		params.push( args[ 0 ] ); /* props */
		f      = false;
		traits = new Super();
		f      = true;
		klass  = Class.create.apply( Class, params );
		traits = null;
		if( opt_super === true ) getClassDef( klass ).Super = Super.prototype;
		return klass;
	};
	
	/* Class.create で作られたクラスのインスタンスが共通で備えるメソッド */
	var CommonProps = {
		kill : function(){
			var instance = this,
				klass    = getClass( instance ),
				def      = getClassDef( klass ),
				data, p, i;
			if( def.isPrivate === true && killPrivateFlag === false ){
				throw new Error( 'PrivateInstance.kill() work in PrivateUser.kill().' );
			};
			Type.isFunction( instance.onKill ) === true && instance.onKill();
			for( p in instance ){
				if( instance.hasOwnProperty && !instance.hasOwnProperty( p ) ) continue;
				delete instance[ p ];
			};
			if( def.pool ){
				def.live && def.live.splice( GET_INDEX( def.live, instance ), 1 );
				def.pool.push( instance );
			};
			if( def.privateClass ){
				i = GET_INDEX( def.userList, instance );
				if( i !== -1 ){
					data            = klass.getPrivateData( instance );
					killPrivateFlag = true;
					data.kill();
					killPrivateFlag = false;
					def.dataList.splice( i, 1 );
					def.userList.splice( i, 1 );
				};
			};
			// myCallback の削除
			// myCallback を受け取った API への通知
		},
		getMyCallback : function( callback ){
			var def       = getClassDef( this ),
				iList     = def.callbackInstanceList,
				rList     = def.callbackRegisterList,
				i, cList, myCallback;
			if( !iList ){
				iList = def.callbackInstanceList = [];
				rList = def.callbackRegisterList = [];
			};
			i = GET_INDEX( iList, this );
			if( i === -1 ){
				cList = [];
				iList.push( this );
				rList.push( cList );
			} else {
				cList = rList[ i ];
				for( i = cList.length; i; ){
					if( cList[ --i ].callback === callback ) return cList[ i ];
				};
			};
			myCallback = new Callback( this, callback );
			cList.push( myCallback );
			return myCallback;
		},
		releaseMyCallback : function( callback ){
			var def   = getClassDef( this ),
				iList = def.callbackInstanceList,
				rList = def.callbackRegisterList,
				i, _i, cList;
			if( !iList ) return;
			i = GET_INDEX( iList, this );
			if( i === -1 ) return;
			cList = rList[ i ];
			_i    = GET_INDEX( cList, callback );
			if( _i === -1 ) return;
			cList.splice( _i, 1 );
			callback.kill();
			if( cList.length !== 0 ) return;
			iList.splice( i, 1 );
			rList.splice( i, 1 );
			if( iList.length !== 0 ) return;
			delete def.callbackInstanceList;
			delete def.callbackRegisterList;
		},
		listenTo : function( eventDispatcher, eventType, callback ){
			//if( eventDispatcher instanceof EventDsipatcher ){
				eventDispatcher.addEventListener( eventType, callback, this );
			//}
		},
		unlistenTo : function( eventDispatcher, eventType, callback ){
			
		}
	};

	/* privateDataclass をもつクラスに追加されるメソッド */
	function newPrivateData( /* instance, args */ ){
		var args         = copyArray( arguments ),
			user         = args.shift(),
			def          = getClassDef( user ),
			privateClass = def.privateClass,
			privateDef   = getClassDef( privateClass ),
			i            = -1,
			data;
		if( def.userList ){
			i = GET_INDEX( def.userList, user );
		} else {
			def.userList = [];
			def.dataList = [];
		};
		if( i !== -1 ){
			throw new Error( 'PrivateData already exist!' );
		};
		dataUser  = user;
		data      = new privateClass( args );
		data.User = user;
		dataUser  = null;	
		return data;
	};
	function getPrivateData( instance ){
		var def = getClassDef( instance ),
			i   = GET_INDEX( def.userList, instance );
		if( i !== -1 ) return def.dataList[ i ];
	};
	
	/*
	 * new の実体．コンストラクタの機能は instance.Constructor に書く．
	 * これにより pool された オブジェクト（破棄されたインスタンス） を再利用できる
	 */
	/* Constructor Real for GeneralClass */
	function C( args ){
		var klass = this,
			def   = getClassDef( klass ),   
			instance,
			userDef;
		if( def.Abstract === true ){
			throw new Error( 'AbstractClass!' );
		};
		if( def.isPrivate === true && dataUser === null ){
			throw new Error( 'use myClass.newPrivateData( instance, ...args )!' );
		};
		f = false;
		instance = def.pool && def.pool.length > 0 ? def.pool.shift() : instance = new klass();
		f = true;
		if( def.Super && !instance.Super ) instance.Super = def.Super;
		if( def.isPrivate === true ){
			userDef = getClassDef( dataUser );
			userDef.dataList.push( instance );
			userDef.userList.push( dataUser );
		} else {
			def.live && def.live.push( instance );
			args = copyArray( arguments );
		};
		def[ CONSTRUCTOR ] && def[ CONSTRUCTOR ].apply( instance, args );
		return instance;
	};
	
	return {
		POOL_OBJECT  : 1,
		ABSTRACT     : 2,
		FINAL        : 4,
		SUPER_ACCESS : 8,
		PRIVATE_DATA : 16,
		create : function( /* displayName, classSetting, opt_PrivateClass, props */ ){
			var args        = copyArray( arguments ),
				displayName = args[ 0 ],
				classSetting,
				opt_pool, opt_abstract, opt_final, opt_private,
				privateDef,
				props,
				klass,
				classDef = {};
			if( Type.isString( displayName ) === true ){
				classDef.displayName = displayName;
				args.shift();
			};
			classSetting = args[ 0 ];
			if( Type.isNumber( classSetting ) === true ){
				opt_pool     = !!( classSetting & Class.POOL_OBJECT  );
				opt_abstract = !!( classSetting & Class.ABSTRACT     );
				opt_final    = !!( classSetting & Class.FINAL        );
				opt_private  = !!( classSetting & Class.PRIVATE_DATA );
				if( opt_final === true && opt_abstract === true ){
					throw new Error( 'final & Abstract!' );
				};				
				args.shift();
			};
			
			if( GET_INDEX( PRIVATE_CLASS_LIST, args[ 0 ] ) !== -1 ){
				privateDef = getClassDef( args[ 0 ] );
				if( privateDef.isPrivate !== true ){
					throw new Error( 'PrivateClass not found! please, Class.create( Class.PRIVATE, {...} ).' );
				} else
				if( privateDef.Abstract === true ){
					throw new Error( 'PrivateClass is Abstract!' );
				};
				classDef.privateClass = args.shift();
			};
			props = args[ 0 ];
			if( props === null || Type.isObject( props ) === false ){
				throw new Error( 'No Class Def!' );
			};
			
			if( Type.isFunction( props[ CONSTRUCTOR ] ) === true ){
				classDef[ CONSTRUCTOR ] = props[ CONSTRUCTOR ];
			};
			
			klass = function(){ var a = arguments; if( f ) return C.apply( a.callee, a )};
			klass.prototype = override( override( traits || {}, props, true ), CommonProps, false );
			
			if( opt_abstract === true ){
				classDef.Abstract = true;
			} else
			if( opt_pool === true ){
				classDef.pool = [];
				if( opt_private === false )classDef.live = [];
			};			
			if( opt_final === true ){
				classDef.Final = true;
			} else {
				klass.inherits = inherits;
			};			
			if( opt_private === true ){
				if( classDef.privateClass ){
					throw new Error( 'Private Data Class has no PrivateClass!' );
				};
				classDef.isPrivate = true;
				PRIVATE_CLASS_LIST.push( klass );
				PRIVATE_DEF_LIST.push( classDef );
			} else {
				if( classDef.privateClass ){
					klass.newPrivateData = newPrivateData;
					klass.getPrivateData = getPrivateData;	
				};
				CLASS_LIST.push( klass );
				DEF_LIST.push( classDef );				
			};
			return klass;
		},
		onShutdown : function(){
			
		},
		getClass : function( instance ){
			return getClass( instance );
		},
		getClassDef : function(){
			
		}
	};
})();

/**
 * Callback 時に thisObject や args を指定したい場合に使用． 
 */
var Callback = Class.create(
	Class.POOL_OBJECT | Class.FINAL, {
	Constructor : function( thisObject, callback, opt_args ){
		if( Type.isFunction( callback ) === false ){
			throw new Error( 'Not function!' );
		};
		this.callback = callback;
		if( thisObject ) this.thisObject = thisObject;
		if( Type.isArray( opt_args ) === true ){
			this.args = opt_args;
		} else
		if( opt_args !== undefined ){
			this.arg = opt_args;
		};
	},
	fire : function( /* args */ ){
		var thisObject = this.thisObject || window,
			args       = Util.copyArray( arguments );
		if( 0 < args.length ){
			if( this.args !== undefined ){
				args.push.apply( args, this.args );
			} else
			if( this.arg !== undefined ){
				args.push( this.arg );
			};
			this.callback.apply( thisObject, args );
		} else {
			if( this.args !== undefined ){
				this.callback.apply( thisObject, this.args );
			} else
			if( this.arg !== undefined ){
				this.callback.call( thisObject, this.arg );
			} else {
				this.callback.call( thisObject );
			};
		};
	},
	registerUser : function( user ){
		if( !this.userList ){
			this.userList = [ user ];
		} else {
			Util.getIndex( this.userList, user ) === -1 && this.userList.push( user );
		};
	},
	onKill : function(){
		var instance = this.thisObject;
		this.userList && Class.getClass( instance ) && instance.releaseMyCalllback( this );
	}
});


/* --------------------------------------------------------------
 * System Timer
 * 
 */

var SystemTimer = ( function(){
	var setTimeout    = window.setTimeout;
	var clearTimeout  = window.clearTimeout;
	var INTERVAL_TIME = 16;
	var TICKET_LIST   = [];
	var timerId       = undefined;
	var next          = 0;
	
	function loop(){
		var i    = 0,
			list = TICKET_LIST;
	    for( i = 0; i < list.length; ){
	        if( list[ i ].fire( next ) !== false ) ++i;
	    };
	    timerId = undefined;
	    update();
	};
	function update(){
		var list = TICKET_LIST,
			l    = list.length,
			n    = 99999999,
			c;
		if( l === 0 ){
			timerId !== undefined && clearTimeout( timerId );
			timerId = undefined;
			return;
		};
	    for( ; l; ){
	    	c = list[ --l ].count;
	        if( n > c ) n = c;
	    };
	    if( next > n || timerId === undefined ){
	    	timerId !== undefined && clearTimeout( timerId );
	    	timerId = setTimeout( loop, INTERVAL_TIME * n );
	    	next = n;
	    };
	};
	
	var TimerTicket = Class.create(
		Class.POOL_OBJECT, {
			Constructor : function( apiuser, callback, time, once, opt_thisObject ){
				this.apiuser  = apiuser;
				this.callback = callback;
				this.time     = time;
				this.count    = time;
				if( once ) this.once = once;
				this.thisObj  = opt_thisObject || apiuser;
			},
			fire : function( c ){
				this.count -= c;
				if( 0 < this.count ) return;
				this.callback.call( this.thisObj );
				if( this.once === true ){
					this.destroy();
					TICKET_LIST.splice( Util.getIndex( TICKET_LIST, this ), 1 );
					return false;
				} else {
					this.count = this.time;
				};
			},
			destroy : function( apiuser, callback ){
				if( apiuser  && apiuser  !== this.apiuser )  return false;
				if( callback && callback !== this.callback ) return false;
				
				this.kill();
				return true;
			}
		}
	);
	
	return {
		add: function( _apiuser, _handler, _time, _once, opt_thisObject ){
			if( Type.isNumber( _time ) === false || _time < INTERVAL_TIME ) _time = INTERVAL_TIME;
			
		    var _ticket = new TimerTicket( _apiuser, _handler, Math.ceil( _time / INTERVAL_TIME ), _once, opt_thisObject );
		    TICKET_LIST.push( _ticket );
		    
		    update();
		},
		remove: function( _apiuser, _handler ){
			var _ticket,
				i = 0;
			while( _ticket = TICKET_LIST[ i ] ){
				if( _ticket.destroy( _apiuser, _handler ) === true ){
					TICKET_LIST.splice( i, 1 );
				} else {
					++i;
				};
			};
		    update();
		}
	};
})();

/* --------------------------------------------------------------
 * Async Callback
 * 
 */
var AsyncCall = ( function(){
	var CALLBACK_LIST = [];
	
	var CallbackTicket = Class.create(
		Class.POOL_OBJECT, {
		Constructor : function( apiuser, callback, args, thisObject ){
			this.apiuser  = apiuser;
			this.callback = callback;
			this.args     = args;
			this.thisObj  = thisObject || apiuser;
		},
		fire : function(){
			var f = this.callback,
				a = this.args,
				t = this.thisObj;
			this.destroy();
			if( Type.isArray( a ) === true ){
				f.apply( t, a );
			} else {
				f.call( t, a );
			};
		},
		destroy : function( apiuser, callback ){
			if( apiuser  && apiuser  !== this.apiuser ) return false;
			if( callback && callback !== this.callback ) return false;
			
			this.kill();
			return true;
		}
	});

	function dispatch(){
		var _ticket = CALLBACK_LIST.shift();
		if( _ticket ){
			_ticket.fire();
			CALLBACK_LIST.length !== 0 && SystemTimer.add( SUPER_USER_KEY, dispatch, 1, true );
		};
	};

	return {
		add: function( _apiuser, _callback, _argments, _thisObject ){
			CALLBACK_LIST.length === 0 && SystemTimer.add( SUPER_USER_KEY, dispatch, 1, true );
			CALLBACK_LIST.push( new CallbackTicket( _apiuser, _callback, _argments, _thisObject ) );
		},
		remove: function( _apiuser, _callback ){
			var _ticket,
				i = 0;
			while( _ticket = CALLBACK_LIST[ i ] ){
				if( _ticket.destroy( _apiuser, _callback ) === true ){
					CALLBACK_LIST.splice( i, 1 );
				} else {
					++i;
				};
			};
		}
	};
})();

/* -----------------------------------------------------------
 * 画像一覧は
 * 	お気に入り画像一覧 > tag:ペン次郎 > ペン次郎：笑う
 *  最近アップロードされた画像 > images
 *  最近使われた画像 > images
 *  キャラクター画像庫 > アニマル系 > tag:ペン次郎 > ペン次郎：笑う
 *  風景画像庫 >
 *  効果画像庫 >
 *  アイテム画像庫 >
 *  
 * 画像一覧を読み込むタイミング
 */
var File = ( function(){
	var DRIVER_LIST             = [];
	
	var FILE_TYPE_IS_FOLDER     = Const.FILE.TYPE.FOLDER,
		numFileType             = Const.FILE.TYPE.XML,
		FILEDATA_RESITER        = [],			// store all of fileData( json object )
		FILEDATA_ACCESS         = [],			// file operations for Kernel only ! hide from Out of File
		FILE_OBJECT_POOL        = [],
		EVENT_LISTENER_REGISTER = [],
		TREE_ARRAY              = [],
		TREE_ACCESS_ARRAY       = [];
	
	var REQUEST_CONTROLER = ( function(){
		var REQUEST_TICKET_RESISTER = [],
			currentTicket           = null,
			currentData             = null,
			DATA_TYPE_ARRAY         = 'json,xml,html,text'.split( ',' ),
			DATA_IS_JSON            = 0,
			DATA_IS_XML             = 1,
			DATA_IS_HTML            = 2,
			DATA_IS_TEXT            = 3,
			numError                = 0;
		
		var RequestTicket = Class.create(
			Class.POOL_OBJECT, {
			Constructor : function( apiuser, type, data, url, onLoad, onError ){
				this.apiuser = apiuser;
				this.type    = type;
				this.data    = data;
				this.url     = url;
				this.onLoad  = onLoad;
				this.onError = onError;
				this.state   = 0;
			},
			load : function( data ){
				AsyncCall.add( this.apiuser, this.onLoad, [ this.data, data ] );
			},
			error : function(){
				AsyncCall.add( this.apiuser, this.onError, this.data );
			}
		});
		
		function request(){
			if( currentTicket !== null || REQUEST_TICKET_RESISTER.length === 0 ) return;
			currentTicket = REQUEST_TICKET_RESISTER.shift();
			$.ajax({
				url:		currentTicket.url,
				dataType:	DATA_TYPE_ARRAY[ currentTicket.type ],
				success:	onSuccess,
				error: 		onError
			});
		};
		function onSuccess( _data ){
			currentTicket.load( _data );
			currentTicket.kill();
			currentTicket = null;
			request();
		};
		function onError(){
			++numError;
			currentTicket.error();
			currentTicket.kill(); // retry
			currentTicket = null;
			request();
		};

		return {
			getNumTask: function(){
				return REQUEST_TICKET_RESISTER.length;
			},
			getNumError: function(){
				return numError;
			},
			getJson: function( _apiuser, _data, _url, _onLoad, _onError ){
				REQUEST_TICKET_RESISTER.push( new RequestTicket( _apiuser, DATA_IS_JSON, _data, _url, _onLoad, _onError ));
				currentTicket === null && request();
			}
		};
	})();

	var FILE_CONTROLER = {
		createTree: function( _apiuser, _rootFileData ){
			var _tree = new TreeClass( _apiuser, _rootFileData );
			TREE_ARRAY.push( _tree );
			return _tree;
		},
		getFileUID: function( FILEDATAorFILE ){
			if( FILEDATAorFILE instanceof FileClass ){
				return FILEDATAorFILE.getUID();
			};
			
			var uid = Util.getIndex( FILEDATA_RESITER, FILEDATAorFILE );
			if( uid === -1 ){
				uid = FILEDATA_RESITER.length;
				FILEDATA_RESITER.push( FILEDATAorFILE );
			};
			return uid;
		},
		getFileDataAccess: function( UIDorFILEorFILEDATA ){
			var _uid, _data = FILE_CONTROLER.getFileData( UIDorFILEorFILEDATA ), _access;

			if( _data === null || typeof _data !== 'object' ) return null;
			for( var i=0, l = FILEDATA_ACCESS.length; i<l; ++i ){
				_access = FILEDATA_ACCESS[ i ];
				if( _access.DATA === _data ) return _access;
			};
			return null;
		},	
		getFileData: function( UIDorFILEorFILEDATA ){
			if( typeof UIDorFILEorFILEDATA === 'number' ){
				return FILEDATA_RESITER[ UIDorFILEorFILEDATA ] || null;
			} else
			if( UIDorFILEorFILEDATA instanceof FileClass ){
				return FILEDATA_RESITER[ UIDorFILEorFILEDATA.getUID() ] || null;
			} else
			if( Util.getIndex( FILEDATA_RESITER, UIDorFILEorFILEDATA ) !== -1 ){
				return UIDorFILEorFILEDATA;
			};
			return null;
		},
		getChildren: function( UIDorFILEorFILEDATA ){
			var _data = FILE_CONTROLER.getFileData( UIDorFILEorFILEDATA );
			return _data !== null ? _data.children || null : null;
		},
		getDriver: function( _file ){
			var _data = FILE_CONTROLER.getFileData( _file );
			return _data !== null && _data.driver ? _data.driver : BASE_DRIVER;
		},
		getUpdateFlag: function( _file, _bit ){
			var _driver = FILE_CONTROLER.getDriver( _file ),
				_policy;
			if( typeof _driver.getUpdatePolicy === 'function' ){
				_policy = _driver.getUpdatePolicy( _file );
				
			}
			if( typeof _policy !== 'number' ) {
				_policy = BASE_DRIVER.getUpdatePolicy( _file )
			}
			return _policy % ( _bit * 2 ) >= _bit;
		},
		move: function( _prentUID, _targetfile, _newFolder, _newIndex, _opt_callback ){
			var _parentData = FILE_CONTROLER.getFileDataAccess( _prentUID ),
				_parentType = _parentData.TYPE,
				_targetData = FILE_CONTROLER.getFileDataAccess( _targetfile ),
				_targetType = _targetData.TYPE;
		},
		replace: function( _uid, _file, _newIndex ){
			
		},
		addEventListener: function( FILEorNULL, _eventType, _callback, opt_thisObject ){
			var _uid = FILEorNULL instanceof FileClass ? FILEorNULL.getUID() : FILEorNULL;
			EVENT_LISTENER_REGISTER.push( new FileEventTicket( _uid, _eventType, _callback, opt_thisObject ));
		},
		removeEventListener: function( FILEorNULL, eventType, callback ){
			var uid  = FILEorNULL instanceof FileClass ? FILEorNULL.getUID() : FILEorNULL,
				list = EVENT_LISTENER_REGISTER,
				i    = 0,
				ticket;
			for( ; i < list.length; ){
				ticket = list[ i ];
				if( ticket.fileUID === uid && ticket.eventType === eventType && ticket.callBack === callback ){
					list.splice( i, 1 );
					ticket.kill();
				} else {
					++i;
				};
			};
		},
		getTreeAccess: function(){
			
		},
		fileEventRellay: function( _uid, _event ){
			var _fileAccess = FILE_CONTROLER.getFileDataAccess( _uid );
			if( _fileAccess === null ) return;
			var _treeUID    =  _fileAccess.TREE.getUID(),
				_treeAccess = TREE_ACCESS_ARRAY[ _treeUID ],
				_data       = _fileAccess.DATA,
				_tree;
			if( !_treeAccess ) return;
			_treeAccess.dispatchFileEvent( _event );
			for( var i=0, l = TREE_ARRAY.length; i<l; ++i ){
				if( i !== _treeUID ){
					_tree = TREE_ARRAY[ i ];
					if( FILE_CONTROLER.getFileData( _tree.getCurrentFile() ) === _data ){
						_treeAccess = TREE_ACCESS_ARRAY[ _tree.getUID() ];
						_treeAccess && _treeAccess.dispatchFileEvent( _event );
					};
				};
			};
		}
	};
	
	var TreeClass = function( apiuser, rootFileData ){
		var PARENT_FILE_RESITER = [],
			ACCESS = {
				apiuser          : apiuser,
				dispatchFileEvent: dispatchFileEvent
			},
			EVENT_LISTENER_ARRAY = [],
			instance             = this,
			rootFile             = new FileClass( instance, null, rootFileData ),
			currentFile          = rootFile;
		
		currentFile.getSeqentialFiles();
		TREE_ACCESS_ARRAY.push( ACCESS );
		
		function dispatchFileEvent( e ){
			var _eventType  = e.eventType,
				_targetFile = e.targetFile,
				_uid        = _targetFile.getUID(),
				_ticket, _type, _callback;
			for( var i=0, l = EVENT_LISTENER_REGISTER.length; i<l; ++i ){
				_ticket   = EVENT_LISTENER_REGISTER[ i ];
				_type     = _ticket.eventType;
				_callback = _ticket.callBack;
				if( _eventType === _type && _uid === _ticket.fileUID ){
					AsyncCall.add( apiuser, _callback, [ _eventType, _targetFile, e.key, e.value ], _ticket.thisObject || _targetFile );
				} else
				if( _type === Const.TREE.EVENT.UPDATE && _eventType === Const.FILE.EVENT.GET_SEQENTIAL_FILES ){
					//_callback( _eventType, _targetFile );
					AsyncCall.add( apiuser, _callback, [ _eventType, _targetFile ], _ticket.thisObject || instance );
				};
			};
		};
		
		this.getUID = function(){
			return Util.getIndex( TREE_ACCESS_ARRAY, ACCESS );
		};
		this.getRootFile = function(){
			return rootFile;
		};
		this.getCurrentFile = function(){
			return currentFile;
		};
		this.hierarchy = function(){
			return PARENT_FILE_RESITER.length;
		};
		this.getParentFileAt = function( _index ){
			var l = PARENT_FILE_RESITER.length;
			if( typeof _index !== 'number' || _index < 0 || _index >= l ) return null;
			return PARENT_FILE_RESITER[ l -1 -_index ];
		};
		this.down = function( _index ){
			if( typeof _index !== 'number' || _index < 0 || _index >= currentFile.getChildFileLength()) return;
			PARENT_FILE_RESITER.unshift( currentFile );
			currentFile = currentFile.getChildFileAt( _index );
			currentFile.getSeqentialFiles();
			return currentFile;
		};
		this.up = function( _index ){
			var l = PARENT_FILE_RESITER.length;
			if( l === 0 ) return null;
			
			if( currentFile ){
				var _currentFile = currentFile;
				currentFile = null;
				_currentFile.destroy();
			};
			if( typeof _index === 'number' ){
				if( _index >= l ) return null;
				currentFile = this.getParentFileAt( _index );
				PARENT_FILE_RESITER.splice( 0, l -_index);
			} else {
				currentFile = PARENT_FILE_RESITER.shift();
			};
			currentFile.getSeqentialFiles();
			return currentFile;	
		};
		this.addTreeEventListener = function( _eventType, _callback, opt_thisObject ){
			FILE_CONTROLER.addEventListener( null, _eventType, _callback, opt_thisObject );
		};
		this.removeTreeEventListener = function( _eventType, _callback ){
			FILE_CONTROLER.removeEventListener( null, _eventType, _callback );
		};
		this.destroy = function( _apiuser ){
			if( _apiuser && apiuser !== _apiuser ) return false;
			// removeEvent
			var _currentFile = currentFile;
			currentFile = rootFile = rootFileData = null;
			// currentFile, rootFile を null にしないと .File.destroy() ができない.
			_currentFile.destroy();
			while( PARENT_FILE_RESITER.length > 0 ){
				_currentFile = PARENT_FILE_RESITER.shift();
				_currentFile.destroy();
			};
			
			AsyncCall.remove( apiuser );
			instance = apiuser = null;
			return true;
		};
	};
	
	var FileEventTicket = Class.create(
		Class.POOL_OBJECT, {
		Constructor : function( uid, eventType, callback, opt_thisObject ){
			this.fileUID    = uid;
			this.eventType  = eventType;
			this.callBack   = callback;
			this.thisObject = opt_thisObject;
		}	
	});
	
	var FileEventClass = function( eventType, file, key, value ){
		this.eventType        = eventType;
		this.targetFile       = file;
		this.updatedAttribute = key;
		this.updatedValue     = value;
	};

/*
 * file の data は object で保持している。
 * File の外からファイルをみるときは、FileClassを通して操作する。
 * fileの変更、それに付随して追加されたイベントは、TreeClassで管理される。
 * treeがdestryされると、fileのイベントリスナーも全て削除される。
 * 他の tree も data の共通する currentFile に対してのみは、file の変更イベントを受け取って流す．
 * 
 */
	
	var FileClass = function( tree, parentData, data ){
		var uid = FILE_CONTROLER.getFileUID( data );
		
		FILEDATA_ACCESS.push( {
			TREE:				tree,
			parentData:			parentData,
			DATA:				data
		} );
		
		tree = parentData = data = null;

		this.getUID = function(){
			return uid;
		};
	};
	
	FileClass.prototype = {
		isChildFile: function( _FILEorFILEDATA ){
			return this.getChildFileIndex( _FILEorFILEDATA) !== -1;
		},
		getSeqentialFiles: function(){
			var _driver = FILE_CONTROLER.getDriver( this );
			if( _driver !== null && typeof _driver.getSeqentialFiles === 'function' ){
				_driver.getSeqentialFiles( this );
			}
		},
		addEventListener: function( _eventType, _callback ){
			FILE_CONTROLER.addEventListener( this, _eventType, _callback );
		},
		removeEventListener: function( _eventType, _callback ){
			FILE_CONTROLER.removeEventListener( this, _eventType, _callback );
		},
		dispatchEvent: function( e ){
			e instanceof FileEventClass && FILE_CONTROLER.fileEventRellay( this.getUID(), e );
		},
		getChildFileLength: function(){
			var children = FILE_CONTROLER.getChildren( this );
			return Type.isArray( children ) === true ? children.length : -1;
		},
		getChildFileIndex: function( _FILEorFILEDATA ){
			var children = FILE_CONTROLER.getChildren( this );
			if( Type.isArray( children ) === false ) return -1;
			var l = children.length,
				_fileData = FILE_CONTROLER.getFileData( _FILEorFILEDATA );
			if( _fileData === null ) return -1;
			for( var i=0; i<l; ++i ){
				if( children[ i ] === _fileData ) return i;
			}
			return -1;
		},
		getChildFileAt: function( _index ){
			var _access = FILE_CONTROLER.getFileDataAccess( this ),
				_children = FILE_CONTROLER.getChildren( this );
			if( typeof _index !== 'number' || _index < 0 || Type.isArray( _children ) === false || _index >= _children.length ) return null;
			var _file = new FileClass( _access.TREE, _access.DATA, _children[ _index ]);
			// _file.init();
			return _file;
		},
		getName: function(){
			var driver = FILE_CONTROLER.getDriver( this );
			if( typeof driver.getName === 'function'){
				return driver.getName( this );
			}
			return BASE_DRIVER.getName( this );
		},
		getThumbnail: function(){
			var driver = FILE_CONTROLER.getDriver( this );
			if( typeof driver.getThumbnail === 'function'){
				return driver.getThumbnail( this );
			}
			return BASE_DRIVER.getThumbnail( this );
		},
		getType: function(){
			var _data = FILE_CONTROLER.getFileData( this );
			return typeof _data.type === 'number' ? _data.type : Const.FILE.TYPE.UNKNOWN;
		},
		getState: function(){
			var _data = FILE_CONTROLER.getFileData( this );
			return typeof _data.state === 'number' ? _data.state : Const.FILE.STATE.OK;
		},
		getSummary: function(){
			var driver = FILE_CONTROLER.getDriver( this );
			if( typeof driver.getSummary === 'function'){
				return driver.getSummary( this );
			}
			return BASE_DRIVER.getSummary( this );
		},
		isWritable: function(){
			return FILE_CONTROLER.getUpdateFlag( this, Const.FILE.UPDATE_POLICY.WRITE );
		},
		isSortable: function(){
			return FILE_CONTROLER.getUpdateFlag( this, Const.FILE.UPDATE_POLICY.SORT );
		},		
		isCreatable: function(){
			return FILE_CONTROLER.getUpdateFlag( this, Const.FILE.UPDATE_POLICY.CREATE );
		},
		isRenamable: function(){
			return FILE_CONTROLER.getUpdateFlag( this, Const.FILE.UPDATE_POLICY.RENAME );
		},
		isDeletable: function(){
			return FILE_CONTROLER.getUpdateFlag( this, Const.FILE.UPDATE_POLICY.DELETE );
		},
		read: function(){
			// simpleDeepCopy
			var driver = FILE_CONTROLER.getDriver( this ),
				data;
			if( Type.isFunction( driver.read ) === true ){
				 data = driver.read( this );
			};
			return BASE_DRIVER.read( data || this );
		},
		write: function( _newData, _onUpdateFunction ){
			var driver = FILE_CONTROLER.getDriver( this );
			if( typeof driver.write === 'function' ){
				return driver.write( this, _newData, _onUpdateFunction );
			};
			return BASE_DRIVER.write( this, _newData, _onUpdateFunction );
		},
		viewerApplicationList: function(){
			var driver = FILE_CONTROLER.getDriver( this );
			if( typeof driver.viewerApplicationList === 'function' ){
				return driver.viewerApplicationList( this );
			};
			return BASE_DRIVER.viewerApplicationList( this );
		},
		editorApplicationList: function(){
			var driver = FILE_CONTROLER.getDriver( this );
			if( typeof driver.editorApplicationList === 'function' ){
				return driver.editorApplicationList( this );
			};
			return BASE_DRIVER.viwerApps( this );
		},
		create: function(){
			
		},
		sort: function(){
			
		},
		onCopy: function(){
			
		},
		onDelete: function(){
			
		},
		move: function( _newFolder, _newIndex, opt_callback ){
			var _access = FILE_CONTROLER.getFileDataAccess( this );
			_access.TREE.move( _access.parentData, this.getUID(), _newFolder, _newIndex, opt_callback );
		},
		replace: function( _newIndex, opt_callback ){
			var _access = FILE_CONTROLER.getFileDataAccess( this );
			_access.TREE.replace( _access.parentData, this.getUID(), _newIndex, opt_callback);
		},
		/**
		 * サーチ
		 * 探しているファイルの属性と値を指定．一致する child の index を配列で返す．
		 */
		search: function( obj, rule ){
			var _children = FILE_CONTROLER.getChildren( this ),
				_child,
				ret = [], k, c;
			for( var i=0, l=_children.length; i<l; ++i ){
				_child = _children[ i ];
				c = true;
				for( k in obj ){
					if( obj[ k ] !== _child[ k ] ){
						c = false;
						break;
					}
				}
				c === true && ret.push( i );
			}
			return ret;
		},
		destroy: function(){
			var _access = FILE_CONTROLER.getFileDataAccess( this );
			var _tree = _access.TREE;
			if( _tree.getCurrentFile() === this ) return;
			if( _tree.getRootFile() === this ) return;
			for( var i=0, l = _tree.hierarchy(); i<l; ++i ){
				if( _tree.getParentFileAt( i ) === this ){
					return;
				}
			}
			var _index = Util.getIndex( FILEDATA_ACCESS, _access );
			if( _index === -1 ) return;
			// event の 削除
			FILEDATA_ACCESS.splice( _index, 1 );
			delete _access.DATA;
			delete _access.TREE;
			delete _access.parentData;
		}
	};

	/*
	 * FileDriverBase
	 */
	var FileDriverBase = function( driverClass ){
		this.getUID = function(){
			return Util.getIndex( API_USER_LIST, driverClass );
		};
		this.getSeqentialFiles = function( _file ){
		};
		this.getName = function( _file ){
			var _data = FILE_CONTROLER.getFileData( _file );
			return _data.name || 'No Name';
		};
		this.getThumbnail = function( _file ){
			var _data = FILE_CONTROLER.getFileData( _file ),
				_type = _data.type,
				_className = '';
			if( _type === Const.FILE.TYPE.FOLDER ){
				_className = 'folder';
			} else
			if( _type === Const.FILE.TYPE.IMAGE ){
				
			} else
			if( _type === Const.FILE.TYPE.TEXT ){
				
			} else
			if( _type === Const.FILE.TYPE.HTML ){
				
			} else
			if( _type === Const.FILE.TYPE.CSV ){
				
			} else
			if( _type === Const.FILE.TYPE.JSON ){
				
			} else
			if( _type === Const.FILE.TYPE.XML ){
				
			};
			return {
				image:		null,
				className:	' file-type-' + _className
			};
		};
		this.getSummary = function( _file ){
			var _data = FILE_CONTROLER.getFileData( _file ),
				_type = _data.type;
			if( _type === Const.FILE.TYPE.FOLDER ){
				return 'folder';
			} else
			if( _type === Const.FILE.TYPE.IMAGE ){
				return 'image file';
			} else
			if( _type === Const.FILE.TYPE.TEXT ){
				return 'text file';
			} else
			if( _type === Const.FILE.TYPE.HTML ){
				return 'html document file';
			} else
			if( _type === Const.FILE.TYPE.CSV ){
				return 'csv daat file';
			} else
			if( _type === Const.FILE.TYPE.JSON ){
				return 'json data file';
			} else
			if( _type === Const.FILE.TYPE.XML ){
				return 'xml data file';
			}
			return '';
		};
		this.getUpdatePolicy = function( _file ){
			// debug用 全てのメニューを許可
			return Const.FILE.UPDATE_POLICY.DSRWC;
		};
		this.read = function( FILEorDATA ){
			var data,
				protects = Const.FILE.DATA_PROPERTY_RESERVED,
				objSrc   = [],
				objCopy  = [],
				getIndex = Util.getIndex;			
			if( FILEorDATA instanceof FileClass ){
				data = FILE_CONTROLER.getFileData( FILEorDATA )
			} else {
				data = FILEorDATA;
			};
			
			function clone( src ) {
				var ret, i, key;
				if( Type.isArray( src ) === true ){
					i = getIndex( objSrc, src );
					if( i !== -1 ) return objCopy[ i ];
					ret = [];
					objSrc[ objSrc.length ]   = src;
					objCopy[ objCopy.length ] = ret;
				} else
				if( Type.isObject( src ) === true ){
					i = getIndex( objSrc, src );
					if( i !== -1 ) return objCopy[ i ];
					ret = {};
					objSrc[ objSrc.length ]   = src;
					objCopy[ objCopy.length ] = ret;
				} else
				if( Type.isNumber( src ) === true || Type.isString( src ) === true || Type.isBoolean( src ) === true ){
					return src;
				} else {
					return src;
				};
				for( key in src ){
					if( getIndex( protects, key ) === -1 ){
						ret[ key ] = clone( src[ key ]);
					};
				};
				return ret;
			};				
			return clone( data );
		};
		this.write = function( _file, _newData, _onUpdateFunction ){
			var _data = FILE_CONTROLER.getFileData( _file ),
				_type = _data.type;
			return false;
		};
		this.viewerApplicationList = function(){
			return [];
		};
		this.editorApplicationList = function(){
			return [];
		};
		this.onCreate = function(){
			
		};
		this.onSort = function(){
			
		};
		this.onCopy = function(){
			
		};
		this.onDelete = function(){
			
		};
	};
	
	var BASE_DRIVER   = new FileDriverBase();
	
	var ROOT_FILEDATA = {
			name: 		'system root',
			type:		FILE_TYPE_IS_FOLDER,
			children:	[]
		},
		SYSTEM_TREE = FILE_CONTROLER.createTree( SUPER_USER_KEY, ROOT_FILEDATA ),
		ROOT_FILE   = SYSTEM_TREE.getRootFile();

	function createFileTypeID(){
		return ++numFileType;
	};
	
	var FileAPIClass = function( driver ){
		var constObject;
		this.createFolderUnderRoot = function( _fileData ){
			if( _fileData !== null && Type.isObject( _fileData ) === true ){
				ROOT_FILEDATA.children.push( _fileData );
				ROOT_FILE.dispatchEvent( new FileEventClass( Const.FILE.EVENT.GET_SEQENTIAL_FILES, ROOT_FILE, 'children', null ));
			}
		};
		this.createFileEvent   = function( _eventType, _file, _key, _value ){
			return new FileEventClass( _eventType, _file, _key, _value );
		};
		this.createFileTypeID  = createFileTypeID;
		this.getFileDataAccess = FILE_CONTROLER.getFileDataAccess;
		this.getFileData       = FILE_CONTROLER.getFileData;
		this.getJson           = function( _data, _url, _onLoad, _onError ){
			REQUEST_CONTROLER.getJson( driver, _data, _url, _onLoad, _onError );
		};
		this.createTree        = function( _rootFile ){
			return FILE_CONTROLER.createTree( driver, _rootFile );
		};
		this.isTreeInstance    = function( _tree ){
			return _tree instanceof TreeClass;
		};
		this.isFileInstance    = function( _file ){
			return _file instanceof FileClass;
		};
		this.isFileEvent       = function( _event ){
			return _event instanceof FileEventClass;
		};
		this.getConst          = function(){
			return Const; // constObject = constObject || clone( Const )
		};
	};
	
	return {
		registerDriver: function( _class ){
			_class.prototype = new FileDriverBase( _class );
			var _driver = new _class();
			
			DRIVER_LIST.push( _driver );
			API_USER_LIST.push( _class );

			return new FileAPIClass( _driver );
		},
		isDriver: function( _driver ){
			return _driver instanceof FileDriverBase;
		},
		isTreeInstance: function( _tree ){
			return _tree instanceof TreeClass;
		},
		isFileInstance: function( _file ){
			return _file instanceof FileClass;
		}
	}
})();


/* ----------------------------------------------------
 * ApplicationManager
 * window resize event, overlayApplication currentAplication に流す
 */	

var APPLICATION_LIST = [];

var ApplicationPrivateData = function(){};
ApplicationPrivateData.prototype = {
	appClass      : null,
	application   : null,
	displayName   : null,
	isOverlay     : false,
	rootElement   : null,
	bgColor       : '#C1CACF',
	uiList        : null,
	formList      : null,
	finderList    : null,
	styleCursor   : null,
	eventRoot     : null,
	fetchResource : 0,
	bootParams    : null,
	phase         : 0,
	cursor        : '',
	w             : 0,
	h             : 0,
	init          : function( appClass, displayName, isOverlay ){
		this.appClass    = appClass;
		// this.application = app;
		this.displayName = displayName;
		this.isOverlay   = isOverlay;
		this.rootElement = document.createElement( 'div' );
		this.styleCursor = this.rootElement.style;
		ApplicationPrivateData.list.push( this );
	},
	detect : function(){
		if( this.rootElement.firstChild && this.fetchResource === 0 ){
			SystemTimer.remove( this.application, this.detect );
			this.onOpen();
		};
	},
	onOpen : function(){
		this.rootElement.style.display = '';
		
		// this.layer !== null && this.layer.onResize( this.w, this.h );
		
		if( this.application.MIN_WIDTH > this.w || this.application.MIN_HEIGHT > this.h ){
			if( Type.isHTMLElement( this.rootElement ) === true ){
				// 小さすぎる！、と表示
			};
		};
		if( this.bootParams.length > 2 ){
			this.application.onOpen.apply( this.application, this.bootParams );
		} else {
			this.application.onOpen( this.w, this.h );
		};
		this.phase = 4;	
	},
	fetchResourceComplete : function(){
		--this.fetchResource;
	}
};
ApplicationPrivateData.list = [];
ApplicationPrivateData.get = function( app ){
	var list = ApplicationPrivateData.list,
		i    = list.length;
	for( ; i; ){
		if( app instanceof list[ --i ].appClass ) return list[ i ];
	};
	return null;
};

var AbstractApplication = function( appClass, displayName, isOverlay ){
	( new ApplicationPrivateData() ).init( appClass, displayName, isOverlay );
};
AbstractApplication.prototype = {
	getUID : function(){
		var data = ApplicationPrivateData.get( this );
		return Util.getIndex( API_USER_LIST, data.appClass );
	},
	init : function(){
		var data = ApplicationPrivateData.get( this );
		// this.rootElement = data.rootElement;
		// data.application = this;
		data.phase = 1;
		data.appClass === Page.appClass && Page.show();
		this.onInit();
		data.phase = 2;
	},
	open : function( w, h /*, _option */ ){
		var data = ApplicationPrivateData.get( this );
		data.phase      = 3;
		data.bootParams = Util.copyArray( arguments );
		data.w          = w;
		data.h          = h;
		if( data.rootElement.innerHTML && data.rootElement.innerHTML.length > 0 ){
			SystemTimer.add( this, data.detect, 16, false, data );
		} else {
			data.onOpen();
		};
	},
	resize : function( w, h ){
		var data = ApplicationPrivateData.get( this );
		if( data.phase !== 4 ) return;
		if( this.MIN_WIDTH > w || this.MIN_HEIGHT > h ){
			if( Type.isHTMLElement( this.rootElement ) === true ){
				// 小さすぎる！、と表示
			};
			return;
		};
		this.onPaneResize( w, h );
	},
	close : function(){
		var data = ApplicationPrivateData.get( this );
		data.phase = 5;
		if( this.onClose() === false ){
			return false;
		};
		if( data.uiList ){ 
			while( data.uiList.length > 0 ) data.uiList.shift().destroy();
		};
		if( data.finderList ){
			while( data.finderList.length > 0 ) data.finderList.shift().destroy();
		};		
		
		data.eventRoot && PointingDeviceEventTree.destroyTree( data.eventRoot );
		MouseEvent.remove( this );
		KeyEvent.remove( this );
		SystemTimer.remove( this );
		AsyncCall.remove( this );
		StyleSheet.unload( this );

		var elm = this.rootElement;
		Util.removeAllChildren( elm );
		elm.parentNode.removeChild( elm );
		
		Application.shutdown( this, data.isOverlay );
		
		data.appClass === Page.appClass && Page.hide();

		data.phase = 6;
		
		var list = ApplicationPrivateData.list;
		list.splice( Util.getIndex( list, data ), 1 );
	},
	createUIGroup : function( node ){
		var data = ApplicationPrivateData.get( this ),
			ui = UI.createUIGroup( this, node );
		if( data.uiList === null ) data.uiList = [];
		data.uiList.push( ui );
		return ui;
	},
	createUIForm : function( nodeOrElm, opt_elmForm ){
		var data = ApplicationPrivateData.get( this ),
			form = UIForm.createForm( this, nodeOrElm, opt_elmForm );
		if( data.formList === null ) data.formList = [];
		data.formList.push( form );
		return form;
	},
	createFinder : function( _elmTarget, _tree, _onSelect, _viewerOption, _editorOption ){
		var data   = ApplicationPrivateData.get( this ),
			finder = Finder.create( this, _elmTarget, _tree, _onSelect, _viewerOption, _editorOption );
		if( data.finderList === null ) data.finderList = [];
		data.finderList.push( finder );
		return finder;
	},
	createDHTML : function( _elm ){
		return DHTML.create( this, _elm );
	},
	addEventListener : function( element, eventType, handler, opt_thisObject ){
		MouseEvent.add( this, element, eventType, handler, opt_thisObject );
	},
	removeEventListener : function( element, eventType, handler ){
		MouseEvent.remove( this, element, eventType, handler );
	},
	getPointingDeviceEventTreeRoot : function(){
		var data = ApplicationPrivateData.get( this );
		if( data.phase === 1 ){
			data.eventRoot   = PointingDeviceEventTree.create( this );
			data.styleCursor = PointingDeviceEventTree._getNodePrivateData( data.eventRoot ).elmMouseCatch.style;
		};		
		return data.eventRoot;
	},
	updateCoursor : function( _cursor ){
		var data = ApplicationPrivateData.get( this );
		if( data.cursor !== _cursor ){
			data.styleCursor.cursor = data.cursor = _cursor;
		};
	},
	fetchCSS : function( url, opt_onload, opt_onerror ){
		var data = ApplicationPrivateData.get( this );
		if( data.phase === 1 ){
			++data.fetchResource;
			StyleSheet.load( this, url, data.fetchResourceComplete, data.fetchResourceComplete, data );
		};
	},
	onInit : function(){},
	onOpen : function(){},
	onClose : function(){ return true; },
	onPaneResize : function( w, h ){},
	addKeyEventListener : function( _eventType, _handler, _keyCode, _shift, _ctrl ){
		KeyEvent.add( this, _eventType, _handler, _keyCode, _shift, _ctrl );
	},
	removeKeyEventListener : function( _eventType, _handler, _keyCode, _shift, _ctrl ){
		KeyEvent.remove( this, _eventType, _handler, _keyCode, _shift, _ctrl );
	},
	shiftEnabled : function(){
		return KeyEvent.shiftEnabled;
	},
	ctrlEnabled : function(){
		return KeyEvent.ctrlEnabled;
	},
	addTimer : function( handler, time, once ){
		SystemTimer.add( this, handler, time, !!once );
	},
	removeTimer : function( handler ){
		SystemTimer.remove( this, handler );
	},
	addAsyncCall : function( _callback, _argments, _thisObject ){
		AsyncCall.add( this, _callback, _argments, _thisObject );
	},
	removeAsyncCall : function( _callback ){
		AsyncCall.remove( this, _callback );
	},
	fetchHTMLElement : function( id ){
		var elm = document.getElementById( id );
		if( elm ){
			elm.removeAttribute( 'id' );
			elm.parentNode.removeChild( elm );
			return elm;
		};
	}
};

var PointingDeviceEventTree = ( function(){
	var ROOT_LIST       = [],
		currentRootData = null,
		targetNodeData  = null,
		forceNodeData   = null,
		hoverList       = [];
	
	function eventRellay( e ){
		var data = forceNodeData, // || targetNodeData,
			x    = e.clientX,
			y    = e.clientY,
			type = e.type,
			list = hoverList,
			i    = 0,
			ret, systemOnly = false, addClass, removeClass,
			parent;
		if( data && data.dispatchEvent( e, type, true ) === true ) return false;
		if( currentRootData === null ) return;
		targetNodeData = currentRootData;
		currentRootData._capcher( x, y );
		targetNodeData.apiuser.updateCoursor( targetNodeData._cursor );
		data = targetNodeData;
		while( data ){
			ret = data.dispatchEvent( e, type, true, systemOnly );
			if( ret === true || ret === false ) break; // systemOnly = true;
			data = data.parentData;
		};
		
		addClass    = Util.addClass;
		removeClass = Util.removeClass;
		for( ; i < list.length; ){
			parent = data = list[ i ];
			while( parent.parentData && parent === parent.parentData.hitChild ){
				parent = parent.parentData;
			};
			if( parent !== currentRootData ){
				data.hover === true && removeClass( data.elm, data.hoverClass );
				delete data.isHover;
				data.events && data.events.mouseout && data.fire( e, 'mouseout', false );
				delete data.hitSelf;
				list.splice( i, 1 );
				continue;
			};
			if( data.hover === true && data.isHover === false ){
				addClass( data.elm, data.hoverClass );
				data.isHover = true;
			};
			if( data.hitSelf === false ){
				data.events && data.events.mouseover && data.fire( e, 'mouseover', true );
				data.hitSelf = true;
			};
			++i;
		};
		return false;
	};	
	
	var NodeClass = function( apiuser, rootData, /*parentLayer,*/ parentData, rangeOrElm, through, clip, hover, cursor, scroll, dragdrop ){
		( new NodePrivateData() ).init( apiuser, rootData, /*parentLayer,*/ parentData, this, rangeOrElm, through, clip, hover, cursor, scroll, dragdrop );
	};
	NodeClass.prototype = {
		createNode : function( rangeOrElmData, through, clip, hover, cursor, scroll, dragdrop ){
			var data = NodePrivateData.get( this ),
				elm;
			if( Type.isHTMLElement( rangeOrElmData ) === true ){
				elm = rangeOrElmData;
			} else
			if( Type.isString( rangeOrElmData ) === true ){
				elm = document.getElementById( rangeOrElmData );
				if( !elm ){
					elm = Util.pullHtmlAsTemplete( rangeOrElmData );
				};
				if( !elm || Type.isHTMLElement( elm ) === false || elm.nodeType !== 1 ){
					throw new Error( "invalid HTMLElement." );
				};
			} else
			if( Type.isObject( rangeOrElmData ) === false || Type.isFinite( rangeOrElmData.x ) === false || Type.isFinite( rangeOrElmData.y ) === false ){
				throw new Error( "No range" );
			};
			
			if( elm && data.elm === null ){
				throw new Error( "MetaLayer don't containe HTMLElement-Layer." );
			};
			if( data.elm && data.elm.style.hasLayout === false ){
				throw new Error( "[ie] OffsetParent is hasLayout === false." );
			};
			
			var newNode = new NodeClass( data.apiuser, data.rootData, data, elm || rangeOrElmData, through, clip, hover, cursor, scroll, dragdrop ),
				newData = NodePrivateData.get( newNode );
			
			if( data.childData === null ) data.childData = [];
			data.childData.push( newData );
			return newNode;
		},
		createNodeAt : function(){
		},
		remove : function(){
			NodePrivateData.get( this ).remove();
		},
		nodeIndex : function( v ){
			return NodePrivateData.get( this ).nodeIndex( v );
		},
		numNode : function(){
			return NodePrivateData.get( this ).numNode();
		},
		disabled : function( v ){
			return NodePrivateData.get( this ).disabled( v );
		},
		childrenDisabled : function( v ){
			return NodePrivateData.get( this ).disabled( v );
		},
		mesure : function(){
			NodePrivateData.get( this ).mesure();
		},
		mesureChildren : function(){
			NodePrivateData.get( this ).mesureChildren();
		},
		update : function( x, y, w, h ){
			NodePrivateData.get( this ).update( x, y, w, h );
		},
		setPosition : function( x, y ){
			NodePrivateData.get( this ).setPosition( x, y );
		},
		setSize : function( w, h ){
			NodePrivateData.get( this ).setSize( w, h );
		},
		cursor : function( v ){
			return NodePrivateData.get( this ).cursor( v );
		},
		x : function( x ){
			return NodePrivateData.get( this ).positionX( x );
		},
		y : function( y ){
			return NodePrivateData.get( this ).positionY( y );
		},
		width : function( w ){
			return NodePrivateData.get( this ).width( w );
		},
		height : function( h ){
			return NodePrivateData.get( this ).height( h );
		},
		getAbsolutePositionX : function(){
			return NodePrivateData.get( this ).getAbsolutePositionX();
		},
		getAbsolutePositionY : function(){
			return NodePrivateData.get( this ).getAbsolutePositionY();
		},
		addEventListener : function( type, handler, opt_thisObject ){
			NodePrivateData.get( this ).addEventListener( type, handler, opt_thisObject );
		},
		removeEventListener : function( type, handler ){
			NodePrivateData.get( this ).removeEventListener( type, handler );
		},
		scrollTo : function( x, y ){
			NodePrivateData.get( this ).scrollTo( x, y );
		},
		scrollX : function( v ){
			return NodePrivateData.get( this ).scrollX( v );
		},
		scrollY : function( v ){
			return NodePrivateData.get( this ).scrollY( v );
		},
		invalidateScrollbar : function(){
			ScrollBarManager.update( NodePrivateData.get( this ) );
		}
	};

	/**
	 * clip : true の場合、子ノードの変更によってヒットエリアを変化させない．elm には overflow:hidden としておくのが通常．
	 */
	var NodePrivateData = function(){};
	NodePrivateData.prototype = {
		elmMouseCatch : null, // rootData only
		eventCounter  : null, // rootData only
		cursorStyle   : null, // rootData only
		node          : null,
		apiuser       : null,
		rootData      : null,
		elm           : null, // resizeTarget
		elmScroll     : null,
		elmScroller   : null,
		elmScrollbar  : null,
		x             : 0,
		y             : 0,
		w             : 0,
		h             : 0,
		t             : 0, // top
		l             : 0, // left
		b             : 0, // bottom
		r             : 0, // right
		absoluteX     : 0,
		absoluteY     : 0,
		_scrollX      : 0,
		_scrollY      : 0,
		scrollingX    : 0,
		scrollingY    : 0,
		_cursor       : '',
		// parentLayer   : null,
		parentData    : null,
		childData     : null,
		events        : null,
		hitChild      : null,
		hitSelf       : false,
		_disabled     : false,
		_childDisabled: false,
		layoutManager : null,
		through       : false,
		clip          : false,
		hover         : false,
		hoverClass    : null,
		isHover       : false,
		scroll        : false,
		dragdrop      : false,
		tooltip       : null,
		init: function( apiuser, rootData, /*parentLayer,*/ parentData, node, rangeOrElm, through, clip, hover, cursor, scroll, dragdrop ){
			this.apiuser     = apiuser;
			this.rootData    = rootData || this;
			// this.parentLayer = parentLayer;
			this.parentData  = parentData;
			this.node        = node;
			this.through     = through;
			this.clip        = !!clip;
			if( cursor ) this._cursor = cursor;   

			if( Type.isHTMLElement( rangeOrElm ) === true ){
				this.elm        = rangeOrElm;
				this.hover      = !!hover;
				this.hoverClass = hover;
				this.scroll     = clip && scroll;				
				this.mesure();
				this.scroll === true && ScrollBarManager.register( this );
			} else {
				this.update( rangeOrElm.x, rangeOrElm.y, rangeOrElm.w, rangeOrElm.h );
			};
			
			NodePrivateData.dataList.push( this );
		},
		mesure : function(){
			var x, y, w, h, parent, _this, _parent;
			if( this.elm ){
				w = this.elm.offsetWidth;
				h = this.elm.offsetHeight;
				_this   = Position.cumulativeOffset( this.elm );
				_parent = this.parentData ? Position.cumulativeOffset( this.parentData.elm ) : [ 0, 0 ];
				x  = _this[ 0 ] - _parent[ 0 ];
				y  = _this[ 1 ] - _parent[ 1 ];				
				if( this.x !== x || this.y !== y || this.w !== w || this.h !== h ){
					this.x = x;
					this.y = y;
					this.w = w;
					this.h = h;
					parent = this.parentData;
					parent && this._updateAbsoluteXY( parent.absoluteX, parent.absoluteY, parent.scrollingX, parent.scrollingY );
					this._updateRectangle();
				};			
			} else {
				this._updateRectangle();
			};
		},
		mesureChildren : function(){
			var nodes, i;
			if( nodes = this.childData ){
				for( i = nodes.length; i; ){
					nodes[ --i ].mesure();
				};
			};
		},
		update : function( x, y, w, h ){
			var updateXY = false,
				_this, _parent,
				parent;
			
			if( this.elm ){
				// width
				if( Type.isFinite( w ) === true ){
					this.elm.style.width = w + 'px';
				} else
				if( Type.isString( w ) === true ){
					this.elm.style.width = w;
					w = this.elm.offsetWidth;
				};
				//update = this.w !== w;
	
				// height
				if( Type.isFinite( h ) === true ){
					this.elm.style.height = h + 'px';
				} else
				if( Type.isString( h ) === true ){
					this.elm.style.height = w;
					h = this.elm.offsetHeight;
				};
				//update = update || this.h !== h;
				
				// x
				if( Type.isFinite( x ) === true ){
					this.elm.style.left = x + 'px';
				} else
				if( Type.isString( x ) === true ){
					this.elm.style.left = x;
					updateXY = true;
				} else {
					updateXY = true;
				};
				
				// y
				if( Type.isFinite( y ) === true ){
					this.elm.style.top = y + 'px';
				} else
				if( Type.isString( y ) === true ){
					this.elm.style.top = y;
					updateXY = true;
				} else {
					updateXY = true;
				};
				if( updateXY === true ){
					_this   = Position.cumulativeOffset( this.elm );
					_parent = this.parentData ? Position.cumulativeOffset( this.parentData.elm ) : [ 0, 0 ];
					x       = _this[ 0 ] - _parent[ 0 ];
					y       = _this[ 1 ] - _parent[ 1 ];
				};
				//update = update || this.x !== x;
				//update = update || this.y !== y;
				
				//update === true && this._updateRectangle();
				// return;
			};
			x = Type.isFinite( x ) === true ? x : this.x;
			y = Type.isFinite( y ) === true ? y : this.y;
			w = Type.isFinite( w ) === true ? w : this.w;
			h = Type.isFinite( h ) === true ? h : this.h;
			if( this.x !== x || this.y !== y ){
				this.x = x;
				this.y = y;
				//console.log( 'xy  ' + ( this.elm ? this.elm.id : '' ) + ' x:' + x + ' y:' + y + ' w:' + w + ' h:' + h + ' absX:' + this.parentData.absoluteX )
				parent = this.parentData;
				parent && this._updateAbsoluteXY( parent.absoluteX, parent.absoluteY, parent.scrollingX, parent.scrollingY );
				this.w === w && this.h === h && this._updateRectangle();
			};
			if( this.w !== w || this.h !== h ){
				this.w = w;
				this.h = h;
				//console.log( 'wh  ' + ( this.elm ? this.elm.id : '' ) + ' x:' + x + ' y:' + y + ' w:' + w + ' h:' + h )
				this._updateRectangle();
			};
			
			ScrollBarManager.update( this );
		},
		_updateAbsoluteXY : function( x, y, sX, sY ){
			var nodes, i;
			this.absoluteX = x = this.x + x;
			this.absoluteY = y = this.y + y;
			if( nodes = this.childData ){
				for( i = nodes.length; i; ){
					nodes[ --i ]._updateAbsoluteXY( x, y, this.scrollingX, this.scrollingY );
				};
			};
		},
		_updateRectangle : function(){
			var w = this.w,
				h = this.h,
				x = this.x,
				y = this.y,
				l = x,
				t = y,
				r = x + w,
				b = y + h,
				nodes = this.childData,
				i, node;
			// self;
			// childnodes
			if( this.clip === false && nodes ){
				for( i = nodes.length; i; ){
					node = nodes[ --i ];
					if( node.l + x < l ) l = x + node.l;
					if( node.t + y < t ) t = y + node.t;
					if( r < node.r + x ) r = x + node.r;
					if( b < node.b + y ) b = y + node.b;
				};
			};
			// update
			if( b !== this.b || r !== this.r || t !== this.t || l !== this.l ){
				this.l = l;
				this.t = t;
				this.r = r;
				this.b = b;
				// this.w = r - x;
				// this.h = b - y;
				this.parentData && this.parentData.clip === false && this.parentData._updateRectangle();
				return true;
			};
		},
		setPosition : function( x, y ){
			this.update( x, y );
		},
		setSize : function( w, h ){
			this.update( undefined, undefined, w, h );
		},
		positionX : function( x ){
			x !== undefined && this.update( x );
			return this.x;
		},
		positionY : function( y ){
			y !== undefined && this.update( undefined, y );
			return this.y;
		},
		width : function( w ){
			w !== undefined && this.update( undefined, undefined, w );
			return this.w;
		},
		height : function( h ){
			h !== undefined && this.update( undefined, undefined, undefined, h );
			return this.h;
		},
		getAbsolutePositionX : function(){
			return this.absoluteX;
		},
		getAbsolutePositionY : function(){
			return this.absoluteY;
		},
		cursor : function( v ){
			if( Type.isString( v ) === true ){
				this._cursor = v;
				this === targetNodeData && this.apiuser.updateCoursor( v );
			};
			return this._cursor;
		},
		addEventListener : function( eventType, handler, opt_thisObject ){
			var node    = this.node,
				counter = this.rootData.eventCounter,
				list, i;
			if( this.events === null ) this.events = {};
			list = this.events[ eventType ];
			if( !list ){
				list = this.events[ eventType ] = [];
			} else {
				for( i = list.length; i; ){
					if( list[ --i ].match( eventType, handler ) === true ){
						return;
					};
				};				
			};
			list.push( new EventTicketClass( this.node, eventType, handler, opt_thisObject ) );
			if( eventType !== 'mouseout' && eventType !== 'mouseover' ){
				if( counter[ eventType ] ){
					++counter[ eventType ];
				} else {
					//console.log( eventType );
					counter[ eventType ] = 1;
					MouseEvent.add( this.apiuser, this.rootData.elmMouseCatch, eventType, eventRellay );
				};				
			};
		},
		removeEventListener : function( eventType, handler ){
			var events  = this.events,
				counter = this.rootData.eventCounter,
				type, list, i = 0;
			if( events === null ) return;
			console.log( ' *** remove ' + eventType );
			for( type in events ){
				list = events[ type ];
				if( eventType && eventType !== type ) continue;
				for( ; i < list.length; ){
					if( list[ i ].destroy( type, handler ) === true ){
						console.log( ' *** removed! ' + type );
						list.splice( i, 1 );
					} else {
						++i;
					};
				};
				if( list.length === 0 ){
					// delete this[ type ];
					delete events[ type ];
				};
				if( counter[ type ] ){
					--counter[ type ];
					if( counter[ type ] === 0 ){
						MouseEvent.remove( this.apiuser, this.rootData.elmMouseCatch, type, eventRellay );
						delete counter[ type ];
					};
				};
			};
		},
		_capcher : function( x, y ){
			var t = this, nodes, child, _x, _y, hit, i;
			if( t._disabled === true ) return false;
			delete t.hitChild;
			x -= t.x;
			y -= t.y;
			if( nodes = t.childData ){
				_x = x - t.scrollingX;
				_y = y - t.scrollingY;
				for( i = nodes.length; i; ){
					child = nodes[ --i ];
					if( child._disabled === false && child.l <= _x && _x < child.r && child.t <= _y && _y < child.b && child._capcher( _x, _y ) === true ){
						t.hitChild = child;
						break;
					};
				};
			};
			if( t.through === true ){
				t.hitChild && t.hitSelf === false && hoverList.push( t );
				return !!t.hitChild;
			};
			hit = 0 <= x && x < t.w && 0 <= y && y < t.h;
			( t.hitChild || hit ) && t.hitSelf === false && hoverList.push( t );
			if( hit === true && t.hitChild === null ) targetNodeData = t;
			return hit || !!t.hitChild;
		},
		fire : function( e, eventType, hit ){
			var list = this.events[ eventType ],
				i    = list.length;
			e = NodePrivateData.createEvent( e, eventType, this, hit );
			for( ; i; ) list[ --i ].fire( e );
			// console.log( eventType + ' x:' + x + ' y:' + y );
		},
		dispatchEvent : function( e, eventType, hit ){
			var ret, list, i, p, child;
			if( !this.events || !( list = this.events[ eventType ] ) ) return;
			
			child = !!this.hitChild;
			e = NodePrivateData.createEvent( e, eventType, this, hit );
			for( i = list.length; i; ){
				ret = list[ --i ].fire( e );
				if( ret === true && child === false ){
					forceNodeData = this;
					return true;
				};
				if( ret === false ) return false;
			};
			forceNodeData  = null;
		},
		scrollTo : function( x, y ){
			this._scrollX = x;
			this._scrollY = y;
			ScrollBarManager.update( this );
		},
		scrollX : function( v ){
			if( Type.isFinite( v ) === true ){
				this._scrillX = v;
				ScrollBarManager.update( this );
			};
			return this.scrollingX; // this._scrollX;
		},
		scrollY : function( v ){
			if( Type.isFinite( v ) === true ){
				this._scrillY = v;
				ScrollBarManager.update( this );
			};
			return this.scrollingY; // this._scrollY;
		},
		nodeIndex : function( v ){
			var list, i;
			if( !this.parentData ) return 0;
			
			list = this.parentData.childData;
			i    = Util.getIndex( list, this );
			if( Type.isFinite( v ) === false || i === v && v < 0 && list.length <= v ) return i;
			
			list.splice( i, 1 );
			list.length === v ? list.push( this ) : list.splice( v, 0, this );
			this._free();
			return v;
		},
		_free : function(){
			if( this.parentData.hitChild === this ){
				this.parentData.hitChild = null;
				this.isHover === true && hoverList.splice( Util.getIndex( hoverList, this ), 1 ) && Util.removeClass( this.elm, this.hoverClass );
				this.isHover = false;
				if( forceNodeData === this ) forceNodeData = null;
				if( targetNodeData  === this ) targetNodeData  = null;
			};			
		},
		numNode : function(){
			return this.childData ? this.childData.length : 0;
		},
		disabled : function( v ){
			if( Type.isBoolean( v ) === true ){
				this._disabled = v;
				if( v === false ){
					this._free();
				};
			};
			return this._disabled;
		},
		childrenDisabled : function( v ){
			if( Type.isBoolean( v ) === true ){
				this._childDisabled = v;
			};
			return this._childDisabled;
		},
		remove : function(){
			if( this === this.rootData ) return;
			var parent = this.parentData,
				nodes  = parent.childData;
			this._destroy();
			if( parent.hitChild === this ) delete parent.hitChild;
			nodes.splice( Util.getIndex( nodes, this ), 1 );
			if( nodes.length === 0 ) delete parent.childData;	
			parent.clip === false && parent._updateRectangle();
		},
		_destroy : function(){
			var nodes = this.childData,
				list  = NodePrivateData.dataList,
				node;
			this.removeEventListener();
			ScrollBarManager.remove( this );
			if( nodes ){
				while( node = nodes.shift() ) node._destroy();
				delete this.childData;
			};
			list.splice( Util.getIndex( list, this ), 1 );
		}
	};
	NodePrivateData.dataList = [];
	NodePrivateData.get = function( node ){
		// if( node instanceof NodePrivateData ) return node;
		// return NodePrivateData.dataList[ layer._getUID() ];
		var list = NodePrivateData.dataList;
		for( var i = list.length; i; ){
			if( list[ --i ].node === node ) return list[ i ];
		};
		return null;
	};
	NodePrivateData.createEvent = function( e, eventType, data, hit ){
		var _e = {
			layerX      : e.clientX - data.absoluteX,
			layerY      : e.clientY - data.absoluteY,
			clientX     : e.clientX,
			clientY     : e.clientY,
			dragOffsetX : e.dragOffsetX,
			dragOffsetY : e.dragOffsetY,
			dragPhase   : e.dragPhase,					
			eventType   : eventType,
			hit         : hit,
			node        : data.node,
			wheelDelta  : e.wheelDelta,
			target      : forceNodeData ? forceNodeData.node : targetNodeData ? targetNodeData.node : null
		};
		return _e;
	};
	
	var EventTicketClass = function( node, eventType, handler, opt_thisObject ){
		this.node    = node;
		this.type    = eventType;
		this.handler = handler;
		this.thisObj = opt_thisObject || node;
	};
	EventTicketClass.prototype = {
		match : function( eventType, handler ){
			if( handler && this.handler !== handler ) return false;
			if( eventType && this.type !== eventType ) return false;
			return true;
		},
		destroy : function( eventType, handler ){
			if( this.match( eventType, handler ) === false ) return false;
			delete this.node;
			delete this.type;
			delete this.handler;
			delete this.thisObj;
			return true;
		},
		fire : ( function(){
			if( Function.prototype.call ){
				return function( e ){
					return this.handler.call( this.thisObj, e );
				};				
			};
			return function( e ){
				var ret;
				this.thisObj._currentHandler = this.handler;
				ret = this.thisObj._currentHandler( e );
				delete this.thisObj._currentHandler;
				return ret;					
			};
		})()
	};
	
/*-------------------------------------
 *  StayHelper
 */
	var StayEventTicketClass = function( node, data, stayhandler, opt_thisObject ){
		node.addEventListener( 'mouseover', this.mouseoverHandler, this );
		this.node       = node;
		this.data       = data;
		this.handler    = stayhandler;
		this.thisObject = opt_thisObject;
	};
	StayEventTicketClass.prototype = Util.extend( new EventTicketClass( null, 'mousestay' ), {
		// type : 'mousestay',
		e    : null,
		mouseoverHandler : function( e ){
			this.e = NodePrivateData.createEvent( e, this.type, this.data, true );
			this.node.addEventListener( 'mouseout',  this.mousestayHandler, this );
			this.node.addEventListener( 'mousemove', this.mousemoveHandler, this );
			SystemTimer.add( this.data.apiuser, this.timeoutHandler, null, this );
		},
		timeoutHandler : function(){
			this.mouseoutHandler();
			return this.fire( this.e );
		},
		mousemoveHandler : function( e ){
			this.e = NodePrivateData.createEvent( e, this.type, this.data, true );
			SystemTimer.remove( this.data.apiuser, this.timeoutHandler );
			SystemTimer.add( this.data.apiuser, this.timeoutHandler, null, this );
		},
		mouseoutHandler : function( e ){
			this.node.removeEventListener( 'mouseout', this.mousestayHandler );
			this.node.removeEventListener( 'mousemove', this.mousemoveHandler );
			SystemTimer.remove( this.data.apiuser, this.timeoutHandler );
			delete this.e;
		}
	});
	
	var ScrollBarManager = ( function(){
		var elmScroller     = document.createElement( 'div' ),
			elmBar          = document.createElement( 'div' ),
			smoothList      = [],
			dragPhase       = 2,
			dragOut         = false,
			currentNodeData = null,
			dragStartY      = 0,
			currentEvent;
		
		function tick(){
			var list = smoothList,
				i, data, y;
			for( i = 0; i < list.length; ){
				data = list[ i ];
				if( data.scrollingY !== data._scrollY ){
					y = data.scrollingY += data.smoothY;
					if( data.smoothY < 0 ){
						y = y < data._scrollY ? data._scrollY : y;
					} else {
						y = data._scrollY < y ? data._scrollY : y;
					};
					data.scrollingY    = y;
					data.elm.scrollTop = -y;
					data.events && data.events.scroll && data.fire( currentEvent, 'scroll', true );
				};
				if( data.scrollingY === data._scrollY ){
					list.splice( i, 1 );
					// data.events && data.events.scroll && data.fire( currentEvent, 'scroll', true );
				} else {
					++i;
				};
			};
			list.length === 0 && SystemTimer.remove( SUPER_USER_KEY, tick );
			currentEvent.type = 'updateAfterScroll';
			AsyncCall.add( data.apiuser, eventRellay, currentEvent ); // スクロール後の更新	
		};
		
		function scrollReady( e ){
			var data = this;
			
			dragOut = false;
			if( data === currentNodeData || dragPhase !== 2 ) return; // Drag中の場合は 他にスクロールを作らない
			currentNodeData && scrollRelease();

			dragPhase = 2;
			data.elm.parentNode.appendChild( elmScroller );
			elmScroller.appendChild( data.elm );
			
			elmScroller.style.cssText = 'position:absolute;left:0;top:0;';
			elmScroller.appendChild( elmBar );	
			
			data.elm.scrollTop = -data.scrollingY;
			data.rootData.addEventListener( 'mousewheel', onMouseWheelScroll, data );
			data.rootData.addEventListener( 'mousedrag',  onMouseDragScroll, data );
			data.addEventListener( 'mouseout',   onMouseOut, data );
			currentNodeData = data;
			ScrollBarManager.update( data );
		};
		function scrollRelease(){
			var data   = currentNodeData;
			var parent = elmScroller.parentNode;
			parent.appendChild( currentNodeData.elm );
			parent.removeChild( elmScroller );
			currentNodeData.elm.scrollTop = -data.scrollingY;
			
			data.rootData.removeEventListener( 'mousewheel', onMouseWheelScroll, data );
			data.rootData.removeEventListener( 'mousedrag',  onMouseDragScroll, data );
			data.removeEventListener( 'mouseout',   onMouseOut, data );
			currentNodeData = null;
		};
		function onMouseOut( e ){
			dragOut = true;
			console.log( 'mouseOut ' + dragPhase );
			dragPhase === 2 && scrollRelease(); // Dragしてのアウトの場合, scroll をリリースしない
		};
		function onMouseWheelScroll( e ){
			var data = this;
			this._scrollY += e.wheelDelta;
			ScrollBarManager.update( this );
			currentEvent = e;
			return true;
		};
		function onMouseDragScroll( e ){
			var data = this;
			//e.dragOffsetY;
			currentEvent = e;
			dragPhase = e.dragPhase;
			switch( dragPhase ){
				case 0:
					dragStartY = this.scrollingY;
					data.rootData.removeEventListener( 'mousewheel', onMouseWheelScroll, data );
				case 1:
					this._scrollY = dragStartY + e.dragOffsetY;
					ScrollBarManager.update( this );				
					return true;
				case 2:
					dragOut === true ? scrollRelease() : data.rootData.addEventListener( 'mousewheel', onMouseWheelScroll, data );
					return false;
			};
		};
		
		return {
			register : function( data ){
				data.addEventListener( 'mouseover', scrollReady, data );
			},
			update : function( data ){
				// if( data !== currentNodeData ) return;
				var isCurrent = data === currentNodeData;
				
				var contentH = data._scrollH = data.elm.scrollHeight,
					clipH    = data.h,
					offsetH  = contentH - clipH,
					scrollY  = data._scrollY = 0 < data._scrollY ? 0 : ( data._scrollY < -offsetH ? -offsetH : data._scrollY ),
					barH, barY;
				if( isCurrent === true ){
					elmScroller.style.width  = data.w + 'px';
					elmScroller.style.height = clipH + 'px';					
				};
				
				if( offsetH < 1 ){
					data._scrollY = scrollY = 0;
					if( isCurrent === true ) elmBar.style.display = 'none';
				} else
				if( isCurrent === true ){
					barH     = Math.floor( clipH * ( clipH / contentH ) );
					barY     = Math.floor( ( clipH - barH ) * - scrollY / offsetH );
					elmBar.style.cssText = [
						'position:absolute;',
						'width:10px;',
						'background-color:#333;',
						'right:2px;',
						'font-size:0;line-height:0;',
						'height:', barH, 'px;',
						'top:', data.y + barY, 'px;'
					].join( '' );					
				};
				data.smoothY = ( scrollY - data.scrollingY ) / 10;
				if( data.scrollingY !== scrollY && Util.getIndex( smoothList, data ) === -1 ){
					smoothList.length === 0 && SystemTimer.add( SUPER_USER_KEY, tick, 16 );
					smoothList.push( data );
				};
			},
			remove : function( data ){
				var list = smoothList,
					i    = Util.getIndex( list, data );
				data === currentNodeData && scrollRelease();
				i !== -1 && list.splice( i, 1 );
			}
		};
	})();
	
	return {
		create : function( apiuser ){
			var	elm  = document.createElement( 'div' ),
				root, data;
			body.appendChild( elm );
			
			root = new NodeClass( apiuser, null, null, elm );
			data = NodePrivateData.get( root );
			
			// elm.style.cssText = 'position:absolute;top:0;left:0;height:100%;';
			elm.className      = 'mouse-operation-catcher';
			elm.unselectable   = 'on';
			data.elmMouseCatch = elm;
			
			data.eventCounter  = {};
			ROOT_LIST.push( data );
			currentRootData    = data;
			targetNodeData     = null;
			forceNodeData      = null;
			
			MouseEvent.add( apiuser, elm, 'mousemove', eventRellay );
			return root;
		},
		onCurrentApplicationChange : function( _application ){
			currentRootData    = null;
			targetNodeData     = null;
			forceNodeData      = null;
			for( var i = ROOT_LIST.length; i; ){
				if( ROOT_LIST[ --i ].apiuser === _application ){
					currentRootData = ROOT_LIST[ i ];
					return;
				};
			};
		},
		destroyTree : function( root ){
			var data = NodePrivateData.get( root );
			MouseEvent.remove( data.apiuser, data.elmMouseCatch, 'mousemove', eventRellay );
			body.removeChild( data.elmMouseCatch );
			data._destroy();
			ROOT_LIST.splice( Util.getIndex( ROOT_LIST, data ), 1 );
			if( currentRootData === data ){
				currentRootData    = null;
				targetNodeData     = null;
				forceNodeData      = null;	
			};
		},
		onSystemShutdown : function(){
			
		},
		isNodeInstance : function( node ){
			return node instanceof NodeClass;
		},
		_getNodePrivateData : function( node ){ // system only
			return NodePrivateData.get( node );
		}
	};
})();

var Application = ( function(){
	
	var LIVE_APPLICATION_LIST = [];
	
	var currentApplication    = null,
		coveredApplication    = null,
		winW                  = 0,
		winH                  = 0;
	
	var ApplicationReference = function( appClass, isOverlay, displayName, id, thumbnailUrl, tailColor ){
		var self          = this;
		var application   = null;
		this.id           = id;
		this.displayName  = displayName;
		this.thumbnailUrl = thumbnailUrl;
		this.tailColor    = tailColor;
		
		function asyncBoot(){
			application = Application.boot( appClass, displayName, self.getUID(), isOverlay, Util.copyArray( arguments ) );
		};
		this.getUID = function(){
			return Util.getIndex( API_USER_LIST, appClass );
		};
		this.getDisplayName = function(){
			return this.displayName;
		};
		this.boot = function( /* _option */ ){
			AsyncCall.add( this, asyncBoot, Util.copyArray( arguments ) );
		};
		this.shutdown = function(){
			if( !application ) return false;
			
			AsyncCall.add( application, ( isOverlay === true ? Overlay.hide : application.close ) );
		};
	};
	
	function asyncBootHome(){
		currentApplication === null && Home.boot();
	};
	function asyncOpen( /* arguments */ ){
		var _arg = Util.copyArray( arguments );
		_arg.unshift( winW, winH );
		currentApplication.open.apply( currentApplication, _arg );
	};
	return {
		register: function( _class, _overlay, _tail, _displayName, _id, _thumbnailUrl, _tailColor ){
			APPLICATION_LIST.push( _class );
			API_USER_LIST.push( _class );
			var _ref = new ApplicationReference( _class, _overlay, _displayName, _id, _thumbnailUrl, _tailColor );
			_tail === true && Home.add( _ref );
			return _ref;
		},
		isApplicationInstance: function( app ){
			return ApplicationPrivateData.get( app ) !== null;
		},
		isApplicationReference: function( _reference ){
			return _reference instanceof ApplicationReference;
		},
		isCurrentAppplication: function( app ){
			return app === currentApplication;
		},
		boot: function( appClass, displayName, uid, isOverlay, arg ){
			if( currentApplication ){
				if( currentApplication.getUID() === uid ) return null;
				if( isOverlay === false && currentApplication.close() === false ) return null;
			};

			appClass.prototype = new AbstractApplication( appClass, displayName, isOverlay );
			
			var application = new appClass(),
				data = ApplicationPrivateData.get( application );
			
			application.rootElement = data.rootElement;
			data.application = application;
			
			coveredApplication = isOverlay === true ? currentApplication : null;
			
			Application.onCurrentApplicationChange( application );
			
			if( isOverlay === false ){
				body.style.backgroundColor = application.bgColor;
				
				body.appendChild( data.rootElement );
				data.rootElement.style.display = 'none';
				application.init();

				application.addAsyncCall( asyncOpen, arg );
			} else {
				Overlay.show( application, arg );
			};
			
			return application;
		},
		shutdown: function( _application, isOverlay ){
			if( isOverlay === false ){
				currentApplication = null;
				AsyncCall.add( SUPER_USER_KEY, asyncBootHome );
			} else {
				Application.onCurrentApplicationChange( coveredApplication );
				coveredApplication = null;
			};
		},
		onCurrentApplicationChange: function( _application ){
			if( Application.isApplicationInstance( _application ) === false ) return;
			if( currentApplication === _application ) return;
			currentApplication = _application;
			MouseEvent.onCurrentApplicationChange( _application );
			PointingDeviceEventTree.onCurrentApplicationChange( _application );
			KeyEvent.updateCurrentListener( _application );
			// InteractiveLayer.onCurrentApplicationChange( _application );
		},
		onApplicationShutdown: function( _application ){
			LIVE_APPLICATION_LIST.splice( Util.getIndex(  LIVE_APPLICATION_LIST, _application ) );
		},
		onWindowResize: function( w, h ){
			winW = w;
			winH = h;
			currentApplication && currentApplication.resize( w, h );
			Overlay.onWindowResize( w, h );
			UI.onWindowResize( w, h );
		},
		onSystemShutdown: function(){
			
		}
	}
})();

/* --------------------------------------------------------------
 * Home
 * 
 */
	var Home = ( function(){
		var APP_REF_LIST    = [];
		var ELM_TAIL_ORIGIN = ( function(){
			var ret = document.createElement( 'div' ),
				h2  = document.createElement( 'h2' );
			ret.className = 'tail-wrapper';
			ret.appendChild( h2 );
			h2.appendChild( document.createTextNode( 'appName' ) );
			return ret;
		})();
		
		var TailClass = function( appRef ){
			this.elm = ELM_TAIL_ORIGIN.cloneNode( true );
			this.destroy = function(){
				appRef = self = elmName = null;
			};			
			
			var self    = this,
				elmName = this.elm.getElementsByTagName( 'h2' )[ 0 ].firstChild;
			
			this.elm.style.backgroundColor = appRef.tailColor;
			elmName.data = appRef.displayName;
		};
		
		var ref = Application.register( function(){
			var self     = this,
				winW     = 0,
				winH     = 0,
				tailList = [],
				elmContainer, elmHeader;
			
			function draw(){
				var tail, elm;
				for( var i=0, l=APP_REF_LIST.length; i<l; ++i ){
					tail = new TailClass( APP_REF_LIST[ i ] );
					tailList.push( tail );
					elm  = tail.elm;
					elmContainer.appendChild( elm );
					self.addEventListener( elm, 'click', onTailClick );
				};
			};
			
			function onTailClick( e ){
				var _children = elmContainer.getElementsByTagName( 'div' );
				for( var i=0, l=_children.length; i<l; ++i ){
					if( this === _children[ i ] ){
						APP_REF_LIST[ i ].boot();
						break;
					};
				};
			};
			
			this.bgColor     = '#0F6D39';
			this.MIN_WIDTH   = 320;
			this.MIN_HEIGHT  = 320;
			this.onInit = function(){
				self.rootElement.id = 'home-root';
				
				elmContainer        = document.createElement( 'div' );
				self.rootElement.appendChild( elmContainer );
				elmContainer.id     = 'home-tail-container';
				
				elmHeader           = document.createElement( 'div' );
				self.rootElement.appendChild( elmHeader );
				elmHeader.id        = 'home-header';
			};
			this.onOpen = function( _w, _h ){
				winW = _w;
				winH = _h;
				draw();
			};
			this.onPaneResize = function( _w, _h ){
				
			};
			this.onClose = function(){
				self.removeEventListener();
				while( tailList.length > 0 ){
					tailList.shift().destroy();
				}
				self = tailList = elmContainer = null;
			};
		}, false, false, 'home', 'home', null );
		
		return {
			add: function( _appRef ){
				if( Application.isApplicationReference( _appRef ) === false ) return;
				Util.getIndex( APP_REF_LIST, _appRef ) === -1 && APP_REF_LIST.push( _appRef );
			},
			boot: function(){
				ref.boot();
			}
		}
	})();

	var Page = ( function(){
		var pageNodes = [],
			appClass, ref,
			ignoreTagList = [ 'script', 'noscript', 'style' ];
		
		var MemoryClass = function( node ){
			this.node = node;
		};
		MemoryClass.prototype = {
			init: function(){
				var node      = this.node,
					_nodeType = node.nodeType;
				if( _nodeType === 1 && Util.getIndex( ignoreTagList, node.tagName.toLowerCase() ) === -1 ){
					this.type    = _nodeType;
					this.display = node.style.display;
				} else
				if( _nodeType === 3 ){
					if( node.data.replace( /\s/g, '' ).length !== 0 ){
						this.type    = _nodeType;
						this.before  = pageNodes.length === 0 ? null : pageNodes[ pageNodes.length - 1 ].node;
					} else {
						body.removeChild( node );
						return false;
					}
				} else {
					// body.removeChild( node );
					return false;
				};
			},
			show: function(){
				if( this.type === 1 ){
					if( this.display ){
						this.node.style.display = this.display;
					} else {
						this.node.style.display = '';
					}
				} else {
					if( this.before ){
						body.insertBefore( this.node, this.before );
					} else {
						body.appendChild( this.node );
					};
				};
			},
			hide: function(){
				if( !this.node.parentNode ){
					return;
				};
				if( this.type === 1 ){
					this.node.style.display = 'none';
				} else {
					body.removeChild( this.node );
				};
			}
		};
		
		return {
			onReady: function(){
				var _children = Util.copyArray( body.childNodes ),
					_mem;
				for( var i = 0, l = _children.length; i<l; ++i ){
					_mem = new MemoryClass( _children[ i ] );
					_mem.init() !== false && pageNodes.push( _mem );
				};
				if( pageNodes.length !== 0 ){
					if( Type.isFunction( gOS.PageApplicationClass ) === true ){
						Page.appClass = gOS.PageApplicationClass;
						Page.appClass.bgColor    = Page.appClass.bgColor;
						Page.appClass.MIN_WIDTH  = Page.appClass.MIN_WIDTH  || 240;
						Page.appClass.MIN_HEIGHT = Page.appClass.MIN_HEIGHT || 240;
					} else {
						Page.appClass = function(){
							var self     = this;
							
							this.bgColor      = '#ffffff';
							this.MIN_WIDTH    = 200;
							this.MIN_HEIGHT   = 200;
							this.onInit       = function(){};
							this.onOpen       = function( _w, _h ){
								KeyEvent.add( self, Const.KEY.EVENT.KEY_DOWN, ref.shutdown, 27 ); // 27.esc
							};
							this.onPaneResize = function( _w, _h ){};
							this.onClose      = function(){};
						};
					};
					ref = Application.register( Page.appClass, false, true, document.title, 'page', null, Page.appClass.tailColor || '#999999' );
					if( Type.isFunction( gOS.PageApplicationClass ) === true ){
						gOS.PageApplicationRef = ref;
					}; 
				};
				delete Page.onReady;
			},
			show: function(){
				for( var i = pageNodes.length; i; ){
					pageNodes[ --i ].show();
				};
			},
			hide: function(){
				for( var i = pageNodes.length; i; ){
					pageNodes[ --i ].hide();
				};				
			},
			boot: function(){
				ref && ref.boot();
			},
			registered: function(){
				return !!ref;
			},
			appClass: null
		}
	})();

/* --------------------------------------------------------------
 * Event
 * 
 *  screenX
 *  スクリーン座標は、コンピュータのディスプレイの左上を原点とする座標系である。screenX, screenY属性で取得できる。Javascritpでは、同名のプロパティとして実装されている。
 *  しかし、これは、現実的には、何の役に立たない。ブラウザ自体がディスプレイのどの位置にいるのかがわからないので、画面上の位置を知ったところで、何にもならないからだ。 
 * 
 *  clientX
 *  ウインドウ座標とは、現在のブラウザのウインドウの、ドキュメントを表示している部分の左上原点とした座標である。
 *  問題は、ウインドウは、必ずしもドキュメント全体を表示するとは限らない。スクロールと呼ばれるUIによって、ドキュメントの一部だけを表示しているかもしれない。
 */
	var XBrowserEvent = ( function(){
		var wrappedHandlerClass,
			wrappedEventClass,
			tmp;
		
		if( window.addEventListener ){
			wrappedHandlerClass = function( ticket ){
				this.handler = function( e ){
					if( ticket.fire( e ) !== false ) return;
					e.preventDefault();
					e.stopPropagation();
					return false;
				};
				this.destroy = function(){
					ticket = null;
					delete this.handler;
					delete this.destroy;
				};
			};
		} else {
			wrappedEventClass = function( e, element ){
				this._event        = e;
				this.type          = e.type;
				this.target        = e.srcElement;
				this.currentTarget = element;
				this.relatedTarget = e.formElement ? e.formElement : e.toElement;
				this.eventPhase    = e.srcElement === element ? 2: 3;
				
				this.clientX       = e.clientX;
				this.clientY       = e.clientY;
				this.screenX       = e.screenX;
				this.screenY       = e.screenY;
				
				this.keyCode       = e.keyCode;
				this.altKey        = e.altKey;
				this.ctrlKey       = e.ctrlKey;
				this.shiftKey      = e.shiftKey;
				
				this.wheelDelta    = e.wheelDelta;
			};
			wrappedEventClass.prototype.stopPropagation = function(){
				this._event.cancelBubble = true;
			};
			wrappedEventClass.prototype.preventDefault  = function(){
				this._event.returnValue = false;
			};

			if( document.attachEvent ){
				wrappedHandlerClass = function( ticket ){
					this.handler = function(){
						if( ticket === null ) alert( window.event.type )
						if( ticket.fire( new wrappedEventClass( window.event, ticket.element ) ) !== false ) return;
						// e.preventDefault();
						// e.stopPropagation();
						window.event.cancelBubble = true;
						window.event.returnValue  = false;
						return false;
					};
					this.destroy = function(){
						ticket = null;
						delete this.handler;
						delete this.destroy;
					};
				};
			} else {
				tmp = {
					list: [],
					find: function( _ticket ){
						for( var i=0, l= tmp.list.length, _item; i<l; ++i ){
							_item = tmp.list[ i ];
							if( _item.element === _ticket.element && _item.eventType === _ticket.eventType ){
								return _item;
							};
						};
						return null;
					}
				};
				tmp.TicketClass = function( _ticket ){
					var self = this;
					this.element   = _ticket.element;
					this.eventType = _ticket.eventType;
					this.tickets   = [ _ticket ];
					this.onDestroy = function(){ self = null; };
					
					this.element[ 'on' + this.eventType ] = function( e ){ return self.fire( e );};
					_ticket = null;
				};
				tmp.TicketClass.prototype = {
					add: function( _ticket ){
						Util.getIndex( this.tickets, ticket ) === -1 && this.tickets.push( _ticket );
					},
					remove: function( _ticket ){
						var i = Util.getIndex( this.tickets, _ticket );
						i !== -1 && this.tickets.splice( i, 1 );
						this.tickets.length === 0 && this.destroy();
					},
					fire: function( e ){
						e = e || new wrappedEventClass( window.event, this.element );
						var i = this.tickets.length,
							cancel;
						for( ; i; ){
							if( this.tickets[ --i ].fire( e ) === false ) cancel = false;
						};
						return cancel;
					},
					destroy: function(){
						this.onDestroy();
						this.element[ 'on' + this.eventType ] = '';
						tmp.list.splice( Util.getIndex( tmp.list, this ), 1 );
						delete this.element;
						delete this.eventType;
						delete this.tickets;
						delete this.onDestroy;
					}
				};
			};
		};

		return {
			add: function( _ticket ){
				if( document.addEventListener ){
					XBrowserEvent.add = function( _ticket ){
						_ticket.wrappedHandler = new wrappedHandlerClass( _ticket );
						_ticket.element.addEventListener( _ticket.eventType, _ticket.wrappedHandler.handler, false );
					};
				} else
				if( document.attachEvent ){
					XBrowserEvent.add = function( _ticket ){
						_ticket.wrappedHandler = new wrappedHandlerClass( _ticket );
						_ticket.element.attachEvent( 'on' + _ticket.eventType, _ticket.wrappedHandler.handler );
					};
				} else {
					XBrowserEvent.add = function( _ticket ){
						var t = tmp.find( _ticket );
						if( t !== null ){
							t.add( _ticket );
						} else {
							tmp.list.push( new tmp.TicketClass( _ticket ) );
						};
					};
				};
				
				XBrowserEvent.add( _ticket );
			},
			remove: function( _ticket ){
				if( document.removeEventListener ){
					XBrowserEvent.remove = function( _ticket ){
						_ticket.element.removeEventListener( _ticket.eventType, _ticket.wrappedHandler.handler, false );
						_ticket.wrappedHandler.destroy();
					};
				} else
				if( document.detachEvent ){
					XBrowserEvent.remove = function( _ticket ){
						_ticket.element.detachEvent( 'on' + _ticket.eventType, _ticket.wrappedHandler.handler );
						_ticket.wrappedHandler.destroy();
					};
				} else {
					XBrowserEvent.remove = function( _ticket ){
						var t = tmp.find( _ticket );
						if( t !== null ){
							t.remove( _ticket );
						};
					};
				};
				
				XBrowserEvent.remove( _ticket );
			}
		}
	})();

/*
 * EventTicketClass
 */
	var EventTicketClass = function( _element, _eventType, _handler, opt_thisObject ){
		this.element        = _element;
		this.eventType      = _eventType;
		this.handler        = _handler;
		this.wrappedHandler = null;
		this.thisObject     = opt_thisObject;
		XBrowserEvent.add( this );
	};
	EventTicketClass.prototype = {
		fire : ( function(){
			if( Function.prototype.call ){
				return function( e ){
					return this.handler.call( this.thisObject || this.element, e );
				};				
			};
			return function( e ){
				var thisObj = this.thisObject || this.element,
					ret;
				thisObj._currentHandler = this.handler;
				ret = thisObj._currentHandler( e );
				delete thisObj._currentHandler;
				return ret;					
			};
		})(),
		match: function( _element, _eventType, _handler ){
			if( _handler   && _handler   !== this.handler )   return false;
			if( _eventType && _eventType !== this.eventType ) return false;
			if( _element   && _element   !== this.element )   return false;
			
			return true;
		},
		destroy: function( _element, _eventType, _handler ){
			if( this.match( _element, _eventType, _handler ) === false ) return false;
			
			XBrowserEvent.remove( this );
			
			delete this.element;
			delete this.eventType;
			delete this.handler;
			delete this.wrappedHandler;
			delete this.thisObject;
			return true;
		}
	};

var ReadyEvent = ( function(){
	var ticketReady,
		ticketLoad,
        timer;

	function webkitDetect(){
		var state = document.readyState;
		if( state === 'loaded' || state === 'complete' ){
			SystemTimer.remove( SUPER_USER_KEY, webkitDetect );
			timer = null;
			onReady();
		};
	};
	function ieDetect(){
		if( this.readyState === 'complete' ){ // this.readyState === 'loaded' || 
			this.onreadystatechange = new Function();
			this.onreadystatechange = null;
			AsyncCall.remove( SUPER_USER_KEY, ieScroll );
			onReady();
		};
	};
	function ieScroll(){
		try {
			document.documentElement.doScroll( 'left' );
		} catch( e ){
			AsyncCall.add( SUPER_USER_KEY, ieScroll );
			return;
		};
		// no errors, fire
		document.onreadystatechange = new Function();
		document.onreadystatechange = null;
		onReady();		
	};
		
	function onReady(){
		ticketReady && ticketReady.destroy();
		ticketLoad  && ticketLoad.destroy();
		ticketReady = ticketLoad = null;
		Page.onReady();
		if( Page.registered() === true ){
			Page.boot();
		} else {
			Home.boot();
		};
	};
	
	// Apple WebKit (Safari, OmniWeb, ...)
	if( document.readyState && !!UA.WEBKIT ){
		SystemTimer.add( SUPER_USER_KEY, webkitDetect, 50 );
	/* } else
		if( document.readyState && UA.isIE && UA.ieVersion < 9 ){
		ieScroll();
		document.onreadystatechange = ieDetect; */
	} else {
		ticketReady = new EventTicketClass( document, 'DOMContentLoaded', onReady );
		ticketLoad  = new EventTicketClass( window, 'load', onReady );
	};
})();




/* =====================================================
 *  ResizeEvent
 * 
 */

var ResizeEvent = ( function(){
	var _globalLock = 0;
	var _resize;
	var root = window;
	var w = 0, h = 0;
	
	function getInnerSize(){
		return {
			w : root.innerWidth || root.clientWidth,
			h : root.innerHeight || root.clientHeight
		};
	}
	function unlock(){
		_globalLock = 0;
	}
	
	if( document.uniqueID ){
		_resize = function(){
			root = (document.compatMode || "") !== "CSS1Compat" ? document.body : document.documentElement;

			// resize agent
			function loop(){
				if( !_globalLock++ ){
					var size = getInnerSize();
					if( w !== size.w || h !== size.h ){// resized
						w = size.w;
						h = size.h;
						// update
						Application.onWindowResize( w, h );
					}
					window.setTimeout( unlock, 0 );
					// delay unlock
				}
				window.setTimeout( loop, 100 );
			}
			loop();
		};
	} else {
		_resize = function(){
			new EventTicketClass( window, 'resize', onResize );
			
			function onResize(){
				if( !_globalLock++ ) {
					var size = getInnerSize();
					if( w !== size.w || h !== size.h ){// resized
						w = size.w;
						h = size.h;
						// update
						Application.onWindowResize( w, h );
					}
					window.setTimeout( unlock, 0 );
				}
			}
			onResize();
		};
	}
	AsyncCall.add( SUPER_USER_KEY, _resize );
	
	return {
		getSize: getInnerSize,
		onSystemShutdown: function(){
			
		}
	}
})();


/* =====================================================
 *  MouseEvent
 * 
 */
	var MouseEvent = ( function(){
		var CLICK_OFFSET   = 2 * 2,
			DRAG_OFFSET    = 4 * 4;		
		
		var EVENT_LIST_MAP = [],
			TMP = {},
			currentEventList;
	/*-------------------------------------
	 * ClickHelper
	 * mousedown, mouseup, の移動距離を調べて clickハンドラ を呼ぶ
	 */
		var ClickEventTicketClass = function( element, clickhandler, opt_thisObject ){
			this.mousedownTicket = new EventTicketClass( element, 'mousedown', this.mousedownHandler, this );
			this.element         = element;
			this.handler         = clickhandler;
			this.thisObject      = opt_thisObject;
		};
		ClickEventTicketClass.prototype = {
			element          : null,
			handler          : null,
			thisObject       : null,
			startX           : 0,
			startY           : 0,
			mousedownTicket  : null,
			mousemoveTicket  : null,
			mouseupTicket    : null,
			mouseoutTicket   : null,
			eventType        : 'click',
			fire             : EventTicketClass.prototype.fire,
			match            : EventTicketClass.prototype.match,
			mousedownHandler : function( e ){
				this.startX = e.clientX;
				this.startY = e.clientY;
				
				this.mousemoveTicket = new EventTicketClass( this.element, 'mousemove', this.mousemoveHandler, this );
				this.mouseupTicket   = new EventTicketClass( this.element, 'mouseup',   this.mouseupHandler,   this );
				this.mouseoutTicket  = new EventTicketClass( this.element, 'mouseout',  this.mouseoutHandler,  this );
				return false;			
			},
			mousemoveHandler : function( e ){
				var offsetX = e.clientX - this.startX,
					offsetY = e.clientY - this.startY;				
				offsetX * offsetX + offsetY * offsetY >= CLICK_OFFSET && this.mouseoutHandler();
				return false;
			},
			mouseupHandler : function( e ){
				this.mouseoutHandler();
				return this.fire( ClickEventTicketClass.createEvent( e ) );
			},
			mouseoutHandler : function( e ){
				this.mousemoveTicket && this.mousemoveTicket.destroy();
				this.mouseupTicket   && this.mouseupTicket.destroy();
				this.mouseoutTicket  && this.mouseoutTicket.destroy();
				if( this.mousemoveTicket ) delete this.mousemoveTicket;
				if( this.mouseupTicket  )  delete this.mouseupTicket;
				if( this.mouseoutTicket )  delete this.mouseoutTicket;
				return false;
			},
			destroy : function( _element, _eventType, _handler ){
				if( this.match( _element, _eventType, _handler ) === false ) return false;
				
				this.mouseoutHandler();
				this.mousedownTicket.destroy();

				delete this.element;
				delete this.handler;
				delete this.thisObject;
				delete this.mousedownTicket;	
				return true;
			}
		};
		if( document.createEvent ){
			ClickEventTicketClass.createEvent = function( e ){
				var _e = document.createEvent( 'MouseEvents' );
				_e.initMouseEvent(
					'click' , false, true, e.view, 
					e.detail, e.screenX, e.screenY, e.clientX, e.clientY, 
					e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 
					e.button, e.relatedTarget
				);
				return _e;
			};					
		} else
		if( document.attachEvent ){
			ClickEventTicketClass.createEvent = function( e ){
				e.type = 'click';
				return e;
			};
		} else {
			
		};
		
	/*-------------------------------------
	 *  WheelHelper
	 */
		var WheelEventTicketClass = ( function(){
			if( UA.GECKO ){
				return function( element, wheelhandler, opt_thisObject ){
					this.wheelTicket = new EventTicketClass( element, 'DOMMouseScroll', this.onGeckoWheel, this );
					this.element     = element;
					this.handler     = wheelhandler;
					this.thisObject  = opt_thisObject;
				};
			} else
			if( true || UA.isIE ){
				return function( element, wheelhandler, opt_thisObject ){
					this.wheelTicket = new EventTicketClass( element, this.eventType, wheelhandler );
					this.element     = element;
					this.handler     = wheelhandler;
					this.thisObject  = opt_thisObject;
				};
			} else {
				TMP.wheelHandlerList = [];
				TMP.wheelThisObjList = [];
				//TMP.wheelLegacy = undefined;
				TMP.onWheel   = function( e ){
					e = e || window.event;
					var cancel = true,
						f = TMP.wheelLegacy, i;
					if( f ) cancel = f.call( this, e );
					
					for( i = TMP.wheelHandlerList.length; i; ){
						if( TMP.wheelHandlerList[ --i ].call( TMP.wheelThisObjList[ i ] || this, e ) === false ) cancel = false;
					};
					return cancel;
				};
				return function( element, wheelhandler, opt_thisObject ){
					this.element     = element;
					this.handler     = wheelhandler;
					this.thisObject  = opt_thisObject;
					
					if( TMP.wheelHandlerList.length === 0 ){
						//TMP.wheelLegacy     = Type.isFunction( window.onmousewheel ) === true ? window.onmousewheel : undefined;
						element.onmousewheel = TMP.onWheel;
					};
					TMP.wheelHandlerList.push( wheelhandler );
					TMP.wheelThisObjList.push( opt_thisObject )
				};
			};
		})();
		WheelEventTicketClass.prototype = {
			eventType : 'mousewheel',
			match     : EventTicketClass.prototype.match,
			destroy   : function( _element, _eventType, _handler ){
				if( this.match( _element, _eventType, _handler ) === false ) return false;
				
				this.wheelTicket && this.wheelTicket.destroy();
				
				delete this.wheelTicket;
				delete this.element;
				delete this.handler;
				delete this.thisObject;
				
				this.onDestroy && this.onDestroy();
				return true;
			}
		};
		if( UA.GECKO ){
			WheelEventTicketClass.prototype.onGeckoWheel = function( e ){
				var _e = document.createEvent( 'MouseEvents' );
				_e.initMouseEvent(
					'mousewheel' , false, true, e.view, 
					e.detail, e.screenX, e.screenY, e.clientX, e.clientY, 
					e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 
					e.button, e.relatedTarget
				);
				_e.wheelDelta = e.detail * -40;
				return this.handler.call( this.thisObject || this.element, _e );
			};
		} else
		if( true || UA.isIE ){

		} else {
			WheelEventTicketClass.prototype.onDestroy = function(){
				TMP.wheelHandlerList.splice( Util.getIndex( TMP.wheelHandlerList, this.handler ), 1 );
				TMP.wheelThisObjList.splice( Util.getIndex( TMP.wheelThisObjList, this.handler ), 1 );
				if( TMP.wheelHandlerList.length === 0 ) this.element.onmousewheel = '';
			};			
		};
		
	/*-------------------------------------
	 *  DragHelper
	 */
		var DragEventTicketClass = function( element, draghandler, opt_thisObject ){
			this.mousedownTicket = new EventTicketClass( element, 'mousedown', this.mousedownHandler, this );
			this.element         = element;
			this.handler         = draghandler;
			this.thisObject      = opt_thisObject;
		};
		DragEventTicketClass.prototype = {
			element         : null,
			handler         : null,
			thisObject      : null,
			startX          : 0,
			startY          : 0,
			dragging        : false,
			mousedownTicket : null,
			mousemoveTicket : null,
			mouseupTicket   : null,
			mouseoutTicket  : null,
			eventType       : 'mousedrag',
			fire            : EventTicketClass.prototype.fire,
			match           : EventTicketClass.prototype.match,
			mousedownHandler: function( e ){
				this.startX = e.clientX;
				this.startY = e.clientY;
				
				this.mousemoveTicket = new EventTicketClass( this.element, 'mousemove', this.dragMoveHandler, this );
				this.mouseupTicket   = new EventTicketClass( this.element, 'mouseup',   this.dragEndHandler,  this );
				this.mouseoutTicket  = new EventTicketClass( this.element, 'mouseout',  this.dragEndHandler,  this );					
			
				return false;
			},
			dragMoveHandler : function( e ){
				var offsetX = e.clientX - this.startX,
					offsetY = e.clientY - this.startY;
				if( this.dragging === false ){
					if( offsetX * offsetX + offsetY * offsetY < DRAG_OFFSET ) return;
					console.log( 'Drag start' );
					// dragStart
					this.dragging = true;
					return this.fire( DragEventTicketClass.createEvent( e, offsetX, offsetY, 0 ) );
				};
				return this.fire( DragEventTicketClass.createEvent( e, offsetX, offsetY, 1 ) );
			},
			dragEndHandler  : function( e ){
				if( this.dragging === true ){
					console.log( 'Drag End ' + e.type );
					this.removeEvents();
					// dragEnd
					return this.fire( DragEventTicketClass.createEvent( e, e.clientX - this.startX, e.clientY - this.startY, 2 ) );
				};
				this.removeEvents();
				return false;
			},
			removeEvents : function(){
				this.dragging = false;
				if( this.mousemoveTicket ){
					this.mousemoveTicket.destroy();
					delete this.mousemoveTicket;
				};
				if( this.mouseupTicket ){
					this.mouseupTicket.destroy();
					delete this.mouseupTicket;
				};
				if( this.mouseoutTicke ){
					this.mouseoutTicket.destroy();
					delete this.mouseoutTicket;
				};				
			},
			destroy : function( _element, _eventType, _handler ){
				if( this.match( _element, _eventType, _handler ) === false ) return false;
				
				this.removeEvents();
				this.mousedownTicket.destroy();

				delete this.element;
				delete this.handler;
				delete this.thisObject;
				delete this.mousedownTicket;	
				return true;
			}
		};
		if( document.createEvent ){
			DragEventTicketClass.createEvent = function( e, offsetX, offsetY, dragPhase ){
				var _e = document.createEvent( 'MouseEvents' );
				_e.initMouseEvent(
					DragEventTicketClass.prototype.eventType , false, true, e.view, 
					e.detail, e.screenX, e.screenY, e.clientX, e.clientY, 
					e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 
					e.button, e.relatedTarget
				);
				_e.dragPhase   = dragPhase;
				_e.dragOffsetX = offsetX;
				_e.dragOffsetY = offsetY;
				return _e;
			};					
		} else
		if( document.attachEvent ){
			DragEventTicketClass.createEvent = function( e, offsetX, offsetY, dragPhase ){
				e.type        = DragEventTicketClass.prototype.eventType;
				e.dragPhase   = dragPhase;
				e.dragOffsetX = offsetX;
				e.dragOffsetY = offsetY;
				return e;
			};
		} else {
			
		};

		return {
			add: function( _apiuser, _element, _eventType, _handler, opt_thisObject ){
				if( isApiUser( _apiuser ) === true &&
					( Type.isHTMLElement( _element ) === true || _element === window || _element === document ) &&
					Type.isString( _eventType ) === true &&
					Type.isFunction( _handler ) === true
				){
					var _uid    = _apiuser.getUID(),
						_events = EVENT_LIST_MAP[ _uid ];
					if( Type.isArray( _events ) === false ){
						_events = EVENT_LIST_MAP[ _uid ] = [];
					} else {
						// 2重登録の禁止
						for( var i=0, l=_events.length; i<l; ++i ){
							if( _events[ i ].match( _element, _eventType, _handler ) === true ) return;
						};
					};
					switch( _eventType ){
						case 'click':
							_events.push( new ClickEventTicketClass( _element, _handler, opt_thisObject ) );
							break;
						case 'mousewheel':
							_events.push( new WheelEventTicketClass( _element, _handler, opt_thisObject ) );
							break;
						case 'mousedrag':
							_events.push( new DragEventTicketClass( _element, _handler, opt_thisObject ) );
							break;
						default:
							_events.push( new EventTicketClass( _element, _eventType, _handler, opt_thisObject ) );
					};
				};
			},
			remove: function( apiuser, element, eventType, handler ){
				if( isApiUser( apiuser ) === true ){
					var uid  = apiuser.getUID(),
						list = EVENT_LIST_MAP[ uid ],
						i    = 0;
					if( Type.isArray( list ) === false ) return;
					for( ;i < list.length; ){
						if( list[ i ].destroy( element, eventType, handler ) === true ){
							list.splice( i, 1 );
						} else {
							++i;
						};
					};
					if( list.length === 0 ){
						EVENT_LIST_MAP[ uid ] = null;
					};
				};
			},
			onCurrentApplicationChange: function(){
				
			},
			onApplicationShutdown: function(){
				
			},
			onSystemShutdown: function(){
				
			}
		}
	})();

/* ----------------------------------------
 * KEY
 * 
 *  - EDITABLE_TEXT_CONTROL
 * 
 *    .SHIFT_DOWN_EVENT:	'shiftDown',
 *    .SHIFT_UP_EVENT:		'shiftUp',
 *    .CTRL_DOWN_EVENT:		'ctrlDown',
 *    .CTRL_UP_EVENT:		'ctrlUp',
 *    .SPACE_DOWN_EVENT:	'spaceDown',
 *    .SPACE_UP_EVENT:		'spaceUp',
 *    .init:				function,
 *    .addKeyDownEvent:		function,
 *    .keyEventDispatcher:	function,
 * 
 * ショートカットキーの監視とテキスト入力(input, textarea)、チェックボックスを管理する。
 * キー入力はdocumentで受けて、テキスト編集中(input, textarea)はそちらにキーイベント流す。
 * 
 */
var KeyEvent = ( function(){
	var EVENT_LIST_MAP = [],
		LOCK_UP        = [],
		LOCK_DOWN      = [],
		application    = null,
		currentList    = null;

	window.focus();
	
	var focusTicket    = null,
		keydownTicket  = null,
		keyupTicket    = null,
		keyPress       = null,
		keypressTicket = null;
	
	function unlock( lock, key ){
		lock.splice( Util.getIndex( lock, key ), 1 );
	};
	
	function onKeyChange( e ){
		var cancel         = false,
			type           = e.type,
			key            = e.keyCode, // || e.which,
			shift          = Type.isBoolean( e.shiftKey ) === true ? e.shiftKey : ( e.modifiers & Event.SHIFT_MASK ),
			ctrl           = Type.isBoolean( e.ctrlKey  ) === true ? e.ctrlKey  : ( e.modifiers & Event.CONTROL_MASK ),
			lock           = type === 'keyup' ? LOCK_UP : LOCK_DOWN,
			i, t;
			
		// block chattering
		if( Util.getIndex( lock, key ) !== -1 ) return;
		lock.push( key );
		AsyncCall.add( SUPER_USER_KEY, unlock, [ lock, key ] );
		
		if( key === 16 || shift === true ){
			KeyEvent.shiftEnabled = type !== 'keyup';
		};
		if( key === 17 || ctrl === true ){
			KeyEvent.ctrlEnabled  = type !== 'keyup';
		};
		for( i = currentList.length; i; ){
			t = currentList[ --i ];
			if( Type.isFunction( t[ type ] ) === true && t.keyCode === key && ( t.shift === undefined || t.shift === shift ) && ( t.ctrl === undefined || t.ctrl === ctrl )){
				if( t[ type ].call( t.apiuser, e ) === false ){
					cancel = true;
					break;
				};
			};
		};
		if( cancel === true || key === 18 || key === 9 || key === 27 || e.altKey === true ){ // 13.enter 18.esc 9.tab 27.esc   || ( key === 13 && overlayEnabled === false)
			return false;
		};
	};
	
	if( UA.isIE === true && UA.ieRenderingVersion < 9 ){
		keyPress = function( e ){
			var key = e.keyCode;
			if( key === 13 || key === 27 ){
				e.type = 'keydown';
				return onKeyChange( e );
			};
		};
	};
	
	var KeyEventTicketClass = function( _apiuser, _type, _onKeydown, _onKeyup, _keyCode, _shift, _ctrl ){
		this.apiuser = _apiuser;
		this.type    = _type;
		this.keydown = _onKeydown;
		this.keyup   = _onKeyup;		
		this.keyCode = _keyCode;
		this.shift   = _shift;
		this.ctrl    = _ctrl;
		_apiuser = _onKeydown = _onKeyup = null;
	}
	KeyEventTicketClass.prototype = {
		match: function( _apiuser, _type, _handler, _keyCode, _shift, _ctrl ){
			if( _apiuser  && _apiuser  !== this.apiuser ) return false;
			if( _type     && _type     !== this.type )    return false;
			if( _handler ){
				if( this.type === 'keydown' ){
					if( _handler !== this.keydown ) return false;
				} else {
					if( _handler !== this.keyup )   return false;
				};
			};
			if( _keyCode  && _keyCode  !== this.keyCode ) return false;
			if( _shift    && _shift    !== this.shift )   return false;
			if( _ctrl     && _ctrl     !== this.ctrl )    return false;
			return true;
		},
		destroy: function( _apiuser, _type, _handler, _keyCode, _shift, _ctrl ){
			if( this.match( _apiuser, _type, _handler, _keyCode, _shift, _ctrl ) === false ) return false;
			
			delete this.apiuser;
			delete this.keydown;
			delete this.keyup;
			
			return true;
		}
	};
	
	function registerEvent( _apiuser, _type, _onKeydown, _onKeyup, _keyCode, _shift, _ctrl ){
		var _uid  = _apiuser.getUID(),
			_list = EVENT_LIST_MAP[ _uid ];
		if( Type.isArray( _list ) === false ){
			_list = EVENT_LIST_MAP[ _uid ] = [];
		}
		for( var i=0, l=_list.length; i<l; ++i ){
			if( _list[ i ].match( _apiuser, _type, _onKeydown || _onKeyup, _keyCode, _shift, _ctrl ) === true ) return;
		}
		_list.push( new KeyEventTicketClass( _apiuser, _type, _onKeydown, _onKeyup, _keyCode, _shift, _ctrl ));
		
		if( _apiuser === application ) KeyEvent.updateCurrentListener( _apiuser );
	};
	
	return {
		add: function( _apiuser, _type, _handler, _keyCode, _shift, _ctrl ){
			if( _type === 'keydown' ){
				registerEvent( _apiuser, _type, _handler, null, _keyCode, _shift, _ctrl );
			} else
			if( _type === 'keyup' ){
				registerEvent( _apiuser, _type, null, _handler, _keyCode, _shift, _ctrl );
			} else
			if( _type === 'keychange' ){
				registerEvent( _apiuser, _type, _handler, _handler, _keyCode, _shift, _ctrl );
			} else
			if( _type === 'cursol' ){
				
			};
		},
		remove: function( _apiuser, _type, _handler, _keyCode, _shift, _ctrl ){
			var _list = EVENT_LIST_MAP[ _apiuser.getUID() ],
				i = 0;
			if( Type.isArray( _list ) === true ){
				while( i < _list.length ){
					if( _list[ i ].destroy( _apiuser, _type, _handler, _keyCode, _shift, _ctrl ) === true ){
						_list.splice( i, 1 );
					} else {
						++i;
					}
				}
			}
			if( _apiuser === application ) KeyEvent.updateCurrentListener( _apiuser );
		},
		shiftEnabled: false,
		ctrlEnabled:  false,
		/*
		 * currentListener
		 *  currrentApplication ( overlay Application ) or
		 *  superuser ( UI )
		 */
		updateCurrentListener: function( _apiuser ){
			application = _apiuser;
			var _uid    = _apiuser.getUID();
			currentList = EVENT_LIST_MAP[ _uid ] || ( EVENT_LIST_MAP[ _uid ] = [] );
			
			var _ticket,
				_down = false,
				_up   = false;
			for( var i=currentList.length; _ticket = currentList[ --i ]; ){
				if( _down === false ) _down = !!_ticket.keydown;
				if( _up   === false ) _up   = !!_ticket.keyup;
				if( _down && _up ) break;
			}
			if( _down === true ){
				keydownTicket = new EventTicketClass( document, 'keydown', onKeyChange );
				keypressTicket = keyPress !== null ? new EventTicketClass( document, 'keypress', keyPress ) : null;
			} else {
				keydownTicket && keydownTicket.destroy();
				keypressTicket && keypressTicket.destroy();
				keydownTicket = keypressTicket = null;
			}
			if( _up === true ){
				keyupTicket   = new EventTicketClass( document, 'keyup', onKeyChange );
			} else {
				keyupTicket && keyupTicket.destroy();
				keyupTicket = null;
			}
			
			if( _down === true || _up === true ){
				focusTicket   = new EventTicketClass( document, 'mouseenter', window.focus );
			} else {
				focusTicket && focusTicket.destroy();
				focusTicket = null;
			}
		},
		onApplicationShutdown: function( _apiuser ){
			KeyEvent.remove( _apiuser );
		},
		onSystemShutdown: function(){
			
		}
	}
})();

/**
 * 
 * http://thudjs.tumblr.com/post/637855087/stylesheet-onload-or-lack-thereof
 */

var StyleSheet = ( function(){
	var head = document.getElementsByTagName( 'head' )[ 0 ];
	
	var TICKET_LIST = [];
	var STATE_LIST  = 'loaded,complete,uninitialized'.split( ',' );
	
	var cssRules, sheet;
	
	var FetchCssTicketClass = function( _apiuser, _url, _elm, _onload, _onerror, opt_thisObject ){
		this.apiusers = [ _apiuser ];
		this.url      = _url;
		this.elm      = _elm;
		this.onload   = [ _onload ];
		this.onerror  = [ _onerror ];
		this.thisObj  = [ opt_thisObject ];
		this.time     = 0;
	};
	FetchCssTicketClass.prototype = {
		match: function( _apiuser, _url ){
			if( _apiuser  && Util.getIndex( this.apiusers, _apiuser ) === -1 ) return false;
			if( _url      && _url      !== this.url     ) return false;
			return true;
		},
		destroy: function( _apiuser, _url ){
			if( this.match( _apiuser, _url ) === false ) return false;
			
			var i = Util.getIndex( this.apiusers, _apiuser );
			
			this.apiusers.splice( i, 1 );
			this.onload.splice( i, 1 );
			this.onerror.splice( i, 1 );
			this.thisObj.splice( i, 1 );
			
			if( this.apiusers.length !== 0 ) return false;
			
			head.removeChild( this.elm );
			this.elm.onreadystatechange = new Function();
			this.elm.onload = null;
			
			delete this.apiusers;
			delete this.url;
			delete this.elm;
			delete this.onload;
			delete this.onerror;
			delete this.thisObj;
			delete this.time;
			
			return true;
		},
		loaded: function(){
        	for( var i = this.onload.length, f; i; ){
        		f = this.onload[ --i ];
        		Type.isFunction( f ) === true && AsyncCall.add( this.apiusers[ i ], f, this.url, this.thisObj[ i ] || this.apiusers[ i ] );
        		this.onload[ i ] = this.onerror[ i ] = null;
        	};
		},
		error: function(){
        	for( var i = this.onerror.length, f; i; ){
        		f = this.onerror[ --i ];
        		Type.isFunction( f ) === true && AsyncCall.add( this.apiusers[ i ], f, this.url, this.thisObj[ i ] || this.apiusers[ i ] );
        		this.onload[ i ] = this.onerror[ i ] = null;
        	};
		},
		check: function(){
			var el = this.elm;
			try {
				return el[ sheet ] && el[ sheet ][ cssRules ].length > 0;
			} catch( e ){
				return false;
			};
		},
		done: false
	};
	
	function getTicket( elm ){
		for( var i = TICKET_LIST.length, t; i; ){
			t = TICKET_LIST[ --i ];
			if( t.elm === elm ) return t;
		};
	};
	
	function detect(){
		var t = getTicket( this ), rs = this.readyState, c;
		if( t && t.done === false && ( !rs || Util.getIndex( STATE_LIST, rs ) !== -1 ) ){
			t.done = true;
        	t.loaded();
        	this.onreadystatechange = new Function();
        	this.onload = null;
		};
	};
	
	function checkTimer(){
		var l = TICKET_LIST.length,
			n = 0, t;
		for( var i = 0; i < l; ++i ){
			t = TICKET_LIST[ i ];
			++t.time;
			if( t.check() === true ){
				t.loaded();
				++n;
			} else
			if( t.time > 99 ){
				t.error();
			} else {
				
			};
		};
		l === n && SystemTimer.remove( SUPER_USER_KEY, checkTimer );
	};
	
	return {
		load: function( _apiuser, _url, opt_onload, opt_onerror, opt_thisObject ){
			_url = Util.getAbsolutePath( _url );
			var t;
			for( var i=TICKET_LIST.length; i; ){
				t = TICKET_LIST[ --i ];
				if( t.match( null, _url ) === true ){
					if( t.match( _apiuser, _url ) === false ){
						t.apiusers.push( _apiuser );
						t.onload.push( opt_onload );
						t.onerror.push( opt_onerror );
						t.thisObj.push( opt_thisObject );
					};
					SystemTimer.add( SUPER_USER_KEY, checkTimer, 333 );
					return;
				};
			};
			var elm = document.createElement( 'link' );
			head.appendChild( elm );
			elm.rel  = 'stylesheet';
			elm.type = 'text\/css';
			elm.onreadystatechange = elm.onload = detect;
			elm.href = _url;
			
			if( !sheet ){ // only assign these once
				cssRules = 'cssRules';
				sheet    = 'sheet';
				if ( !( sheet in elm ) ) { // MSIE uses non-standard property names
					cssRules = 'rules';
					sheet    = 'styleSheet';
				};
			};
			
			TICKET_LIST.push( new FetchCssTicketClass( _apiuser, _url, elm, opt_onload, opt_onerror, opt_thisObject ) );
			
			SystemTimer.add( SUPER_USER_KEY, checkTimer, 333 );
		},
		unload: function( _apiuser, _url ){
			_url = _url ? Util.getAbsolutePath( _url ) : null;
            var t;
			for( var i = 0; i < TICKET_LIST.length; ){
				t = TICKET_LIST[ i ];
				if( t.destroy( _apiuser, _url ) === true ){
					TICKET_LIST.splice( i, 1 );
				} else {
					++i;
				}
			};
			if( TICKET_LIST.length === 0 ){
				SystemTimer.remove( SUPER_USER_KEY, checkTimer );
			}
		}
	}
})();

/*
 * AssetLoader
 * fetchCSS
 * fetchJson
 * fetchHtml
 * fetchImage
 * fetchLocalFile
 * fetchLocalStorage
 */

var Image = ( function(){
	var TASK_LIST = [];
	/* 
	 * FetchClass original is
	 * 
	 * LICENSE: MIT?
	 *  URL: http://d.hatena.ne.jp/uupaa/20080413/1208067631
	 *  AUTHOR: uupaa.js@gmail.com
	 * 
	 */
	function detect(){
		for( var i=0, t; i < TASK_LIST.length; ){
			t = TASK_LIST[ i ];
			if( t.complete() === true ){
				TASK_LIST.splice( i, 1 );
			} else {
				++i;
			};
		};
		TASK_LIST.length === 0 && SystemTimer.remove( SUPER_USER_KEY, detect );
	};
	function getTask( img ){
		for( var i = TASK_LIST.length; i; ){
			if( TASK_LIST[ --i ].img === img ) return TASK_LIST[ i ];
		};
	};
	function onError(){
		var task = getTask( this );
		if( task.finish === true ) return;
		task.finish = true;
		AsyncCall.add( task.apiuser, task.asyncCallback, null, task );
	};
	function onLoad(){
		// if( finish === true ) return; // これがあると firefox3.6 で駄目、、、
		// if( timer ) return; // これがあると safari3.2 で駄目、、、
		var task = getTask( this );
		task.finish = true;
		TASK_LIST.splice( Util.getIndex( TASK_LIST, task ), 1 );
		if( window.opera && !task.img.complete ){
			AsyncCall.add( task.apiuser, task.asyncCallback, null, task );
			return;
		};
		task.size = Util.getImageSize( this );
		AsyncCall.add( task.apiuser, task.asyncCallback, null, task );
	};


	var FetchClass = function( apiuser, abspath, onLoadCallback, onErrorCallback, timeout ){
		this.apiuser         = apiuser;
		this.abspath         = abspath;
		this.onLoadCallback  = onLoadCallback;
		this.onErrorCallback = onErrorCallback;
		this.timeout         = timeout;
		this.tick            = 0;
	};
	FetchClass.prototype = {
		img: null,
		size: null,
		tick: 0,
		finish: false,
		load: function(){
			var img     = this.img = document.createElement( 'img' ); //var img = new Image(); ではieでimgのsizeが取れない、、、removeChildも失敗し、imgSizeGetterにimgが残る
			img.onabort = img.onerror = onError;
			img.onload  = onLoad;
			img.src     = this.abspath;
		},
		complete: function(){
			if( this.finish === true ) return true;
			if( this.img.complete ){
				this.finish = true;
				if( this.img.width ) return true;
				AsyncCall.add( this.apiuser, this.asyncCallback, null, this );
				return true;
			};
			if( ( this.tick += 250 ) > this.timeout ){
				this.finish = true;
				AsyncCall.add( this.apiuser, this.asyncCallback, null, this );
				return true;
			};
		},
		asyncCallback: function(){
			this.size ? this.onLoadCallback( this.abspath, this.size.width, this.size.height ) : this.onErrorCallback( this.abspath );
			this.destroy();
		},
		destroy: function(){
			this.finish  = true;
			this.img.src = this.img.onload = this.img.onabort = this.img.onerror = '';
			delete this.img;
			delete this.size;
			delete this.onLoadCallback;
			delete this.onErrorCallback;
		},
		stop: function(){
			timer !== null && window.clearTimeout( timer );
			destroy();
		}
	};
	
	return {
		load: function( URLorELM, onLoad, onError, opt_timeout ){
			var src, fetch;
			if( Type.isString( URLorELM ) === true ){
				src = URLorELM;
			} else
			if( Type.isHTMLElement( URLorELM ) === true && URLorELM.tagName.toLowerCase() === 'img' ){
				src = URLorELM.src;
			} else {
				return;
			};
			
			fetch = new FetchClass(
				Util.getAbsolutePath( src ),
				onLoad, onError,
				Type.isFinite( opt_timeout ) === true ? opt_timeout : undefined
			);
			TASK_LIST.push( fetch );
			
			SystemTimer.add( SUPER_USER_KEY, detect, 250 );
		},
		unload: function(  ){
			
		}
	};
})();


/* ----------------------------------------
 * 
 */

var Overlay = ( function(){
	var elmContainer, elmShadow, elmCloseButton,
		bootParams,
		application    = null,
		visible        = false,
		bodyOverflow   = '',
		windowW, windowH;

	function onCloseClick( e ){
		Overlay.hide();
		return false;
	};
	function asyncInit( /* arguments */ ){
		application.init();
		//application.rootElement.style.display = 'none';
		
		elmContainer.style.cssText = "top:" + body.scrollTop + 'px;display:none;';
		$( elmContainer ).stop().fadeIn( onFadeInComplete );		
	};
	function asyncOpen( /* arguments */ ){

		
		
	};
	function onFadeInComplete(){
		KeyEvent.add( application, Const.KEY.EVENT.KEY_DOWN, Overlay.hide, 27 ); // 27.esc
		MouseEvent.add( application, elmCloseButton, 'click', onCloseClick );
		
		var _arg = bootParams; //Util.copyArray( arguments );
		_arg.unshift( windowW, windowH );
		application.open.apply( application, _arg );		
	};
	function onFadeOutComplete(){	
		Util.removeAllChildren( elmContainer );
		body.removeChild( elmContainer );
		elmContainer = elmShadow = elmCloseButton = null;
	};
	return {
		show: function( _application, _bootParams ){
			if( visible === true && application === _application ) return;
			if( Application.isApplicationInstance( _application ) === false ) return;
			
			elmContainer = document.createElement( 'div' );
			body.appendChild( elmContainer );
			
			elmContainer.id = 'overlay-container';
			
			bodyOverflow        = body.style.overflow;
			body.style.overflow = 'hidden';
			
			elmShadow = document.createElement( 'div' );
			elmContainer.appendChild( elmShadow );
			elmShadow.id = 'overlay-shadow';
			
			elmCloseButton  = document.createElement( 'div' );
			elmContainer.appendChild( elmCloseButton );
			elmCloseButton.id = 'overlay-close-button';
			elmCloseButton.appendChild( document.createTextNode( 'x' ) );
			
			elmContainer.style.display = 'none'; // hide for fadeIn
			
			visible     = true;
			application = _application;
			
			//asyncInit();
			elmContainer.insertBefore( application.rootElement, elmCloseButton );
			_application.addAsyncCall( asyncInit );
			// _application.addAsyncCall( asyncOpen,  );
			
			bootParams = _bootParams;			
		},
		hide: function(){
			if( visible === false ) return;
			if( application.close() === false ) return false;
			
			body.style.overflow = bodyOverflow;
			
			$( elmContainer ).stop().css( {
				filter:		'',
				opacity:	''
			}).fadeOut( onFadeOutComplete );
			visible = false;
			
			application = null;
		},
		onWindowResize: function( _windowW, _windowH ){
			windowW = _windowW;
			windowH = _windowH;			
			
			if( application === null ) return;
			
			elmContainer.style.height = _windowH + 'px';
			elmContainer.style.top    = body.scrollTop + 'px';

			elmShadow.style.height = _windowH + 'px';

			AsyncCall.add( application, application.resize, [ _windowW, _windowH ] );
		}
	}
})();

/* ----------------------------------------
 * UI
 * 
 * keyEventRellay
 *  form -> overlay -> view
 * 
 */

var UI = ( function(){
	var UI_LIST     = [],
		currentUser = null,
		currentList = null,
		currentUi   = null,
		currentItem = null,
		windowW     = 0,
		windowH     = 0;

	var CLASSNAME_COMBOBOX_OPTION = 'combobox-option',
		CLASSNAME_COMBOBOX_OPTION_CURRENT = CLASSNAME_COMBOBOX_OPTION + ' combobox-option-current',
		ELM_COMBOBOX = ( function(){
			var ret       = document.createElement( 'a' ),
				elmToggle = document.createElement( 'span' ),
				elmValue  = document.createElement( 'span' );
			ret.href = '#';
			ret.appendChild( elmToggle );
			ret.appendChild( elmValue );
			elmToggle.className = 'combobox-toggle';
			elmValue.className  = 'combobox-value';
			
			elmToggle.appendChild( document.createTextNode( '▼' ));
			elmValue.appendChild( document.createTextNode( 'null' ));
			return ret;
		})();
	
	var UIItemPrivateData = function(){};
	UIItemPrivateData.prototype = {
		groupData   : null,
		item        : null,
		elm         : null,
		node        : null,
		focus       : false,
		visible     : true,
		enabled     : true,
		value       : null,
		onUpdate    : null,
		validator   : null,
		elmValue    : null,
		elmBox      : null,
		elmA        : null,
		elmToggle   : null,
		elmValue    : null,
		selectIndex : 0,
		optionList  : null,
		init    : function( groupData, item, elm, value, onUpdate, validator, focus, visible, enabled ){
			this.groupData = groupData;
			this.item      = item;
			this.elm       = elm;
			this.value     = value;
			this.onUpdate  = onUpdate;
			this.validator = validator;			
			this.focus     = !!focus;
			this.visible   = !!visible;
			this.enabled   = !!enabled;
			UIItemPrivateData.list.push( this );
		},
		destroy : function(){
			var list = UIItemPrivateData.list;
			list.splice( Util.getIndex( list, this ), 1 );
			
			list = this.groupData.itemList;
			var i = Util.getIndex( list, this.item );
			i !== -1 && list.splice( i, 1 );
			
			this.node && this.node.remove();
		}
	};
	UIItemPrivateData.list = [];
	UIItemPrivateData.get = function( item ){
		var list = UIItemPrivateData.list;
		for( i = list.length; i; ){
			if( list[ --i ].item === item ) return list[ i ];
		};
		return null;
	};
	
/* --------------------------------
 * TextInputManager
 */
	var TextInputManager = ( function(){
		var elmInput = ( function(){
			var ret  = document.createElement( 'input' );
			ret.type = 'text';
			ret.id   = 'ui-textinput';
			return ret;
		})();
		var currentData;
		
		function updateWrapperPosition(){
			var p = Position.cumulativeOffset( currentData.elmValue ),
				w = currentData.elmValue.offsetWidth - 2,
				_w;		
			elmInput.style.cssText = [
				'left:',   p[ 0 ], 'px;',
				'top:',    p[ 1 ], 'px;',//,
				'width:',  w, 'px;'//,
				//'height:', data.elmValue.offsetHeight, 'px;',
				//'position:absolute;'
			].join( '' );
			
			//_w = elmInput.offsetWidth;
			//if( w !== _w ) elmInput.style.width = ( w - ( _w - w ) ) + 'px;';	
		};
		
		return {
			show: function( data ){
				// this.groupData.node.addEventListener( 'mouseout' );
				currentData = data;

				body.appendChild( elmInput );
				elmInput.value = data.value;
				updateWrapperPosition();
				
				elmInput.focus();
				elmInput.select();
				
				SystemTimer.add( SUPER_USER_KEY, updateWrapperPosition, 500 );
			},
			hide : function( data ){
				if( currentData !== data ) return;
				currentData = null;
				body.removeChild( elmInput );
				var ret = elmInput.value;
				elmInput.value = '';
				SystemTimer.remove( SUPER_USER_KEY, updateWrapperPosition );
				return ret;
			},
			update : function( data ){
				elmInput.value = data.value;
			},
			onWindowResize: function( _w, _h ){
				AsyncCall.add( currentUser, updateWrapperPosition );
			}
		};
	})();
	
	var TextInputClass = function( groupData, elmWrapper, elmValue, onUpdate, validater ){
		var data = new UIItemPrivateData();
		data.init( groupData, this, elmWrapper, elmValue.innerHTML, onUpdate, validater, false, true, true );
		Util.addClass( elmValue, 'editable-text' );
		data.elmValue = elmValue;
		this.value( data.value );
		data.node = groupData.node.createNode( elmWrapper, false, true, 'ui-inpittext-hover', 'pointer' );
		data.node.addEventListener( 'click', this.focus, this );
		//MouseEvent.add( groupData.apiuser, elmWrapper, 'click', instance.focus );
	};
	TextInputClass.prototype = {
		value : function( value ){
			var data = UIItemPrivateData.get( this );
			if( Type.isString( value ) === true || Type.isNumber( value ) === true ){
				data.elmValue.innerHTML = data.value = '' + value;
				data.focus === true && TextInputManager.update( data );
			};
			data.focus === true && this.blur();
			return data.value;
		},
		focus : function( e ){
			var data = UIItemPrivateData.get( this );
			data.focus = true;
			start( data );
			TextInputManager.show( data );
			return false;
		},
		blur : function( keep ){
			var data = UIItemPrivateData.get( this ),
				newValue;
			if( data.focus === false ) return;
			newValue = TextInputManager.hide( data );
			newValue = keep !== 27 ? ( data.validater ? '' + data.validater( newValue ) : newValue ) : data.value; // 27:ESC

			data.elmValue.innerHTML = newValue;
			
			data.onUpdate && newValue !== data.value && AsyncCall.add( data.groupData.apiuser, data.onUpdate, [ newValue, data.value ], this );
			
			data.value = newValue;
			data.focus = false;
			finish( data );
		},
		enabled : function( v ){
			var data = UIItemPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.enabled !== v ){
				Util.toggleClass( data.elm, 'ui-textinput-disabled', !v );
				if( data.focus === true && v === false ) this.blur();
				data.enabled = v;
				data.node.disabled( !( data.visible && v ) );
			};
			return data.enabled;
		},
		visible : function( v ){
			var data = UIItemPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.visible !== v ){
				data.elm.style.display = v ? '' : 'none';
				if( data.focus === true && v === false ) this.blur();
				data.visible = v;
				data.node.disabled( !( data.enabled && v ) );
			};
			return data.visible;
		},
		destroy : function(){
			var data = UIItemPrivateData.get( this );
			data.focus === true && TextInputManager.hide( data );
			data.destroy();
		}
	};

/* --------------------------------
 * TextInputManager
 */
	var FileInputManager = ( function(){
		var currentData,
			elmForm,
			elmFileInput,
			elmWrap,
			evt;
		
		function updateWrapperPosition(){
			var p = Position.cumulativeOffset( currentData.elmValue ),
				w = currentData.elmValue.offsetWidth,
				_w;		
			elmWrap.style.cssText = [
				'left:',   p[ 0 ], 'px;',
				'top:',    p[ 1 ], 'px;',//,
				'width:',  w, 'px;'//,
				//'height:', data.elmValue.offsetHeight, 'px;',
				//'position:absolute;'
			].join( '' );
			
			_w = elmWrap.offsetWidth;
			if( w !== _w ) elmWrap.style.width = ( w - ( _w - w ) ) + 'px';	
		};
		
		function change( e ){
			var data = currentData,
				file = data.elmFileInputReal.value;
			file = file.split( '\\' );
			file = file[ file.length - 1 ];
			if( data.value !== file ){
				data.onUpdate && AsyncCall.add( data.groupData.apiuser, data.onUpdate, [ file, data.value ], this );
				data.elmValue.innerHTML = data.value = file;
			};
			currentData.item.blur();
		};
		function asyncMouseout(){
			currentData && currentData.item.blur();
		};
		function onClick(){
			MouseEvent.remove( currentUser, elmFileInput, 'mouseout', asyncMouseout );
			MouseEvent.remove( currentUser, elmFileInput, 'click', onClick );	
		};
		return {
			show : function( data ){
				currentData = data;
				
				elmFileInput = data.elmFileInputReal;
				elmWrap      = elmFileInput.parentNode;
				// 
				
				updateWrapperPosition();
				elmFileInput.focus();
				//data.node.addEventListener( 'change', change, data );
				evt = new EventTicketClass( elmFileInput, 'change', change );
				MouseEvent.add( currentUser, elmFileInput, 'mouseout', asyncMouseout );
				MouseEvent.add( currentUser, elmFileInput, 'click', onClick );
				// currentData.elmFileInputReal.onchange = change;
				SystemTimer.add( SUPER_USER_KEY, updateWrapperPosition, 500 );
			},
			hide : function( data ){
				if( currentData !== data ) return;
				// data.node.removeEventListener( 'change', change );
				evt.destroy();
				// MouseEvent.remove( currentUser, elmFileInput, 'mouseout', asyncMouseout );
				onClick();
				//currentData.elmFileInputReal.onchange = null;
				elmWrap.style.display = 'none';
				currentData = elmFileInput = null;
				SystemTimer.remove( SUPER_USER_KEY, updateWrapperPosition );
			},
			onWindowResize: function( _w, _h ){
				AsyncCall.add( currentUser, updateWrapperPosition );
			}
		};
	})();
	
	var FileInputClass = function( groupData, elmWrapper, onUpdate, validater, elmFileInputReal, elmValue ){
		var data = new UIItemPrivateData();
		data.init( groupData, this, elmWrapper, null, onUpdate, null, false, true, true );
		data.node = groupData.node.createNode( elmWrapper, false, true, 'ui-fileinput-hover', 'pointer' );
		data.elmValue = elmValue;
		data.elmFileInputReal = elmFileInputReal;
		data.node.addEventListener( 'mouseover', this.focus, this );
	};
	FileInputClass.prototype = {
		value : function(){
			return data.value;
		},
		focus : function(){
			var data = UIItemPrivateData.get( this );
			data.focus = true;
			Util.addClass( data.elm, 'fileinput-has-focus' );
			start( data );
			FileInputManager.show( data );
		},
		blur : function( keyCode ){
			var data = UIItemPrivateData.get( this );
			Util.removeClass( data.elm, 'fileinput-has-focus' );
			data.focus = false;
			FileInputManager.hide( data );
			finish( data );
		},
		enabled : function( v ){
			var data = UIItemPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.enabled !== v ){
				if( data.focus === true && v === false ) this.blur();
				Util.toggleClass( data.elm, 'fileinput-disabled', !v );
				data.enabled = v;
				data.node.disabled( !( data.visible && v ) );
			};
			return data.enabled;
		},
		visible : function( v ){
			var data = UIItemPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.visible !== v ){
				if( data.focus === true && v === false ) this.blur();
				data.elm.style.display = v ? '' : 'none';
				data.visible = v;
				data.node.disabled( !( data.enabled && v ) );
			};
			return data.visible;
		},
		destroy : function(){
			var data = UIItemPrivateData.get( this );
			data.focus === true && FileInputManager.hide( data );
			data.destroy();
		}
	};
	
	var ButtonClass = function( groupData, elmWrapper, onUpdate ){
		var data = new UIItemPrivateData();
		data.init( groupData, this, elmWrapper, null, onUpdate, null, false, true, true );
		data.node = groupData.node.createNode( elmWrapper, false, true, 'ui-button-hover', 'pointer' );
		data.node.addEventListener( 'click', onUpdate );
		//MouseEvent.add( groupData.apiuser, elmWrapper, 'click', onUpdate );
	};
	ButtonClass.prototype = {
		focus : function(){
			var data = UIItemPrivateData.get( this );
			data.focus = true;
			Util.addClass( data.elm, 'button-has-focus' );
			start( data );
		},
		blur : function( keyCode ){
			var data = UIItemPrivateData.get( this );
			keyCode === 13 && data.onUpdate && data.onUpdate();
			Util.removeClass( data.elm, 'button-has-focus' );
			data.focus = false;
			finish( data );
		},
		enabled : function( v ){
			var data = UIItemPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.enabled !== v ){
				Util.toggleClass( data.elm, 'button-disabled', !v );
				data.enabled = v;
				data.node.disabled( !( data.visible && v ) );
			};
			return data.enabled;
		},
		visible : function( v ){
			var data = UIItemPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.visible !== v ){
				data.elm.style.display = v ? '' : 'none';
				data.visible = v;
				data.node.disabled( !( data.enabled && v ) );
			};
			return data.visible;
		},
		destroy : function(){
			var data = UIItemPrivateData.get( this );
			// MouseEvent.remove( data.groupData.apiuser, data.elm );
			data.destroy();
		}
	};

	var ComboBoxClass = function( groupData, elmWrapper, onUpdate ){
		var elmA   = ELM_COMBOBOX.cloneNode( true ),
			data   = new UIItemPrivateData();
		data.init( groupData, this, elmWrapper, null, onUpdate, null, false, true, true );
		
		data.elmBox      = Util.getElementsByClassName( elmWrapper, 'combobox' )[ 0 ];
		data.elmBox.appendChild( elmA );
		data.elmA        = elmA;
		data.elmToggle   = Util.getElementsByClassName( elmA, 'combobox-toggle' )[ 0 ];
		data.elmValue    = Util.getElementsByClassName( elmA, 'combobox-value' )[ 0 ].firstChild;
		data.selectIndex = 0;
		data.optionList  = [];

		data.node = groupData.node.createNode( elmWrapper, false, true, 'ui-combobox-hover', 'pointer' );
		data.node.addEventListener( 'click', this.focus, this );
	};
	ComboBoxClass.prototype = {
		focus : function( e ){
			var data = UIItemPrivateData.get( this );
			data.node.removeEventListener( 'click', this.focus );
			data.focus = true;
			data.elmA.className = 'combobox-has-focus';
			start( data );
			OptionControl.show( data );
			return false;
		},
		blur : function( keyCode ){
			var data = UIItemPrivateData.get( this );
			OptionControl.hide( this );
			data.focus = false;
			data.elmA.className = '';
			finish( data );
			data.node.addEventListener( 'click', this.focus, this );
		},
		enabled : function( v ){
			var data = UIItemPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.enabled !== v ){
				Util.toggleClass( data.elm, 'ui-combobox-disabled', !v );
				if( data.focus === true && v === false ) this.blur();
				data.enabled = v;
				data.node.disabled( !( data.visible && v ) );
			};
			return data.enabled;
		},
		visible : function( v ){
			var data = UIItemPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.visible !== v ){
				data.elm.style.display = v ? '' : 'none';
				if( data.focus === true && v === false ) this.blur();
				data.visible = v;
				data.node.disabled( !( data.enabled && v ) );
			};
			return data.visible;
		},
		value : function( _value ){
			var data = UIItemPrivateData.get( this ),
				i    = 0,
				list = data.optionList,
				l    = list.length,
				_option;
			if( Type.isString( _value ) === true && data.value !== _value ){
				for( ; i < l; ++i ){
					_option = list[ i ];
					if( _value === _option.value ){
						data.value = _value;
						data.index = i;
						data.elmValue.data = _option.displayValue;
						if( data.focus === true ){
							OptionControl.update( this, _value );
						};
						data.onUpdate && AsyncCall.add( data.groupData.apiuser, data.onUpdate, _value, this );
						break;
					};
				};
			};
			return data.value;
		},
		selectIndex : function(){
			var data = UIItemPrivateData.get( this );
			return data.selectIndex;
		},
		createOption : function( _displayValue, _value, _isSelected ){
			var data   = UIItemPrivateData.get( this ),
				option = null,
				list   = data.optionList,
				i      = list.length,
				_option, i;
			_value      = _value || _displayValue;
			_isSelected = !!_isSelected;
			for( ; i; ){
				_option = list[ --i ];
				if( _value === _option.value ){
					option = _option;
					break;
				};
			};
			if( _isSelected === true ){
				data.selectIndex   = list.length;
				data.elmValue.data = _displayValue;
			};			
			option === null && list.push( new OptionDataClass( _displayValue, _value, _isSelected ) );
		},
		destroy : function(){
			var data   = UIItemPrivateData.get( this );
			data.focus === true && OptionControl.hide( this );
			// this.blur();
			// MouseEvent.remove( data.groupData.apiuser, data.elm );
			data.optionList.length = 0;
			data.destroy();
		}
	};
	var OptionDataClass = function( displayValue, value, isCurrent ){
		this.displayValue = displayValue;
		this.value        = value || displayValue;
		this.current      = isCurrent;
		displayValue = value = null;
	};

	var OptionControl = ( function(){
		var ELM_OPTION_WRAPPER = ( function(){
				var ret = document.createElement( 'div' );
				ret.className = 'option-container';
				return ret;
			})(),
			ELM_OPTION_ORIGIN = ( function(){
				var ret = document.createElement( 'a' );
				ret.appendChild( document.createTextNode( 'option' ) );
				ret.href = '#';
				return ret;
			})();

		var OptionClass = function( option ){
			this.elm     = ELM_OPTION_ORIGIN.cloneNode( true );
			this.data    = option;
			this.init();
		};
		OptionClass.prototype = {
			init: function(){
				ELM_OPTION_WRAPPER.appendChild( this.elm );
				this.elm.firstChild.data = this.data.displayValue;
				this.current( this.data.current );
				MouseEvent.add( SUPER_USER_KEY, this.elm, 'mousedown', onOptionSelect );// onclick では 選択ボックス 隠すように body に設定した onmouseup が先に動いてしまう！
			},
			current: function( _current ){
				this.elm.className = _current === true ? CLASSNAME_COMBOBOX_OPTION_CURRENT : CLASSNAME_COMBOBOX_OPTION;
				this.data.current  = _current;
				currentOption      = _current === true ? this : currentOption;
			},
			destroy: function(){
				MouseEvent.remove( SUPER_USER_KEY, this.elm );
				Util.removeAllChildren( this.elm );
				ELM_OPTION_WRAPPER.removeChild( this.elm );
				delete this.elm;
				delete this.data;
			}
		};
		
		function onOptionSelect( e ){
			var i = 0,
				l = OPTION_LIST.length,
				_option;
			for( ; i < l; ++i ){
				_option = OPTION_LIST[ i ];
				if( this === _option.elm ){
					updateCurrrentOption( _option.data.value, true );
					currentCombobox.blur();
					break;
				};
			};
			return false;
		};
		
		var OPTION_LIST     = [],
			currentCombobox = null,
			apiuser,
			elm,
			currentOption,
			currentIndex;
		
		function updateCurrrentOption( _value, _updateCombobox ){
			var _option,
				i = OPTION_LIST.length;
			for( ; i; ){
				_option = OPTION_LIST[ --i ];
				if( _value === _option.data.value ){
					currentOption && currentOption.current( false );
					_option.current( true );
					currentOption = _option;
					currentIndex  = i;
					_updateCombobox === true && currentCombobox.value( _value );
					break;
				};
			};
		};
		function bodyMouseupHandler(){
			currentCombobox.blur();
			OptionControl.hide( currentCombobox );
		};
		function updateWrapperPosition(){
			var position = Util.getAbsolutePosition( elm );

			ELM_OPTION_WRAPPER.style.cssText = [
				'width:', elm.offsetWidth - 2, 'px;',
				'left:',  position.x, 'px;',
				'top:',   position.y + elm.offsetHeight, 'px;'
			].join( '' );
		};
		function change( e ){
			var l   = OPTION_LIST.length,
				i   = currentIndex + ( e.keyCode === 40 ? -1 : 1 );
			if( currentCombobox === null || l < 2 ) return;
			i = i < 0 ?
					l - 1 :
					i < l ? i : 0;
			updateCurrrentOption( OPTION_LIST[ i ].data.value, true );
			return false;
		};
		return {
			show: function( data ){
				var combobox = data.item,
					list     = data.optionList,
					i        = 0,
					l        = list.length;
				if( currentItem !== combobox || currentCombobox === combobox ) return;
				currentCombobox && currentCombobox.blur();
				
				apiuser         = data.groupData.apiuser;
				currentCombobox = combobox;
				elm             = data.elmBox;
				
				for( ; i < l; ++i ){
					OPTION_LIST.unshift( new OptionClass( list[ i ] ) );
				};
				MouseEvent.add( SUPER_USER_KEY, document, 'mouseup', bodyMouseupHandler );
				KeyEvent.add( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, change, 38 );
				KeyEvent.add( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, change, 40 );
				//KeyEvent.add( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, onEnter, 13 );
				//KeyEvent.updateCurrentListener( SUPER_USER_KEY );
				
				body.appendChild( ELM_OPTION_WRAPPER );
				
				updateCurrrentOption( combobox.value(), false );
				updateWrapperPosition();
				
				SystemTimer.add( SUPER_USER_KEY, updateWrapperPosition, 500 );
			},
			hide: function( _combobox ){
				if( currentCombobox !== _combobox || currentCombobox === null ) return;

				var _option;
				while( _option = OPTION_LIST.shift() ){
					_option.destroy();
				};
				
				body.removeChild( ELM_OPTION_WRAPPER );
				
				MouseEvent.remove( SUPER_USER_KEY, document, 'mouseup', bodyMouseupHandler );
				KeyEvent.remove( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, change );
				KeyEvent.remove( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, change );
				//KeyEvent.remove( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, onEnter );
				//KeyEvent.updateCurrentListener( apiuser );
				
				SystemTimer.remove( SUPER_USER_KEY, updateWrapperPosition, 500 );
				
				apiuser         = null;
				currentCombobox = null;
				currentOption   = null;
				currentIndex    = 0;				
			},
			onEnter: function(){
				currentCombobox.value( currentOption.data.value );
				//currentCombobox.blur();
				//OptionControl.hide( currentCombobox );
			},
			update: function( data, _value ){
				if( currentCombobox !== data.item || currentItem !== data.item ) return;
				if( currentOption.data.value === _value ) return;
				updateCurrrentOption( _value, true );
			},
			onWindowResize: function( _w, _h ){
				currentCombobox && AsyncCall.add( apiuser, updateWrapperPosition );
			}
		};
	})();
	
	var UIGroupPrivateData = function(){};
	UIGroupPrivateData.prototype = {
		apiuser  : null,
		node     : null,
		uigroup  : null,
		itemList : null,
		visible  : true,
		enabled  : true,
		init     : function( apiuser, node, uigroup ){
			this.apiuser  = apiuser;
			this.node     = node;
			this.uigroup  = uigroup;
			this.itemList = [];
			UIGroupPrivateData.list.push( this );
		},
		destroy  : function(){
			
		}
	};
	UIGroupPrivateData.list = [];
	UIGroupPrivateData.get  = function( uigroup ){
		var list = UIGroupPrivateData.list,
			i    = list.length;
		for( ; i; ){
			if( list[ --i ].uigroup === uigroup ) return list[ i ];
		};
		return null;
	};
	
	var UIGroupClass = function( apiuser, node ){
		( new UIGroupPrivateData() ).init( apiuser, node, this );
	};
	UIGroupClass.prototype = {
		focus : function( _value ){
			var data = UIGroupPrivateData.get( this );
			/*
			if( _value === true ){
				if( currentItem ){
					start( apiuser, self, currentItem );
				} else
				if( itemList.length > 0 ){
					start( apiuser, self, itemList[ 0 ] );
				};
			} else
			if( _value === false ){
				finish( apiuser, self, currentItem );
			} else
			*/
			if( _value && Util.getIndex( data.itemList, _value ) !== -1 ){
				// currentItem = _value;
				currentList = data.itemList;
			};
			return currentUi === this; 
		},
		blur : function(){
			var data = UIGroupPrivateData.get( this );
			if( currentList === data.itemList ){
				currentList = null;
			};
		},
		createInputText : function( elmWrapper, onUpdate, validater ){
			var data     = UIGroupPrivateData.get( this ),
				elmValue = Util.getElementsByClassName( elmWrapper, 'editable-value' )[ 0 ],
				ret;
			if( elmValue ){
				ret = new TextInputClass( data, elmWrapper, elmValue, onUpdate, validater );
				data.itemList.push( ret );
				return ret;
			};
			alert( 'error createInputText' );
		},
		createButton : function( elm, onClick ){
			var data = UIGroupPrivateData.get( this ),
				ret  = new ButtonClass( data, elm, onClick );
			data.itemList.push( ret );
			return ret;
		},
		createFileInput : function( elm, onUpdate, validater, elmFileInputReal ){
			var data     = UIGroupPrivateData.get( this ),
				elmValue = Util.getElementsByClassName( elm, 'fileinput-value' )[ 0 ],
				ret;
			if( elmValue ){
				ret = new FileInputClass( data, elm, onUpdate, validater, elmFileInputReal, elmValue );
				data.itemList.push( ret );
				return ret;
			};
			return ret;
		},
		createCombobox : function( elm, onUpdate, optionList ){
			var data = UIGroupPrivateData.get( this ),
				ret  = new ComboBoxClass( data, elm, onUpdate, optionList );
			data.itemList.push( ret );
			return ret;
		},
		createCheckBox : function(){
			
		},
		createRadio : function(){
			
		},
		createSlider : function(){
			
		},
		visible : function( v ){
			var data = UIGroupPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.visible !== v ){
				for( var i = data.itemList.length; i; ){
					data.itemList[ --i ].visible( v );
				};
				data.visible = v;
				data.node.disabled( !( data.enabled && v ) );
			};
			return data.visible;
		},
		enabled : function( v ){
			var data = UIGroupPrivateData.get( this );
			if( Type.isBoolean( v ) === true && data.enabled !== v ){
				for( var i = data.itemList.length; i; ){
					data.itemList[ --i ].enabled( v );
				};
				data.enabled = v;
				data.node.disabled( !( data.visible && v ) );
			};
			return data.enabled;
		},
		destroy : function(){
			var data = UIGroupPrivateData.get( this ),
				_item;
			if( currentUi === this ){
				currentItem.blur();
				// finish( UIItemPrivateData.get( currentItem ) );
			};			
			while( _item = data.itemList.shift() ){
				_item.destroy();
			};
			data.destroy();
		}
	};
	
	function start( data ){
		if( currentItem !== data.item ){
			currentUi !== data.groupData.uigroup && currentUi && currentUi.blur();
			
			currentItem !== null && currentItem.blur();
			
			currentUser = data.groupData.apiuser;
			currentUi   = data.groupData.uigroup;
			currentItem = data.item;
			
			currentUi.focus( currentItem );
			
			// if( currentUser !== _apiuser ) {
				KeyEvent.add( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, onKeyDown, 13 );
				KeyEvent.add( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, onKeyDown, 27 );
				KeyEvent.add( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, onKeyDown,  9 );
				KeyEvent.updateCurrentListener( SUPER_USER_KEY );
			// };
		};
	}
	function finish( data ){
		if( currentItem === data.item ){
			currentUi.blur();
			
			currentUser = null;
			currentUi   = null;
			currentItem = null;
			currentList = null;
			
			KeyEvent.remove( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, onKeyDown, 13 );
			KeyEvent.remove( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, onKeyDown, 27 );
			KeyEvent.remove( SUPER_USER_KEY, Const.KEY.EVENT.KEY_DOWN, onKeyDown,  9 );
			KeyEvent.updateCurrentListener( data.groupData.apiuser );
		};
	};

	function onKeyDown( e ){
		if( currentItem === null ) return true;
		var keyCode = e.keyCode,
			index   = Util.getIndex( currentList, currentItem );
		if( keyCode === 13 || keyCode === 27 || keyCode === 9 || keyCode === 18 || e.altKey === true ){ // 13.return 27.esc 9.tab 18.alt
			keyCode === 9  && tabShift( index, e.shiftKey === true ? -1 : 1 );
			keyCode === 13 && currentItem instanceof ComboBoxClass && OptionControl.onEnter();
			keyCode === 13 && tabShift( index, 1 );			
			currentItem && currentItem.blur( keyCode );
			return false;
		};
	};

	function tabShift( index, way ){
		var l = currentList.length,
			i = index + way,
			item;
		if( l < 2 ) return;
		while( i !== index ){
			i = i < 0 ?
				l - 1 :
				i < l ? i : 0; // 0 < i < l
			item = currentList[ i ];
			if( item.enabled() === true && item.visible() === true ){
				AsyncCall.add( currentUser, item.focus, null, item );
				return;
			};
			i += way;
		};
	};
	
	return {
		createUIGroup: function( apiuser, node ){
			var uid  = apiuser.getUID(),
				list = UI_LIST[ uid ],
				ui   = new UIGroupClass( apiuser, node );
			if( Type.isArray( list ) === false ){
				list = UI_LIST[ uid ] = [];
			};
			list.push( ui );
			return ui;
		},
		onWindowResize: function( w, h ){
			windowW = w;
			windowH = h;
			currentItem instanceof ComboBoxClass && OptionControl.onWindowResize( w, h );
			currentItem instanceof TextInputClass && TextInputManager.onWindowResize( w, h );
			currentItem instanceof FileInputClass && FileInputManager.onWindowResize( w, h );
		},
		onCurrentApplicationChange: function( _apiuser ){
			currentList = UI_LIST[ _apiuser.getUID() ];
		},
		onApplicationShutdown: function( _apiuser ){
			KeyEvent.remove( _apiuser );
		},
		onSystemShutdown: function(){
			
		}
	};
})();

var UIForm = ( function(){
	var FORM_LIST           = [];
	var CLASSNAME_FORM      = 'uiform-invisible';
	var CLASSNAME_FILE_WRAP = 'ui-fileinput-wrapper';
	var FormItemData = function(){};
    var windowW, windowH;
	FormItemData.prototype = {
		formData : null,
		uiItem   : null,
		init : function( formData, uiItem ){
			this.formData = formData;
			this.uiItem   = uiItem;
		},
		onUpdate : function( v ){
			// var index = Util.getIndex( this.formData.itemList, this );
		}
	};
	
	var FormPrivateData = function(){};
	FormPrivateData.prototype = {
		apiuser  : null,
		node     : null,
		form     : null,
		elmForm  : null,
		itemList : null,
		visible  : true,
		enabled  : true,
		init     : function( apiuser, from, node, elm, elmForm ){
			this.apiuser      = apiuser;
			this.form         = form;
			this.ui           = apiuser.createUIGroup( node );
			this.node         = node;
			this.elm          = elm;
			this.elmForm      = elmForm;
			this.itemList     = [];
			elmForm.className = CLASSNAME_FORM;
			FormPrivateData.list.push( this );
			
			var forms = Util.copyArray( elmForm.getElementsByTagName( '*' ) ),
				l     = forms.length,
				i     = 0,
				items = 'input,select,textarea,button',
				form, data, el, wrap;
			for( ; i<l; ++i ){
				form = forms[ i ];
				if( form.nodeType !== 1 ) continue;
				switch( form.tagName.toLowerCase() ){
					case 'input':
						switch( form.type.toLowerCase() ){
							case 'text':
								break;
							case 'file':
								el = document.createElement( 'div' );
								el.className = 'uiform-file-container';
								el.appendChild( document.createElement( 'div' ) );
								el.appendChild( document.createElement( 'div' ) );
								el.firstChild.className = 'uiform-label';
								el.lastChild.className  = 'uiform-file fileinput-value';
								// opera9 don't work for opera9;
								//el = Util.pullHtmlAsTemplete( '<div class="uiform-file-container"><div class="uiform-label"></div><div class="uiform-file fileinput-value"></div></div>' );
								elm.appendChild( el );
								data = new FormItemData();
								wrap = document.createElement( 'div' );
								form.parentNode.insertBefore( wrap, form );
								wrap.className = CLASSNAME_FILE_WRAP;
								wrap.appendChild( form );
								data.init( this, this.ui.createFileInput( el, data.onUpdate, null, form ) );
								this.itemList.push( data );
								break;
							case 'button':
								break;
							default:
								continue;
						};
						break;
					case 'select':
						break;
					case 'button':
						break;
					case 'textarea':
						break;
					default:
						continue;
				};
			};
		},
		destroy  : function(){
			
		}
	};
	FormPrivateData.list = [];
	FormPrivateData.get  = function( from ){
		var list = FormPrivateData.list,
			i    = list.length;
		for( ; i; ){
			if( list[ --i ].form === form ) return list[ i ];
		};
		return null;
	};
	
	var FormClass = function( apiuser, node, elm, elmForm ){
		( new FormPrivateData() ).init( apiuser, this, node, elm, elmForm );
	};
	FormClass.prototype = {
		createTextInput : function(){
			
		},
		createMultiLineInput : function(){
			
		},
		createFileInput : function(){
			
		},
		createButton : function(){
			
		},
		createComboBox : function(){
			
		},
		submit : function(){
			
		}
	};
	
	return {
		createForm: function( apiuser, nodeOrElm, opt_elmForm ){
			var uid  = apiuser.getUID(),
				list = FORM_LIST[ uid ],
				node, elm, form;
			if( PointingDeviceEventTree.isNodeInstance( nodeOrElm ) === true ){
				node = nodeOrElm;
				elm  = PointingDeviceEventTree._getNodePrivateData( nodeOrElm ).elm;
			} else {
				// App が eventTree を持っている？
				// App が eventTree を持っていない
				elm  = nodeOrElm;
			};  
			form = new FormClass( apiuser, node, elm, opt_elmForm );
			if( Type.isArray( list ) === false ){
				list = FORM_LIST[ uid ] = [];
			};
			list.push( form );
			return form;
		},
		onWindowResize: function( w, h ){
			windowW = w;
			windowH = h;
			currentItem instanceof ComboBoxClass && OptionControl.onWindowResize( w, h );
			currentItem instanceof TextInputClass && TextInputManager.onWindowResize( w, h );
			currentItem instanceof FileInputClass && FileInputManager.onWindowResize( w, h );
		},
		onCurrentApplicationChange: function( _apiuser ){
		},
		onApplicationShutdown: function( _apiuser ){
		},
		onSystemShutdown: function(){
			
		}
	};
})();

var Finder = ( function(){
	var FINDER_LIST              = [],
		ELM_ORIGIN_LOCATION_ITEM = Util.pullHtmlAsTemplete( '<div class="finder-location-item"></div>' ),
		HTML_FINDER_ICON = ( function(){
			return ( UA.isIE === true && UA.ieVersion < 8 ?
			[
				'<div class="finder-icon fnder-icon-ie7">',
					'<div class="finder-icon-handle"></div>',
					'<div class="file-icon"><div></div></div>',
					'<span class="finder-icon-cell finder-icon-ie-filename">',
						'<span class="finder-icon-vertical-middle-outer">',
							'<span class="finder-icon-vertical-middle-inner">',
								'<span class="finder-icon-filename break-word">file name</span>',
							'</span>',
						'</span>',
					'</span>',
					'<span class="finder-icon-cell finder-icon-ie-summary">',
						'<span class="finder-icon-vertical-middle-outer">',
							'<span class="finder-icon-vertical-middle-inner">',
								'<span class="finder-icon-summary break-word">file descriptiion</span>',
							'</span>',
						'</span>',
					'</span>',
					'<div class="finder-icon-down"></div>',
				'</div>'
			] :
			[
				'<div class="finder-icon fnder-icon-modern">',
					'<div class="finder-icon-handle"></div>',
					'<div class="file-icon"><div></div></div>',
					'<div class="finder-icon-filename break-word">file name</div>',
					'<div class="finder-icon-summary break-word">file descriptiion</div>',
					'<div class="finder-icon-down">&gt;</div>',
				'</div>'
			] ).join( '' );
		})(),
		ELM_ORIGIN_FINDER_ICON = Util.pullHtmlAsTemplete( HTML_FINDER_ICON ),
		ICON_HEIGHT            = Util.getElementSize( ELM_ORIGIN_FINDER_ICON ).height;
	
	// t : 時間
    // b : 開始の値(開始時の座標やスケールなど)
    // c : 開始と終了の値の差分
    // d : Tween(トゥイーン)の合計時間

	function easeOutQuad( t, b, c, d ){
		t /= d;
		return -c * t*( t-2 ) + b;
	};
	
/**
 * FinderIconClass
 */
	var FinderIconClass = function(){};
	FinderIconClass.prototype = {
		finderData       : null,
		file             : null,
		elm              : null,
		node             : null,
		_index           : -1,
		_style           : -1,
		init : function( page, file, w, index, style ){
			if( !this.elm ) this.elm  = ELM_ORIGIN_FINDER_ICON.cloneNode( true );

			if( this.page !== page ){
				this.page = page;
				page.elm.appendChild( this.elm );
				this.node && this.node.remove();
				this.node = page.node.createNode( this.elm, false, true, 'finder-icon-hover', '' );
			};
			if( this.file !== file ){
				this.file && this.file.destroy();
				this.file   = file;
				this._index = index;
				this.draw( w );
				return;
			};
			if( this._index !== index ){
				this._index = index;
				this.resize( w );
			};
		},
		index : function( _index ){	
			return this._index;
		},
		style : function( _style ){
			return this._style;
		},
		draw : function( w ){
			var file       = this.file,
				elm        = this.elm,
				thumb      = file.getThumbnail(),
				elmThumb   = Util.getElementsByClassName( elm, 'file-icon' )[ 0 ].firstChild,
				elmName    = Util.getElementsByClassName( elm, 'finder-icon-filename' )[ 0 ],
				elmDesc    = Util.getElementsByClassName( elm, 'finder-icon-summary' )[ 0 ];
			if( thumb.image ){
				elmThumb.className = 'has-thumbnail';
				elmThumb.style.backgroundImage = [ 'url(', thumb.image, ')' ].join( '' );
			} else {
				elmThumb.className = thumb.className;
				elmThumb.style.backgroundImage = '';
			};
			
			elmName.firstChild.data = file.getName();
			elmDesc.firstChild.data = file.getSummary();
			
			this.resize( w );
		},
		resize : function( w ){
			this.node.update( 0, this._index * ICON_HEIGHT, w );
		},
		onEditorClick : function( e ){
			this.onEditorCallback && this.onEditorCallback( this.file, this.file.editorApplicationList()[ 0 ] );
			return false;
		},
		onViwerClick : function( e ){
			this.onViewerCallback && this.onViewerCallback( this.file, this.file.viewerApplicationList()[ 0 ] );
			return false;
		},
		onActionClick : function( e ){
			this.onActionCallback && this.onActionCallback( this.file );
			return false;
		},
		destroy : function(){
			this.elm && this.elm.parentNode.removeChild( this.elm );
			this.file && this.file.destroy();
			this.node && this.node.remove();
			delete this.page;
			delete this.file;
			delete this.node;
			delete this._index;
			delete this._style;
			FinderIconClass.pool.push( this );
		}	
	};
	FinderIconClass.pool = [];
	FinderIconClass.get = function( page, file, w, index, style ){
		var _icon = FinderIconClass.pool.length > 0 ? FinderIconClass.pool.shift() : new FinderIconClass();
		_icon.init( page, file, w, index, style );
		return _icon;
	};

/**
 * PathClass
 */
	var PathClass = function(){};
	PathClass.prototype = {
		finderData : null,
		elm        : null,
		node       : null,
		file       : null,
		_index     : null,
		w          : 0,
		init : function( finderData, file, index ){
			if( !this.elm ) this.elm  = ELM_ORIGIN_LOCATION_ITEM.cloneNode( true );
			
			if( this.finderData !== finderData ){
				this.finderData = finderData;
				finderData.elmPath.appendChild( this.elm );
				this.node && this.node.remove();
				delete this.node;
			};
			if( this.file !== file ){
				this.file = file;
				this.draw();
			};
			this._index = index;
			if( !this.node ) this.node = finderData.nodePath.createNode( this.elm, false, true, 'finder-path-hover', 'pointer' );
		},
		draw  : function(){
			this.elm.className = 'file-icon-' + this.file.getType();
			this.elm.innerHTML = this.file.getName();			
		},
		textWidth : function(){
			this.elm.style.width = 'auto';
			var ret = this.elm.offsetWidth;
			this.elm.style.width = '';
			return ret + 15;
		},
		update : function( x, w ){
			this.node.update( x - 15, undefined, w );
		},
		index : function( _index ){
			return this._index;
		},
		destroy : function(){
			this.finderData.elmPath.removeChild( this.elm );
			this.node && this.node.remove();
			
			delete this.finderData;
			delete this.elm;
			delete this.node;
			delete this.file;
			delete this._index;
			PathClass.pool.push( this );
		}
	};
	PathClass.pool = [];
	PathClass.get  = function( finderData, file, index ){
		var _bread = PathClass.pool.length > 0 ? PathClass.pool.shift() : new PathClass();
		_bread.init( finderData, file, index );
		return _bread;
	};
	
	
	/**
	 * Page
	 */
	var PageClass = function(){};
	PageClass.prototype = {
		nodeRoot     : null,
		elmRoot      : null,
		elmScroll    : null,
		elm          : null,
		node         : null,
		folder       : null,
		iconList     : null,
		sliding      : false,
		currentX     : 0,
		panTime      : 0,
		startX       : 0,
		offsetX      : 0,
		panTotalTime : 0,
		isPanOut     : false,
		init : function( nodeRoot, elmRoot, elmScroll ){
			this.nodeRoot  = nodeRoot;
			this.elmRoot   = elmRoot;
			this.elmScroll = elmScroll;
			
			if( this.elm === null ){
				this.elm = document.createElement( 'div' );
			};
			elmScroll.appendChild( this.elm );
			this.elm.style.cssText = 'position:absolute;top:0;';
			// this.elm.style.display = 'none';
			this.node = this.nodeRoot.createNode( this.elm, true, false );
			if( this.iconList === null ){
				this.iconList = [];
			};
		},
		panInReady : function( way ){
			this.elm.style.display = '';
			var x = this.sliding === true ? this.currentX : way * this.nodeRoot.width();
			this.startX       = this.currentX = x;
			this.targetX      = 0;
			this.offsetX      = -x;
			this.panTime      = 0;
			this.panTotalTime = 20;
			this.sliding      = true;
			this.isPanOut     = false;
			// this.elm.style.left = x + 'px';
			this.node.x( x );
		},
		panOutReady : function( way ){
			var x = -way * this.nodeRoot.width();
			this.startX       = this.currentX || 0;
			this.targetX      = x;
			this.offsetX      = x - this.startX;
			this.panTime      = 0;
			this.panTotalTime = 20;
			this.sliding      = true;
			this.isPanOut     = true;
		},
		pan : function(){
			var page = this,
				x    = page.currentX = easeOutQuad( page.panTime, page.startX, page.offsetX, page.panTotalTime );
			// page.elm.style.left = x + 'px';
			this.node.x( x );
			if( page.panTotalTime < ++page.panTime ){
				delete page.panTime;
				delete page.startX;
				delete page.offsetX;					
				delete page.panTotalTime;
				delete page.sliding;
				if( this.isPanOut === true ) this.elm.style.display = 'none';
			};
		},
		draw : function( folder ){
			var _w = this.nodeRoot.width();
			this.folder = folder;
			var data     = this,
				iconList = data.iconList,
				i        = 0,
				j        = 0,
				l        = folder.getChildFileLength(),
				m        = iconList.length,
				scrollY  = -this.nodeRoot.scrollY(),
				rootH    = scrollY + this.nodeRoot.height(),
				icon;

			for( ; i < l; ++i ){
				if( ( i + 1 ) * ICON_HEIGHT < scrollY || rootH < i * ICON_HEIGHT ) continue;
				if( j < m ){
					iconList[ j ].init( this, folder.getChildFileAt( i ), _w, i, data.style );
				} else {
					iconList.push( FinderIconClass.get( this, folder.getChildFileAt( i ), _w, i, data.style ) );
				};
				j++;
			};
			data.elmRoot.className    = folder.getState() === Const.FILE.STATE.LOADING ? 'finder-body loading' : 'finder-body';
			// data.elmRoot.style.height = ( data.h - data.headH ) + 'px';
			
			while( j < iconList.length ) iconList.pop().destroy();
			data.elmScroll.style.height = ( l * ICON_HEIGHT ) + 'px';
		},
		onScroll : function(){
			var _w       = this.nodeRoot.width();
			
			var data     = this,
				iconList = data.iconList,
				folder   = this.folder,
				i        = 0,
				j        = 0,
				l        = folder.getChildFileLength(),
				scrollY  = -this.nodeRoot.scrollY(),
				rootH    = scrollY + this.nodeRoot.height(),
				startIndex = 0 < iconList.length ? iconList[ 0 ]._index : 0,
				icon;
			
			// console.log( ' > ' + scrollY + ' , ' + rootH )
			for( ; i < l; ++i ){
				if( ( i + 1 ) * ICON_HEIGHT < scrollY || rootH < i * ICON_HEIGHT ){
					if( iconList.length <= j ) continue;
					icon = iconList[ j ];
					if( icon._index !== i ) continue;
					icon.destroy();
					iconList.splice( j, 1 );
					continue;
				};
				if( iconList.length <= j || iconList[ j ]._index !== i ){
					if( i < startIndex ){
						iconList.splice( j, 0, FinderIconClass.get( this, folder.getChildFileAt( i ), _w, i, data.style ) );			
					} else
					if( startIndex + iconList.length <= i ){
						iconList.push( FinderIconClass.get( this, folder.getChildFileAt( i ), _w, i, data.style ) );			
					};					
				};
				++j;
			};
			
			//while( j < iconList.length ) iconList.pop().destroy();
		},
		resize : function( w ){
			var list = this.iconList,
				i    = list.length;
			for( ; i; ) list[ --i ].resize( w );
		},
		destroy : function(){
			var icon;
			while( icon = this.iconList.shift() ) icon.destroy();
			
			this.elm.parentNode.removeChild( this.elm );
		}
	};
	
	var ApplicationButton = function(){};
	ApplicationButton.prototype = {
		elm     : null,
		button  : null,
		app     : null,
		file    : null,
		fileUID : -1,
		init    : function( ui, elmParent, app, file ){
			if( this.elm === null ){
				this.elm = document.createElement( 'div' );
			};
			elmParent.appendChild( this.elm );
			this.elm.className = 'button';
			this.elm.innerHTML = app.getDisplayName();
			
			var that = this;
			this.button = ui.createButton( this.elm, function(){
				that.onClick();
				// that = null;
			} );
			
			this.app     = app;
			this.file    = file;
			this.fileUID = file.getUID();
		},
		onClick : function(){
			this.app.boot( this.file );
			return false;
		},
		destroy : function(){
			var elm = this.elm;
			elm.parentNode.removeChild( elm );
			
			this.button.destroy();
			//this.kill()
			//this.elm = elm;
		}
	};
	
	var DetailPageClass = function(){};
	DetailPageClass.prototype = Util.extend( new PageClass(), {
		appButtons : null,
		init : function( finderData ){
			this.finderData = finderData;
			this.apiuser    = finderData.apiuser;
			this.nodeRoot   = finderData.nodeRoot;
			this.elmRoot    = finderData.elmRoot;
			this.elmScroll  = finderData.elmScroll;
			
			if( this.elm === null ){
				this.elm = Util.pullHtmlAsTemplete( [
					'<div class="finder-detail">',
						'<div class="file-icon"><div></div></div>',
						'<div class="finder-detail-filename break-word">file name</div>',
						'<div class="finder-detail-summary break-word">file descriptiion</div>',
						'<div>View this file</div>',
						'<div class="viewer-apps"></div>',						
						'<div>Edit this file</div>',
						'<div class="editor-apps"></div>',
					'</div>'
				].join( '' ) );
			};
			this.elm.style.display = 'none';
			this.elmScroll.appendChild( this.elm );
			this.node = this.nodeRoot.createNode( this.elm, true, false );
			
			this.ui = this.apiuser.createUIGroup( this.node );
			this.appButtons = [];
		},
		draw : function( file ){
			var elm        = this.elm,
				thumb      = file.getThumbnail(),
				elmThumb   = Util.getElementsByClassName( elm, 'file-icon' )[ 0 ].firstChild,
				elmName    = Util.getElementsByClassName( elm, 'finder-detail-filename' )[ 0 ],
				elmDesc    = Util.getElementsByClassName( elm, 'finder-detail-summary' )[ 0 ],
				tmpButtons = Util.copyArray( this.appButtons ),
				apps, app, elmContainer, button;
			if( thumb.image ){
				elmThumb.className = 'has-thumbnail';
				elmThumb.style.backgroundImage = [ 'url(', thumb.image, ')' ].join( '' );
			} else {
				elmThumb.className = thumb.className;
				elmThumb.style.backgroundImage = '';
			};
			
			elmName.firstChild.data = file.getName();
			elmDesc.firstChild.data = file.getSummary();
			this.node.width( this.nodeRoot.width() );
			this.node.height( this.nodeRoot.height() );
			
			this.appButtons.length = 0;
			
			apps         = file.viewerApplicationList();
			elmContainer = Util.getElementsByClassName( elm, 'viewer-apps' )[ 0 ];
			for( i = 0; i < apps.length; ++i ){
				button = 0 < tmpButtons.length ? tmpButtons.shift() : new ApplicationButton();
				button.init( this.ui, elmContainer, apps[ i ], file );
				this.appButtons.push( button );
			};
			apps         = file.editorApplicationList();
			elmContainer = Util.getElementsByClassName( elm, 'editor-apps' )[ 0 ];
			for( i = 0; i < apps.length; ++i ){
				button = 0 < tmpButtons.length ? tmpButtons.shift() : new ApplicationButton();
				button.init( this.ui, elmContainer, apps[ i ], file );
				this.appButtons.push( button );
			};
			
			while( button = tmpButtons.shift() ) button.destroy();
			
			this.resize();
		},
		pan : function(){
			var page = this,
				x    = page.currentX = easeOutQuad( page.panTime, page.startX, page.offsetX, page.panTotalTime );
			// page.elm.style.left = x + 'px';
			this.node.x( x );
			if( page.panTotalTime < ++page.panTime ){
				delete page.panTime;
				delete page.startX;
				delete page.offsetX;					
				delete page.panTotalTime;
				delete page.sliding;
				if( this.isPanOut === true ) this.elm.style.display = 'none';
			};
		},
		onScroll : function(){
			
		},
		resize : function(){
			this.elmScroll.style.height = this.nodeRoot.height() + 'px';
		},
		destroy : function(){
			var button;
			while( button = this.appButtons.shift() ) button.destroy();
			this.ui.destroy();
			this.node.remove();
		}
	});
	
/**
 * FinderPrivateData
 */
	var FinderPrivateData = Class.create(
		Class.PRIVATE_DATA, {
		finder       : null,
		apiuser      : null,
		elmRoot      : null,
		nodeRoot     : null,
		elmScroll    : null,
		elmPath      : null,
		nodePath     : null,
		tree         : null,
		onSelect     : null,
		viewerOption : null,
		editorOption : null,
		pathList     : null,
		headH        : 0,
		iconW        : 0,
		iconH        : 0,
		style        : 0,
		pageIcons1   : null,
		pageIcons2   : null,
		panInPage    : null,
		panOutPage   : null,
		pageDetail   : null,
		currentFile  : null,
		Constructor : function( finder, apiuser, elm, tree, onSelect, viewerOption, editorOption ){
			this.finder       = finder;
			this.apiuser      = apiuser;
			if( PointingDeviceEventTree.isNodeInstance( elm ) === true ){
				this.nodeRoot = elm;
				this.elmRoot  = PointingDeviceEventTree._getNodePrivateData( elm ).elm;
			} else {
				// App が eventTree を持っている？
				// App が eventTree を持っていない
				this.elmRoot  = elm;
			};
			this.nodeRoot.addEventListener( 'click', this.onIconClick, this );
			this.nodeRoot.addEventListener( 'scroll', this.onScroll, this );
			
			this.elmScroll    = document.createElement( 'div' );
			this.elmRoot.appendChild( this.elmScroll );
			this.elmScroll.className     = 'finder-elm-scroll';
			this.elmScroll.style.cssText = 'width:100%;overflow:hidden;';
			
			this.tree         = tree;
			this.onSelect     = onSelect;
			this.viewerOption = viewerOption;
			this.editorOption = editorOption;
			
			var size          = Util.getElementSize( ELM_ORIGIN_FINDER_ICON );
			this.iconW        = size.width;
			this.iconH        = size.height;
			
			tree.addTreeEventListener( Const.TREE.EVENT.UPDATE, this.draw, this );
			Util.addClass( this.elmRoot, 'finder-body' );
			
			if( this.panInPage === null ){
				this.pageIcons1 = new PageClass();
				this.pageIcons2 = new PageClass();
				this.pageDetail = new DetailPageClass();
			};
			this.pageIcons1.init( this.nodeRoot, this.elmRoot, this.elmScroll );
			this.pageIcons2.init( this.nodeRoot, this.elmRoot, this.elmScroll );
			this.pageDetail.init( this );
		},
		onIconClick : function( e ){
			if( this.panInPage === this.pageDetail ) return;
			
			var target = e.target,
				list   = this.panInPage.iconList,
				i, icon,
				file;
			if( target === this.nodeRoot ) return;
			for( i = list.length; i; ){
				icon = list[ --i ];
				if( icon.node === target ){
					i = icon._index;
					file = this.currentFile.getChildFileAt( i );
					if( target.width() - 30 < e.layerX ){
						this.tree.down( i );
						this.draw( this.w, this.h, 1, true );
					} else
					if( file.getChildFileLength() !== -1 || file.getType() === Const.FILE.TYPE.FOLDER ){
						this.tree.down( i );
						this.draw( this.w, this.h, 1 );
					} else
					if( Type.isFunction( this.onSelect ) === true ){ /* && this.onSelect( file ) === true */
						this.onSelect( file );
					} else {
						this.tree.down( i );
						this.draw( this.w, this.h, 1 );
					};
					file.destroy();
					break;
				};
			};
		},
		onScroll : function( e ){
			this.panInPage.onScroll( e );
		},
		onPathClick : function( e ){
			var target = e.target,
				i      = target.nodeIndex();
			if( target === this.nodePath || this.nodePath.numNode() - 1 === i ) return;
			this.tree.up( i );
			this.draw( this.w, this.h, -1 );
		},
		draw : function( w, h, way, showDetail ){
			var data = this, page;
			data.w = w = Type.isFinite( w ) === true ? w : data.w;
			data.h = h = Type.isFinite( h ) === true ? h : data.h;
			
			var file     = this.currentFile = this.tree.getCurrentFile(),
				isFolder = showDetail !== true && ( file.getChildFileLength() !== -1 || file.getType() === Const.FILE.TYPE.FOLDER );
			
			data.elmPath && data.drawPath( w );
			page = this.panInPage;
			if( Type.isNumber( way ) === true ){
				if( page.sliding === false ){
					if( isFolder === true ){
						this.panInPage = page === this.pageIcons1 ? this.pageIcons2 : ( page === this.pageIcons2 ? this.pageIcons1 : this.panOutPage );
					} else {
						this.panInPage = this.pageDetail;
					};
					this.panOutPage = page;
				};
				this.panInPage.panInReady( way );
				//this.panInPage.elm.className = 'panIN';
				this.panOutPage.panOutReady( way );
				//this.panOutPage.elm.className = 'panOut';
				this.nodeRoot.disabled( true );
				SystemTimer.add( this.apiuser, this.tick, 16, false, this );
			} else {
				if( isFolder === true ){
					this.panInPage = page === null ? this.pageIcons1 : page;
				} else {
					this.panInPage = this.pageDetail;
				};
			};
			this.panInPage.draw( file );
			
			data.nodeRoot.invalidateScrollbar();
		},
		tick : function(){
			if( this.panInPage.sliding === false && this.panOutPage.sliding === false ){
				SystemTimer.remove( this.apiuser, this.tick );
				this.nodeRoot.disabled( false );
				this.nodeRoot.invalidateScrollbar();
				return;
			};
			this.panInPage.sliding === true && this.panInPage.pan();
			this.panOutPage.sliding  === true && this.panOutPage.pan();
		},
		drawPath : function( w ){
			if( !this.elmPath.parentNode ) return;
			w = this.nodePath.width();
			var data      = this,
				tree      = data.tree,
				pathList  = data.pathList,
				i         = 0,
				l         = tree.hierarchy() + 1,
				m         = pathList.length,
				wList     = [],
				totalW    = 0,
				minW      = FinderPrivateData.MIN_PATH_WIDTH,
				file, path, pathW, offset, remove, pathX = 0, fit = false;
			
			for( ; i < l; ++i ){
				file = i !== l - 1 ? tree.getParentFileAt( i ) : this.currentFile;
				if( i < m ){
					pathList[ i ].init( this, file, i );
				} else {
					pathList.push( PathClass.get( this, file, i ) );
				};
			};
			while( l < pathList.length ) pathList.pop().destroy();
			
			for( i = l; i; ){
				pathW = pathList[ --i ].textWidth();
				wList.push( pathW );
				totalW += pathW;
			};
			
			//if( minW * ( l + 1 ) * 1.2 < w ){
				console.log( totalW + ' , ' + w )
				while( true ){
					if( fit === true ) break;
					for( i = 0; i < l; ++i ){
						offset = totalW - w;
						if( offset <= 0 ){
							fit = true;
							break;
						};
						remove = l - i;
						remove = offset < remove ? offset : remove;
						pathW  = wList[ i ];
						if( pathW - remove < minW ){
							totalW -= ( pathW - minW );
							wList[ i ] = minW;
						} else {
							wList[ i ] = pathW - remove;
							totalW -= remove;
						};
					};
				};
				for( i = 0; i < l; ++i ){
					path  = pathList[ i ];
					pathW = wList[ i ];
					path.update( pathX, pathW );
					pathX += pathW;
				};			
			//} else {
				
			//};
		},
		createPath : function( node ){
			if( this.elmPath ) return;
			
			if( PointingDeviceEventTree.isNodeInstance( node ) === true ){
				this.nodePath = node;
				this.elmPath  = PointingDeviceEventTree._getNodePrivateData( node ).elm;
				
				node.addEventListener( 'click', this.onPathClick, this );
				Util.addClass( this.elmPath, 'finder-path' );
				// this.elmPath  = document.createElement( 'div' );
				// this.elmPath.className = ;
				this.pathList = [];
				// this.headH    = 0;
				AsyncCall.add( this.apiuser, this.draw, null, this );
			};
		},
		onKill : function(){
			this.tree.removeTreeEventListener( Const.TREE.EVENT.UPDATE, this.draw );

			if( this.pathList ){
				while( this.pathList.length > 0 ) this.pathList.shift().destroy();
			};
			
			this.pageIcons1.destroy();
			this.pageIcons2.destroy();
			this.pageDetail.destroy();
			this.nodeRoot.remove();
			
			FINDER_LIST.splice( Util.getIndex( FINDER_LIST, this.finder ), 1 );
			var data = ApplicationPrivateData.get( this.apiuser ),
				list = data.finderList,
				i    = Util.getIndex( list, this.finder );
			i !== -1 && list.splice( i, 1 );
		}
	});
	FinderPrivateData.MIN_PATH_WIDTH = 25;

/**
 * FinderClass
 */
	var Finder = Class.create(
		FinderPrivateData, {
		Constructor : function( application, elmRoot, tree, onSelect, viewerOption, editorOption ){
			Finder.newPrivateData( this, this, application, elmRoot, tree, onSelect, viewerOption, editorOption );
		},
		MIN_WIDTH  : 200,
		MIN_HEIGHT : 200,
		resize : function( w, h ){
			var data = Finder.getPrivateData( this );
			data.panInPage && data.panInPage.resize( w );
		},
		createPath : function( node ){
			return Finder.getPrivateData( this ).createPath( node );
		},
		destroy : function(){
			this.kill();
		}
	});

	return {
		init: function(){
			
		},
		create: function( application, elmTarget, tree, onSelect, viewerOption, editorOption ){
			//if( Application.isApplicationInstance( _application ) === false ) return;
			
			var finder = new Finder( application, elmTarget, tree, onSelect, viewerOption, editorOption );
			FINDER_LIST.push( finder );
			return finder;
		},
		registerFinderHead: function(){
			
		},
		registerFinderPane: function( _finderPane ){
			
		},
		isFinderInstance: function( _finder ){
			return _finder instanceof Finder;
		},
		isFinderPaneInstance: function(){
			
		},
		isFinderHeadInstance: function(){
		}
	};
})();


/*
 * -- len, %
 * marginBottom, marginLeft, marginRight, marginTop, margin
 * padding, paddingBottom, paddingLeft, paddingRight, paddingTop
 * fontSize, textIndent
 * height, width
 * bottom, left, right, top			(len, %)
 *
 * -- len
 * borderBottomWidth, borderLeftWidth, borderRightWidth, borderTopWidth, borderWidth,
 * letterSpacing
 *
 * -- color
 * backgroundColor
 * borderBottomColor, borderLeftColor, borderRightColor, borderTopColor, borderColor
 * color
 *
 * -- special
 * clip			rect(0px, 40px, 40px, 0px);
 * backgroundPosition	(len, %)
 * opacity
 * lineHeight		(len, %, num)
 * zIndex			( order )
 */

var DHTML = ( function(){
	
	var TICKET_ARRAY = [],
		fpms         = 50,
		round        = Math.round,
		cround       = function( v ){ return round( v * 100 ) / 100 };
	
	function startAnimation( _elm, _cssObject, _onComplete, _onEnterFrame, _numFrames ){
		var _ticket, i = TICKET_ARRAY.length;
		for( ; i; ){
			_ticket = TICKET_ARRAY[ --i ];
			if( _ticket.elm === _elm ){
				return;
			};
		};
		
		var _currentValues     = [],
			_offsetValues      = [],
			_endValues         = [],
			_targetProperties  = [],
			_units             = [];
		var target, current,
			inlineStyle    = CSS.getInlineStyle( _elm ),
			currentStyle   = CSS.getWrappedStyle( _elm ),
			targetStyle    = CSS.getWrappedStyle( _elm, _cssObject );
			targetStyle.pxPerEm = currentStyle.get( 'fontSize' )._toPx();
		for( var p in _cssObject ){
			p       = Util.camelize( p );
			target  = targetStyle.get( p );
			current = currentStyle.get( p );

			if( target.isValid() === false || current.isValid() === false || current.equal( target ) !== false ){
				target.clear();
				current.clear();
				continue;
			};
			
			current.convert( target );
			// alert( current.getValue() + ' , ' + target.getValue() )
			_currentValues.push( current.getValue() );
			_offsetValues.push( current.getOffset( target ) );
			_endValues.push( target.getValue() );
			_targetProperties.push( p );
			_units.push( target.getUnit() );

			// IE has trouble with opacity if it does not have layout
			// Force it by setting the zoom level			
			if( p === 'opacity' && SPECIAL.hasLayout ){
				if( SPECIAL.hasLayout( _elm ) === false ) inlineStyle.zoom = 1;
				inlineStyle.filter = current.getValueText();
			} else {
				inlineStyle[ p ]   = current.getValueText();
			};
			
			target.clear();
			current.clear();
		};
		
		var i, cssTexts = [];
		for( i = 0; i < _numFrames; ++i ){
			if( i < _numFrames - 1 ){
				tickValue( _currentValues, _offsetValues, _numFrames );
				cssTexts.push( createCssText( _currentValues, _targetProperties, targetStyle, inlineStyle ) );
			} else {
				cssTexts.push( createCssText( _endValues, _targetProperties, targetStyle, inlineStyle ) );
			};
		};
		
		TICKET_ARRAY.push( new AnimationTaskClass(
			_elm, cssTexts,
			Type.isFunction( _onComplete ) === true   ? _onComplete   : null,
			Type.isFunction( _onEnterFrame ) === true ? _onEnterFrame : null,
			_numFrames
		) );
		
		currentStyle.clear();
		targetStyle.clear();		
		SystemTimer.add( SUPER_USER_KEY, onEnterFrame, 1000 / fpms );
	};
	
	function tickValue( current, offset, numFrames ){
		if( Type.isArray( current ) === true ){
			var ret, i = current.length;
			for( ; i; ){
				--i;
				ret = tickValue( current[ i ], offset[ i ], numFrames );
				if( Type.isNumber( ret ) === true ) current[ i ] = ret;
			};
		} else {
			return current + offset / numFrames;
		};
	};
	function createCssText( update, props, style, inline ){
		var prop;
		for( var i = props.length; i; ){
			prop = style.get( props[ --i ] );
			prop.setValue( update[ i ] );
			inline[ Util.uncamelize( prop.name ) ] = prop.getValueText();
			//if( prop.name === 'backgroundColor' ) alert( prop.getValueText() + '|' + update[ i ].join( ',') )
			prop.clear();
		};
		return CSS.toCssText( inline );
	};
	
	function onEnterFrame(){
		var _ticket, l,
			i = 0;
		while( i < TICKET_ARRAY.length ){
			_ticket = TICKET_ARRAY[ i ];
			l       = _ticket.cssTexts.length;
			_ticket.elm.style.cssText = _ticket.cssTexts.shift();
			if( l === 1 ){
				_ticket.onComplete && _ticket.onComplete();
				delete _ticket.elm;
				delete _ticket.cssTexts;
				delete _ticket.onComplete;
				delete _ticket.onEnterFrame;
				delete _ticket.numFrame;
				TICKET_ARRAY.splice( i, 1 );
			} else {
				_ticket.onEnterFrame && _ticket.onEnterFrame( l / _ticket.numFrame );
				++i;
			};
		};
		if( TICKET_ARRAY.length === 0 ){
			SystemTimer.remove( SUPER_USER_KEY, onEnterFrame );
		};
	};
	
	var AnimationTaskClass = function( elm, cssTexts, onEnterFrame, onComplete, numFrame ){
		this.elm          = elm;
		this.cssTexts     = cssTexts;
		this.onEnterFrame = onEnterFrame;
		this.onComplete   = onComplete;
		this.numFrame     = numFrame;
	};
	
	var VisualEffectClass = function( elm ){
		this.elm = elm;
	};
	VisualEffectClass.prototype = {
		anime : function( _cssObject, _onComplete, _onEnterFrame, _time ){
			var _numFrames = Math.floor( _time / fpms );
			startAnimation( this.elm, _cssObject, _onComplete, _onEnterFrame, _numFrames );
		},
		fadeIn : function(){
			
		},
		fadeOut : function(){
			
		},
		update : function( x, y, w, h ){
			var _cssText = this.elm.style.cssText;
		}
	};
	
	return {
		create: function( application, _elm ){
			return new VisualEffectClass( _elm );
		},
		isInstanceOfVisualEffect: function( _instance){
			return _instance instanceof VisualEffectClass;
		}
	}
})();


/* --------------------------------------------
 * 
 */

	Application.onCurrentApplicationChange( SUPER_USER_KEY );
	
	SERVICE_LIST.push( MouseEvent );
	
	new EventTicketClass( window, 'unload', function(){
		var _service;
		while( SERVICE_LIST.length > 0 ){
			_service = SERVICE_LIST.shift();
			Type.isFunction( _service.onSystemShutdown ) === true && _service.onSystemShutdown();
		}
	});
	// beforeunload


/* ---------------------------------------------
 * broadcast to global
 */
	window.gOS = {};
	
	gOS.registerApplication = Application.register;
	gOS.registerDriver      = File.registerDriver;
	
})( window, document );
