using System;
using System.Collections.Generic;
using System.Data;
using System.IO;

namespace OnlineSalesAutoCrop.CoreAPI.Models.Global
{
	public sealed class ImportHelper : IDisposable
	{
		#region Declaration

		private DataTable _table;

		#endregion

		#region Constructor & Desctructor

		public ImportHelper()
		{
			Delimiter = '\0';
			_table = null;
		}

		~ImportHelper()
		{
			_table?.Dispose();
		}

		#endregion

		#region Properties

		#region Property Delimiter: char

		public static char Delimiter { get; set; }

		#endregion

		#endregion

		#region Formats Class

		public sealed class Formats
		{
			public static readonly IFormatter Text = new TextFormatter();
			public static readonly IFormatter Excel = new ExcelFormatter();

			private Formats() { }
		}

		#endregion

		#region IFormater Interface

		public interface IFormatter
		{
			/// <summary>
			/// Return the DataTable after successful operation.
			/// </summary>
			/// <param name="fileSpec">Valid name of the file.</param>
			/// <param name="firstRowColumnHeader">Used the first line as header.</param>
			/// <returns>DataTable if successful.</returns>
			DataTable Import(string fileSpec, bool firstRowColumnHeader);
		}

		#endregion

		#region Excel Formatter Implementation

		public class ExcelFormatter : IFormatter
		{
			#region Declaration & Constructor

			public ExcelFormatter()
			{
			}

			#endregion

			#region Functions

			public DataTable Import(string fileSpec, bool firstRowColumnHeader)
			{
				try
				{
					return ExcelReader.GetDataSetFromFile(fileSpec: fileSpec, firstRowColumnHeader: firstRowColumnHeader).Tables[0];
				}
				catch (Exception e)
				{
					throw new InvalidOperationException(e.Message, e);
				}
			}

			#endregion
		}

		#endregion

		#region Text Formatter Implementation

		public class TextFormatter : IFormatter
		{
			#region Declaration & Construct

			private DataTable _dt;
			private readonly char CharQlfr = '"';
			private readonly string StringQlfr = "\"";
			private readonly string ReplacingQlfr = "\"\"";
			private readonly string ReplacedChar = ((char)1).ToString();

			public TextFormatter()
			{
				_dt = null;
			}

			#endregion

			#region Properties

			private int EndColumnIndex { get; set; }

			#endregion

			#region Functions

			private void MakeColumn(bool hasHeader, string line)
			{
				try
				{
					string[] columns = ParseData(line);
					EndColumnIndex = columns.Length - 1;
					if (hasHeader)
					{
						for (int i = 0; i <= EndColumnIndex; i++)
						{
							DataColumn dc = new(string.Format("Col{0}", i))
							{
								Caption = columns[i],
								ReadOnly = true,
								DataType = typeof(string)
							};
							_dt.Columns.Add(dc);
						}
					}
					else
					{
						for (int i = 0; i <= EndColumnIndex; i++)
						{
							DataColumn dc = new(string.Format("Col{0}", i))
							{
								Caption = string.Format("Column {0}", i),
								ReadOnly = true,
								DataType = typeof(string)
							};
							_dt.Columns.Add(dc);
						}
					}
				}
				catch (Exception e)
				{
					throw new InvalidOperationException(e.Message, e);
				}
			}

			private string[] ParseData(string data)
			{
				List<string> cols = [];
				try
				{
					//To be use as a temporary
					List<string> tmpCols = [];

					//Parse qualifier
					data = ParseQualifier(tmpCols, data);

					//Make actual columns data
					tmpCols = [];
					ParseData(tmpCols, data);

					//Convert back to original value
					foreach (var item in tmpCols)
					{
						string value = item.Replace(ReplacedChar, StringQlfr);
						cols.Add(value);
					}
					tmpCols.Clear();
				}
				catch (Exception e)
				{
					throw new InvalidOperationException(e.Message, e);
				}
				return [.. cols];
			}

			private void ParseData(List<string> cols, string data)
			{
				try
				{
					//Find out position of qualifier
					int pos = data.IndexOf(CharQlfr);

					//If there is no qualifier in the data do nothing 
					if (pos == -1)
					{
						cols.AddRange(data.Split(Delimiter));
						return;
					}

					//Second part of the data after qualifies
					string part2 = data[pos..];

					//If qualifier is beginning of the data 
					if (pos == 0)
					{
						//Remove beginning qualifier 
						string part1 = data[1..];

						//Find out next position of the qualifier, next qualifier must exists since qualifier starts 
						pos = part1.IndexOf(CharQlfr);

						//Read remaining data by removing the delimiter
						part2 = part1[(pos + 1)..];

						//Remove delimiter if still exists at the start of the data
						if (part2.StartsWith(Delimiter.ToString()))
							part2 = part2[1..];

						//Read data just before the position
						part1 = part1[..pos];

						//Remove delimiter if still exists at the start of the data
						if (part1.EndsWith(Delimiter.ToString()))
							part1 = part1[..^1];

						//Add to the column collection
						cols.Add(part1);
					}
					else
					{
						//Read data just before the position
						string part1 = data[..pos];

						//Remove end delimiter if still exists
						if (part1.EndsWith(Delimiter.ToString()))
							part1 = part1[..^1];

						//If this is a valid data, Add to column collection splitting by delimiter of the first part
						if (part1.Length > 0)
							cols.AddRange(part1.Split(Delimiter));
					}

					//Call recursively by using second part of the data
					ParseData(cols, part2);
				}
				catch (Exception e)
				{
					throw new InvalidOperationException(e.Message, e);
				}
			}

			private string ParseQualifier(List<string> cols, string data)
			{
				try
				{
					//Find out the position of qualifier
					int pos = data.IndexOf(CharQlfr);

					//If there is no qualifier in the data do nothing
					if (pos == -1)
					{
						cols.Add(data);
					}
					else
					{
						//Read left part of the data of current position need to add into collection 
						string remData = data[..pos];
						cols.Add(remData);

						//Scan each character of remaining part of the data
						for (int i = pos; i < data.Length; i++)
						{
							//Read character at current position 
							string value = data.Substring(i, 1);

							//Add this character into collection
							if (i == pos)
							{
								cols.Add(value);
							}
							else
							{
								//Read two characters from current position 
								string pairValue = i + 1 < data.Length ? data.Substring(i, 2) : data.Substring(i, 1);

								//If current character is a qualifier but is not a replaceable qualifier 
								if (value[0] == CharQlfr && !pairValue.Equals(ReplacingQlfr))
								{
									//Add this character into collection
									cols.Add(value);

									//If current position is the last position of the data just exit from loop 
									if (i + 1 >= data.Length)
										break;

									//Read remaining part of the data
									data = data[(i + 1)..];

									//Call this function recursively and exit from loop 
									ParseQualifier(cols, data);
									break;
								}
								else if (pairValue.Equals(ReplacingQlfr))             //If this is a replaceable qualifier just replace the with the special character
								{
									value = ReplacedChar;

									//Increase the current position by 1
									i++;
								}

								//Add value into collection 
								cols.Add(value);
							}
						}
					}
				}
				catch (Exception e)
				{
					throw new InvalidOperationException(e.Message, e);
				}

				return string.Join("", [.. cols]);
			}

			public DataTable Import(string fileSpec, bool firstRowColumnHeader)
			{
				try
				{
					int rowIdx = -1;
					bool firstLine = true;
					_dt = new DataTable("TextReader");
					using StreamReader sr = new(fileSpec);
					while (sr.Peek() != -1)
					{
						string line = sr.ReadLine();

						if (line.Trim().Length <= 0)
							continue;

						if (firstLine)
							MakeColumn(firstRowColumnHeader, line);

						if (firstRowColumnHeader && firstLine)
						{
							firstLine = false;
							continue;
						}
						firstLine = false;

						rowIdx++;
						DataRow dr = _dt.NewRow();
						object[] columns = ParseData(line);
						for (int i = 0; i < columns.Length; i++)
						{
							dr[string.Format("Col{0}", i)] = columns[i];
						}
						_dt.Rows.Add(dr);
					}

					sr.Close();
				}
				catch (Exception e)
				{
					throw new InvalidOperationException(e.Message, e);
				}

				return _dt;
			}

			#endregion
		}

		#endregion

		#region Functions

		public void Reset()
		{
			_table = new DataTable("ImportHelper");
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="formatter"></param>
		/// <param name="fileSpec"></param>
		/// <returns></returns>
		public DataTable Import(IFormatter formatter, string fileSpec)
		{
			if (formatter == null)
				throw new ArgumentException("Need to specify a formatter", nameof(formatter));

			try
			{
				_table = Import(formatter, fileSpec, false);
			}
			catch (Exception e)
			{
				throw new InvalidOperationException(e.Message, e);
			}

			return _table;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="formatter"></param>
		/// <param name="fileSpec"></param>
		/// <param name="firstRowColumnHeader"></param>
		/// <returns></returns>
		public DataTable Import(IFormatter formatter, string fileSpec, bool firstRowColumnHeader)
		{
			if (formatter == null)
				throw new ArgumentException("Need to specify a formatter", nameof(formatter));

			if (string.IsNullOrEmpty(fileSpec) || string.IsNullOrWhiteSpace(fileSpec))
				throw new InvalidOperationException("Provide a valid file name to read.");

			if (formatter == Formats.Text && Delimiter == '\0')
				throw new InvalidOperationException("Provide a field delimiter of the file.");

			try
			{
				_table = formatter.Import(fileSpec, firstRowColumnHeader);
			}
			catch (Exception e)
			{
				throw new InvalidOperationException(e.Message);
			}

			return _table;
		}

		/// <summary>
		/// 
		/// </summary>
		public void Dispose()
		{
			_table?.Dispose();
			_table = null;

			GC.SuppressFinalize(this);
		}

		#endregion
	}
}