package charactermanaj.util;

import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Properties;

/**
 * xml形式のリソース上のプロパティファイルのローカライズされた読み込みを行うためのクラス.<br>
 * リソースは、単純名、言語名を末尾に付与したもの、言語名と国を末尾に付与したもの、言語名と国とバリアントを末尾に付与したもの、の順で読み取られる.<br>
 * 順番に読み込んで重ね合わせる.<br>
 * 一度読み込んだものはキャッシュされ次回以降は読み取られない.<br>
 */
public class LocalizedResourcePropertyLoader extends ResourceLoader {
	
	/**
	 * プロパティファイル群と、それに対するキャッシュ
	 */
	private HashMap<ResourceNames, Properties> propCache = new HashMap<ResourceNames, Properties>();
	
	/**
	 * シングルトンインスタンス
	 */
	private static final LocalizedResourcePropertyLoader inst = new LocalizedResourcePropertyLoader();
	
	/**
	 * プライベートコンストラクタ
	 */
	private LocalizedResourcePropertyLoader() {
		super();
	}
	
	/**
	 * インスタンスを取得する
	 * @return インスタンス
	 */
	public static LocalizedResourcePropertyLoader getInstance() {
		return inst;
	}
	
	/**
	 * リソース名を指定してデフォルトのロケールでローカライズされたリソースプロパティを読み込む.<br>
	 * リソースはxml形式である。リソース名には.xmlを付与しない.(自動的に内部で付与される.)
	 * @param name リソース名
	 * @return プロパティ
	 */
	public Properties getLocalizedProperties(String name) {
		return getLocalizedProperties(name, null);
	}
	
	/**
	 * リソース名を指定して指定したロケールでローカライズされたリソースプロパティを読み込む.<br>
	 * リソースはxml形式である。リソース名には.xmlを付与しない.(自動的に内部で付与される.)
	 * @param name リソース名
	 * @param locale ロケール、nullの場合はデフォルトのロケール
	 * @return プロパティ
	 */
	public Properties getLocalizedProperties(String name, Locale locale) {
		if (name == null || name.length() == 0) {
			throw new IllegalArgumentException();
		}
		if (locale == null) {
			locale = Locale.getDefault();
		}

		String language = locale.getLanguage();
		String country = locale.getCountry();
		String variant = locale.getVariant();
		
		String[] resourceNames = {
			name + ".xml",
			name + "_" + language + ".xml",
			name + "_" + language + "_" + country + ".xml",
			name + "_" + language + "_" + country + "_" + variant + ".xml",
		};

		return getProperties(new ResourceNames(resourceNames));
	}

	/**
	 * リソース名群をもとにキャッシュもしくはプロパティをロードして返す.<br>
	 * キャッシュされていない場合はプロパティをロードして、それをキャッシュに格納する.<br>
	 * リソースが一つも存在しない場合は実行時例外を発生させる.<br>
	 * @param resourceNames リソース名群
	 * @return プロパティ
	 */
	protected Properties getProperties(ResourceNames resourceNames) {
		if (resourceNames == null) {
			throw new IllegalArgumentException();
		}
		Properties prop;
		synchronized (propCache) {
			prop = propCache.get(resourceNames);
			if (prop == null) {
				prop = loadProperties(resourceNames);
				propCache.put(resourceNames, prop);
			}
		}
		if (prop == null) {
			throw new RuntimeException("missing resource: " + resourceNames);
		}
		return prop;
	}

	/**
	 * リソース名群からリソースプロパティをロードして返す.<br>
	 * 一つも存在しない場合はnullを返す.<br>
	 * @param resourceNames リソース群名
	 * @return プロパティ
	 */
	protected Properties loadProperties(ResourceNames resourceNames) {
		if (resourceNames == null) {
			throw new IllegalArgumentException();
		}

		ClassLoader loader = getClassLoader();
		
		boolean foundResource = false;
		Properties props = new Properties();
		for (String resourceName : resourceNames) {
			URL resource = loader.getResource(resourceName);
			if (resource != null) {
				Properties org = new Properties();
				try {
					InputStream is = resource.openStream();
					try {
						org.loadFromXML(is);
					} finally {
						is.close();
					}
				} catch (Exception ex) {
					throw new RuntimeException("resource loading error." + resource, ex);
				}
				foundResource = true;
				
				props.putAll(org);
			}
		}
		
		if (foundResource) {
			return props;
		}
		return null;
	}
}
