using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FLY.Modbus.WithThread
{
    /// <summary>
    /// 使用线程版本
    /// 支持自定义逻辑
    /// </summary>
    public class ModbusMapper_Client : ModbusMapper
    {
        /// <summary>
        /// 更新周期，单位ms，
        /// </summary>
        public int UpdateInterval = 100;

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

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

        class RegWrite
        {
            public DataToRegs dr;
            public object val;

            public RegWrite(DataToRegs dr, object val)
            {
                this.dr = dr;
                this.val = val;
            }
        }

        List<RegWrite> rws = new List<RegWrite>();

        /// <summary>
        /// tcp client
        /// </summary>
        public ClientTCP Client { get; set; }
        CancellationTokenSource cts;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ep">服务器ep</param>
        public ModbusMapper_Client(IPEndPoint ep)
        {
            Client = new ClientTCP(ep);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Client.ToString();
        }

        /// <summary>
        /// 启动
        /// </summary>
        public void Start()
        {
            lock (this)
            {
                if (IsRunning)
                    return;
                IsRunning = true;
                cts = new CancellationTokenSource();
                Task.Factory.StartNew(OnPoll_update, cts.Token);
            }
        }
        
        /// <summary>
        /// 停止
        /// </summary>
        public void Stop()
        {
            lock (this)
            {
                if (!IsRunning)
                    return;
                cts.Cancel();
                Client.Close();
            }
        }

        void OnPoll_update()
        {
            Stopwatch stopwatch = new Stopwatch();
            bool isTimeForRead = true;
        _connect:
            while (true)
            {
                if (cts.IsCancellationRequested)
                    goto _end;
                

                bool isconnected = Client.Connect();

                if (cts.IsCancellationRequested)
                    goto _end;
                

                if (isconnected)
                    break;
                else
                    Thread.Sleep(1000);
            }

            //刚连接成功。

            //从本地PC 属性获取value 更新到 本地PLC 寄存器，
            //目的: 当从PLC读取寄存器值时，能比较不同，产生变化事件
            foreach (var areaManager in mAreaManager)
                areaManager.SetAllIsPlcValueChanged();

            //清空 写缓存
            lock (rws)
            {
                rws.Clear();
            }

            stopwatch.Restart();
            isTimeForRead = true;
            while (true)
            {
                if (cts.IsCancellationRequested)
                    goto _end;

                if (stopwatch.ElapsedMilliseconds >= UpdateInterval)
                {
                    isTimeForRead = true;
                }

                if (isTimeForRead)
                {
                    isTimeForRead = false;
                    TimeSpan elapsed = stopwatch.Elapsed;
                    ActUpdateInterval = elapsed;
                    stopwatch.Restart();
                    
                    if (!UpdateReadData())
                    {
                        if (cts.IsCancellationRequested)//可能是外部强制关闭
                            goto _end;

                        goto _error;//连接断开，终止更新线程
                    }
                }

                //输出写入数据
                if (!UpdateWriteData())
                {
                    if (cts.IsCancellationRequested)//可能是外部强制关闭
                        goto _end;

                    goto _error;//连接断开，终止更新线程
                }

                Thread.Sleep(30);
            }

        _error:
            ErrorCnt++;
            goto _connect;

        _end:
            Client.Close();
            IsRunning = false;
        }

        /// <summary>
        /// 更新一次读全部数据
        /// </summary>
        /// <returns></returns>
        bool UpdateReadData()
        {
            foreach (var areaManager in mAreaManager)
            {
                areaManager.MakePlanReal();
            }

            //int curr_area_idx = 0;
            foreach (var areaManager in mAreaManager)
            {
                foreach(Plan p_doing in areaManager.plan)
                {
                    switch (p_doing.area)
                    {
                        case PLCAddressArea.Coil:
                            {
                                if (!Client.Do_01(p_doing.addr, p_doing.num, out IEnumerable<bool> values))
                                    return false;
                                SetModbusData(PLCAddressArea.Coil, p_doing.addr, values);
                            }
                            break;
                        case PLCAddressArea.Register:
                            {
                                if (!Client.Do_03(p_doing.addr, p_doing.num, out IEnumerable<UInt16> values))
                                    return false;
                                SetModbusData(PLCAddressArea.Register, p_doing.addr, values);
                            }
                            break;
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// PC -> PLC
        /// 为命名数据赋值，会改变Modbus的寄存器值，并产生通知
        /// </summary>
        /// <param name="dr">数据映射</param>
        /// <param name="val">数据值，不能为空</param>
        /// <returns></returns>
        public override void SetNameData(DataToRegs dr, object val)
        {
            RegWrite rw = new RegWrite(dr, val);
            lock (rws)
            {
                rws.Add(rw);
            }
        }

        /// <summary>
        /// 提取全部写命令缓冲区 输出
        /// </summary>
        bool UpdateWriteData()
        {
            while (true)
            {
                RegWrite rw;
                lock (rws)
                {
                    if (rws.Count() == 0)
                        break;
                    rw = rws.First();
                    rws.RemoveAt(0);
                }
                //强制令读时，推送数据
                rw.dr.isPlcValueChanged = true;
                object plc_value = rw.dr.ToPLCObj(rw.val);
                switch (rw.dr.dataArea)
                {
                    case PLCAddressArea.Coil:
                        if (!Client.Do_0F((UInt16)rw.dr.addr, new bool[] {(bool)plc_value }))
                            return false;
                        break;
                    default:
                        //case PLCAddressArea.Register:
                        if (!Client.Do_10((UInt16)rw.dr.addr, (UInt16[])plc_value))
                            return false;
                        break;
                }
            }
            return true;
        }

    }
}
