using AwInitilizer.Interface;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace AwInitilizer.Assist
{
    public class TcpSerializer : Interface.ISerialPort
    {
        public event EventHandler<byte[]> OnDataReceived;
        public event PropertyChangedEventHandler PropertyChanged;

        public string IP { get; private set; }

        private ConnectStatus _ConnectStatus = ConnectStatus.Cleared;
        public ConnectStatus ConnectStatus
        {
            get => _ConnectStatus;
            set
            {
                if (_ConnectStatus != value)
                {
                    _ConnectStatus = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ConnectStatus"));
                }
            }
        }

        private IPAddress iPAddress;
        private IPEndPoint iPEndPoint;
        private Socket tcpSocket;

        public TcpSerializer(string ip = "192.168.1.10",int port = 8234)
        {
            IP = ip;

            iPAddress = IPAddress.Parse(ip);
            iPEndPoint = new IPEndPoint(iPAddress, port);
        }

        public async Task OpenAsync()
        {
            await Conenct();
        }

        public void Open()
        {
            _ = Conenct();
        }

        public void Close()
        {
            ConnectStatus = ConnectStatus.DisConnected;
            try
            {
                tcpSocket?.Close();
                tcpSocket?.Dispose();
            }
            catch
            {

            }
        }

        public void WriteData(byte[] msg)
        {
            if (ConnectStatus != ConnectStatus.Connected)
                return;
            tcpSocket.Send(msg);
        }

        private async Task Conenct()
        {
            ConnectStatus = ConnectStatus.Connecting;

            tcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            tcpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            var connectTask = tcpSocket.ConnectAsync(iPEndPoint);
            //var asyncResult = tcpSocket.BeginConnect(iPEndPoint, OnTcpConnectComplete, null);
            //await Task.Delay(2 * 1000);
            await Task.WhenAny(connectTask, Task.Delay(2 * 1000));
            if (tcpSocket.Connected)
            {
                ConnectStatus = ConnectStatus.Connected;

                var stateObject = new StateObject(bufferSize: 512, tcpSocket);
                WaitForData(stateObject);
            }
            else
            {
                ConnectStatus = ConnectStatus.ConnectionFail;

                //Try Clear - disconnect
                Close();
            }
        }

        private void OnTcpConnectComplete(IAsyncResult ar)
        {
            if (tcpSocket.Connected)
            {
                ConnectStatus = ConnectStatus.Connected;

                var stateObject = new StateObject(bufferSize: 512, tcpSocket);
                WaitForData(stateObject);
            }
            else
            {
                ConnectStatus = ConnectStatus.ConnectionFail;
            }
        }

        private void WaitForData(StateObject stateObject)
        {
            try
            {
                var soc = stateObject.sSocket;

                if (!soc.Connected) { return; }

                IAsyncResult iar = soc.BeginReceive(
                    stateObject.sBuffer,
                    0,
                    stateObject.sBuffer.Length,
                    SocketFlags.None,
                    new AsyncCallback(ReveiveCallBack),
                    stateObject);
            }
            catch (Exception e)
            {
                if (e != null)
                {
                    if (e.Message != null)
                        Console.WriteLine(e.Message);
                    if (e.StackTrace != null)
                        Console.WriteLine(e.StackTrace);
                }
            }
        }

        private void ReveiveCallBack(IAsyncResult ar)
        {
            StateObject stateObject = (StateObject)ar.AsyncState;

            if (!stateObject.sSocket.Connected)
            {
                //server Diconnected
                HandleDisconnected();
                return;
            }

            int bytesReceived;
            try
            {
                bytesReceived = stateObject.sSocket.EndReceive(ar);
            }
            catch (SocketException e)
            {
                if (e != null)
                {
                    if (e.Message != null)
                        System.Diagnostics.Debug.WriteLine(e.Message);
                    if (e.StackTrace != null)
                        System.Diagnostics.Debug.WriteLine(e.StackTrace);
                }

                //server Diconnected
                HandleDisconnected();
                return;
            }

            if (bytesReceived == 0)
            {
                stateObject.sSocket.Disconnect(true);
                HandleDisconnected();
                return;
            }

            byte[] receivedBytes = new byte[bytesReceived];
            Array.Copy(stateObject.sBuffer, 0, receivedBytes, 0, bytesReceived);

            OnDataReceived?.Invoke(this, receivedBytes);

            //continue receive
            WaitForData(stateObject);
        }

        private void HandleDisconnected()
        {
            Close();
        }
    }

    internal class StateObject
    {
        public byte[] sBuffer;
        public Socket sSocket;

        public StateObject(int bufferSize, Socket sock, int timeOutMilliSecond = -1)
        {
            sBuffer = new byte[bufferSize];
            sSocket = sock;
        }
    }
}