package daruma.util;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import daruma.util.PropertyReader;
import java.io.InputStream;
import java.net.URL;
import org.w3c.dom.Element;
import daruma.xml.util.XMLFormatConverter;
import javax.xml.transform.TransformerException;

import daruma.util.Itk;


/**
 * ログを出力するクラス。<BR>
 * ログ出力にはJakartaプロジェクトのLog4jを利用します。<BR>
 * ログレベル、出力先、出力ファイルの大きさ、管理世代数は
 * 設定ファイル(daruma.properties)で管理します。<BR>
 * なお、設定ファイルで設定したレベルより低いレベルのログは出力されません。<BR>
 */
public class LogWriter {

	// logger名
	private static Logger logger = null;

	// ログレベルに関わらず、ログを書き出すか否か
	private static boolean isVerboseMode = false;

	private static boolean initialized = false;

	// ログ名
	private static final String LOG_NAME = "daruma.log.name";

	/**
	 * LogWriterのコンストラクタ。<BR>
	 * 以下の処理が行われる。<BR>
	 * <ol>
	 * <li>Loggerの取得。これはプロパティ管理クラス
	 *（ReadProperty）を利用し取得する。</li>
	 * <li>環境設定ファイルに設定されているログ設定ファイルに従いログの設定。</li>
	 * </ol>
	 */
	private static void initialize() {
		String log_name = PropertyReader.getProperty(LOG_NAME);

		logger = Logger.getLogger(log_name);
		PropertyConfigurator.configure(PropertyReader.getFileName());
		initialized = true;
	}

	/**
	 * LogWriterのコンストラクタ。<BR>
	 * 以下の処理が行われる。<BR>
	 * <ol>
	 * <li>Loggerの取得。これはプロパティ管理クラス
	 *（ReadProperty）を利用し取得する。</li>
	 * <li>引数で与えられるURIに設定されているログ設定ファイルに従いログの設定。</li>
	 * </ol>
	 */
	public static void initialize( URL url ) {
		String log_name = PropertyReader.getProperty(LOG_NAME);

		logger = Logger.getLogger(log_name);
		PropertyConfigurator.configure(url);
		initialized = true;
	}

	/**
	 * LogWriterを初期化された状態にする。
	 * すでに初期化されている場合はなにもしない。
	 */
	private static boolean ensureInitialized() {

		if ( ! initialized ) {
			initialize();
			initialized = true;
		}

		return (logger != null);
	}


	/**
	 * verbose モードに移行します。
	 * verbose モードでは各メッセージのログレベルに関わらずログが
	 * 出力されます。
	 */
	public static void setVerbose()
	{
		isVerboseMode = true;
	}


	/**
	 * ログを出力します。<BR>
	 * 本クラスのインスタンスが生成されていない場合は、
	 * インスタンスが生成され、ログの設定が行われます。<br>
	 *
	 * @param str 出力メッセージ
	 */
	public static synchronized void write(String str) {

		if ( ! ensureInitialized() ) {
		    return;
		}

		// ログレベルが設定されていない場合は、
		// DEBUGとして扱う
		write("DEBUG", str);
	}

	/**
	 * ログを出力します。<BR>
	 * 本クラスのインスタンスが生成されていない場合は、
	 * インスタンスを生成し、ログの設定が行われます。<BR>
	 * パラメータのログレベル（LEVEL）は以下の5種類が指定可能です。<BR>
	 * 下の物ほどより重要なエラーとなります。
	 * <ul>
	 * <li>DEBUG：開発時に動作確認が必要な個所。</li>
	 * <li>INFO ：メソッドの開始・終了個所、その他の動作情報。</li>
	 * <li>WARN ：正常動作に影響を与えないが、開発者が注意を払うべき事象発生個所。</li>
	 * <li>ERROR：正常動作を妨げるような事象発生個所。速やかにプログラムの動作を停止すべき問題が発生。</li>
	 * <li>FATAL：プログラムの動作を続けられない問題が発生。</li>
	 * </ul>
	 * @param level レベルはERROR, WARN, INFO, DEBUGの4種類の中から入力を行います。
	 * @param str 出力するメッセージ
	 */
	public static synchronized void write(String level, String str) {

		if ( ! ensureInitialized() ) {
		    return;
		}

		// ログレベルが設定されていない場合は、
		// DEBUGとして扱う
		if (level == null) {
			level = "DEBUG";
		}

		/* !!! [06/08/17 15:14 I.Noda] !!!
		 * 取り合えず debug のために、System.err にはかならず
		 * 出力するようにする。
		 * 一応、qwrite でレベルチェックはしているので、
		 * 無駄には表示しない。
		 */
		System.err.println(level + ":" + str) ;

		if (level.equalsIgnoreCase("ERROR")) {
			logger.error(str);
		} else if (level.equalsIgnoreCase("WARN")) {
			logger.warn(str);
		} else if (level.equalsIgnoreCase("INFO")) {
			logger.info(str);
		} else if (level.equalsIgnoreCase("DEBUG")) {
			logger.debug(str);
		} else if (level.equalsIgnoreCase("FATAL")) {
			logger.fatal(str);
			// throw new RuntimeException();
		} else {
		    /* !!! [06/08/17 15:14 I.Noda] !!!
		     * log level のエラー情報出力に追加
		     */
		    System.err.println
			("Missing/Unknown log level at LogWriter::write():"
			 + "level=" + level);
		}

		/*
		// for debug, turn on if you don't know where this log is from.
		StackTracePrinter.print();
		System.err.println();
		*/
	}

	/**
	 * 可変長引数を用いたログ出力関数。
	 *
	 * @param level ログ出力レベルを示す文字列。
	 *              ERROR, WARN, INFO, DEBUGの内から一つを選択する。
	 * @param args 出力する内容
	 */
	public static void qwrite(String level, Object ... args) {
	    try {
		if(isEnabled(level)) {
		    StringBuilder logstr = new StringBuilder();
		    for(Object obj: args) {
			if(obj instanceof Element) {
			    logstr.append(XMLFormatConverter.toString((Element)obj)) ;
			} else {
			    logstr.append(obj);
			}
		    }
		    write(level, logstr.toString());
		}
	    } catch (TransformerException ex) {
		System.err.println
		    ("Fatal!!. "
		     + "Something wrong to convert XML node to String.\n"
		     + "objs=") ;
		for(Object obj: args) {
		    System.err.println(obj.toString()) ;
		}
		ex.printStackTrace(System.err);
		throw new RuntimeException();
	    }
	}


	/**
	 * ログレベルのチェック<BR>
	 */
	public static boolean isEnabled(String level) {

		if ( ! ensureInitialized() ) {
		    return false;
		}

		if ( isVerboseMode ) {
			return true;
		}

		if (level.equalsIgnoreCase("ERROR")) {
			return logger.isEnabledFor(org.apache.log4j.Level.ERROR) ;
		} else if (level.equalsIgnoreCase("WARN")) {
			return logger.isEnabledFor(org.apache.log4j.Level.WARN);
		} else if (level.equalsIgnoreCase("INFO")) {
			return logger.isEnabledFor(org.apache.log4j.Level.INFO);
		} else if (level.equalsIgnoreCase("DEBUG")) {
			return logger.isEnabledFor(org.apache.log4j.Level.DEBUG);
		} else if (level.equalsIgnoreCase("FATAL")) {
			return logger.isEnabledFor(org.apache.log4j.Level.FATAL) ;
		} else {
		    /* !!! [06/08/17 15:14 I.Noda] !!!
		     * log level のエラー情報出力に追加
		     */
		    System.err.println
			("Missing/Unknown log level at LogWriter::write():"
			 + "level=" + level + ".");
		    return false ;
		}
	}
}
