package jp.ehobby.util.syslog;

import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Calendar;

import jp.ehobby.info.SystemInfo;
import jp.ehobby.io.SyslogWriter;


/**
 * Syslog を扱う.
 *
 * @author kei-n
 */
public class Syslog {

	/** Syslog Server のポート.		*/
	public static final int SERVER_PORT          = SyslogWriter.SYSLOG_SERVER_PORT;

	/** ローカルアドレス.			*/
	private static final String LOCALHOST        = "localhost";					//$NON-NLS-1$

	/** TIMESTAMPのフォーマット.	*/
	private static final String TIMESTAMP_FORMAT = "%s %2d %02d:%02d:%02d ";	//$NON-NLS-1$

	/** HOST名部分の最大サイズ.		*/
	private static final int MAX_HOSTNAME_SIZE   = 255;

	/** TAG部分の最大サイズ.		*/
	private static final int MAX_TAG_SIZE        = 32;

	/** 月の文字列表現.	*/
	private static final String[] MONTH_STRING = {
		"Jan", "Feb", "Mar",	//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
		"Apr", "May", "Jun",	//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
		"Jul", "Aug", "Sep",	//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
		"Oct", "Nov", "Dec"		//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
	};


	/** syslog に書き出す Writer.	*/
	private final SyslogWriter writer;


	/**
	 * ローカルの syslog に出力する syslog を構築します.
	 *
	 * @throws SocketException ソケットエラー
	 * @throws UnknownHostException 不明なホストエラー
	 */
	public Syslog() throws SocketException, UnknownHostException {
		this(LOCALHOST);
	}


	/**
	 * 指定されたホストの syslog に出力する syslog を構築します.
	 *
	 * @param host ホストまたはアドレス
	 * @throws UnknownHostException 不明なホスト名
	 * @throws SocketException ソケットエラー
	 */
	public Syslog(final String host) throws SocketException, UnknownHostException {
		this(host, SERVER_PORT);
	}


	/**
	 * 指定されたホストの syslog に出力する syslog を構築します.
	 *
	 * @param host ホストまたはアドレス
	 * @param port ポート番号
	 * @throws UnknownHostException 不明なホスト名
	 * @throws SocketException ソケットエラー
	 */
	public Syslog(final String host, final int port) throws SocketException, UnknownHostException {
		this.writer = new SyslogWriter(host, port);
	}


	/**
	 * ログ分類 user-level message の syslog を出力します.
	 *
	 * @param severity 重要度
	 * @param tag      タグ(最大30文字、": "が自動で付与される)
	 * @param comment  コメント
	 * @throws IOException ログ書き込み失敗
	 */
	public void logger(
			final SyslogSeverity severity,
			final String         tag,
			final String         comment
			) throws IOException {
		logger(SyslogFacility.USER_LEVEL, severity, null, tag, comment);
	}


	/**
	 * syslog を出力します.
	 *
	 * @param facility ログの分類
	 * @param severity 重要度
	 * @param host     ホスト名
	 * @param tag      タグ(最大30文字、": "が自動で付与される)
	 * @param comment  コメント
	 * @throws IOException ログ書き込み失敗
	 */
	public void logger(
			final SyslogFacility facility,
			final SyslogSeverity severity,
			final String host,
			final String tag,
			final String comment) throws IOException {
		StringBuffer sb = new StringBuffer();
		sb.append(createPriority(facility, severity));
		sb.append(createTimestamp());
		sb.append(createHostName(host));
		sb.append(createTag(tag));
		sb.append(comment);
		this.writer.write(sb.toString());
	}


	/**
	 * Syslog用のソケットをcloseします.
	 * @throws IOException close失敗
	 */
	public void close() throws IOException {
		this.writer.close();
	}


	////////////////////////////////////////////////////////////////////////////
	//
	// private static
	//

	/**
	 * PRI部分を生成します.
	 *
	 * @param facility ログの分類
	 * @param severity 重要度
	 * @return PRI部分
	 */
	private static String createPriority(final SyslogFacility facility, final SyslogSeverity severity) {
		int pri = facility.getPriorityValue() + severity.getValue();
		StringBuffer sb = new StringBuffer();
		sb.append('<');
		sb.append(pri);
		sb.append('>');
		return sb.toString();
	}


	/**
	 * TIMESTAMP部分を生成します.
	 *
	 * @return TIMESTAMP部分
	 */
	private static String createTimestamp() {
		Calendar cal = Calendar.getInstance();
		int month  = cal.get(Calendar.MONTH);
		int day    = cal.get(Calendar.DAY_OF_MONTH);
		int hour   = cal.get(Calendar.HOUR_OF_DAY);
		int minute = cal.get(Calendar.MINUTE);
		int second = cal.get(Calendar.SECOND);

		// MMM dd HH:MM:SS
		String timestampStr = String.format(TIMESTAMP_FORMAT,
				MONTH_STRING[month],
				Integer.valueOf(day),
				Integer.valueOf(hour),
				Integer.valueOf(minute),
				Integer.valueOf(second));
		return timestampStr;
	}


	/**
	 * ホスト名部分を生成します.
	 * ホストが指定されている場合、指定された host を返します.
	 *
	 * @param host ホスト名
	 * @return ホスト名部分の文字列
	 */
	private static String createHostName(final String host) {
		StringBuffer sb = new StringBuffer();
		if (host == null) {
			sb.append(SystemInfo.getLocalHostName());
		} else if (host.length() > MAX_HOSTNAME_SIZE) {
			sb.append(host.substring(0, MAX_HOSTNAME_SIZE));
		} else {
			sb.append(host);
		}
		sb.append(' ');
		return sb.toString();
	}


	/**
	 * TAB部分を生成します.
	 * 指定されたタグ tag に、"[PID]: " が付与されます.
	 * 指定されたタグ tag が長い場合、"[PID]" を付与せず、tag が切り詰められます.
	 *
	 * @param tag TAG文字
	 * @return PIDを含んだTAG
	 */
	private static String createTag(final String tag) {
		if (tag == null) { return ""; }						//$NON-NLS-1$
		String pid       = getPidString();
		int    tagSize   = tag.getBytes().length;
		int    pidSize   = pid.getBytes().length;
		int    totalSize = tagSize + pidSize + 2;			// +2 は、": "分

		StringBuffer sb = new StringBuffer();
		if (totalSize < MAX_TAG_SIZE) {
			sb.append(tag);
			sb.append(pid);
		} else {
			// 指定されたTAG文字部分を切り詰める(PIDは付与しない)
			sb.append(tag.substring(0, MAX_TAG_SIZE));
		}
		sb.append(": ");									//$NON-NLS-1$
		return sb.toString();
	}


	/**
	 * PIDの文字列表現を返します.
	 * PIDが取得できない場合、空文字を返します.
	 *
	 * @return PIDの文字列表現
	 */
	private static String getPidString() {
		StringBuffer sb = new StringBuffer();
		int pid = SystemInfo.getPid();
		if (pid >= 0) {
			sb.append('[');
			sb.append(pid);
			sb.append(']');
		}
		return sb.toString();
	}

}
