using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using log4net;

namespace ChuPiao.Common.Core.Socket
{
    /// <summary>
    /// Socket通讯客户端
    /// </summary>
    public class SocketClient : ISocketClient
    {
        private IPEndPoint _remoteIpEndPoint;
        private System.Net.Sockets.Socket _socket;
        private int _receiveTimeout;
        private int _sendTimeout;
        private int _pingIdleTime;
        private string _pingMsg;
        private Thread _pingThread;
        private ILog _logger;
        private bool _pingIsRun;
        private int _retryNum = 3;
        private NetworkStream _networkStream;
        private Encoding _charset;
        private bool _isStart;

        //最后交易时间
        private volatile int _lastTranslation;

        public SocketClient(string host, int port,int receiveTimeout,int sendTimeout,int pingIdleTime,string pingMsg,ILog logger)
        {
            _remoteIpEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
            _receiveTimeout = receiveTimeout;
            _sendTimeout = sendTimeout;
            _pingIdleTime = pingIdleTime;
            _pingMsg = pingMsg;
            _logger = logger;
            _charset = Encoding.UTF8;

            _isStart = false;

            _pingIsRun = true;
            _pingThread = new Thread(new ThreadStart(DoPing));
            _pingThread.IsBackground = true;
        }
        /// <summary>
        /// 发送ping包
        /// </summary>
        private void DoPing()
        {
            while (_pingIsRun)
            {
                //睡眠延时时间
                int delay=0;
                if(!Connected)
                {
                    lock (this)
                    {
                        ReConnect();
                    }
                    delay = 0;
                }
                else
                {
                    delay = _pingIdleTime - (Environment.TickCount - _lastTranslation);
                    if(delay<=0)
                    {
                        //_logger.Debug("链接空闲，需要发送ping包");
                        SendPing();
                    }
                }
                try
                {
                    if(delay>0)
                    {
                        Thread.Sleep(delay);
                    }
                    else
                    {
                        Thread.Sleep(_pingIdleTime);
                    }
                }
                catch (ThreadInterruptedException e)
                {
                }
            }
        }

        private void SendPing()
        {
            lock (this)
            {
                try
                {
                    if(!Connected)
                    {
                        ReConnect();
                        return;
                    }
                    int delay = _pingIdleTime - (Environment.TickCount - _lastTranslation);
                    if(delay>0)
                    {
                        _logger.Debug("Ping等待锁的时间里，已有其他包正常发送，不需要Ping");
                        return;
                    }
                    Console.WriteLine("发送心跳包");
                    Send(_pingMsg);
                    Read();
                    _logger.Debug("ping成功，连接正常");
                }
                catch (Exception e)
                {
                    bool isIOException = e is IOException;
                    if(!isIOException)
                    {
                        _logger.Info(string.Concat("非IO异常原因，SocketClient重新建立连接：",e));
                    }
                    Console.WriteLine(string.Concat("发送心跳包异常。错误信息：", e));
                    ReConnect();
                }
            }
        }
        /// <summary>
        /// 发送消息包
        /// </summary>
        /// <param name="message"></param>
        private void Send(string message)
        {
            //Console.WriteLine(string.Concat("发送消息包：",message));
            byte[] contentData = _charset.GetBytes(message);
            //因为BitConverter.GetBytes是按小端字节序转换成byte数组，而网络字节序是大端字节序，所以进行转换
            byte[] headerData = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(contentData.Length));
            _networkStream.Write(headerData,0,headerData.Length);
            _networkStream.Write(contentData,0,contentData.Length);
            _networkStream.Flush();
        }
        /// <summary>
        /// 读取回复消息包
        /// </summary>
        /// <returns></returns>
        private string Read()
        {
            string content = string.Empty;
            byte[] buffer = new byte[4];
            _networkStream.Read(buffer, 0, buffer.Length);
            //因为BitConverter.GetBytes是按小端字节序转换成byte数组，而网络字节序是大端字节序，所以进行转换
            int contentLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(buffer, 0));
            if (contentLength > 0)
            {
                byte[] contentData = new byte[contentLength];

                int offset = 0;
                int num = 0;
                do
                {
                    num = _networkStream.Read(contentData, offset, contentLength - offset);
                    offset += num;
                } while (num>0&&offset < contentLength);
                if(offset!=contentLength)
                {
                    throw new Exception("交易包长度与实际交易内容长度不符");
                }
                _lastTranslation = Environment.TickCount;
                content = _charset.GetString(contentData);
            }
            return content;
        }

        /// <summary>
        /// 启动客户端
        /// </summary>
        private void Start()
        {
            try
            {
                Connect();
            }
            catch (IOException e)
            {
                _logger.Error(e);
            }
            _pingIsRun = true;
            if(_pingThread==null)
            {
                _pingThread = new Thread(new ThreadStart(DoPing));
                _pingThread.IsBackground = true;
            }
            _pingThread.Start();
            _isStart = true;
        }

        /// <summary>
        /// 连接到远程服务器
        /// </summary>
        private void Connect()
        {
            for (int i = 0; i < _retryNum; i++)
            {
                try
                {
                    _socket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    _socket.SendTimeout = _sendTimeout;
                    _socket.ReceiveTimeout = _receiveTimeout;
                    _socket.NoDelay = true;
                    _socket.Connect(_remoteIpEndPoint);
                    _logger.Info(string.Concat("连接到",_remoteIpEndPoint.ToString(),"成功！"));
                    _lastTranslation = Environment.TickCount;//更新最后交易时间
                    if (_socket.Connected)
                    {
                        break;
                    }
                }
                catch (Exception exception)
                {
                    _logger.Info(string.Concat("连接到", _remoteIpEndPoint.ToString(), "异常。错误信息：",exception));
                    Console.WriteLine(string.Concat("异常：", exception));

                    if(i==_retryNum-1)
                    {
                        throw new IOException(string.Concat("连接到",_remoteIpEndPoint.ToString()," ",_retryNum,"次失败"));
                    }
                    _logger.Info(string.Concat("尝试第",i+1, "次连接到", _remoteIpEndPoint.ToString()));
                }
            }
            _networkStream = new NetworkStream(_socket, true);
        }
        /// <summary>
        /// 重新连接
        /// </summary>
        private void ReConnect()
        {
            _logger.Info("连接断掉，正在进行重连……");
            Console.WriteLine("连接断掉，正在进行重连……");
            try
            {
                _socket.Close();
            }
            catch (IOException e)
            {
                _logger.Error(e);
            }
            try
            {
                Connect();
            }
            catch (IOException e)
            {
                _logger.Error(e);
            }
        }
        /// <summary>
        /// 客户端是否处于连接状态
        /// </summary>
        private bool Connected
        {
            get { return _socket != null && _socket.Connected; }
        }

        #region Implementation of ISocketClient

        /// <summary>
        /// 提交请求
        /// </summary>
        /// <param name="requestMsg"></param>
        /// <returns></returns>
        public string CommitRequest(string requestMsg)
        {
            if(!_isStart)
            {
                lock (this)
                {
                    if(!_isStart)
                    {
                        Start();
                    }
                }
            }
            if(!Connected)
            {
                throw new IOException(string.Concat("等待SocketClient 建立连接,最大时间：" , _pingIdleTime , "毫秒"));
            }
            lock (this)
            {
                if (!Connected)
                {
                    throw new IOException(string.Concat("等待SocketClient 建立连接,最大时间：", _pingIdleTime, "毫秒"));
                }
                try
                {
                    Send(requestMsg);
                    return Read();
                }
                catch (Exception e)
                {
                    bool isIOException = e is IOException;
                    if (!isIOException)
                    {
                        _logger.Info(string.Concat("非IO异常原因，SocketClient主动关闭连接：", e));
                    }
                    try
                    {
                        _socket.Close();
                    }
                    catch (Exception ex)
                    {
                        _logger.Error(ex);
                    }
                    _pingThread.Interrupt();
                    _pingThread = null;
                    _isStart = false;
                    throw;
                }
            }
        }

        /// <summary>
        /// 关闭客户端
        /// </summary>
        public void Close()
        {
            _pingIsRun = false;
            if (_pingThread!=null)
            {
                _pingThread.Interrupt();
            }
            _pingThread = null;
            if(_socket!=null)
            {_socket.Close();}
            _isStart = false;
        }

        #endregion
    }
}

