package jp.ehobby.util.syslog;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

import jp.ehobby.info.SystemInfo;

import org.junit.Test;


/**
 * Syslog 単体テスト.
 */
@SuppressWarnings("static-method")
public class SyslogTest {

	/**
	 * Syslog構築.
	 *
	 * @method Syslog()
	 * @process new Syslog()を実行する.
	 * @spec    Syslog が構築されること.
	 *
	 * @throws SecurityException セキュリティエラー
	 * @throws IOException IOエラー
	 */
	@Test
	public void testSyslog() throws IOException {
		Syslog syslog = new Syslog();
		assertNotNull(syslog);
		syslog.close();
	}





	/**
	 * Syslog構築(サーバ指定).
	 *
	 * @method Syslog(String)
	 * @process new Syslog(String)を実行する.
	 * @spec    指定されたSyslogサーバに書き出す Syslog が構築されること.
	 *
	 * @throws SecurityException セキュリティエラー
	 * @throws IOException IOエラー
	 */
	@Test
	public void testSyslogString() throws IOException {
		Syslog syslog = new Syslog("127.0.0.1");	//$NON-NLS-1$
		assertNotNull(syslog);
		syslog.close();
	}



	/**
	 * Syslog構築(サーバ, ポート番号指定).
	 *
	 * @method Syslog(String, int)
	 * @process new Syslog(String, int)を実行する.
	 * @spec    指定されたサーバ、ポートに書き出すSyslog が構築されること.
	 *
	 * @throws SecurityException セキュリティエラー
	 * @throws IOException IOエラー
	 * @throws InterruptedException join失敗
	 */
	@Test
	public void testSyslogStringInt() throws IOException, InterruptedException {
		Syslog syslog = new Syslog("localhost", 999);	//$NON-NLS-1$
		assertNotNull(syslog);


		// テスト用 Syslog サーバ構築
		TestSyslogServer server = new TestSyslogServer(999);
		server.start();

		// Syslog書き出し
		String msg = "LOG MESSAGE";						//$NON-NLS-1$
		syslog.logger(SyslogSeverity.DEBUG, null, msg);

		server.join();
		String data = server.getData();

		// 書き出しデータ検証
		checkResult(
				data,
				"<15>",									//$NON-NLS-1$
				SystemInfo.getLocalHostName(),
				null,
				msg,
				true);

		syslog.close();
	}


	/**
	 * ログ書き出し.
	 *
	 * @method logger(SyslogSeverity, String, String)
	 * @process logger にてログを書き出す.
	 * @spec    Syslogサーバにログが書き出されること
	 *
	 * @throws SecurityException セキュリティエラー
	 * @throws IOException IOエラー
	 * @throws InterruptedException join失敗
	 */
	@Test
	public void testLoggerSyslogSeverityStringString() throws InterruptedException, IOException {
		// testSyslogStringInt にて実施
	}


	/**
	 * ログ書き出し(host, Tag指定).
	 *
	 * @method logger(SyslogFacility, SyslogSeverity, String, String, String)
	 * @process logger にてログを書き出す.
	 * @spec    Syslogサーバにログが書き出されること
	 *
	 * @throws SecurityException セキュリティエラー
	 * @throws IOException IOエラー
	 * @throws InterruptedException join失敗
	 */
	@Test
	public void testLoggerSyslogFacilitySyslogSeverityStringStringString() throws InterruptedException, IOException {

		Syslog syslog = new Syslog();

		// テスト用 Syslog サーバ構築
		TestSyslogServer server = new TestSyslogServer(Syslog.SERVER_PORT);
		server.start();

		// Syslog書き出し
		String host = "HOSTNAME";						//$NON-NLS-1$
		String tag  = "TAG";							//$NON-NLS-1$
		String msg  = "LOG MESSAGE";					//$NON-NLS-1$
		syslog.logger(SyslogFacility.KERNEL, SyslogSeverity.WARNING, host, tag, msg);

		server.join();
		String data = server.getData();
		// 書き出しデータ検証
		checkResult(
				data,
				"<4>",									//$NON-NLS-1$
				host,
				tag,
				msg,
				true);

		syslog.close();
	}


	/**
	 * ログ書き出し(host名オーバ).
	 *
	 * @method logger(SyslogFacility, SyslogSeverity, String, String, String)
	 * @process logger にてログを書き出す.
	 * @spec    Syslogサーバにログが書き出されること
	 *
	 * @throws SecurityException セキュリティエラー
	 * @throws IOException IOエラー
	 * @throws InterruptedException join失敗
	 */
	@Test
	public void testLoggerSyslogFacilitySyslogSeverityStringStringStringOverHostName() throws InterruptedException, IOException {

		Syslog syslog = new Syslog();

		// テスト用 Syslog サーバ構築
		TestSyslogServer server = new TestSyslogServer(Syslog.SERVER_PORT);
		server.start();

		// Syslog書き出し
		String host = "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345";												//$NON-NLS-1$
		String tag  = "TAG";												//$NON-NLS-1$
		String msg  = "LOG MESSAGE";										//$NON-NLS-1$
		syslog.logger(SyslogFacility.KERNEL, SyslogSeverity.WARNING, host + '6', tag, msg);

		server.join();
		String data = server.getData();
		// 書き出しデータ検証
		checkResult(
				data,
				"<4>",									//$NON-NLS-1$
				host,
				tag,
				msg,
				true);

		syslog.close();
	}


	/**
	 * ログ書き出し(TAGオーバ).
	 *
	 * @method logger(SyslogFacility, SyslogSeverity, String, String, String)
	 * @process logger にてログを書き出す.
	 * @spec    Syslogサーバにログが書き出されること
	 *
	 * @throws SecurityException セキュリティエラー
	 * @throws IOException IOエラー
	 * @throws InterruptedException join失敗
	 */
	@Test
	public void testLoggerSyslogFacilitySyslogSeverityStringStringStringOverMessage() throws InterruptedException, IOException {

		Syslog syslog = new Syslog();

		// テスト用 Syslog サーバ構築
		TestSyslogServer server = new TestSyslogServer(Syslog.SERVER_PORT);
		server.start();

		// Syslog書き出し
		String host = "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345678901234567890123456789012345678901234567890"	//$NON-NLS-1$
					+ "12345";												//$NON-NLS-1$
		String tag  = "12345678901234567890123456789012";					//$NON-NLS-1$
		String msg  = "LOG MESSAGE";										//$NON-NLS-1$

		syslog.logger(SyslogFacility.KERNEL, SyslogSeverity.WARNING, host, tag + '3', msg);

		server.join();
		String data = server.getData();
		// 書き出しデータ検証
		checkResult(
				data,
				"<4>",									//$NON-NLS-1$
				host,
				tag,
				msg,
				false);

		syslog.close();
	}


	/**
	 * Syslogのclose.
	 *
	 * @method close()
	 * @process close()を実行する.
	 * @spec    Syslog用のストリームが閉じられること.
	 */
	@Test
	public void testClose() {
		// testLoggerSyslogSeverityStringString 参照
	}


	/**
	 * Syslogのclose.
	 *
	 * @method close()
	 * @process close()を実行する.
	 * @spec    Syslog用のストリームが閉じられること.
	 */
	@Test
	public void testTestSyslogServer() {
		// 他のテストにて実施
	}

	/**
	 * 結果の確認.
	 * 結果が指定された PRI, TAG, COMMENT と異なる場合、
	 * AssertionError を throw します.
	 *
	 * @param data Syslogに書き出したデータ
	 * @param pri     PRI部分
	 * @param tag     TAG部分
	 * @param comment COMMENT部分
	 * @param isPid PIDを追加するか否か
	 */
	private void checkResult(
			final String data,
			final String pri,
			final String host,
			final String tag,
			final String comment,
			final boolean isPid) {

		// 1. PRI部分確認
		int offset = 0;
		int pos = data.indexOf(pri);
		assertEquals(0, pos);
		offset = pri.length() + 1;

		// 2. 日時部分確認
		// 可変のためとりあえずチェック無し
		// String dateStr = data.substring(offset, 16);
		offset += 15;

		// 3. ホスト部分確認
		String resHostName = data.substring(offset, offset + host.length());
		assertEquals(host, resHostName);
		offset += host.length() + 1;

		// 4. タグ部分確認
		if (tag != null) {
			int    pid       = SystemInfo.getPid();
			String tagStr;
			if (isPid) {
				tagStr = tag + '[' + pid + ']' + ':';
			} else {
				tagStr = tag + ':';
			}
			String resTagStr = data.substring(offset, offset + tagStr.length());
			assertEquals(tagStr, resTagStr);
			offset += tagStr.length() + 1;
		}

		// 5. メッセージ部分確認
		String resMessage = data.substring(offset);
		assertEquals(comment, resMessage);
	}


	////////////////////////////////////////////////////////////////////////////
	//
	// テスト用クラス
	//
	/**
	 * テスト用SyslogServer.
	 * @author kei-n
	 *
	 */
	class TestSyslogServer extends Thread {

		/** サーバポート.	*/
		private DatagramSocket servSocket;

		/** 受信データ.		*/
		private String recvData;

		/**
		 * テスト用SyslogServerを構築します.
		 *
		 * @param port ポート番号
		 * @throws SocketException ソケットエラー
		 */
		public TestSyslogServer(final int port) throws SocketException {
			this.servSocket = new DatagramSocket(port);
		}


		/**
		 * データ受信.
		 */
		@Override
		public void run() {
			byte[]         buffer = new byte[4096];
			DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
			try {
				this.servSocket.receive(packet);
				this.recvData = new String(packet.getData(), 0, packet.getLength());
			} catch (IOException e) {
				// NOP
			} finally {
				this.servSocket.close();
			}
		}

		/**
		 * 受信データを取得します.
		 * @return 受信データ
		 */
		public String getData() {
			return this.recvData;
		}

	}
}
