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}\"";
    }
}