/*
 * 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.aspect;

import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.sourceforge.mergedoc.pleiades.Pleiades;
import jp.sourceforge.mergedoc.pleiades.Pleiades.OS;
import jp.sourceforge.mergedoc.pleiades.log.Logger;
import jp.sourceforge.mergedoc.pleiades.resource.Files;
import jp.sourceforge.mergedoc.pleiades.resource.PropertySet;

/**
 * Eclipse ベース製品のファイル情報を参照するためのクラスです。
 * <p>
 * @author cypher256
 */
public class EclipseFiles {

	/** Eclipse ホーム・ディレクトリー */
	private static File home;

	/** OSGI ホーム・ディレクトリー */
	private static File osgiHome;

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

	/** Eclipse のバージョン */
	private static String version;

	/**
	 * このクラスを初期化します。
	 */
	public static void init(String configurationDir) throws IOException {

		File configIniFile = getConfigIniFile(configurationDir);

		// Eclipse ではない場合 (IDEA など)
		if (configIniFile == null) {
			File configDir = new File(getSystemUserDir(), configurationDir);
			pleiadesConfigurationPath = new File(configDir, Pleiades.PACKAGE_NAME);
		}

		// Eclipse の場合
		else {
			pleiadesConfigurationPath = getPleiadesConfigPath(configIniFile);
		}
		pleiadesConfigurationPath.mkdirs();
	}

	/**
	 * config.ini ファイルを取得します。
	 * @return config.ini ファイル
	 */
	private static File getConfigIniFile(String configurationDir) {

		final String CONFIG_INI = "config.ini";
		final String CONFIG_DIR_INI = configurationDir + "/" + CONFIG_INI;

		File configIniFile = new File(getSystemUserDir(), CONFIG_DIR_INI);
		if (configIniFile.exists()) {
			return configIniFile;
		}
		File eclipseDir = Files.resourceRoot.getParentFile().getParentFile().getParentFile();

		// config ディレクトリがデフォルトの名前の場合の検索 (ディレクトリー＋ファイル名で検索)
		for (File dir = eclipseDir; dir != null; dir = dir.getParentFile()) {
			configIniFile = new File(dir, CONFIG_DIR_INI);
			if (configIniFile.exists()) {
				return configIniFile;
			}
		}

		// config ディレクトリが -configuration で変更されている場合の検索 (ファイル名だけで検索)
		for (File dir = eclipseDir; dir != null; dir = dir.getParentFile()) {
			for (File d : dir.listFiles(Files.createDirectoryFilter())) {
				configIniFile = new File(d, CONFIG_INI);
				if (configIniFile.exists()) {
					return configIniFile;
				}
			}
		}

		// Eclipse からの実行ではない場合
		return null;
	}

	/**
	 * Java 実行時のカレント・ディレクトリーを取得します。
	 * @return カレント・ディレクトリー
	 */
	private static String getSystemUserDir() {
		return System.getProperty("user.dir");
	}

	/**
	 * Eclipse 設定から Pleiades 構成保存用のパスを取得します。
	 * @param configIniFile config.ini ファイル
	 * @return Pleiades 構成保存パス
	 */
	private static File getPleiadesConfigPath(File configIniFile) {

		// config.ini の位置を元に ホーム・ディレクトリーを取得
		File configPath = configIniFile.getParentFile();
		home = configPath.getParentFile();
		PropertySet configIni = new PropertySet(configIniFile);

		// バンドル・ホームを取得
		// 通常は ホーム・ディレクトリーと同じですが、p2agent を使用した場合、
		// インストール時に指定したバンドル・プール・ロケーションとなり、
		// このディレクトリー配下の plugins に基本的なプラグイン群が格納されます。

		String osgiJarPath = configIni.get("osgi.framework");
		if (osgiJarPath != null) {
			File jarFile = new File(osgiJarPath.replace("file:", ""));
			if (jarFile.exists()) {
				osgiHome = jarFile.getParentFile().getParentFile();
			}
		}
		if (osgiHome == null) {
			osgiHome = home;
		}

		// config.ini からユーザー・コンフィグレーション・パスの取得
		final String CONFIG_AREA_KEY = "osgi.configuration.area";
		String configArea = configIni.get(CONFIG_AREA_KEY);
		if (configArea == null) {
			configArea = System.getProperty(CONFIG_AREA_KEY);
		}
		File userConfigPath = null;
		if (configArea != null) {
			Matcher mat = Pattern.compile("^@([\\w\\.]+)(.+)$").matcher(configArea);
			if (mat.find()) {
				String base = System.getProperty(mat.group(1));
				if (base != null) {
					File baseDir = new File(base);
					if (baseDir.exists()) {
						String childPath = mat.group(2);
						userConfigPath = new File(baseDir, childPath);
					}
				}
			}
		}

		// config.ini に指定がある場合
		if (userConfigPath != null) {
			return new File(userConfigPath, Pleiades.PACKAGE_NAME);
		}
		// config.ini に指定がない場合
		else {
			if (Pleiades.getOS() == OS.WINDOWS) {
				// Windows の場合は configPath デフォルト
				// <インストールディレクトリ>/configuration/
				return new File(configPath, Pleiades.PACKAGE_NAME);

			} else {
				// Linux、Mac の場合はマルチユーザ環境での書き込み権なしに対応
				// <ユーザホーム>/.eclipse/<パッケージ名>/<インストールパスの - 区切り>/
				File base = new File(System.getProperty("user.home"), ".eclipse/" + Pleiades.PACKAGE_NAME);
				String instPath = home.getPath().replace("/", "-");
				return new File(base, instPath);
			}
		}
	}

	/**
	 * Eclipse 製品のホーム・ディレクトリーを取得します。
	 * @return Eclipse ホーム・ディレクトリー。Eclipse ではない場合 (IDEA など) は null。
	 */
	public static File getHome() {
		return home;
	}

	/**
	 * Eclipse 製品の OSGI ホーム・ディレクトリーを取得します。
	 * @return Eclipse OSGI ホーム・ディレクトリー。Eclipse ではない場合 (IDEA など) は null。
	 */
	public static File getOsgiHome() {
		return osgiHome;
	}

	/**
	 * Pleiades 構成保存パスを取得します。
	 * @return Pleiades 構成保存パス
	 */
	public static File getPleiadesConfigurationPath() {
		return pleiadesConfigurationPath;
	}

	/**
	 * Eclipse のバージョン文字列を取得します。
	 * @return Eclipse バージョン文字列。取得できない場合は空文字。
	 */
	public static synchronized String getVersion() {

		if (version != null) {
			return version;
		}
		version = "";
		if (osgiHome == null) {
			return version;
		}

		// Eclipse のバージョン取得
		File featuresFolder = new File(osgiHome, "features");
		if (!featuresFolder.exists()) {
			return version;
		}
		for (File folder : featuresFolder.listFiles()) {
			String folderName = folder.getName();
			if (folderName.startsWith("org.eclipse.platform_")) {
				version = folderName.replaceFirst(".*?_(\\d+\\.\\d+).*", "$1");
				break;
			}
		}
		return version;
	}

	/**
	 * 前回起動時から Eclipse プラグインが更新されているか判定します。
	 * @return 1 つでも更新されている場合は true
	 */
	public static boolean isPluginUpdated() throws IOException {

		long start = System.nanoTime();
		File pluginModifiedFile = new File(pleiadesConfigurationPath, ".plugin-modified");
		long prevLastModified = pluginModifiedFile.lastModified();

		long lastModified = prevLastModified;
		lastModified = getLastModified(new File(osgiHome, "plugins"), lastModified);
		lastModified = getLastModified(new File(osgiHome, "dropins"), lastModified);

		final Logger log = Logger.getLogger(EclipseFiles.class);
		log.info("前回のプラグイン更新最終時刻: %tF %tT", prevLastModified, prevLastModified);
		log.info("今回のプラグイン更新最終時刻: %tF %tT", lastModified, lastModified);

		boolean isUpdated = false;
		if (lastModified > prevLastModified) {
			pluginModifiedFile.createNewFile();
			pluginModifiedFile.setLastModified(lastModified);
			isUpdated = true;
		}
		Analyses.end(EclipseFiles.class, "isPluginUpdated", start);
		return isUpdated;
	}

	/**
	 * 指定したディレクトリーに含まれるディレクトリーの最新の更新時刻を取得します。
	 * @param parentDir 親ディレクトリー
	 * @param latest 最新時刻
	 * @return 最新時刻
	 */
	private static long getLastModified(File parentDir, long latest) {

		if (!parentDir.exists()) {
			return latest;
		}
		String parentName = parentDir.getName();
		if (parentName.equals("features")) {
			return latest;
		}

		boolean isParentPluginsDir = parentName.equals("plugins");

		for (File dir : parentDir.listFiles(Files.createDirectoryFilter())) {

			if (isParentPluginsDir) {
				long modified = dir.lastModified();
				if (modified > latest) {
					latest = modified;
				}
			} else {
				latest = getLastModified(dir, latest); // 再帰
			}
		}

		if (isParentPluginsDir) {
			for (File file : parentDir.listFiles(Files.createSuffixFilter(".jar"))) {

				long modified = file.lastModified();
				if (modified > latest) {
					latest = modified;
				}
			}
		}
		return latest;
	}
}
