package charactermanaj.model;

import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.Properties;

import charactermanaj.Main;
import charactermanaj.util.ErrorMessageHelper;

/**
 * アプリケーションの全域にわたる設定.<br>
 * アプリケーション設定は、クラスパス上のリソース、コートベース直下のappConfig.xml、ユーザーごとのappConfig.xmlの順に読み込まれます.<br>
 * @author seraphy
 */
public final class AppConfig {

	private static final String CONFIG_NAME = "appConfig.xml";

	private static final String CHARACTER_BASE_DIR = "characters.dir";
	
	private static final String DEFAULT_PROFILE_NAME = "default.profile.name";
	
	public static final String BASENAME = ".charactermanaj";
	
	public static final String APP_VERSION = "1.0";
	
	/**
	 * アプリケーションの実行位置を示す.<br>
	 * クラスパスのディレクトリ、もしくはjarファイルを示す.<br>
	 */
	private final File codeBase;
	
	/**
	 * ユーザーごとのアプリケーションデータ保存先.<br>
	 */
	private final File appDataDir;

	/**
	 * アプリケーション設定
	 */
	private Properties config = new Properties();
	
	
	/**
	 * シングルトンインスタンス
	 */
	private static final AppConfig singleton = new AppConfig();
	
	
	/**
	 * インスタンスを取得する.
	 * @return インスタンス
	 */
	public static AppConfig getInstance() {
		return singleton;
	}

	/**
	 * プライベートコンストラクタ
	 */
	private AppConfig() {
		// コートベースを取得
		codeBase = initBaseDir();
		// ユーザーごとのアプリケーションデータ保存先を取得
		appDataDir = initAppDateDir();
		// デフォルトプロパティの構築
		renovateAndValidate();
	}
	
	/**
	 * コードベースを取得する.
	 */
	private File initBaseDir() {
		try {
			ProtectionDomain pdomain = Main.class.getProtectionDomain();
			CodeSource codeSource = pdomain.getCodeSource();
			if (codeSource != null) {
				URL codeBaseUrl = codeSource.getLocation();
				if (codeBaseUrl.getProtocol().equals("file")) {
					File codeBase = new File(codeBaseUrl.getPath());
					return codeBase;
				}
			}
		} catch (Exception ex) { // セキュリティ例外等
			ex.printStackTrace();
			ErrorMessageHelper.showErrorDialog(null, ex);
			// 継続する.
		}

		// さしあたりカレントディレクトリをコードベースと見立てる.
		return new File(".").getAbsoluteFile();
	}
	
	/**
	 * ユーザーごとのアプリケーションデータ保存先を取得する.
	 */
	private File initAppDateDir() {
		String appData = null;
		try {
			// システムプロパティ「APPDATA」を探す
			appData = System.getProperty("APPDATA");
			if (appData == null) {
			}
			// なければ環境変数APPDATAを探す
			// Windows2000/XP/Vista/Windows7には存在する.
			appData = System.getenv("APPDATA");
			
		} catch (SecurityException e) {
			// 無視する.
		}
		if (appData == null) {
			// なければシステムプロパティ「user.home」を使う
			// このプロパティは必ず存在する
			appData = System.getProperty("user.home");
		}
		// ディレクトリ名は「.」で始まることで、MacOSX/Linux上で不可視フォルダにする。
		return new File(appData, BASENAME);
	}
	
	/**
	 * プロパティのデフォルト値を設定する.
	 */
	private void renovateAndValidate() {
		if (!config.containsKey(CHARACTER_BASE_DIR)) {
			config.setProperty(CHARACTER_BASE_DIR, "../characters");
		}
		if (!config.containsKey(DEFAULT_PROFILE_NAME)) {
			config.setProperty(DEFAULT_PROFILE_NAME, "default");
		}
	}
	
	/**
	 * プロパティをロードする.<br>
	 * リソース上の/appConfig.xml、コードベース下のappConfig.xml、アプリケーションデータ保存先のappConfig.xmlの順に読み取る.<br>
	 * 存在しないか、読み取りに失敗した場合は、該当ファイルはスキップされる.<br>
	 */
	public void loadConfig() {
		Properties config = new Properties();
		try {
			URL[] urls = {
					getClass().getResource("/appConfig.xml"),
					new File(codeBase, "../" + CONFIG_NAME).getCanonicalFile().toURL(),
					new File(getAppDataDir(), CONFIG_NAME).toURL(),
			};
			for (URL url : urls) {
				try {
					InputStream is = url.openStream();
					if (is != null) {
						try {
							config.loadFromXML(is);
						} finally {
							is.close();
						}
					}
				} catch (Exception ex) {
					// 無視する
				}
			}
		} catch (Exception ex) {
			throw new RuntimeException("appConfig.xml load failed.", ex);
		}
		this.config = config;

		renovateAndValidate();
	}

	/**
	 * プロパティをアプリケーションデータの保存先に保存する.
	 * @throws IOException 保存に失敗した場合
	 */
	public void saveConfig() throws IOException {
		File configStore = new File(getAppDataDir(), CONFIG_NAME);
		OutputStream os = new BufferedOutputStream(new FileOutputStream(configStore));
		try {
			config.storeToXML(os, "appConfig.xml", "UTF-8");
		} finally {
			os.close();
		}
	}
	
	/**
	 * 全ユーザー共通のキャラクターデータディレクトリを取得する.
	 * プロパティが絶対パスを指定するか、相対パスの場合はコードベースの相対として返される.<br>
	 * @return 全ユーザー共通のキャラクターデータディレクトリ
	 */
	public File getSystemCharactersDir() {
		File characterBaseDir = new File(config.getProperty(CHARACTER_BASE_DIR));
		try {
			if (!characterBaseDir.isAbsolute()) {
				characterBaseDir = new File(codeBase, characterBaseDir.getPath());
			}
			return  characterBaseDir.getAbsoluteFile().getCanonicalFile();

		} catch (Exception ex) {
			ex.printStackTrace();
			throw new RuntimeException("invalid characterBaseDir: " + characterBaseDir, ex);
		}
	}

	/**
	 * ユーザー固有のキャラクターデータディレクトリを取得する.
	 * @return ユーザー固有のキャラクターデータディレクトリ
	 */
	public File getUserCharactersDir() {
		File characterBaseDir = new File(getAppDataDir(), "characters");
		characterBaseDir.mkdirs();
		return characterBaseDir;
	}
	
	/**
	 * アプリケーションデータの保存先ディレクトリを取得する.
	 * @return アプリケーションデータの保存先ディレクトリ
	 */
	public File getAppDataDir() {
		appDataDir.mkdirs();
		return appDataDir;
	}

	
	
	/**
	 * デフォルトプロファイル名を取得する.
	 * @return デフォルトプロファイル名
	 */
	public String getDefaultProfileId() {
		return config.getProperty(DEFAULT_PROFILE_NAME);
	}
	
	/**
	 * プロファイル選択ダイアログのプロファイルのサンプルイメージの背景色
	 * @return サンプルイメージの背景色
	 */
	public Color getSampleImageBgColor() {
		return Color.white; // TODO: プロパティから取得するように変更する.
	}

	/**
	 * デフォルトのイメージ背景色を取得する.
	 * @return デフォルトのイメージ背景色
	 */
	public Color getDefaultImageBgColor() {
		return Color.white; // TODO: プロパティから取得するように変更する.
	}

	/**
	 * 使用中アイテムの背景色を取得する.
	 * @return 使用中アイテムの背景色
	 */
	public Color getCheckedItemBgColor() {
		return Color.cyan.brighter(); // TODO: プロパティから取得するように変更する.
	}
	
	/**
	 *　選択アイテムの背景色を取得する 
	 * @return 選択アイテムの背景色
	 */
	public Color getSelectedItemBgColor() {
		return Color.orange;  // TODO: プロパティから取得するように変更する.
	}
	
	/**
	 * 不備のあるデータ行の背景色を取得する.
	 * @return 不備のあるデータ行の背景色
	 */
	public Color getInvalidBgColor() {
		return Color.red.brighter().brighter();   // TODO: プロパティから取得するように変更する.
	}

	/**
	 * JPEG画像変換時の圧縮率を取得する.
	 * @return 圧縮率
	 */
	public float getCompressionQuality() {
		return 1.f; // TODO: プロパティから取得するように変更する.
	}

	/**
	 * エクスポートウィザードのプリセットにパーツ不足時の警告色(前景色)を取得する.
	 * @return エクスポートウィザードのプリセットにパーツ不足時の警告色(前景色)
	 */
	public Color getExportPresetWarningsForegroundColor() {
		return Color.red; // TODO: プロパティから取得するように変更する.
	}
	
	/**
	 * JARファイル転送用バッファサイズ.<br>
	 * @return JARファイル転送用バッファサイズ.
	 */
	public int getJarTransferBufferSize() {
		return 4096;
	}

	/**
	 * ZIPファイル名のエンコーディング.<br>
	 * @return ZIPファイル名のエンコーディング.<br>
	 */
	public String getZipNameEncoding() {
		return "MS932";
	}

	/**
	 * ディセーブルなテーブルのセルのフォアグラウンドカラーを取得する.
	 * @return ディセーブルなテーブルのセルのフォアグラウンドカラー
	 */
	public Color getDisabledCellForgroundColor() {
		return Color.gray;
	}
	
	/**
	 * ディレクトリを監視する間隔(mSec)を取得する.
	 * @return ディレクトリを監視する間隔(mSec)
	 */
	public int getDirWatchInterval() {
		return 7 * 1000;
	}
	
	/**
	 * ディレクトリの監視を有効にするか?
	 * @return ディレクトリの監視を有効にする場合はtrue
	 */
	public boolean isEnableDirWatch() {
		return true;
	}
	
	/**
	 * ファイル転送に使うバッファサイズ.<br>
	 * @return バッファサイズ
	 */
	public int getFileTransferBufferSize() {
		return 4096;
	}
	
	/**
	 * プレビューのインジケータを表示するまでのディレイ(mSec)を取得する.
	 * @return プレビューのインジケータを表示するまでのディレイ(mSec)
	 */
	public long getPreviewIndicatorDelay() {
		return 300;
	}
	
	/**
	 * 情報ダイアログの編集ボタンを「開く」アクションにする場合はtrue、「編集」アクションにする場合はfalse
	 * @return trueならばOpen、falseならばEdit
	 */
	public boolean isInformationDialogOpenMethod() {
		return true;
	}
}
