using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.Logger.Servers.Tcp
{
    // This file is part of the EonaCat project(s) which is released under the Apache License.
    // See the LICENSE file or go to https://EonaCat.com/License for full license details.

    public class Tcp : IDisposable
    {
        private string _hostname = "127.0.0.1";
        private int _port = 514;
        private TcpClient _tcp;
        private readonly SemaphoreSlim _sendLock = new(1, 1);
        private bool _disposed;

        public event EventHandler<Exception> OnException;

        public bool IsConnected { get; private set; }
        public string Nickname { get; set; }
        public string IpPort => $"{_hostname}:{_port}";
        public List<ELogType> TypesToLog { get; set; }

        public Tcp(string hostname, int port, string nickname = null, List<ELogType> typesToLog = null)
        {
            _hostname = hostname ?? throw new ArgumentNullException(nameof(hostname));
            _port = port >= 0 ? port : throw new ArgumentException("Port must be zero or greater.");
            Nickname = nickname ?? IpPort;
            TypesToLog = typesToLog;

            SetTcp();
        }

        public string Hostname
        {
            get => _hostname;
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(nameof(Hostname));
                }

                _hostname = value;
                SetTcp();
            }
        }

        public int Port
        {
            get => _port;
            set
            {
                if (value < 0)
                {
                    throw new ArgumentException("Port must be zero or greater.");
                }

                _port = value;
                SetTcp();
            }
        }

        internal void SetTcp()
        {
            if (_disposed)
            {
                return;
            }

            try
            {
                DisposeTcp();
                _tcp = new TcpClient(_hostname, _port);
                IsConnected = true;
            }
            catch
            {
                IsConnected = false;
            }
        }

        internal void DisposeTcp()
        {
            IsConnected = false;

            try
            {
                _tcp?.Close();
                _tcp?.Dispose();
            }
            catch
            {
                // Silent fail
            }
            finally
            {
                _tcp = null;
            }
        }

        public async Task WriteAsync(byte[] data)
        {
            if (_disposed || data == null || data.Length == 0)
            {
                return;
            }

            await InternalWriteAsync(data);
        }

        public async Task WriteAsync(string data)
        {
            if (_disposed || string.IsNullOrWhiteSpace(data))
            {
                return;
            }

            var bytes = Encoding.UTF8.GetBytes(data);
            await InternalWriteAsync(bytes);
        }

        private async Task InternalWriteAsync(byte[] data)
        {
            if (_disposed)
            {
                return;
            }

            await _sendLock.WaitAsync();
            try
            {
                if (!IsConnected || _tcp == null)
                {
                    SetTcp();
                }

                if (!IsConnected || _tcp == null)
                {
                    return;
                }

                using var stream = _tcp.GetStream();
                await stream.WriteAsync(data, 0, data.Length);
                await stream.FlushAsync();
            }
            catch (Exception ex)
            {
                OnException?.Invoke(this, ex);
                IsConnected = false;
            }
            finally
            {
                _sendLock.Release();
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            _disposed = true;

            if (disposing)
            {
                DisposeTcp();
                _sendLock.Dispose();
            }
        }

        ~Tcp()
        {
            Dispose(false);
        }
    }
}
