using Log;
using System.Diagnostics;
using System.Text;
using Universal;

namespace Logger;

public class Logger : ILog
{
    private readonly ManualResetEvent _exitWait = new(false);
    private readonly ManualResetEvent _quitEvent = new(false);

    private EventQueue<LogInfo>? _logEventQueue;
    private FileStream? _logStream;
    private StreamWriter? _logWriter;
    private string _name = "UnInitialized";

    public string? Directory { get; internal set; }
    public string? FilePath { get; internal set; }

    bool ILog.Initialize(string fileName)
    {
        if (_logEventQueue is not null)
            return false;
        _logEventQueue = new(LogHandler);

        if (string.IsNullOrEmpty(fileName))
            fileName = "UnInitialized";

        string[] temp = fileName.Split('.');
        if (temp.Length > 1)
            fileName = temp[0];
        this._name = fileName;

        this.FilePath = Path.Combine(Environment.CurrentDirectory, "Logger", $"{fileName}_{DateTime.Now:MMdd_HH_mm_ss}.log");
        this.Directory = Path.GetDirectoryName(this.FilePath);

        if (string.IsNullOrEmpty(this.Directory))
            return false;

        if (!System.IO.Directory.Exists(this.Directory))
            System.IO.Directory.CreateDirectory(this.Directory);

        try
        {
            _logStream = new(this.FilePath, FileMode.OpenOrCreate);
            _logWriter = new(_logStream);
            _logWriter.WriteLine($"Log Start on {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
            _logWriter.Flush();
        }
        catch
        {
            return false;
        }

        return true;
    }

    bool ILog.Error(string message, DateTime? dateTime)
    {
        if (_logEventQueue is null)
            ((ILog)this).Initialize(string.Empty);

        this._logEventQueue?.Enqueue(new(LogLevel.Error, message, dateTime));
        return true;
    }

    bool ILog.Fatal(string message, DateTime? dateTime)
    {
        if (_logEventQueue is null)
            ((ILog)this).Initialize(string.Empty);

        this._logEventQueue?.Enqueue(new(LogLevel.Fatal, message, dateTime));
        return true;
    }

    bool ILog.Info(string message, DateTime? dateTime)
    {
        if (_logEventQueue is null)
            ((ILog)this).Initialize(string.Empty);

        this._logEventQueue?.Enqueue(new(LogLevel.Info, message, dateTime));
        return true;
    }

    bool ILog.Warning(string message, DateTime? dateTime)
    {
        if (_logEventQueue is null)
            ((ILog)this).Initialize(string.Empty);

        this._logEventQueue?.Enqueue(new(LogLevel.Warning, message, dateTime));
        return true;
    }

    bool ILog.Debug(string message, DateTime? dateTime)
    {
        if (_logEventQueue is null)
            ((ILog)this).Initialize(string.Empty);

        this._logEventQueue?.Enqueue(new(LogLevel.Debug, message, dateTime));
        return true;
    }

    void LogHandler(LogInfo logInfo)
    {
        StringBuilder sb = new();
        sb.Append(logInfo.Time.ToString("HH:mm:ss fff\t"));
        sb.Append(logInfo.Level switch
        {
            LogLevel.Debug => $"Debug\t\t",
            LogLevel.Info => "\t\t\t",
            LogLevel.Warning => "!!!\t\t\t",
            LogLevel.Error => "xxx\t\t\t",
            LogLevel.Fatal => "XXXXXX\t\t",
            _ => "Unknow\t"
        });

        string[] message = logInfo.Message.Replace(Environment.NewLine, ";").Split(';');
        sb.Append(message[0]);
        this._logWriter?.WriteLine(sb);

        for (int i = 1; i < message.Length; i++)
            this._logWriter?.WriteLine($"\t\t\t\t\t\t\t{message[i]}");

        if (message.Length > 1)
            this._logWriter?.WriteLine();

        _logWriter?.Flush();

#if DEBUG
        Debug.WriteLine(this._name + sb);
        Console.WriteLine(this._name + sb);
        for (int i = 1; i < message.Length; i++)
        {
            Debug.WriteLine($"\t\t\t\t\t\t\t{message[i]}");
            Console.WriteLine($"\t\t\t\t\t\t\t{message[i]}");
        }
#endif

        //On Dispose
        if (this._quitEvent.WaitOne(0) && _logEventQueue?.Count == 0)
        {
            this._logEventQueue = null;
            this._logWriter?.Dispose();
            this._logStream?.Dispose();
            _exitWait.Set();
        }
    }

    public void Dispose()
    {
        this._quitEvent.Set();
        _exitWait.WaitOne();
    }
}
