using System;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Threading;
using SystemX.Net.Platform.Common.Event;
using SystemX.Net.Platform.Common.Util;
using static SystemX.PLC.Interface.PLCCommDefinition;

namespace SystemX.PLC.Interface.McProtocol
{
    public class PLCModMxCMgr
    {
        public event EventPLCHandler EventPLC;
        public FunctionEventHandler EventAlwaysUpdate { get; set; }
        public FunctionEventHandler EventChangeUpdate { get; set; }
        public PLCDeviceMXC plcDev;

        PLCConnectionInfo PLCInfo;

        public string CPU_TYPE_STR { set; get; } //Example: "Q03UDECPU";        
        public string PLC_IP_ADDR { set; get; }
        public int PLC_PORT_NUMBER { set; get; }
        public string CONNECTION_TYPE_STR { set; get; }
        public int TIMEOUT { set; get; }
        public bool IsOpened { get; set; } = false;

        public PLCModMxCMgr(PLCConnectionInfo infoinput)
        {
            PLCInfo = infoinput;
        }

        public bool CloseDevice()
        {
            bool bResult = plcDev.DevClose();

            IsOpened = !bResult;

            return true;
        }

        public bool OpenDevice()
        {
            if (IsOpened == true)
                return IsOpened;

            plcDev = new PLCDeviceMXC();
            EventChangeUpdate = new FunctionEventHandler();

            plcDev.FuncEvtHndl = EventChangeUpdate;
            plcDev.DeviceID = PLCInfo.Name;
            plcDev.CPU_TYPE_STR = PLCInfo.CPUType;
            plcDev.PLC_IP_ADDR = PLCInfo.IP;
            plcDev.PLC_PORT_NUMBER = Convert.ToInt32(PLCInfo.Port);
            plcDev.CONNECTION_TYPE_STR = PLCInfo.ConnectionType != "UDP" ? "TCP" : "UDP";
            plcDev.READPORT = (PLCInfo.RWType == PLCCommDefinition.ePLCRWType.Read.ToString()) ? true : false;
            plcDev.TIMEOUT = Convert.ToInt32(PLCInfo.Timeout);

            EventChangeUpdate.OnReceive += FuncEvtHndl_OnPLCReceive;

            Task<bool> conPing = new Task<bool>(() => PingToPLC());

            conPing.Start();

            Task<bool> conPLC = conPing.ContinueWith(x => ConnectToPLC());

            return true;
        }

        public bool PingToPLC()
        {
            try
            {
                Ping pingtoPLC = new Ping();
                int nCount = 0;

                while (true)
                {
                    PingReply reply = pingtoPLC.Send(plcDev.PLC_IP_ADDR);

                    if (reply.Status == IPStatus.Success)
                        break;

                    LogMessage.MessageOutput.ConsoleWrite($"PLC Connection Failed. Retry #{nCount} - IP:{plcDev.PLC_IP_ADDR}, Port:{plcDev.PLC_PORT_NUMBER}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                    Thread.Sleep(1000);
                    nCount++;
                }

                return true;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($" - PLC Connection error: {PLCInfo.Name}, {PLCInfo.IP}:{PLCInfo.Port} - {PLCInfo.ConnectionType}", ConsoleColor.Blue, LogMessage.LogMessageLevel.NONE);
                LogMessage.MessageOutput.ConsoleWrite($"   -> Reason: {ex.Message}", ConsoleColor.Blue, LogMessage.LogMessageLevel.NONE);
                throw;
            }
        }

        public bool ConnectToPLC()
        {

            try
            {
                IsOpened = plcDev.DevOpen();

                if (IsOpened == false)
                    plcDev = null;

                LogMessage.MessageOutput.ConsoleWrite($"PLC Connection Success. - IP:{plcDev.PLC_IP_ADDR}, Port:{plcDev.PLC_PORT_NUMBER} ", ConsoleColor.Green, LogMessage.LogMessageLevel.NONE);

                return IsOpened;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($" - PLC Connection error: {PLCInfo.Name}, {PLCInfo.IP}:{PLCInfo.Port} - {PLCInfo.ConnectionType}", ConsoleColor.Blue, LogMessage.LogMessageLevel.NONE);
                LogMessage.MessageOutput.ConsoleWrite($"   -> Reason: {ex.Message}", ConsoleColor.Blue, LogMessage.LogMessageLevel.NONE);
                throw;
            }
        }

        private void FuncEvtHndl_OnPLCReceive(object sender, object objdata)
        {
            string data = objdata as string;

            if (data == null)
                return;

            if (data.Contains(";"))
            {
                string address = data.Split(';')[0];
                int value = Convert.ToInt32(data.Split(';')[1]);

                EventPLC?.Invoke(address, value);
            }
        }

        public async Task SingleScanStartAsync()
        {
            if (!IsOpened) return;

            await plcDev.SingleScanStartAsync();
        }

        public bool AddDevices(string deviceNames)
        {
            if (!IsOpened) return false;

            return plcDev.AddDevices(deviceNames);
        }

        public int ReadDevice(string deviceName)
        {
            try
            {
                if (!IsOpened) return -1;
                return plcDev.ReadDevice(deviceName);
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Device Reading Error in PLCModMxCMgr: {deviceName}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);
                LogMessage.MessageOutput.ConsoleWrite($" - Detail: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);
                throw;
            }
        }

        public int[] ReadBlockDevice(string deviceName, int nLength)
        {
            try
            {
                if (!IsOpened) return null;
                return plcDev.ReadBlockDevice(deviceName, nLength);
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Device Reading Error in PLCModMxCMgr: {deviceName}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);
                LogMessage.MessageOutput.ConsoleWrite($" - Detail: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);
                throw;
            }
        }

        public bool WriteDevice(string deviceName, int value)
        {
            if (!IsOpened) return false;
            return plcDev.WriteDevice(deviceName, value);
        }

        public bool WriteBlockDevice(string deviceName, int nLength, int[] aValues)
        {
            if (!IsOpened) return false;
            return plcDev.WriteBlockDevice(deviceName, nLength, aValues);
        }

        public bool WriteBitDevice(string deviceName, int[] value)
        {
            if (!IsOpened) return false;
            return plcDev.WriteBitDevice(deviceName, value);
        }
    }
}
