package core.config;

import java.io.IOException;
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.UnaryOperator;

import org.apache.logging.log4j.LogManager;

import core.exception.PhysicalException;

/**
 * 動作環境保持
 *
 * @author Tadashi Nakayama
 */
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 var 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 var 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;
	}

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

	/**
	 * キーに関連した値を配列で返す。
	 *
	 * @param key キー
	 * @return 値
	 */
	public static String[] getEnvArray(final String key) {
		final var 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();
				setProperties(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 setProperties(final String name, final Map<String, String> map) {
		final UnaryOperator<String> toResource = res -> {
			final var bundle = ResourceBundle.getBundle(res);
			final var locale = Objects.toString(bundle.getLocale(), "");
			return (locale.isEmpty() ? res : res + "_" + locale).replace('.', '/') + ".properties";
		};

		// 設定ファイル読込
		try (var is = Env.class.getClassLoader().getResourceAsStream(toResource.apply(name))) {
			if (is != null) {
				final var p = new Properties();
				p.load(is);
				for (final var 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());
		}
	}
}
