#region Using
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace Wankuma.IO
{
	/// <summary>
	/// CSVデータを読み取ります。
	/// </summary>
	public class CSVReader : IDisposable
	{
		#region プロパティ
		#region protected System.IO.StreamReader Reader 実際のリーダである、ストリームリーダ
		/// <summary>
		/// 内部用リーダ
		/// </summary>
		private System.IO.TextReader reader_;
		/// <summary>
		/// 実際のリーダ
		/// </summary>
		private System.IO.TextReader Reader
		{
			get { return reader_; }
			set { reader_ = value; }
		}
		#endregion
		#region protected char 区切り文字  CSVの区切り文字
		/// <summary>
		/// CSVの区切り文字
		/// 初期値は','
		/// </summary>
		private char 区切り文字_ = ',';

		/// <summary>
		/// CSVの区切り文字
		/// 初期値は','
		/// </summary>
		public char 区切り文字
		{
			get { return this.区切り文字_; }
			private set { this.区切り文字_ = value; }
		}
		#endregion
		#region protected bool ファイル終了フラグ  ファイルが終端まで行っているかのフラグ
		/// <summary>
		/// ファイルが終端まで行っているかのフラグ
		/// </summary>
		private bool ファイル終了フラグ_;
		/// <summary>
		/// ファイルが終端まで行っているかのフラグ
		/// </summary>
		public bool ファイル終了フラグ
		{
			get { return ファイル終了フラグ_; }
			private set { ファイル終了フラグ_ = value; }
		}
		#endregion
		#region protected bool 行終了フラグ  行が最後まで行っているかのフラグ
		/// <summary>
		/// 行が最後まで行っているかのフラグ
		/// </summary>
		private bool 行終了フラグ_;

		/// <summary>
		/// 行が最後まで行っているかのフラグ
		/// </summary>
		public bool 行終了フラグ
		{
			get { return 行終了フラグ_; }
			private set { 行終了フラグ_ = value; }
		}
		#endregion
		#endregion
		#region public CSVReader(System.IO.Stream stream)　指定したストリーム用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// <summary>
		/// 指定したストリーム用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// </summary>
		/// <param name="stream">読み込まれるストリーム。</param>
		/// <exception cref="System.ArgumentNullException">stream が null です。</exception>
		/// <exception cref="System.ArgumentException">stream が読み取りをサポートしていません。</exception>
		public CSVReader(System.IO.Stream stream)
		{
			if (stream == null)
			{
				throw new ArgumentNullException("stream");
			}
			this.Init(stream);
		}
		#endregion
		#region public CSVReader(System.IO.Stream stream, char 区切り文字)　指定したストリーム用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// <summary>
		/// 指定したストリーム用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// </summary>
		/// <param name="stream">読み込まれるストリーム。</param>
		/// <param name="区切り文字">カンマ以外の区切り文字を利用したい場合に使います。</param>
		/// <exception cref="System.ArgumentNullException">stream が null です。</exception>
		/// <exception cref="System.ArgumentException">stream が読み取りをサポートしていません。</exception>
		public CSVReader(System.IO.Stream stream, char 区切り文字)
		{
			if (stream == null)
			{
				throw new ArgumentNullException("stream");
			}
			this.Init(stream);
			this.区切り文字 = 区切り文字;
		}
		#endregion
		#region public CSVReader(System.IO.TextReader reader)　指定したリーダ用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// <summary>
		/// 指定したリーダ用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// </summary>
		/// <param name="reader">読み込みを行うリーダ</param>
		/// <exception cref="System.ArgumentNullException">stream が null です。</exception>
		/// <exception cref="System.ArgumentException">stream が読み取りをサポートしていません。</exception>
		public CSVReader(System.IO.TextReader reader)
		{
			if (reader == null)
			{
				throw new ArgumentNullException("reader");
			}
			this.Reader = reader;
		}
		#endregion
		#region public CSVReader(System.IO.TextReader reader, char 区切り文字)　指定したリーダ用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// <summary>
		/// 指定したリーダ用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// </summary>
		/// <param name="reader">読み込みを行うリーダ</param>
		/// <param name="区切り文字">カンマ以外の区切り文字を利用したい場合に使います。</param>
		/// <exception cref="System.ArgumentNullException">stream が null です。</exception>
		/// <exception cref="System.ArgumentException">stream が読み取りをサポートしていません。</exception>
		public CSVReader(System.IO.TextReader reader, char 区切り文字)
		{
			if (reader == null)
			{
				throw new ArgumentNullException("reader");
			}
			this.Reader = reader;
			this.区切り文字 = 区切り文字;
		}
				#endregion
		#region public CSVReader(string path)　指定したファイル名用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// <summary>
		/// 指定したファイル名用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// </summary>
		/// <param name="path">読み込まれる完全なファイルパス。</param>
		/// <exception cref="System.IO.IOException">path に、ファイル名、ディレクトリ名、またはボリューム ラベルとしては不正または無効な構文が含まれています。</exception>
		/// <exception cref="System.ArgumentNullException">path が null です。</exception>
		/// <exception cref="System.ArgumentException">path が空の文字列 ("") です。</exception>
		/// <exception cref="System.IO.DirectoryNotFoundException">割り当てられていないドライブであるなど、指定されたパスが無効です。</exception>
		/// <exception cref="System.IO.FileNotFoundException">ファイルが見つかりません。</exception>
		public CSVReader(string path)
		{
			if (path == null)
			{
				throw new ArgumentNullException("path");
			}
			this.Init(path, Encoding.UTF8);
		}
#endregion
		#region public CSVReader(string path, char 区切り文字)　指定したファイル名用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// <summary>
		/// 指定したファイル名用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// </summary>
		/// <param name="path">読み込まれる完全なファイルパス。</param>
		/// <param name="区切り文字">カンマ以外の区切り文字を利用したい場合に使います。</param>
		/// <exception cref="System.IO.IOException">path に、ファイル名、ディレクトリ名、またはボリューム ラベルとしては不正または無効な構文が含まれています。</exception>
		/// <exception cref="System.ArgumentNullException">path が null です。</exception>
		/// <exception cref="System.ArgumentException">path が空の文字列 ("") です。</exception>
		/// <exception cref="System.IO.DirectoryNotFoundException">割り当てられていないドライブであるなど、指定されたパスが無効です。</exception>
		/// <exception cref="System.IO.FileNotFoundException">ファイルが見つかりません。</exception>
		public CSVReader(string path, char 区切り文字)
		{
			if (path == null)
			{
				throw new ArgumentNullException("path");
			}
			this.Init(path, Encoding.UTF8);
			this.区切り文字 = 区切り文字;
		}
				#endregion
		#region public CSVReader(string path, Encoding encoding)  指定したファイル名用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// <summary>
		/// 指定したファイル名用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// </summary>
		/// <param name="path">読み込まれる完全なファイルパス。</param>
		/// <param name="encoding">使用する文字エンコーディング。</param>
		/// <exception cref="System.IO.IOException">path に、ファイル名、ディレクトリ名、またはボリューム ラベルとしては不正または無効な構文が含まれています。</exception>
		/// <exception cref="System.ArgumentNullException">path が null です。</exception>
		/// <exception cref="System.ArgumentException">path が空の文字列 ("") です。</exception>
		/// <exception cref="System.IO.DirectoryNotFoundException">割り当てられていないドライブであるなど、指定されたパスが無効です。</exception>
		/// <exception cref="System.IO.FileNotFoundException">ファイルが見つかりません。</exception>
		public CSVReader(string path, Encoding encoding)
		{
			if (path == null)
			{
				throw new ArgumentNullException("path");
			}
			if (encoding == null)
			{
				throw new ArgumentNullException("encoding");
			}
			this.Init(path, encoding);
		}
#endregion
		#region public CSVReader(string path, Encoding encoding, char 区切り文字)  指定したファイル名用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// <summary>
		/// 指定したファイル名用の System.IO.StreamReader を利用して新しいインスタンスを初期化します。
		/// </summary>
		/// <param name="path">読み込まれる完全なファイルパス。</param>
		/// <param name="encoding">使用する文字エンコーディング。</param>
		/// <param name="区切り文字">カンマ以外の区切り文字を利用したい場合に使います。</param>
		/// <exception cref="System.IO.IOException">path に、ファイル名、ディレクトリ名、またはボリューム ラベルとしては不正または無効な構文が含まれています。</exception>
		/// <exception cref="System.ArgumentNullException">path が null です。</exception>
		/// <exception cref="System.ArgumentException">path が空の文字列 ("") です。</exception>
		/// <exception cref="System.IO.DirectoryNotFoundException">割り当てられていないドライブであるなど、指定されたパスが無効です。</exception>
		/// <exception cref="System.IO.FileNotFoundException">ファイルが見つかりません。</exception>
		public CSVReader(string path, Encoding encoding, char 区切り文字)
		{
			if (path == null)
			{
				throw new ArgumentNullException("path");
			}
			if (encoding == null)
			{
				throw new ArgumentNullException("encoding");
			}

			this.Init(path, encoding);
			this.区切り文字 = 区切り文字;
		}
		#endregion
		#region private void Init(System.IO.Stream stream)  初期化内部処理
		/// <summary>
		/// 初期化内部処理
		/// </summary>
		/// <param name="stream">読み込まれるストリーム。</param>
		/// <exception cref="System.ArgumentNullException">stream が null です。</exception>
		/// <exception cref="System.ArgumentException">stream が読み取りをサポートしていません。</exception>
		private void Init(System.IO.Stream stream)
		{
			this.Reader = new System.IO.StreamReader(stream);
		}
		#endregion
		#region private void Init(string path, Encoding encoding)  初期化内部処理
		/// <summary>
		/// 初期化内部処理
		/// </summary>
		/// <param name="path">読み込まれる完全なファイルパス。</param>
		/// <param name="encoding">使用する文字エンコーディング。</param>
		/// <exception cref="System.ArgumentNullException">stream が null です。</exception>
		/// <exception cref="System.ArgumentException">stream が読み取りをサポートしていません。</exception>
		private void Init(string path, Encoding encoding)
		{
			this.Reader = new System.IO.StreamReader(path, encoding);
		}
		#endregion
		#region public void MoveNextRow()  次の行の処理に移ります。
		/// <summary>
		/// 次の行の処理に移ります。
		/// </summary>
		/// <returns>次の行があればtrue</returns>
		public bool MoveNextRow()
		{
			while (this.行終了フラグ == false)
			{
				this.ReadColumn();
			}
			if (this.ファイル終了フラグ == true)
			{
				return false;
			}
			else
			{
				this.行終了フラグ = false;
				return true;
			}
		}
		#endregion
		#region public string ReadColumn()  カラムの読み出し処理を行います。
		/// <summary>
		/// カラムの読み出し処理を行います。
		/// </summary>
		/// <returns>読み出したカラムの文字列</returns>
		public string ReadColumn()
		{
			//行繰り越し処理が終わっていないので常に””を返す
			if (this.行終了フラグ == true)
			{
				return "";
			}

			StringBuilder ReturnSB = new StringBuilder();

			while (this.Reader.Peek() >= 0)
			{
				char 処理対象文字 = Convert.ToChar(this.Reader.Read());

				if (ループ処理( ReturnSB, 処理対象文字))
				{
					//ファイルの最後まで読み取っていたら終了フラグを立てる
					if (this.Reader.Peek() < 0)
					{
						this.行終了フラグ = true;
						this.ファイル終了フラグ = true;
					}
					return ReturnSB.ToString();
				}
			} //while
			this.行終了フラグ = true;
			this.ファイル終了フラグ = true;
			return ReturnSB.ToString();

		}
#endregion
		#region private bool ループ処理(StringBuilder ReturnSB, char 処理対象文字)  ループの実態処理
		/// <summary>
		/// ループの実態処理
		/// ループ中は区切り文字を探しつつ処理を行う
		/// </summary>
		/// <param name="ReturnSB">文字列抽出結果を構築するためのStringBuilder</param>
		/// <param name="処理対象文字">処理対象の文字</param>
		/// <returns>文字列の区切りまで到達している場合にはtrueを返す</returns>
		private bool ループ処理(StringBuilder ReturnSB, char 処理対象文字)
		{
			if (処理対象文字 == '\"')
			{
				//文字列処理に入ると内部で、文字列中が終了するまで読み進む
				文字列中処理(ReturnSB);
			}
			else if (処理対象文字 == this.区切り文字)
			{
				//区切りなので、完了
				return true;
			}
			else if (処理対象文字 == '\r')
			{
				//改行処理
				char 先読み文字 = Convert.ToChar(this.Reader.Peek());
				if (先読み文字 == '\n')
				{
					this.行終了フラグ = true;
					//空読み
					this.Reader.Read();
					return true;
				}
				else
				{
					ReturnSB.Append(処理対象文字);
				}
			}
			else
			{
				ReturnSB.Append(処理対象文字);
			}
			return false;
		}
#endregion
		#region private void 文字列中処理(StringBuilder ReturnSB) ダブルクォートでくくられている文字列中の処理
		/// <summary>
		/// ダブルクォートでくくられている文字列中の処理
		/// </summary>
		/// <param name="ReturnSB">文字列抽出結果を構築するためのStringBuilder</param>
		private void 文字列中処理(StringBuilder ReturnSB)
		{
			while (this.Reader.Peek() >= 0)
			{
				char 処理対象文字 = Convert.ToChar(this.Reader.Read());

				//ダブルクォートの場合
				if (処理対象文字 == '\"')
				{
					//先読みをしてダブルクォートであれば、1つのダブルクォートを代入するが、次がダブルクォートでなければ文字列処理中断
					char 先読み文字 = Convert.ToChar(this.Reader.Peek());
					if (先読み文字 == '\"')
					{
						//空読み
						this.Reader.Read();
						ReturnSB.Append("\"");
					}
					else
					{
						//文字列処理が中断
						return ;
					}
				}
				else if (処理対象文字 == '\r')
				{
					//\rの場合次が\nの場合には先読みしておいて、\r\nを設定する。
					char 先読み文字 = Convert.ToChar(this.Reader.Peek());
					if (先読み文字 == '\n')
					{
						//空読み
						this.Reader.Read();
					}
					ReturnSB.Append("\r\n");
				}
				else if (処理対象文字 == '\n')
				{
					//\nだけの場合にも\r\nを設定する
					ReturnSB.Append("\r\n");
				}
				else
				{
					ReturnSB.Append(処理対象文字);
				}
			} //while

			//文字列処理中にストリームの情報が無くなった
			return ;

		}//文字列中処理
#endregion
		#region 終了処理
		#region public void Dispose(bool disposing)
		/// <summary>
		/// 実際の終了処理
		/// </summary>
		/// <param name="disposing"></param>
		public void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (this.Reader != null)
				{
					try
					{
						this.Reader.Close();
					}
					catch
					{
						//例外は握りつぶします。
					}
				}
			}
			else
			{
				//try
				//{
				//  //独自ファイナライズ処理
				//}
				//finally
				//{
				//}
			}
		}
#endregion
		#region public void Dispose()
		/// <summary>
		/// Dispose
		/// </summary>
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}
#endregion
		#region ~CSVReader()
		/// <summary>
		/// デストラクタ
		///  終了処理を呼び出します。
		/// </summary>
		~CSVReader()
		{
			this.Dispose(false);
		}
#endregion
		#endregion
	}//class
}//namespace
