using FLY.Modbus;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FLY.FilmCasting.AutoDie.Server
{
    public class PLCLink : IPLCLink
    {

        /// <summary>
        /// 一次读取的数据量
        /// </summary>
        const int MaxRegsOfOneRead = 50;
        const int MaxCoilsOfOneRead = 100;

        //D200	4x201 通道数量
        //D201	4x202 设置值 更新
        //D202	4x203 当前值 更新 //不用
        //D400	4x401 设置值 160个
        //D600	4x601 当前值 160个 //不用
        //M3	0x4	风环开关
        //M4	0x5	检测电流

        public FLY.Modbus.WithThread.ClientTCP mclient;
        /// <summary>
        /// 更新周期，单位ms，
        /// </summary>
        public int UpdateInterval = 100;

        /// <summary>
        /// 实际更新间隔
        /// </summary>
        public TimeSpan ActUpdateInterval { get; private set; }

 
        /// <summary>
        /// 加热通道数
        /// </summary>
        [DoNotCheckEquality]
        public UInt16 ChannelCnt { get; set; }
        /// <summary>
        /// 加热量更新
        /// </summary>
        [DoNotCheckEquality]
        public UInt16 HeatUpdate { get; set; }

        /// <summary>
        /// 当前电流 有没？
        /// </summary>
        public bool HasElectricity { get; private set; }
        /// <summary>
        /// 风机是否启动？
        /// </summary>
        public bool HasFan { get; private set; }
        /// <summary>
        /// 加热量更新 读
        /// </summary>
        public UInt16 HeatUpdate_R { get; private set; }

        public int Errno { get; set; } = -1;


        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// 工作中
        /// </summary>
        public bool IsRunning { get; private set; }
        /// <summary>
        /// 异常次数
        /// </summary>
        public int ErrorCnt { get; private set; }

        CancellationTokenSource cts;

        public PLCLink(IPEndPoint ep)
        {
            mclient = new Modbus.WithThread.ClientTCP(ep);
            this.PropertyChanged += PLCLink_PropertyChanged;
        }

        class RegWrite
        {
            public PLCAddressArea dataArea;
            public int addr;
            public object val;

            public RegWrite(PLCAddressArea dataArea, int addr, object val)
            {
                this.dataArea = dataArea;
                this.addr = addr;
                this.val = val;
            }
        }
        List<RegWrite> rws = new List<RegWrite>();


        private void PLCLink_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(ChannelCnt))
            {
                //D200	4x201	通道数量
                RegWrite regWrite = new RegWrite(PLCAddressArea.Register, 200, new UInt16[] { ChannelCnt });
                lock (rws)
                {
                    rws.Add(regWrite);
                }
            }
            else if (e.PropertyName == nameof(HeatUpdate))
            {
                //D201	4x202	设置值 更新
                RegWrite regWrite = new RegWrite(PLCAddressArea.Register, 201, new UInt16[] { HeatUpdate });
                lock (rws)
                {
                    rws.Add(regWrite);
                }
            }
        }


        public void Start()
        {
            lock (this)
            {
                if (IsRunning)
                    return;
                IsRunning = true;
                cts = new CancellationTokenSource();
                Task.Factory.StartNew(OnPoll_update, cts.Token);
            }

        }
        public void Stop()
        {
            lock (this)
            {
                if (!IsRunning)
                    return;
                cts.Cancel();
                mclient.Close();
            }
        }


        void OnPoll_update()
        {
            Stopwatch stopwatch = new Stopwatch();


        _connect:
            while (true)
            {
                if (cts.IsCancellationRequested)
                    goto _end;


                bool isconnected = mclient.Connect();

                if (cts.IsCancellationRequested)
                    goto _end;


                if (isconnected)
                    break;
                else
                    Thread.Sleep(1000);
            }
            lock (rws)
            {
                rws.Clear();
            }
            stopwatch.Restart();
            while (true)
            {
                if (cts.IsCancellationRequested)
                    goto _end;

                if (stopwatch.ElapsedMilliseconds >= UpdateInterval)
                {
                    //检查上次更新周期是否完成
                    ActUpdateInterval = stopwatch.Elapsed;
                    stopwatch.Restart();

                    if (!UpdateReadData())
                    {
                        if (cts.IsCancellationRequested)//可能是外部强制关闭
                            goto _end;

                        goto _error;//连接断开，终止更新线程
                    }
                    Errno = 0;
                }
                
                //输出写入数据
                if (!UpdateWriteData())
                {
                    if (cts.IsCancellationRequested)//可能是外部强制关闭
                        goto _end;

                    goto _error;//连接断开，终止更新线程
                }
                
                Thread.Sleep(30);
            }

        _error:
            Errno = -1;
            ErrorCnt++;
            goto _connect;

        _end:
            mclient.Close();
            Errno = -1;
            IsRunning = false;
        }

        bool UpdateReadData()
        {
            //M3	0x4	风环开关
            //M4	0x5	检测电流
            if (!mclient.Do_01(3, 2, out IEnumerable<bool> values))
                return false;
            HasFan = values.ElementAt(0);
            HasElectricity = values.ElementAt(1);

            //D201	4x202	设置值 更新
            if (!mclient.Do_03(201, 1, out IEnumerable<UInt16> values2))
                return false;

            HeatUpdate_R = values2.ElementAt(0);
            
            return true;
        }

        bool UpdateWriteData()
        {
            while (true)
            {
                RegWrite rw;
                lock (rws)
                {
                    if (rws.Count() == 0)
                        break;
                    rw = rws.First();
                    rws.RemoveAt(0);
                }

                switch (rw.dataArea)
                {
                    case PLCAddressArea.Register:
                        if (!mclient.Do_10((UInt16)rw.addr, (IEnumerable<UInt16>)rw.val))
                            return false;
                        break;
                }
            }
            return true;
        }

        /// <summary>
        /// 设置加热量
        /// </summary>
        /// <param name="values"></param>
        public void SetHeat(IEnumerable<UInt16> values)
        {
            UInt16[] buf = values.ToArray();
            List<RegWrite> writes = new List<RegWrite>();
            //D400	4x401	设置值 160个
            int addr = 400;

            int cnt = buf.Count();
            int offset = 0;
            //每次最多 MaxRegsOfOneRead
            while (true)
            {
                int cnt1 = cnt;
                if (cnt1 > MaxRegsOfOneRead)
                    cnt1 = MaxRegsOfOneRead;
                UInt16[] buf1 = new UInt16[cnt1];
                Array.Copy(buf, offset, buf1, 0, cnt1);
                RegWrite rw = new RegWrite(PLCAddressArea.Register, addr+offset, buf1);
                writes.Add(rw);
                offset += cnt1;
                cnt -= cnt1;

                if (cnt == 0)
                {
                    break;
                }
            }
            lock (rws)
            {
                rws.AddRange(writes);
            }
        }

    }
}
