using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace EonaCat.Logger.Servers.Udp
{
    // 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 Udp : IDisposable
    {
        const int MaxUdpPacketSize = 4096;
        private string _Hostname = "127.0.0.1";
        private int _Port = 514;
        internal UdpClient _udp;

        public bool IsConnected { get; private set; }
        public string Nickname { get; set; }
        public string IpPort => _Hostname + ":" + _Port;

        /// <summary>
        /// Udp Server
        /// </summary>
        /// <param name="hostname"></param>
        /// <param name="port"></param>
        /// <param name="nickname"></param>
        /// <param name="typesToLog"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public Udp(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.");
            TypesToLog = typesToLog;
            Nickname = nickname ?? IpPort;
            SetUdp();
        }

        public string Hostname
        {
            get => _Hostname;
            set
            {
                if (string.IsNullOrEmpty(value))
                    throw new ArgumentNullException(nameof(Hostname));

                _Hostname = value;
                SetUdp();
            }
        }

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

                _Port = value;
                SetUdp();
            }
        }

        public List<ELogType> TypesToLog { get; set; }

        internal void SetUdp()
        {
            try
            {
                DisposeUdp();
                _udp = new UdpClient(_Hostname, _Port);
                IsConnected = true;
            }
            catch
            {
                IsConnected = false;
            }
        }

        internal void DisposeUdp()
        {
            if (_udp != null)
            {
                IsConnected = false;

                try
                {
                    _udp?.Close();
                    _udp.Dispose();
                }
                catch
                {
                    // Do nothing
                }
                _udp = null;
            }
        }

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

        public async Task WriteAsync(byte[] data, bool dontFragment = false)
        {
            if (_udp == null || data == null)
            {
                return;
            }

            if (!IsConnected)
            {
                SetUdp();
            }

            if (IsConnected)
            {
                _udp.DontFragment = dontFragment;

                int maxChunkSize = MaxUdpPacketSize;
                int offset = 0;

                while (offset < data.Length)
                {
                    int chunkSize = Math.Min(maxChunkSize, data.Length - offset);
                    byte[] chunk = new byte[chunkSize];
                    Array.Copy(data, offset, chunk, 0, chunkSize);

                    await _udp?.SendAsync(chunk, chunk.Length);
                    offset += chunkSize;
                }
            }
        }

        public async Task WriteAsync(string data, bool dontFragment = false)
        {
            if (_udp == null || string.IsNullOrEmpty(data))
            {
                return;
            }

            if (!IsConnected)
            {
                SetUdp();
            }

            if (IsConnected)
            {
                var sendData = Encoding.UTF8.GetBytes(data);
                _udp.DontFragment = dontFragment;

                int maxChunkSize = MaxUdpPacketSize;
                int offset = 0;

                while (offset < sendData.Length)
                {
                    int chunkSize = Math.Min(maxChunkSize, sendData.Length - offset);
                    byte[] chunk = new byte[chunkSize];
                    Array.Copy(sendData, offset, chunk, 0, chunkSize);

                    await _udp?.SendAsync(chunk, chunk.Length);
                    offset += chunkSize;
                }
            }
        }

        ~Udp()
        {
            Dispose();
        }
    }
}
