#pragma once

namespace FDK
{
	/// コンストラクタで指定されたストリームに対して Trace ログを出力するリスナー。
	/// Trace::Listeners に Add() すれば、デフォルトの出力ウィンドウに加えて、Add したリスナーにも同じメッセージが出力されるようになる。
	public ref class CTraceLogListener : TraceListener
	{
	public:
		CTraceLogListener( StreamWriter^ stream, List<String^>^ list呼び出し元として表示しない名前 )
			: TraceListener()
		{
			this->Disposed = gcnew CDisposed();

			this->lockObject = gcnew Object();

			this->streamWriter = stream;
			this->dt空行の最終出力日時 = DateTime::Now;
			this->list呼び出し元として表示しない名前 = list呼び出し元として表示しない名前;
		}
		~CTraceLogListener()
		{
			if( this->Disposed->bDispose済みまたは処理中である )
				return;		// 例外を発出させない。

			if( !this->Disposed->t開始を宣言する() )
				return;		// 例外を発出させない。


			//if( this->streamWriter != nullptr )		closeしたりdeleteしたりしてはいけないようだ。
			//{
			//	this->streamWriter->Close();
			//	SAFE_REF_DELETE( this->streamWriter );
			//}

			//GC::SuppressFinalize( this );   --> コンパイラが勝手にやる
		}

		virtual void Flush() override
		{
			this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );

			lock l( this->lockObject );
			this->streamWriter->Flush();
		}
		virtual void TraceEvent( TraceEventCache^ eventCache, String^ source, TraceEventType eventType, int id, String^ message ) override
		{
			this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );

			lock l( this->lockObject );

			this->t必要なら空行を出力する();
			this->t日時を出力する();
			this->tスレッドIDを出力する();
			this->tイベント種別を出力する( eventType );
			this->t呼び出し元を出力する();
			this->tインデントを出力する();
			this->streamWriter->WriteLine( message );
		}
		virtual void TraceEvent( TraceEventCache^ eventCache, String^ source, TraceEventType eventType, int id, String^ format, ... array<Object^>^ args ) override
		{
			this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );

			lock l( this->lockObject );

			this->t必要なら空行を出力する();
			this->t日時を出力する();
			this->tスレッドIDを出力する();
			this->tイベント種別を出力する( eventType );
			this->t呼び出し元を出力する();
			this->tインデントを出力する();
			this->streamWriter->WriteLine( String::Format( format, args ) );
		}
		virtual void Write( String^ message ) override
		{
			this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );

			lock l( this->lockObject );
			this->streamWriter->Write( message );
		}
		virtual void WriteLine( String^ message ) override
		{
			this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );

			lock l( this->lockObject );
			this->streamWriter->WriteLine( message );
		}

	protected:
		StreamWriter^ streamWriter;

	private:
		CDisposed^		Disposed;
		List<String^>^	list呼び出し元として表示しない名前;
		DateTime		dt空行の最終出力日時;
		Object^			lockObject;

		void t必要なら空行を出力する()
		{
			//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );		private なのでノーチェック。

			TimeSpan ts空行の出力間隔 = TimeSpan::FromSeconds( 2.000 );	// 2秒以上あいたら空行を出力。

			DateTime dt現在日時 = DateTime::Now;
			TimeSpan ts経過日時 = ( dt現在日時 - this->dt空行の最終出力日時 ).Duration();

			if( ts経過日時 >= ts空行の出力間隔 )
			{
				this->streamWriter->WriteLine( "    :" );
				this->dt空行の最終出力日時 = dt現在日時;
			}
		}
		void t日時を出力する()
		{
			//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );		private なのでノーチェック。

			DateTime now = DateTime::Now;
			this->streamWriter->Write( String::Format( "{0:D4}/{1:D2}/{2:D2} {3:D2}:{4:D2}:{5:D2}.{6:D3} ", gcnew array<Object^> { now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Millisecond } ) );
		}
		void tスレッドIDを出力する()
		{
			//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );		private なのでノーチェック。

			String^ name = Thread::CurrentThread->Name;
			
			if( String::IsNullOrEmpty( name ) )
				name = Thread::CurrentThread->ManagedThreadId.ToString("D4");
			
			this->streamWriter->Write( "[" + name + "]" );
		}
		void tイベント種別を出力する( TraceEventType eventType )
		{
			//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );		private なのでノーチェック。

			switch( eventType )
			{
			case TraceEventType::Critical:
				this->streamWriter->Write( "【※致命的】" );
				break;
			
			case TraceEventType::Error:
				this->streamWriter->Write( "【エラー】" );
				break;

			case TraceEventType::Warning:
				this->streamWriter->Write( "【警告】" );
				return;

			default:
				return;
			}
		}
		void tインデントを出力する()
		{
			//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );		private なのでノーチェック。

			if( 0 > this->IndentLevel )
				this->IndentLevel = 0;

			if( 0 > this->IndentSize )
				this->IndentSize = 0;

			for( int i = 0; i < this->IndentLevel; i++ )
				for( int j = 0; j < this->IndentSize; j++ )
					this->streamWriter->Write( ' ' );
		}
		void t呼び出し元を出力する()
		{
			//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTraceLogListener" );		private なのでノーチェック。

			// シンボル情報はデバッグバージョンでしか埋め込まれない。

#ifdef _DEBUG
			StackTrace^ stackTrace = gcnew StackTrace();

			for( int i = 0; i < stackTrace->FrameCount; i++ )
			{
				System::Reflection::MethodBase^ method = stackTrace->GetFrame( i )->GetMethod();
				String^ callerName = ( nullptr == method->DeclaringType ) ? method->Name : method->DeclaringType->FullName;		// グローバル関数の場合、DeclaringType は null になる。

				if( false == this->list呼び出し元として表示しない名前->Contains( callerName ) )
				{
					if( nullptr != method->DeclaringType )
					{
						this->streamWriter->Write( "[" + callerName + "." + method->Name + "] " );
					}
					else
					{
						this->streamWriter->Write( "[" + callerName + "] " );
					}
					break;
				}
			}
#endif
		}
	};
}
