using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QMAPP.FJC.Entity.EnergyManage;
using QMAPP.FJC.BLL.Common;

namespace QMAPP.FJC.BLL.EnergyManage
{
    public class MeterReader
    {
        #region CONST
        /// <summary>
        /// 电表响应报文头
        /// </summary>
        const byte RESPONSE_HEAD = 0xCC;
        /// <summary>
        /// 电表查询报文头
        /// </summary>
        const byte REQUEST_HEAD = 0xAA;
        /// <summary>
        /// 抄表命令
        /// </summary>
        const byte CMD_READMETER = 0x1;
        /// <summary>
        /// 查询表信息
        /// </summary>
        const byte CMD_GETMETERINFO = 0x09;
        /// <summary>
        /// 复位表
        /// </summary>
        const byte CMD_RESETMETER = 0x0E;
        const Int16 CMD_LEN_READMETER = 10;
        const Int16 CMD_LEN_GETMETERINFO = 9;
        const Int16 CMD_LEN_RESETMETER = 9;


        /// <summary>
        /// 电表查询指令集 0x01抄表 0x09查询表信息 0x0E复位表
        /// </summary>
        static byte[] cmdids = new byte[] { CMD_READMETER, CMD_GETMETERINFO, CMD_RESETMETER };
        /// <summary>
        /// 电表查询指令长度
        /// </summary>
        static Int16[] cmdlens = new Int16[] { CMD_LEN_READMETER, CMD_LEN_GETMETERINFO, CMD_LEN_RESETMETER };
        /// <summary>
        /// 电表响应超时时间
        /// </summary>
        const int RESPONSE_TIMEOUT = 2500;
        #endregion

        /// <summary>
        /// 抄全部表
        /// </summary>
        public static void ReadAllMeter()
        {
            try
            {
                DAL.EnergyManage.MeterDAL meterdal=new DAL.EnergyManage.MeterDAL();
                List<Meter> Meters = meterdal.GetList(new Meter { STATE = "1" });
                foreach (var mg in Meters.GroupBy(p => p.NET_ADDR))
                {
                    using (System.Net.Sockets.Socket comServer = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp))
                    {
                        try
                        {
                            var connresult = comServer.BeginConnect(mg.First().IPAddr, mg.First().Port, null, null);
                            if (!connresult.AsyncWaitHandle.WaitOne(2000))//连接超时
                            {
                                var meter = mg.First();
                                meterdal.SaveReading(meter
                                        , -1
                                        , false
                                        , string.Format("电表({0}-{1});网络({2});错误({3})"
                                            , meter.METER_CODE
                                            , meter.NET_ID
                                            , meter.NET_ADDR
                                            , ReadMeterError.SOCKET_CONNECT_FAILED));
                                continue;
                            }
                            comServer.EndConnect(connresult);
                            foreach (var meter in mg.OrderBy(p=>p.METER_CODE))
                            {
                                ReadMeterError error;
                                var reading = ReadMeter(comServer, meter.SysNum, meter.MeterNum, meter.UserNum, out error);
                                var result= error == ReadMeterError.SUCCESS;
                                meterdal.SaveReading(meter
                                    , reading * meter.RATIO
                                    , result
                                    , result ? "" : string.Format("电表({0}-{1});网络({2});错误({3})"
                                        , meter.METER_CODE
                                        , meter.NET_ID
                                        , meter.NET_ADDR
                                        , error));
                            }
                        }
                        catch (System.Net.Sockets.SocketException ex)//串口服务器连接异常
                        {
                            var meter = mg.First();
                            meterdal.SaveReading(meter
                                    , -1
                                    , false
                                    , string.Format("电表({0}-{1});网络({2});错误({3})"
                                        , meter.METER_CODE
                                        , meter.NET_ID
                                        , meter.NET_ADDR
                                        , ReadMeterError.SOCKET_CONNECT_FAILED));
                            continue;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        /// <summary>
        /// 抄表
        /// </summary>
        /// <param name="comServer">串口服务器SOCKET</param>
        /// <param name="sysnum">电表系统号</param>
        /// <param name="meternum">电表号</param>
        /// <param name="usernum">用户号</param>
        /// <param name="error">错误消息</param>
        /// <returns>电表读数</returns>
        private static double ReadMeter(System.Net.Sockets.Socket comServer, Int16 sysnum, Int16 meternum, byte usernum, out ReadMeterError error)
        {
            try
            {
                var requestdata = GetReadCommand(sysnum, meternum, usernum);
                bool sended = false;
                List<byte> responsedata = new List<byte>();
                System.Net.Sockets.SocketError socketerr;
                while (true)
                {
                    var buffer = new byte[1024];
                    var asyResult = comServer.BeginReceive(buffer, 0, 1024, System.Net.Sockets.SocketFlags.None, null, null);
                    if (!sended)
                    {
                        comServer.Send(requestdata);
                        sended = true;
                    }
                    if (asyResult.AsyncWaitHandle.WaitOne(RESPONSE_TIMEOUT))//电表响应超时
                    {
                        var rlen = comServer.EndReceive(asyResult, out socketerr);
                        responsedata.AddRange(buffer.Take(rlen));
                        if (CheckResopnse(responsedata, requestdata))
                        {
                            error = ReadMeterError.SUCCESS;
                            return BitConverter.ToInt32(responsedata.Skip(9).Take(4).Reverse().ToArray(), 0) * 0.1;
                        }
                        if (rlen == 0)
                        {
                            error = ReadMeterError.SOCKET_REMOTE_CLOSED;
                            break;
                        }
                    }
                    else
                    {
                        comServer.Disconnect(true);
                        //var rlen = comServer.EndReceive(asyResult, out socketerr);
                        var cr= comServer.BeginConnect(comServer.RemoteEndPoint, null, null);
                        cr.AsyncWaitHandle.WaitOne(RESPONSE_TIMEOUT);
                        error = ReadMeterError.METER_RESPONSE_TIMEOUT;
                        break;
                    }
                }
                return -1;
            }
            catch (System.Net.Sockets.SocketException se)
            {
                error = ReadMeterError.SOCKET_ERROR;
                return -1;
            }

        }
        /// <summary>
        /// 检查电表返回数据
        /// </summary>
        /// <param name="responsedata">返回报文</param>
        /// <param name="requestdata">查询报文</param>
        /// <returns></returns>
        private static bool CheckResopnse(List<byte> responsedata, byte[] requestdata)
        {
            var headIndex = responsedata.IndexOf(RESPONSE_HEAD); //检查返回数据是否包含响应报文头
            if (headIndex != 0)//报文头不是第一个字节或不包含报文头
            {
                //移出无用数据
                responsedata.RemoveRange(0, headIndex > 0 ? headIndex : responsedata.Count);
            }
            if (responsedata.Count < 5)//返回数据长度不应小于5 报文头(1)+包长(2)+校验位(2)
            {
                return false;
            }
            //第1,2位数据为包长 高位在前
            UInt16 ResponseLen = BitConverter.ToUInt16(new byte[] { responsedata[2], responsedata[1] }, 0);
            if (ResponseLen > (responsedata.Count - 1))//验证返回数据长度是否达到报文包长
            {
                return false;
            }
            //返回数据的末尾两位为校验位 高位在前
            var checksum = BitConverter.ToUInt16(responsedata.Skip(responsedata.Count - 2).Take(2).Reverse().ToArray(), 0);
            //对数据重新进行CRC校验
            var crcCheck = CRC.GetKey(responsedata.Skip(1).Take(ResponseLen - 2).ToArray());
            if (checksum != crcCheck) //比对校验位
            {
                //移除无用的数据
                responsedata.RemoveAt(0);
                return false;
            }
            if (responsedata[3] != CMD_READMETER)//验证指令ID是否为抄表指令
            {
                //移除无用的数据
                responsedata.RemoveAt(0);
                return false;
            }
            if (!responsedata.Skip(3).Take(6).SequenceEqual(requestdata.Skip(3).Take(6)))//验证返回的系统号表号用户号是否一致
            {
                //移除无用的数据
                responsedata.RemoveAt(0);
                return false;
            }
            if (ResponseLen < 29)//单表抄表返回数据长度为29个字节
            {
                //移除无用的数据
                responsedata.RemoveAt(0);
                return false;
            }

            return true;

        }
        /// <summary>
        /// 生成抄表命令
        /// </summary>
        /// <param name="sysnum">电表系统号</param>
        /// <param name="meternum">电表号</param>
        /// <param name="usernum">用户号</param>
        /// <returns>抄表命令报文</returns>
        private static byte[] GetReadCommand(Int16 sysnum, Int16 meternum, byte usernum)
        {
            byte cmdid = cmdids[0];
            Int16 len = cmdlens[0];
            byte[] lenbytes = BitConverter.GetBytes(len).Reverse().ToArray();//报文包长
            byte[] sysnumbytes = BitConverter.GetBytes(sysnum).Reverse().ToArray();//系统号
            byte[] meternumbytes = BitConverter.GetBytes(meternum).Reverse().ToArray();//电表号
            List<byte> cmd = new List<byte>();
            cmd.AddRange(lenbytes);
            cmd.Add(cmdid);
            cmd.AddRange(sysnumbytes);
            cmd.AddRange(meternumbytes);
            if (cmdid == CMD_READMETER)
            {
                cmd.Add(usernum);//用户号
            }
            UInt16 checksum = (UInt16)CRC.GetKey(cmd.ToArray()); //校验位
            byte[] checksumbytes = BitConverter.GetBytes(checksum).Reverse().ToArray();
            cmd.AddRange(checksumbytes);
            cmd.Insert(0, REQUEST_HEAD);//插入查询报文头
            return cmd.ToArray();
        }
    }
    /// <summary>
    /// 抄表错误代码
    /// </summary>
    public enum ReadMeterError
    {
        /// <summary>
        /// 读取成功
        /// </summary>
        SUCCESS,
        /// <summary>
        /// Socket错误连接被关闭
        /// </summary>
        SOCKET_REMOTE_CLOSED,
        /// <summary>
        /// Socket错误
        /// </summary>
        SOCKET_ERROR,
        /// <summary>
        /// Socket连接失败
        /// </summary>
        SOCKET_CONNECT_FAILED,
        /// <summary>
        /// 电表响应超时
        /// </summary>
        METER_RESPONSE_TIMEOUT,
        /// <summary>
        /// 电表响应数据错误
        /// </summary>
        METER_DATA_ERROR
    }
}