package core.config;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

import org.apache.logging.log4j.LogManager;

import core.exception.PhysicalException;

/**
 * 動作環境保持
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class Env {

	/** プロパティ名 */
	private static final String PROP_NAME = "environment";

	/** 自身保持用 */
	private static final AtomicReference<Env> INSTANCE = new AtomicReference<>();

	/** 格納用ハッシュ */
	private final ConcurrentMap<String, String> env = new ConcurrentHashMap<>();

	/**
	 * コンストラクタ
	 *
	 */
	private Env() {
		if (INSTANCE.get() != null) {
			throw new AssertionError();
		}
	}

	/**
	 * キーに関連した値を返す。
	 *
	 * @param key キー
	 * @return 値
	 */
	public static String getEnv(final String key) {
		return getEnv(key, "");
	}

	/**
	 * キーに関連した値を返す。
	 *
	 * @param key キー
	 * @param def デフォルト
	 * @return 値
	 */
	public static boolean getEnv(final String key, final boolean def) {
		final String ret = getEnvInstance().getEnvMap().get(key);
		if (Objects.toString(ret, "").isEmpty()) {
			return def;
		}
		return Boolean.parseBoolean(ret);
	}

	/**
	 * キーに関連した値を返す。
	 *
	 * @param key キー
	 * @param def デフォルト
	 * @return 値
	 */
	public static int getEnv(final String key, final int def) {
		final String ret = getEnvInstance().getEnvMap().get(key);
		if (!Objects.toString(ret, "").isEmpty()) {
			try {
				return Integer.parseInt(ret.replaceAll("[, ]", ""));
			} catch (final NumberFormatException ex) {
				LogManager.getLogger().info(ex.getMessage());
				return def;
			}
		}
		return def;
	}

	/**
	 * キーに関連した値を返す。
	 *
	 * @param key キー
	 * @param def デフォルト
	 * @return 値
	 */
	public static String getEnv(final String key, final String def) {
		String ret = getEnvInstance().getEnvMap().get(key);
		if (Objects.toString(ret, "").isEmpty()) {
			ret = def;
		}
		return ret;
	}

	/**
	 * キーに関連した値を配列で返す。
	 *
	 * @param key キー
	 * @return 値
	 */
	public static String[] getEnvArray(final String key) {
		final String ret = getEnvInstance().getEnvMap().get(key);
		if (Objects.toString(ret, "").isEmpty()) {
			return new String[0];
		}
		return ret.split(",", -1);
	}

	/**
	 * エントリセット取得
	 * @return エントリセット
	 */
	public static Set<Entry<String, String>> entrySet() {
		return Collections.unmodifiableSet(getEnvInstance().getEnvMap().entrySet());
	}

	/**
	 * インスタンス取得
	 * @return Env
	 */
	private static Env getEnvInstance() {
		if (INSTANCE.get() == null) {
			if (INSTANCE.compareAndSet(null, new Env())) {
				INSTANCE.get().env.clear();
				setPropaties(PROP_NAME, INSTANCE.get().env);
			}
		}
		return INSTANCE.get();
	}

	/**
	 * Envマップ取得
	 * @return Envマップ
	 */
	private ConcurrentMap<String, String> getEnvMap() {
		return this.env;
	}

	/**
	 * 環境変数をセットする。
	 *
	 * @param name プロパティ名
	 * @param map マップオブジェクト
	 */
	public static void setPropaties(final String name, final Map<String, String> map) {
		final Function<ResourceBundle, String> toResourceName = resource -> {
			String ret = name;
			final String locale = Objects.toString(resource.getLocale(), null);
			if (!Objects.toString(locale, "").isEmpty()) {
				ret = name + "_" + locale;
			}
			return ret.replace('.', '/') + ".properties";
		};

		try {
			// 設定ファイル読込
			final ResourceBundle resource = ResourceBundle.getBundle(name);
			try (InputStream is = Env.class.getClassLoader().getResourceAsStream(
					toResourceName.apply(resource))) {
				if (is != null) {
					final Properties p = new EncodingProperties(resource);
					p.load(is);
					for (final Entry<Object, Object> ent : p.entrySet()) {
						map.put(String.valueOf(ent.getKey()), String.valueOf(ent.getValue()));
					}
				}
			} catch (final IOException ex) {
				LogManager.getLogger().error(ex.getMessage(), ex);
				throw new PhysicalException(ex);
			}
		} catch (final MissingResourceException ex) {
			LogManager.getLogger().info(ex.getMessage());
		}
	}
}
