using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Threading;
using SystemX.Net.Platform.Common.Event;
using SystemX.Net.Platform.Common.Util;

namespace SystemX.PLC.Interface.MxComponent
{
    /// <summary>
    /// Example to represent how to build a device manager.
    /// A Device Manager constructs a device in run-time environment using a defined dll name in the configuration xml.
    /// The named dll for a device is bound in run-time environment as well.    
    /// Device manager is an inherited class from a CpDeviceManagerBase and a corresponding interface.
    /// </summary>
    public class PLCModMxCMgr
    {
        public FunctionEventHandler EventAlwaysUpdate { get; set; }
        public FunctionEventHandler EventChangeUpdate { get; set; }
        public PLCDeviceMXC plcMXCompDev;
        public PLCConnectionInfo PLCInfo;

        public bool IsOpened { get; set; } = false;

        public PLCModMxCMgr(PLCConnectionInfo infoinput, FunctionEventHandler evtHndlAlways, FunctionEventHandler evtHndlChanges)
        {
            PLCInfo = infoinput;

            EventAlwaysUpdate = evtHndlAlways;
            EventChangeUpdate = evtHndlChanges;
        }

        public bool CloseDevice()
        {
            if (plcMXCompDev == null)
                return true;

            bool bResult = plcMXCompDev.DevClose();

            IsOpened = !bResult;

            return true;
        }

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

            plcMXCompDev = new PLCDeviceMXC();

            plcMXCompDev.STATION_ID = Convert.ToInt32(PLCInfo.Port);
            plcMXCompDev.EventAlwaysUpdate = EventAlwaysUpdate;
            plcMXCompDev.EventChangeUpdate = EventChangeUpdate;

            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(PLCInfo.IP);

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

                    LogMessage.MessageOutput.ConsoleWrite($"PLC Connection Failed. Retry #{nCount} - IP:{PLCInfo.IP}, Station ID:{PLCInfo.Port}", 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 = plcMXCompDev.DevOpen();

                if (IsOpened == false)
                    plcMXCompDev = null;

                LogMessage.MessageOutput.ConsoleWrite($"PLC Connection Success. - IP:{PLCInfo.IP}, Port:{PLCInfo.Port} ", 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;
            }
        }

        public void SingleScanStartAsync()
        {
            if (!IsOpened) return;

            plcMXCompDev.SingleScanStartAsync();
        }

        public void NowScanDataArea()
        {
            if (!IsOpened) return;

            plcMXCompDev.NowDataAreaScan();
        }

        public void StartMonitor()
        {
            plcMXCompDev.StartMonitor();
        }

        public void StopMonitor()
        {
            plcMXCompDev.StopMonitor();
        }

        public bool AddDevices(PLCAddressTemplate deviceReg, bool bOperation)
        {
            try
            {
                if (!IsOpened) return false;

                if (bOperation)
                    plcMXCompDev.AddOperationDevices(deviceReg);
                else
                    plcMXCompDev.AddDataDevices(deviceReg);

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

        public void UpdateArea(List<PLCAddressTemplate> vTemplates, bool bRefineWrongChar)
        {
            foreach (PLCAddressTemplate keyTemplate in vTemplates)
                plcMXCompDev.UpdateDevice(keyTemplate, bRefineWrongChar);
        }

        public void UpdateSelectArea(PLCAddressTemplate keyTemplate, bool bRefineWrongChar)
        {
            plcMXCompDev.UpdateDevice(keyTemplate, bRefineWrongChar);
        }

        public string GetDeviceValue(PLCAddressTemplate tmplt)
        {
            try
            {
                if (!IsOpened) return string.Empty;

                return plcMXCompDev.GetDeviceValue(tmplt);
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Device Get Value Error: {tmplt.PLCAddress}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);
                LogMessage.MessageOutput.ConsoleWrite($" - Detail: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);

                throw;
            }
        }

        public string ReadDevice(PLCAddressTemplate tmplt, bool bAsciiReverse = false)
        {
            try
            {
                if (!IsOpened) return string.Empty;

                plcMXCompDev.UpdateDevice(tmplt, true, bAsciiReverse);

                return tmplt.Value;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Device Reading Error: {tmplt.PLCAddress}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);
                LogMessage.MessageOutput.ConsoleWrite($" - Detail: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);

                throw;
            }
        }

        public bool WriteDevice(PLCAddressTemplate tmplt, string value)
        {
            try
            {
                if (!IsOpened) return false;

                return plcMXCompDev.WriteDevice(tmplt, value);
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Device Writting Error: {tmplt.PLCAddress}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);
                LogMessage.MessageOutput.ConsoleWrite($" - Detail: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);
                throw;
            }
        }
    }
}
