/*
 * Copyright (c) 2005- Shinji Kashihara.
 * All rights reserved. This program are made available under
 * the terms of the Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at epl-v10.html.
 */
package jp.sourceforge.mergedoc.pleiades;

import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;

import jp.sourceforge.mergedoc.pleiades.log.Dialogs;
import jp.sourceforge.mergedoc.pleiades.log.Logger;
import jp.sourceforge.mergedoc.pleiades.log.PopupFileLogger;
import jp.sourceforge.mergedoc.pleiades.runtime.Analyses;
import jp.sourceforge.mergedoc.pleiades.runtime.Applications;
import jp.sourceforge.mergedoc.pleiades.runtime.LauncherEclipseTransformer;
import jp.sourceforge.mergedoc.pleiades.runtime.LauncherIdeaTransformer;
import jp.sourceforge.mergedoc.pleiades.runtime.LauncherTransformer;

/**
 * Pleiades を起動するためのエージェントです。
 * <p>
 * @author cypher256
 */
public class Pleiades {

	/** OS 種類 */
	public static enum OS {
		LINUX, MAC, WINDOWS,
	}

	/** ロガー */
	private static Logger log;

	/** Pleiades パッケージ名 */
	public static final String PACKAGE_NAME = Pleiades.class.getPackage().getName();

	/** OS */
	private static OS os;

	/** Pleiades 起動オプション */
	private static PleiadesOption pleiadesOption;

	/** コンフィグレーション・パス */
	private static File pleiadesConfigurationPath;

	/** インストゥルメンテーション */
	private static Instrumentation instrumentation;

	/**
	 * 起動エントリー・ポイントとなると premain メソッドです。
	 * <p>
 	 * @param agentArg -javaagent 起動引数
	 * @param inst インストゥルメンテーション
	 * @throws Throwable 処理できないエラーが発生した場合
	 */
	public static void premain(final String agentArg, final Instrumentation inst) throws Throwable {

		// Eclipse ファイル > 再開 で premain が複数回呼び出されるバグに対応
		if (instrumentation != null) {
			return;
		}
		instrumentation = inst;

		long start = System.nanoTime();
		try {
			// Pleiades 起動オプション作成
			pleiadesOption = new PleiadesOption(agentArg);

			// 初期化
			init();

		} catch (Throwable e) {

			// ポップアップ・メッセージを表示
			Dialogs.fatal(e, "Pleiades 初期化時にエラーが発生しました。", e.getMessage());
			throw e;
		}

		try {
			// Swing のフォントを初期化。下記の理由により Windows 以外は処理しない。
			//---------------------------------------------------------------------------
			// ・Mac OSX では非同期で行うと強制終了。
			// ・同期処理でもタイミングによりイベントディスパッチスレッドで NPE が発生する。
			// ・Fedora6, 7 や CentOS では NPE が発生する。
			if (getOS() == OS.WINDOWS) {

				// Aptana でファイルタブから「Local Filesystem」を開くと VM クラッシュ。
				// L&F 変更は問題が多いため廃止。
				/*
				Asyncs.execute(new Runnable() {
					public void run() {
						try {
							initSwingFont();
						} catch (Throwable e) {
							log.debug("Swing の LookAndFeel 設定に失敗しました。" + e);
						}
					}
				});
				*/
			} else if (getOS() == OS.MAC) {
				pleiadesOption.isNoMnemonic = true;
			}

			Analyses.end(Pleiades.class, "premain", start);
			startLauncherTransformer(agentArg);

		} catch (Throwable e) {

			StringBuilder msg = new StringBuilder();
			msg.append("AOP 起動トランスフォーマーの開始に失敗しました。\n");

			// クラスキャッシュと実クラス不整合。ログファイルなど使用中のため削除はできない。
			if (pleiadesConfigurationPath != null && pleiadesConfigurationPath.exists()) {
				msg.append(pleiadesConfigurationPath);
				msg.append("\n上記ディレクトリーを削除して、");
			}
			msg.append("起動オプションに -clean を指定して起動してください。");
			log.fatal(e, msg.toString());

			System.exit(-1);
		}
	}

	/**
	 * 起動に必要な情報を初期化します。
	 */
	private static void init() throws IOException {

		long start = System.nanoTime();

		// Eclipse ファイル情報を初期化
		Applications.init();
		pleiadesConfigurationPath = Applications.getPleiadesConfigurationPath();

		// ロガーの初期化
		File logFile = new File(pleiadesConfigurationPath, "pleiades.log");
		Logger.init(PopupFileLogger.class, pleiadesOption.logLevel, logFile);
		log = Logger.getLogger(Pleiades.class);
		log.info("Pleiades キャッシュ保管パス: " + pleiadesConfigurationPath);

		Analyses.end(Pleiades.class, "init", start);
	}

	/**
	 * 起動トランスフォーマーを開始します。
	 * <p>
	 * @param agentArg -javaagent 起動引数
	 */
	private static void startLauncherTransformer(final String agentArg) {

		String message = " AOP 起動トランスフォーマーを開始します。Pleiades 起動オプション:" + agentArg;

		// Eclipse
		if (System.getProperty("java.class.path").contains("org.eclipse.")) {
			log.info("Eclipse" + message);
			pleiadesOption.appName = "eclipse";
			pleiadesOption.configXmlFile = "pleiades-config.xml";
			instrumentation.addTransformer(new LauncherEclipseTransformer());

		} else {

			// IDEA
			if (System.getProperty("idea.paths.selector") != null) {
				log.info("IDEA" + message);
				pleiadesOption.appName = "idea";
				pleiadesOption.configXmlFile = "pleiades-config-idea.xml";
				pleiadesOption.appOverrideProp = "translation-override-idea.properties";
				instrumentation.addTransformer(new LauncherIdeaTransformer());
			}
			// その他
			else {
				log.info("Java アプリケーション" + message);
				pleiadesOption.configXmlFile = "pleiades-config-default.xml";
				instrumentation.addTransformer(new LauncherTransformer());
			}
		}
	}

	/**
	 * インストゥルメンテーションを取得します。
	 * <p>
	 * @return インストゥルメンテーション
	 */
	public static Instrumentation getInstrumentation() {
		return instrumentation;
	}

	/**
	 * Pleiades 起動オプションを取得します。
	 * <p>
	 * @return Pleiades 起動オプション
	 */
	public static PleiadesOption getPleiadesOption() {
		if (pleiadesOption == null) {
			pleiadesOption = new PleiadesOption(null); // for JUnit
		}
		return pleiadesOption;
	}

	/**
	 * Pleiades コンフィグレーション・パスからの相対パスを指定してファイルを取得します。
	 * <p>
	 * 通常、Pleiades コンフィグレーション・パスは
	 * configuration/jp.sourceforge.mergedoc.pleiades
	 * で、Eclipse の config.ini の指定がある場合はそれが優先されます。
	 * <p>
	 * @param path Pleiades コンフィグレーション・パスからの相対パス
	 * @return ファイル
	 */
	public static File getResourceFile(String path) {
		return new File(pleiadesConfigurationPath, path);
	}

	/**
	 * OS の種類を取得します。
	 * @return OS
	 */
	public static OS getOS() {
		if (os != null) {
			return os;
		}
		String osName = System.getProperty("os.name", "").toLowerCase();
		if (osName.contains("mac")) {
			os = OS.MAC;
		} else if (osName.contains("windows")) {
			os = OS.WINDOWS;
		} else {
			os = OS.LINUX;
		}
		return os;
	}

	// L&F 変更は問題が多いため廃止。2010.02.07
	/**
	 * Swing で使用するフォントを初期化します。
	 * Eclipse のプラグインは通常 SWT が使用されていますが、
	 * 一部のプラグインでは Swing も利用されています。
	 * <p>
	 * @throws ClassNotFoundException LookAndFeel クラスが見つからなかった場合
	 * @throws InstantiationException クラスの新しいインスタンスを生成できなかった場合
	 * @throws IllegalAccessException クラスまたは初期化子にアクセスできない場合
	 * @throws UnsupportedLookAndFeelException lnf.isSupportedLookAndFeel() が false の場合
	private static void initSwingFont() throws ClassNotFoundException,
			InstantiationException, IllegalAccessException,
			UnsupportedLookAndFeelException {

		long start = System.nanoTime();

		// LookAndFeel の設定
		UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		Toolkit.getDefaultToolkit().setDynamicLayout(true);

		// フォント設定
		Object propoFont = new FontUIResource("MS UI Gothic",	Font.TRUETYPE_FONT, 12);
		Object fixedFont = new FontUIResource("MS Gothic",		Font.TRUETYPE_FONT, 12);

		// 一旦すべてのコンポーネントのフォントをプロポーショナルにする。
		// フォントリソースかの判定は値を取得し instanceof FontUIResource が
		// 安全だが、UIDefaults の Lazy Value を生かすため末尾の文字列で判定。
		for (Object keyObj : UIManager.getLookAndFeelDefaults().keySet()) {
			String key = keyObj.toString();
			if (key.endsWith("font") || key.endsWith("Font")) {
				UIManager.put(key, propoFont);
			}
		}

		// 特定コンポーネントのフォントを等幅にする
		UIManager.put("TextPane.font", fixedFont);
		UIManager.put("TextArea.font", fixedFont);

		Analyses.end(Pleiades.class, "initSwingFont<THREAD>", start);
	}
	 */
}
