using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using ToolKitlib;
using System.Net.Sockets;
using System.Net;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
using System.Security.AccessControl;

namespace DianBiao
{
    class Dianbiao
    {
        static byte[] longinBytes = new byte[] { 0x68, 0x32, 0x00, 0x32, 0x00, 0x68, 0xC9, 0x59, 0x04, 0x01, 0x00, 0x00, 0x02, 0x70, 0x00, 0x00, 0x01, 0x00, 0x9A, 0x16 };
        static byte[] OKReturn = new byte[] { 0x68, 0x32, 0x00, 0x32, 0x00, 0x68, 0x0B, 0x59, 0x04, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x01, 0x00, 0xCA, 0x16 };
        static byte[] A1;
        static byte[] A2;
        static object A3;

        static string IP { get { return "172.16.1.49"; } }
        static int Port { get { return 6800; } }
        static log4net.ILog log;//日志插件

        public static Socket serverSocket;
        /// <summary>
        /// 在线用户列表
        /// </summary>
        public static List<string> idOnLine = new List<string>();
        /// <summary>
        /// 存储IP及Socket--便于服务器与指定客户端通信--Socket.Send ()
        /// </summary>
        public static Dictionary<string, Socket> OnLineDic = new Dictionary<string, Socket>();

        /// <summary>
        /// 循环获取数据时间
        /// </summary>
        public static int loopTime = 10;

        public static Encoding encoding = Encoding.Default;

        static void Main(string[] args)
        {
            //初始化日志
            log4net.Config.XmlConfigurator.Configure();
            log = log4net.LogManager.GetLogger("loginfo");
            init();
            while (true)
            {
                if (OnLineDic.Count == 0) continue;
                Thread.Sleep(1000 * loopTime);
            }
        }


        /// <summary>
        /// 初始化服务器
        /// </summary>
        public static void init()
        {
            //TCP
            try
            {
                //调用socket(函数创建一个用于通信的套接字。
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                //给已经创建的套接宁绑定一个端口号
                IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(IP), Port);
                serverSocket.Bind(endPoint);

                //调用listen(函数使套接宁成为—个监听
                serverSocket.Listen(1000);//最大连接数

                //开启监听任务
                Task.Run(new Action(() =>
                {
                    ListenConnection();
                }));

            }
            catch (Exception e)
            {
                log.Info(string.Format("服务器初始化异常:{0}", e.Message));
            }
        }

        /// <summary>
        /// 监听任务
        /// </summary>
        private static void ListenConnection()
        {
            while (true)
            {
                //调用accept() 函数来接受客户端的连接，这是就可以和客户端通信了。
                Socket clientSocket = serverSocket.Accept();//新用户连接后触发返回新的socket（阻塞）
                string ipPort = clientSocket.RemoteEndPoint.ToString();//连接用户的IP及端口
                addOnLine(ipPort, clientSocket, true);
                //Console.WriteLine(clientSocket.RemoteEndPoint.ToString() + "上线了");
                log.Info(string.Format("{0}上线了", clientSocket.RemoteEndPoint.ToString()));
                Task.Run(() => ReceiveMsg(clientSocket));//针对单个客户端开启线程（接收）

            }
        }

        /// <summary>
        /// 接收消息--接收到进入触发
        /// </summary>
        /// <param name="clientSocket"></param>
        private static void ReceiveMsg(Socket clientSocket)
        {
            while (true)
            {
                byte[] bytes = new byte[1024];
                int length = -1;
                try
                {
                    length = clientSocket.Receive(bytes);//返回字节数
                }
                catch (Exception)
                {
                    //用户下线--更新在线列表
                    addOnLine(clientSocket.RemoteEndPoint.ToString(), clientSocket, false);
                    //Console.WriteLine(clientSocket.RemoteEndPoint.ToString() + "下线了");
                    log.Info(string.Format("{0}下线了", clientSocket.RemoteEndPoint.ToString()));
                    break;//结束线程
                }
                if (length == 0)
                {
                    //用户下线--更新在线列表
                    addOnLine(clientSocket.RemoteEndPoint.ToString(), clientSocket, false);
                    //Console.WriteLine(clientSocket.RemoteEndPoint.ToString() + "下线了");
                    log.Info(string.Format("{0}下线了", clientSocket.RemoteEndPoint.ToString()));
                    break;//结束线程
                }

                if (length > 0)
                {
                    //string message = Encoding.Default.GetString(bytes, 0, length);
                    string message = ToolKit.byteArrayToHexString(bytes, length);
                    log.Info(string.Format("接收报文:{0}", message));
                    string mag = message.Replace(" ", "");
                    byte[] frame = ToolKit.hexStringToByteArray(mag);
                    //收到消息--mag                   

                    //协议判断                   
                    #region CRC
                    //控制域、地址域、链路用户数据（应用层）字节总数
                    var count = frame.Length - 1 - 2;//-2(crc和结束字节)
                    int crc_ = 0;
                    for (int i = 6; i <= count; i++)
                        crc_ += (int)(frame[i]);
                    var crc = (crc_ & 0xFF).ToString("X2");
                    if (crc.ToUpper() != Convert.ToString(frame[count + 1], 16).ToUpper())
                    {
                        log.Info(string.Format("CRC校验错误：{0}", crc));
                        return;
                    }
                    //Console.WriteLine($"CRC：{crc}");
                    #endregion

                    if (frame[12] == 0x02)//AFN=02H
                    {
                        //登录
                        if (DT_Fn(frame) == 1)
                        {
                            clientSocket.Send(OKReturn);
                            return;
                        }
                        //心跳
                        else if (DT_Fn(frame) == 3)
                        {
                            clientSocket.Send(OKReturn);
                            return;
                        }
                    }

                    analysis(frame);
                }
            }
        }

        /// <summary>
        /// 更新在线用户列表
        /// </summary>
        /// <param name="clientIp">用户IP端口号套接</param> 
        /// <param name="clientSocket">Socket</param>
        /// <param name="isAdd">用是否增加</param>
        private static void addOnLine(string clientIp, Socket clientSocket, bool isAdd)
        {
            try
            {
                if (isAdd)
                {
                    idOnLine.Add(clientIp);
                    OnLineDic.Add(clientIp, clientSocket);
                }
                else
                {
                    idOnLine.Remove(clientIp);
                    OnLineDic.Remove(clientIp);
                }
            }
            catch (Exception e)
            {
                log.Info(string.Format("更新在线用户异常:{0}", e.Message));
            }
            /*Invoke(new Action(() =>
            {
                if (isAdd) { idOnLine.Add(clientIp); }
                else { idOnLine.Remove(clientIp); }
            }));*/
        }


        /// <summary>
        /// 解析电表数据
        /// </summary>
        /// <param name="frame"></param>
        private static void analysis(byte[] frame)
        {
            Console.WriteLine();

            #region L
            var a = frame[1] & 0x03;//& 0000 0011
            /*1:Q / GDW 130——2005
            2:376.1
            3:保留
            4:禁止*/

            //用户数据长度
            var b = (frame[2] << 8 | frame[1]) >> 2;
            Console.WriteLine($"协议：{a}\n长度:{b}");
            #endregion

            #region 控制域
            //DIR = 0，由主站发出，DIR = 1，由终端发出
            object dir = frame[6] & 0x80;//& 1000 0000
            dir = Convert.ToString((int)dir, 2).PadLeft(8, '0');
            dir = dir.ToString()[0];

            //PRM=1，报文来自启动站；PRM=0，报文来自终端
            object prm = frame[6] & 0x40;//& 0100 0000
            prm = Convert.ToString((int)prm, 2).PadLeft(8, '0');
            prm = prm.ToString()[1];

            //FCB（上行）
            object fcb = frame[6] & 0x20;//& 0010 0000
            fcb = Convert.ToString((int)fcb, 2).PadLeft(8, '0');
            fcb = fcb.ToString()[2];
            //ACD（下行）
            object acd = frame[6] & 0x20;//& 0010 0000
            acd = Convert.ToString((int)acd, 2).PadLeft(8, '0');
            acd = acd.ToString()[2];

            //FCV=1，表示FCB位有效；FCV=0，表示FCB位无效。
            object fvc = frame[6] & 0x10;//& 0001 0000
            fvc = Convert.ToString((int)fvc, 2).PadLeft(8, '0');
            fvc = fvc.ToString()[3];

            //功能码
            object LoginFrame = frame[6] & 0x0F;//& 0000 1111
            //Console.WriteLine($"DIR:{dir}\nPRM:{prm}\n功能码:{LoginFrame}");
            #endregion

            #region 地址域
            //行政区划码A1
            //A1 = (frame[7] & 0x0F) + (frame[7] >> 4) * 10 + (frame[8] & 0x0F) * 100 + (frame[8] >> 4) * 1000;
            //A1 = A1.ToString().PadLeft(4, '0');
            A1 = new byte[] { frame[7], frame[8] };
            //终端地址A2(十六进制)
            //A2 = (frame[10] << 8) + frame[9];
            //A2 = A2.ToString().PadLeft(4, '0');
            A2 = new byte[] { frame[9], frame[10] };
            //主站地址和组地址标志A3
            A3 = frame[11].ToString().PadLeft(2, '0');

            //A3 D0 D0=0_表示终端地址_A2_为单地址；D0=1_表示终端地址_A2_为组地址
            var A3D0 = int.Parse(A3.ToString()) & 0x01;//& 0000 0001
            switch ((A3Type)A3D0)
            {
                case A3Type.终端地址为单地址:
                    A3 = (A3Type)A3D0;
                    break;
                case A3Type.终端地址为组地址:
                    A3 = (A3Type)A3D0;
                    break;
            }

            //A3 D1～D7 主站地址_MSA
            var msa = (frame[11]) >> 1;

            //Console.WriteLine($"行政区划码A1:{A1}\n终端地址A2:{A2}\n主站地址和组地址标志A3:{A3}\nMSA:{msa}");
            #endregion

            #region 应用层数据
            //frame[12] ~ frame[frame.Length-1-2]
            /*List<byte> bytes1 = new List<byte>();
            for (int i = 12; i <= frame.Length - 1 - 2; i++)
                bytes1.Add(frame[i]);
            byte[] AE = ToolKit.listToBytes(bytes1);
            Array.Reverse(AE);//调整大小端
            */

            //应用层功能码（AFN）
            var afn = ToolKit.byteArrayToHexString(new byte[] { frame[12] }, false);

            //帧序列域（SEQ）
            //TpV --TpV=0，附加信息域中无时间标签Tp TpV = 1，附加信息域中带有时间标签Tp
            object tpv = frame[13] & 0x80;//& 1000 0000
            tpv = Convert.ToString((int)tpv, 2).PadLeft(8, '0');
            tpv = tpv.ToString()[0];

            // FIR-FIN 为 1-1 单帧
            //FIR
            object fir = frame[13] & 0x40;//$ 0100 0000
            fir = Convert.ToString((int)fir, 2).PadLeft(8, '0');
            fir = fir.ToString()[1];
            //FIN
            object fin = frame[13] & 0x20;//& 0010 0000
            fin = Convert.ToString((int)fin, 2).PadLeft(8, '0');
            fin = fin.ToString()[2];

            //CON位置“1”，表示需要对该帧报文进行确认；置“0”，表示不需要对该帧报文进行确认
            object con = frame[13] & 0x10;//& 0001 0000
            con = Convert.ToString((int)con, 2).PadLeft(8, '0');
            con = con.ToString()[3];

            //PSEQ/RSEQ 传输类别【(S1服务）发送/无回答传输服务】--启动报文 PFC=14 PRM=1，FIR=1，FIN=1，CON=0，PSEQ-14'
            object pseq = frame[13] & 0x0F;//& 0000 1111

            //信息点DA -- DA1  DA2
            dynamic da1 = (Convert.ToString(frame[14], 2));
            if (da1 == "0")
                da1 = 0;
            else if (frame[14] == 0xFF)
                da1 = 0;
            else
                da1 = (da1.ToString()).Length;
            int da2 = (int)(frame[15]);
            int pn;//Fn=(DA2-1)8+DA1
            if (frame[14] == 0 && frame[15] == 0)
                pn = 0;//DA1-DA2全为“0”时，表示终端信息点，用_p0_表示
            else if (frame[14] == 0xFF && frame[15] == 0)
                pn = 0; //DA1 = 0xFF、DA2 = 0x00，表示所有有效测量点（不含p0）。
            else
                pn = (da2 - 1) * 8 + da1;



            //Console.WriteLine($"AFN：{afn}\nTpV:{tpv}\nFIR:{fir}\nFIN:{fin}\nCON:{con}\nPn:{pn}\nF:{DT_Fn(frame)}");

            //时间 Td_d --frame[20].ToString() frame[19].ToString() frame[18].ToString()
            dynamic td_d;
            if (tpv.ToString() == "1")
            {
                td_d = Convert.ToString(frame[20], 16) + "年" + Convert.ToString(frame[19], 16) + "月" + Convert.ToString(frame[18], 16) + "日";
                Console.WriteLine($"Td_d:{td_d}");
            }

            #endregion


        }

        private static void sendRequest()
        {
            List<byte> fn_dt1 = new List<byte>() { 0x10, 0x20, 0x40, 0x80 };//F5 F6 F7 F8

            //请求时间10 03 23 日 月 年
            var td_d_1 = DateTime.Today.AddDays(-1);
            var td_d_2 = td_d_1.ToString().Split(new char[] { '/', ' ', ':' });//年 月 日 时 分 秒
            var sadfas = (td_d_2[0][2].ToString() + td_d_2[0][3].ToString());
            var td_d = new byte[] { Convert.ToByte(td_d_2[2], 16), Convert.ToByte(td_d_2[1], 16), Convert.ToByte(sadfas, 16) };

            //时间标签2E 54 05 13 17 00   2E 秒 分 时 日 00
            DateTime currentDateTime = DateTime.Now;
            var _clock = currentDateTime.ToString().Split(new char[] { '/', ' ', ':' });//年 月 日 时 分 秒
            var clock = new byte[] { Convert.ToByte(_clock[5], 16), Convert.ToByte(_clock[4], 16), Convert.ToByte(_clock[3], 16), Convert.ToByte(_clock[2], 16) };

            //68 56 00 56 00 68 4B (A1-59 04) (A2-02 00) 02 0D (SEQ-EE) (Pn-FF 00) (Fn- (fn_dt1) 00) (td_d) (6时钟clock) (cs) 16

            for (int i = 0; i < fn_dt1.Count; i++)
            {
                OnLineDic[idOnLine[0]].Send(new byte[] 
                { 
                    0x68, 0x56, 0x00, 0x56, 0x00, 0x68,
                    0x4B, (byte)(A1)[0], (byte)A1[1] ,A2[0],A2[1],0x02,
                    0x0D,0xEE,
                    0xFF,0x00, fn_dt1[i],0x00,
                    td_d[0],td_d[1],td_d[2],
                    clock[0],clock[1],clock[2],clock[3],clock[4],clock[5],

                    CRC_(new byte[]{ 0x4B, (byte)(A1)[0], (byte)A1[1] ,A2[0],A2[1],0x02,
                    0x0D,0xEE,
                    0xFF,0x00, fn_dt1[i],0x00,
                    td_d[0],td_d[1],td_d[2],
                    clock[0],clock[1],clock[2],clock[3],clock[4],clock[5]})

                    ,0x16});
            }
        }

        static byte CRC_(byte[] frame) 
        {
            int crc_ = 0;
            for (int i = 6; i <= frame.Length; i++)
                crc_ += (int)(frame[i]);
            var crc = (crc_ & 0xFF).ToString("X2");
            return Convert.ToByte(crc, 16);
        }

        /// <summary>
        /// 获取Fn
        /// </summary>
        /// <param name="frame"></param>
        /// <returns></returns>
        public static dynamic DT_Fn(byte[] frame)
        {
            //DT 
            dynamic dt1 = (Convert.ToString(frame[16], 2));
            if (dt1 == "0")
                dt1 = 0;
            else
                dt1 = (dt1.ToString()).Length;
            int dt2 = (int)(frame[17]);
            int fn;//Fn = DT2 * 8 + DT1
            fn = dt2 * 8 + dt1;
            return fn;
        }


        enum DIRType
        {
            下行,
            上行
        }

        enum PRMType
        {
            来自从动站,
            来自启动站
        }

        /// <summary>
        /// A3
        /// D0 = 0_表示终端地址_A2_为单地址；D0 = 1_表示终端地址_A2_为组地址
        /// </summary>
        enum A3Type
        {
            终端地址为单地址,
            终端地址为组地址
        }

        /// <summary>
        /// TpV=0,无时间;TpV=1,有时间
        /// </summary>
        enum TPVType
        {
            无时间标签,
            有时间标签
        }


        /// <summary>
        /// FIR、FIN
        /// </summary>
        enum FIRFINType
        {
            中间帧,
            结束帧,
            第1帧 = 10,
            单帧,
        }


        enum CONType
        {
            无需确认,
            需确认
        }

    }
}
