using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
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;
        internal TcpClient _tcp;

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

        /// <summary>
        /// Tcp 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 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.IsNullOrEmpty(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();
            }
        }

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

        internal void SetTcp()
        {
            try
            {
                DisposeTcp();
                _tcp = new TcpClient(_Hostname, _Port);
                IsConnected = true;
            }
            catch
            {
                IsConnected = false;
            }
        }

        internal void DisposeTcp()
        {
            if (_tcp != null)
            {
                IsConnected = false;

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

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

        public async Task WriteAsync(byte[] data)
        {
            if (_tcp == null)
            {
                return;
            }

            if (data == null)
            {
                return;
            }

            if (!IsConnected)
            {
                SetTcp();
            }

            if (IsConnected)
            {
                using var stream = _tcp.GetStream();
                await stream.WriteAsync(data, 0, data.Length);
                await stream.FlushAsync();
            }
        }

        public async Task WriteAsync(string data)
        {
            if (_tcp == null)
            {
                return;
            }

            if (string.IsNullOrEmpty(data))
            {
                return;
            }

            if (!IsConnected)
            {
                SetTcp();
            }

            if (IsConnected)
            {
                var sendData = Encoding.UTF8.GetBytes(data);
                using var stream = _tcp.GetStream();
                await stream.WriteAsync(sendData, 0, sendData.Length);
                await stream.FlushAsync();
            }
        }

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