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

	/**
	 * コンストラクタ
	 */
	initialize: function(properties) {
		this.plugins = {};
		this.properties = properties || {};
		this.loader = new maskat.lang.Thread(this, this.process, null, 100);
	},

	install: function() {
		for (var pluginId in this.properties) {
			if (this.properties[pluginId].enabled) {
				this.installPlugin(pluginId);
			}
		}

		/* プラグインローダーのスレッドを起動 */
		if (!this.loader.isRunning()) {
			this.loader.start();
		}
	},

	start: function() {
		for (var pluginId in this.plugins) {
			this.startPlugin(pluginId);
		}
	},

	/**
	 * プラグインローダーのスレッドから一定間隔で実行されるメソッドです。
	 *
	 * プラグインごとに動作ステータスを確認し、ライフサイクルを管理します。
	 * 動作ステータスは以下のように進行します。
	 *
	 *  - INIT:    plugin.js を読み込み中
	 *  - INSTALL: プラグイン (plugin.js) を読み込み中 
	 *  - LOADING: リソース (JavaScript/CSS など) を読み込み中
	 *  - READY:   リソースのロードが完了し、実行可能
	 *  - RUNNING: プラグインを実行中
	 */
	process: function() {
		try {
			/* 各プラグインの状態によって処理を分岐 */
			for (var pluginId in this.plugins) {
				var plugin = this.getPlugin(pluginId); 
				if (plugin) {
					switch (plugin.getStatus()) {
					case maskat.core.Plugin.INSTALL:
						/* インストール済みの場合はロード処理を実行 */
						this.loadPlugin(pluginId);
						break;
			
					case maskat.core.Plugin.LOADING:
						/* ロード中の場合はロードが完了しているか確認 */
						if (plugin.isLoaded()) {
							plugin.setStatus(maskat.core.Plugin.READY); 
						}
						break;
					}
				} else {
					/*
					 * プラグイン (plugin.js) の自動読み込み中の場合は
					 *インストール処理を再試行
					 */
					this.installPlugin(pluginId);
				}
			}
			
			/* すべてのプラグインのロードが完了した場合、スレッドを停止 */
			if (this.isReady()) {
				this.loader.stop();
				this.onReady();
			}
		} catch (e) {
			this.loader.stop();
			var logger = maskat.log.LogFactory.getLog("maskat.core");
			logger.error(e.getMessages ? e.getMessages().join("\n") : e.message);
		}
	},

	/**
	 * すべてのプラグインのロードが完了しているかどうかを返します。
	 *
	 * @return ロードが完了している場合は true, それ以外の場合は false
	 */
	isReady: function() {
		for (var pluginId in this.plugins) {
			var plugin = this.getPlugin(pluginId);
			if (!plugin || plugin.getStatus() < maskat.core.Plugin.READY) {
				return false;
			}
		}
		return true;
	},

	/**
	 * すべてのプラグインのロード完了時に実行されるコールバックメソッドです。
	 *
	 * このメソッドはサブクラスやインスタンスメソッドでオーバーライドされる
	 * ことを意図しています。
	 */
	onReady: function() {
		/* NOP */
	},

	/**
	 * 指定されたプラグイン識別子を持つプラグインを検索し、プラグインの
	 * インスタンスを返却します。
	 * 
	 * @return プラグインのインスタンス
	 */
	getPlugin: function(pluginId) {
		return this.plugins[pluginId];
	},

	/**
	 * 新しいプラグインをインストールします。
	 *
	 * @param pluginId プラグイン識別子
	 */
	installPlugin: function(pluginId) {
		var pluginClass = maskat.core.Plugin.registry[pluginId]; 
		var plugin = this.getPlugin(pluginId);

		/* プラグインクラスが未登録の場合、plugin.js を自動的に読み込む */		
 		if (!pluginClass) {
 			if (typeof(plugin) == "undefined") {
				maskat.app.loadJavaScript(
					maskat.location + pluginId + "/plugin.js", true);
				this.plugins[pluginId] = false;
			}
			return;
		}

		/* プラグインを取得 (または生成) する */
		if (!plugin) {
			plugin = new pluginClass();
			this.plugins[pluginId] = plugin; 
		}

		/* すでにインストール処理を開始済みの場合は何もしない */
		if (plugin.getStatus() >= maskat.core.Plugin.INSTALL) {
			return;
		}
		plugin.setStatus(maskat.core.Plugin.INSTALL);
		plugin.setProperties(this.properties[pluginId]);

		/* 依存関係にあるプラグインをインストール */
		var dependencies = plugin.getDependencies();
		if (dependencies) {
			for (var i = 0; i < dependencies.length; i++) {
				this.installPlugin(dependencies[i]);
			}
		}
	},

	/**
	 * プラグインのロード処理を実行します。
	 *
	 * 指定されたプラグインが他のプラグインに依存している場合、依存している
	 * プラグインのロード後にロード処理が開始されます。
	 *
	 * @param pluginId プラグイン識別子
	 */
	loadPlugin: function(pluginId) {
		var plugin = this.getPlugin(pluginId);

		/* すでにロード処理を開始済みの場合は何もしない */
		if (plugin.getStatus() >= maskat.core.Plugin.LOADING) {
			return;
		}

		/* 依存関係にあるプラグインのロード完了まで待機する */
		var dependencies = plugin.getDependencies();
		if (dependencies) {
			for (var i = 0; i < dependencies.length; i++) {
				var dependee = this.getPlugin(dependencies[i]);
				if (!dependee || dependee.getStatus() < maskat.core.Plugin.READY) {
					return;
				}
			}
		}

		/* プラグインのロード処理を実行 */
		if (!plugin.isLoaded()) {
			plugin.load();
		}
		plugin.setStatus(maskat.core.Plugin.LOADING);
	},

	/**
	 * プラグインの実行を開始します。
	 *
	 * 指定されたプラグインが他のプラグインに依存している場合、依存している
	 * プラグインの実行後に実行が開始されます。
	 *
	 * @param pluginId プラグイン識別子
	 */
	startPlugin: function(pluginId){
		var plugin = this.getPlugin(pluginId);

		/* すでに実行中の場合は何もしない */
		if (plugin.getStatus() >= maskat.core.Plugin.RUNNING) {
			return;
		}
		plugin.setStatus(maskat.core.Plugin.RUNNING);

		/* 依存関係にあるプラグインを再帰的に実行 */
		var dependencies = plugin.getDependencies();
		if (dependencies) {
			for (var i = 0; i < dependencies.length; i++) {
				this.startPlugin(dependencies[i]);
			}
		}

		/* プラグインを実行 */
		plugin.start(this);
	}

});
