using Edge.Core.Parser.BinaryParser.MessageEntity;
using Edge.Core.Parser.BinaryParser.Util;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Edge.Core.Processor.Communicator
{
    public class TcpClientCommunicatorWithForwarding<T> : TcpClientCommunicator<T> where T : MessageTemplateBase
    {
        /// <summary>
        /// 0 is for false, 1 is for true.
        /// </summary>
        private int isInTcpConnecting = 0;

        /// <summary>
        /// 0 for not started, 1 for started.
        /// </summary>
        private int isStarted = 0;

        private string forwardingToIpAddress;
        private int forwardingToPortNumber;
        private TcpClient tcpClient;

        public TcpClientCommunicatorWithForwarding(IMessageCutter<byte[]> binaryMsgCutter,
            Edge.Core.Parser.BinaryParser.ParserBase parser,
            string pumpServerTcpListeningIpAddress,
            int pumpServerTcpListeningPortNumber,
            string forwardingToIpAddress,
            int forwardingToPortNumber,
            string customLoggerFileName,
            IServiceProvider services
            ) : base(binaryMsgCutter, parser, pumpServerTcpListeningIpAddress, pumpServerTcpListeningPortNumber, customLoggerFileName, services)
        {
            this.forwardingToIpAddress = forwardingToIpAddress;
            this.forwardingToPortNumber = forwardingToPortNumber;
            base.OnRawDataReceived += (rawData) =>
            {
                try
                {
                    if (this.isInTcpConnecting == 1) return;
                    if (logger.IsEnabled(LogLevel.Debug))
                        base.logger.LogDebug("TCP to " + this.tcpClient.Client.RemoteEndPoint + " Forwarding outgoing--->: 0x" + rawData.ToHexLogString());
                    this.tcpClient.Client.Send(rawData);
                }
                catch (Exception exx)
                {
                    base.logger.LogError("Forwarding tcp msg to "
                        + this.forwardingToIpAddress + ":" + this.forwardingToPortNumber
                        + " Send exceptioned, will re-build connection, detail: " + exx);
                    this.SetupTcpClientConnectionToForwardingPeerInBackground();
                }
            };
        }

        public override Task<bool> Start()
        {
            if (0 == Interlocked.CompareExchange(ref this.isStarted, 1, 0))
            {
                try
                {
                    this.SetupTcpClientConnectionToForwardingPeerInBackground();
                    return Task.FromResult(true);
                }
                catch (Exception exxx)
                {
                    return Task.FromResult(false);
                }
            }

            return base.Start();
        }

        private void SetupTcpClientConnectionToForwardingPeerInBackground()
        {
            if (0 == Interlocked.CompareExchange(ref this.isInTcpConnecting, 1, 0))
                ThreadPool.QueueUserWorkItem(o =>
                {
                    while (!base.disposed)
                    {
                        try
                        {
                            base.logger.LogInformation("FCC as the client side is establishing to forwarding peer "
                                + this.forwardingToIpAddress + ":" + this.forwardingToPortNumber);
                            if (this.tcpClient != null) this.tcpClient.Dispose();
                            this.tcpClient = new TcpClient(this.forwardingToIpAddress, this.forwardingToPortNumber);
                            base.logger.LogInformation("FCC has established to forwarding peer server("
                                + this.forwardingToIpAddress + ":" + this.forwardingToPortNumber
                                + "), local port: " + this.tcpClient.Client.LocalEndPoint.ToString());
                            byte[] buffer = new byte[tcpReceiveBufferSize];
                            this.tcpClient.GetStream().BeginRead(buffer, 0,
                                tcpReceiveBufferSize,
                                this.Tcp_DataReceived, new System.Tuple<TcpClient, byte[]>(this.tcpClient, buffer));
                            this.isInTcpConnecting = 0;
                            return;
                        }
                        catch (Exception exxx)
                        {
                            base.logger.LogError("Setup tcp client which connect to forwarding peer("
                                + this.forwardingToIpAddress + ":" + this.forwardingToPortNumber
                                + ") exceptioned: " + exxx + "\r\n Will keep retrying...");
                            Thread.Sleep(3000);
                        }
                    }
                });
        }

        private void Tcp_DataReceived(IAsyncResult ar)
        {
            System.Tuple<TcpClient, byte[]> parameter = null;
            byte[] dataRead = null;
            int bytesReadCount = 0;
            try
            {
                parameter = (System.Tuple<TcpClient, byte[]>)(ar.AsyncState);
                dataRead = parameter.Item2;
                bytesReadCount = parameter.Item1.Client.EndReceive(ar);
                if (bytesReadCount == 0)
                    throw new Exception("tcp client received 0 count data which indicates the connection is broken, trigger disconnection");
            }
            catch (Exception tcpExp)
            {
                base.logger.LogInformation("Tcp client EndReceive from " + this.forwardingToIpAddress
                    + " exceptioned: " + tcpExp + "\r\n will re-build connection...");
                this.SetupTcpClientConnectionToForwardingPeerInBackground();
                return;
            }

            try
            {
                if (logger.IsEnabled(LogLevel.Debug))
                    base.logger.LogDebug("TCP from " + parameter.Item1.Client.RemoteEndPoint.ToString() + " <---Forwarding Incoming: 0x" + dataRead.Take(bytesReadCount).ToHexLogString());
                var data = dataRead.Take(bytesReadCount).ToArray();
                base.tcpClient.Client.Send(data);
                byte[] buffer = new byte[tcpReceiveBufferSize];
                parameter.Item1.GetStream().BeginRead(buffer, 0,
                    tcpReceiveBufferSize,
                    this.Tcp_DataReceived, new System.Tuple<TcpClient, byte[]>(parameter.Item1, buffer));
            }
            catch (Exception ex)
            {
                base.logger.LogError(" Exception in Forwarding Tcp_DataReceived(...):\r\n" + ex.ToString() + "\r\n will rebuild the connection until succeed.");
                this.SetupTcpClientConnectionToForwardingPeerInBackground();
                return;
            }
        }
    }
}
