package jp.sf.amateras.functions;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;

import jp.sf.amateras.functions.utils.RequestUtils;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * Log4jを使ってログを出力するための関数を提供します。
 * <p>
 * このクラスが提供する関数を使用して出力したログは以下のルールにしたがって作成された<code>Logger</code>を用いて出力されます。
 * <ol>
 *   <li>HttpServletRequest#getServletPath()でサーブレットパスを取得</li>
 *   <li>サーブレットパスの先頭の / と末尾の .jsp を除去</li>
 *   <li><strong>jsp.&lt;サーブレットパスの / を . に変換した文字列&gt;</strong> をLogger名とする</li>
 * </ol>
 * JSPのパスとLogger名の例を以下に示します。Log4側ではこのLogger名に応じてアペンダを定義することでJSPからのログ出力を制御することができます。
 * <ul>
 *   <li>/index.jsp の場合 jsp.index</li>
 *   <li>/admin/index.jsp の場合 jsp.admin.index</li>
 * </ul>
 *
 * @author Naoki Takezoe
 */
public class Log4j {

	/**
	 * サーブレットパスからLogger名への変換結果をキャッシュしておくマップ。
	 */
	private static Map<String, String> loggerNameMap = new ConcurrentHashMap<String, String>();

	/**
	 * サーブレットパスから<code>Logger</code>の名前を取得します。
	 *
	 * @param servletPath <code>HttpServletRequest#getServletPath()</code>で取得したサーブレットパス。
	 * @return <code>Logger</code>の名前
	 */
	protected static String getLoggerName(String servletPath){
		// Logger名がキャッシュに存在する場合
		if(loggerNameMap.containsKey(servletPath)){
			return loggerNameMap.get(servletPath);
		}

		String path = servletPath;
		String loggerName = "";

		if(path.endsWith(".jsp")){
			loggerName = "jsp.";
			path = path.replaceFirst("\\.jsp$", "");
		}

		path = path.replace('/', '.');
		if(path.startsWith(".")){
			path = path.substring(1);
		}

		loggerName = loggerName + path;

		// Logger名をキャッシュに保存しておく
		loggerNameMap.put(servletPath, loggerName);

		return loggerName;
	}

	/**
	 * ログ出力用の各メソッドで使用する<code>Logger</code>を取得します。
	 *
	 * @return ログ出力用の各メソッドで使用する<code>Logger</code>
	 */
	protected static Logger getLogger(){
		HttpServletRequest request = RequestUtils.getRequest();
		return Logger.getLogger(getLoggerName(request.getServletPath()));
	}

	/**
	 * TRACEレベルでを出力します。
	 *
	 * @param message ログに出力するメッセージ
	 */
	public static void trace(Object message){
		getLogger().trace(message);
	}

	/**
	 * DEBUGレベルでログを出力します。
	 *
	 * @param message ログに出力するメッセージ
	 */
	public static void debug(Object message){
		getLogger().debug(message);
	}

	/**
	 * INFOレベルでログを出力します。
	 *
	 * @param message ログに出力するメッセージ
	 */
	public static void info(Object message){
		getLogger().info(message);
	}

	/**
	 * WARNレベルでログを出力します。
	 *
	 * @param message ログに出力するメッセージ
	 */
	public static void warn(Object message){
		getLogger().warn(message);
	}

	/**
	 * ERRORレベルでログを出力します。
	 *
	 * @param message ログに出力するメッセージ
	 */
	public static void error(Object message){
		getLogger().error(message);
	}

	/**
	 * FATALレベルでログを出力します。
	 *
	 * @param message ログに出力するメッセージ
	 */
	public static void fatal(Object message){
		getLogger().fatal(message);
	}

	/**
	 * TRACEログが有効な場合のみ引数で渡したオブジェクトをJSPに表示します。
	 *
	 * @param message 表示するオブジェクト
	 * @return 表示する文字列（TRACEログが無効な場合は空文字列）
	 */
	public static String printTrace(Object message){
		return print(Level.TRACE, message);
	}

	/**
	 * DEBUGログが有効な場合のみ引数で渡したオブジェクトをJSPに表示します。
	 *
	 * @param message 表示するオブジェクト
	 * @return 表示する文字列（DEBUGログが無効な場合は空文字列）
	 */
	public static String printDebug(Object message){
		return print(Level.DEBUG, message);
	}

	/**
	 * WARNログが有効な場合のみ引数で渡したオブジェクトをJSPに表示します。
	 *
	 * @param message 表示するオブジェクト
	 * @return 表示する文字列（WARNログが無効な場合は空文字列）
	 */
	public static String printWarn(Object message){
		return print(Level.WARN, message);
	}

	/**
	 * INFOログが有効な場合のみ引数で渡したオブジェクトをJSPに表示します。
	 *
	 * @param message 表示するオブジェクト
	 * @return 表示する文字列（INFOログが無効な場合は空文字列）
	 */
	public static String printInfo(Object message){
		return print(Level.INFO, message);
	}

	/**
	 * ERRORログが有効な場合のみ引数で渡したオブジェクトをJSPに表示します。
	 *
	 * @param message 表示するオブジェクト
	 * @return 表示する文字列（ERRORログが無効な場合は空文字列）
	 */
	public static String printError(Object message){
		return print(Level.ERROR, message);
	}

	/**
	 * FATALログが有効な場合のみ引数で渡したオブジェクトをJSPに表示します。
	 *
	 * @param message 表示するオブジェクト
	 * @return 表示する文字列（FATALログが無効な場合は空文字列）
	 */
	public static String printFatal(Object message){
		return print(Level.FATAL, message);
	}

	/**
	 * TRACEログが有効かどうかを返します。
	 *
	 * @return TRACEログが有効な場合true、無効な場合false
	 */
	public static boolean isTraceEnabled(){
		return getLogger().isEnabledFor(Level.TRACE);
	}

	/**
	 * DEBUGログが有効かどうかを返します。
	 *
	 * @return DEBUGログが有効な場合true、無効な場合false
	 */
	public static boolean isDebugEnabled(){
		return getLogger().isEnabledFor(Level.DEBUG);
	}

	/**
	 * INFOログが有効かどうかを返します。
	 *
	 * @return INFOログが有効な場合true、無効な場合false
	 */
	public static boolean isInfoEnabled(){
		return getLogger().isEnabledFor(Level.INFO);
	}

	/**
	 * WARNログが有効かどうかを返します。
	 *
	 * @return WARNログが有効な場合true、無効な場合false
	 */
	public static boolean isWarnEnabled(){
		return getLogger().isEnabledFor(Level.WARN);
	}

	/**
	 * ERRORログが有効かどうかを返します。
	 *
	 * @return ERRORログが有効な場合true、無効な場合false
	 */
	public static boolean isErrorEnabled(){
		return getLogger().isEnabledFor(Level.ERROR);
	}

	/**
	 * FATALログが有効かどうかを返します。
	 *
	 * @return FATALログが有効な場合true、無効な場合false
	 */
	public static boolean isFatalEnabled(){
		return getLogger().isEnabledFor(Level.FATAL);
	}

	private static String print(Level level, Object message){
		if(getLogger().isEnabledFor(level)){
			if(message == null){
				return "null";
			}
			return message.toString();
		}
		return "";
	}

}
