using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

namespace System.Text.Csv
{

    public class CsvSerializer<T>
    {
        private readonly PropertyInfo[] props;
        private readonly static Regex numbers = new Regex(@"^[0-9\-\+\.]+$");

        public string Separator { get; set; } = ";";
        public string NewLine { get; set; } = "\r\n";
        public string Quot { get; set; } = "\"";
        public Encoding Encoding { get; set; } = Encoding.UTF8;
        public string FloatingPointFormat { get; set; } = "#0.000#";
        public string DateTimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
        public string objDateTimeOffsetFormat { get; set; } = "yyyy-MM-dd HH:mm:ss zzz";
        public string TimeOnlyFormat { get; set; } = "HH:mm:ss";
        public string DateOnlyFormat { get; set; } = "yyyy-MM-dd";

        public CsvSerializer()
        {
            props = typeof(T).GetProperties();
        }

        public void Serialize(IEnumerable<T> data, Stream toStream)
        {
            if (!data.Any())
                return;

            if(!props.Any())
                return;

            void HandleRow(IEnumerable<object?> rowData)
            {
                var row = string.Join(Separator, rowData);
                var bytes = Encoding.GetBytes(row + NewLine);
                toStream.Write(bytes);
            }


            HandleRow(props.Select(p => p.Name));

            foreach ( var item in data)
                HandleRow(props.Select(p => CsvSerializer<T>.Escape(Fromat(p.GetValue(item)))));
        }

        private string Fromat(object? obj)
        {
            if (obj is double objDouble)
                return objDouble.ToString(FloatingPointFormat);

            if (obj is float objfloat)
                return objfloat.ToString(FloatingPointFormat);

            if (obj is DateTime objDateTime)
                return objDateTime.ToString(DateTimeFormat);

            if (obj is DateTimeOffset objDateTimeOffset)
                return objDateTimeOffset.ToString(objDateTimeOffsetFormat);

            if (obj is DateOnly objDateOnly)
                return objDateOnly.ToString(DateOnlyFormat);

            if (obj is TimeOnly objTimeOnly)
                return objTimeOnly.ToString(TimeOnlyFormat);

            return obj?.ToString() ?? string.Empty;
        }

        private static string Escape(string inString)
        {
            if (numbers.IsMatch(inString))
                return inString;

            return $"\"{inString}\"";
        }
    }

}