/*
 * blanco Framework
 * Copyright (C) 2004-2010 IGA Tosiki
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 */
package blanco.log.custom;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ResourceBundle;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

import blanco.log.logging.BlancoLogLoggingConstants;
import blanco.log.resourcebundle.BlancoLogProgramMessage;

/**
 * java.util.logging のログ出力形式を Log4J 風の出力形式に加工する目的のカスタム・ログフォーマッター。
 * 
 * @author IGA Tosiki
 */
public class BlancoLogFormatter extends Formatter {
    /**
     * blancoLogメッセージクラス。
     */
    protected final BlancoLogProgramMessage fMsg = new BlancoLogProgramMessage();

    /**
     * スタックトレースをログ出力するかどうか。 ジョブ監視ソフトで監視する場合を想定し、デフォルト false
     * としています。(多くのログ監視ソフトは、ログ中のスタックトレースが苦手と想定)
     */
    public static final boolean IS_OUTPUT_THROWN = false;

    /**
     * ログ上の日付形式。ミリ秒は「.」で区切ります。
     */
    final SimpleDateFormat sdFormat = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss.SSS");

    /**
     * 自ホスト名。ロギングで利用。
     */
    String fLocalHostName = null;

    /**
     * {@inheritDoc}
     */
    public String format(final LogRecord argLogRecord) {
        final StringBuffer buf = new StringBuffer(1024);

        try {
            logRecordMessage2Buffer(argLogRecord, buf);
        } catch (ThreadDeath ex) {
            return fMsg.getMblgcf01();
        }

        if (IS_OUTPUT_THROWN) {
            if (argLogRecord.getThrown() != null) {
                // 例外オブジェクトをログファイルに出力。
                logRecordThrown2Buffer(argLogRecord.getThrown(), buf);
            }
        }

        return buf.toString();
    }

    /**
     * ログメッセージを文字列化します。
     * 
     * @param argLogRecord
     * @param argBuf
     */
    void logRecordMessage2Buffer(final LogRecord argLogRecord,
            final StringBuffer argBuf) {
        final Date now = new Date(argLogRecord.getMillis());
        synchronized (sdFormat) {
            argBuf.append(sdFormat.format(now));
        }

        // Log4j 風のログ出力を行います。
        argBuf.append(" [");
        switch (argLogRecord.getLevel().intValue()) {
        case BlancoLogLoggingConstants.FINEST:
        case BlancoLogLoggingConstants.FINER:
            argBuf.append("TRACE");
            break;
        case BlancoLogLoggingConstants.FINE:
            argBuf.append("DEBUG");
            break;
        case BlancoLogLoggingConstants.CONFIG:
            argBuf.append("CONF ");
            break;
        case BlancoLogLoggingConstants.INFO:
            argBuf.append("INFO ");
            break;
        case BlancoLogLoggingConstants.WARNING:
            argBuf.append("WARN ");
            break;
        case BlancoLogLoggingConstants.SEVERE:
            boolean isFatal = false;
            if (argLogRecord.getMessage().length() >= 5) {
                final String head = argLogRecord.getMessage().substring(0, 5);
                if ("fatal".equals(head.toLowerCase())) {
                    // メッセージが fatal または FATAL で開始される場合にのみ fatal にマッピング。
                    isFatal = true;
                }
            }
            if (isFatal) {
                argBuf.append("FATAL");
            } else {
                argBuf.append("ERROR");
            }
            break;
        default:
            argBuf.append(Integer.toString(argLogRecord.getLevel().intValue()));
            argBuf.append("  ");
            break;
        }
        argBuf.append("] ");

        if (fLocalHostName == null) {
            try {
                fLocalHostName = InetAddress.getLocalHost().getHostName();
            } catch (UnknownHostException e) {
                fLocalHostName = "noname(" + e.toString() + ")";
            }
        }
        argBuf.append(fLocalHostName);
        argBuf.append(" ");

        argBuf.append(getMessaegWithBundle(argLogRecord));
        // 改行は付与しません。
        // argBuf.append("\n");
    }

    /**
     * ログのバンドルを適用できるものは適用した後のメッセージを取得します。
     * 
     * これを実施することにより、バンドル対応したログメッセージについて、バンドルに渡す ID から実際のメッセージを読み替えます。
     * 
     * @param argLogRecord
     * @return
     */
    String getMessaegWithBundle(final LogRecord argLogRecord) {
        final StringBuilder result = new StringBuilder();

        final ResourceBundle resourceBundle = argLogRecord.getResourceBundle();
        final String origMesssage = argLogRecord.getMessage();
        String message = null;
        if (origMesssage != null && resourceBundle != null) {
            try {
                if (argLogRecord.getParameters() == null) {
                    message = resourceBundle.getString(origMesssage);
                } else {
                    final MessageFormat messageFormat = new MessageFormat(
                            resourceBundle.getString(origMesssage));
                    final StringBuffer strbuf = new StringBuffer();
                    // 与えられた引数を元に置換文字列を置き換えます。
                    messageFormat.format(argLogRecord.getParameters(), strbuf,
                            null);
                    message = strbuf.toString();
                }
            } catch (Exception ex) {
                // リソース登録なし。なにもできません。
            }
        }
        if (message == null) {
            // ローカライズ不能な場合には、もとのものをそのまま利用します。
            message = origMesssage;
        }
        if (message == null) {
            message = "";
        }
        // 改行コードは空白に変換します。
        result.append(message.replace('\n', ' ').replace('\r', ' '));

        return result.toString();
    }

    /**
     * 例外オブジェクトをメッセージ化します。
     * 
     * @param argEx
     * @param argBuf
     */
    void logRecordThrown2Buffer(final Throwable argEx, final StringBuffer argBuf) {
        final StringWriter writerResult = new StringWriter(1024);
        final PrintWriter writer = new PrintWriter(writerResult);
        argEx.printStackTrace(writer);
        writer.close();

        argBuf.append(writerResult.toString());
        // スタックトレースの最後には改行が最初から付いています。このため、更なる改行付与は不要です。
    }
}
