using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using Opc.Ua;
using System;
using System.Collections.Generic;
using System.Net.NetworkInformation;

namespace IOB_WIN_OPC_UA.IobOpc
{
    public class OpcUaMBH : OpcUa
    {
        #region Protected Fields

        protected bool testDone = false;

        #endregion Protected Fields

        #region Public Constructors

        /// <summary>
        /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation con la
        /// gestione specifica per MBH OPC-UA (es OPC, Cimolai per Baglietto)
        /// https://github.com/OPCFoundation/UA-.NETStandard
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IOBConf">Configurazione (legacy)</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public OpcUaMBH(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            lgInfo("Init OpcUa MBH");
            sendKeyRichiesta = true;
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Processo i task richiesti e li elimino dalla coda 1:1
        /// </summary>
        /// <param name="task2exe"></param>
        public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
        {
            // uso metodo base x ora
            return base.executeTasks(task2exe, codTav);
        }

        /// <summary>
        /// Effettua reset del contapezzi, NON POSSIBILE in questa versione
        /// </summary>
        /// <returns></returns>
        public override bool resetContapezziPLC(string codTav)
        {
            lgInfo("OpcUaMBH.resetContapezziPLC - none");
            bool answ = false;
            return answ;
        }

        /// <summary>
        /// Effettua IMPOSTAZIONE FORZATA del contapezzi, NON POSSIBILE in questa versione
        /// </summary>
        /// <returns></returns>
        public override bool setcontapezziPLC(int newPzCount, string codTav)
        {
            bool answ = false;
            return answ;
        }

        #endregion Public Methods

        #region Protected Methods

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO
        /// </summary>
        protected override void decodeToBaseBitmap()
        {
            DateTime adesso = DateTime.Now;
            CheckLiveData(adesso);
            // init a zero...
            B_input = 0;

            /* -----------------------------------------------------
             *  STATE MACHINE 60 STD / SIMULA
             *------------------------------------------------------
             * bitmap MAPO
             * B0: POWER_ON
             * B1: RUN
             * B2: pzCount
             * B3: allarme
             * B4: manuale
             * B5: SlowTC (NON gestito qui)
             * B6: warm-up / cool-down / setup
             * B7: emergenza ARMATA (1=ok, 0 = premuta)
             ---------------------------------------------------- */

            // se valido il check ping lo eseguo... altrimenti lo do x buono
            bool checkPing = !opcUaParams.pingAsPowerOn;
            string currRun = "N.A.";
            if (!checkPing)
            {
                checkPing = (testPingMachine == IPStatus.Success);
            }
            // bit 0 (poweron) imposto a 1 SE pingo + PowerOn=="ON"...
            bool powerOnOk = checkPing && hasPowerOn;
            // procedo SOLO SE mi da ping OK...
            if (checkPing)
            {
                B_input = powerOnOk ? 1 : 0;

                procRunMode(ref currRun);

                // salvo running come = working...
                isRunning = isWorking;

                // se ho emergenza premuta --> emergenza!
                if (hasEStopArmed)
                {
                    B_input += (1 << 7);
                }
                // se ho emergenza premuta --> emergenza!
                if (isWarmUpCoolDown)
                {
                    B_input += (1 << 6);
                }
                // se ho almeno 1 allarme E NON SONO IN AUTO --> ALARM!
                if (hasError)
                {
                    B_input += (1 << 3);
                }
                if (isWorking)
                {
                    // RUN = LAVORA!
                    B_input += (1 << 1);
                }
                else if (powerOnOk && (!isReady || isManual))
                {
                    // se NON ready --> manual
                    B_input += (1 << 4);
                }
            }

            // se abilitata gestione ODL esegue eventuale chiusura

            // se abilitato watchdog...
            if (opcUaParams.WatchDog.IsEnabled)
            {
                if (adesso.Subtract(lastWatchDogPLC).TotalSeconds > 2)
                {
                    lastWatchDogPLC = adesso;
                    WatchDog++;
                    WatchDog = WatchDog > opcUaParams.WatchDog.MaxVal ? 0 : WatchDog;

                    lgTrace($"WatchDog val: {WatchDog}");
                    try
                    {
                        WriteValue commWriteVal = new WriteValue();
                        commWriteVal.NodeId = new NodeId(opcUaParams.WatchDog.MemConfWrite);
                        commWriteVal.AttributeId = Attributes.Value;
                        commWriteVal.Value = new DataValue();
                        commWriteVal.Value.Value = WatchDog;

                        List<WriteValue> nodes2Write = new List<WriteValue>();
                        nodes2Write.Add(commWriteVal);

                        UA_ref.WriteNodes(nodes2Write);
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in gestione WatchDog, valore attuale {WatchDog}{Environment.NewLine}{exc}");
                    }
                }
            }
            else
            {
                lgTrace("WatchDog disabilitato");
            }

            // solo se non ho veto check
            int vFactor = 2;
            if (vetoCheckStatus < adesso)
            {
                lgDebug($"Stato variabili checkPing: {testPingMachine}");
                // imposto veto per vetoSeconds...
                vetoCheckStatus = adesso.AddSeconds(vetoSeconds * vFactor);
            }

            // log opzionale!
            if (verboseLog)
            {
                lgDebug($"Trasformazione checkPing: {checkPing} | hasPowerOn: {hasPowerOn} | B_input: {B_input} | currRun = {currRun}");
            }
        }

        /// <summary>
        /// Effettua vera scrittura parametri
        /// </summary>
        /// <param name="updatedPar"></param>
        protected override void plcWriteParams(ref List<objItem> updatedPar)
        {
            dataConf currMem = null;
            int byteSize = 0;
            string memAddrWrite = "";
            string serObj = "";
            if (updatedPar != null)
            {
                List<WriteValue> nodes2Write = new List<WriteValue>();
                // controllo i parametri... ne gestisco 4...
                foreach (var item in updatedPar)
                {
                    try
                    {
                        memAddrWrite = "";
                        int valInt = 0;
                        double valReal = 0;
                        // cerco in area memMapWrite...
                        if (memMap.mMapWrite.ContainsKey(item.uid))
                        {
                            // recupero!
                            currMem = memMap.mMapWrite[item.uid];
                            byteSize = currMem.size;
                            memAddrWrite = currMem.memAddr;

                            WriteValue commWriteVal = new WriteValue();
                            commWriteVal.NodeId = new NodeId(currMem.memAddr);
                            commWriteVal.AttributeId = Attributes.Value;
                            commWriteVal.Value = new DataValue();
                            commWriteVal.Value.Value = item.reqValue;

                            // faccio preliminarmente upsertKey...
                            upsertKey(currMem.name, currMem.value);
                            serObj = JsonConvert.SerializeObject(item);
                            lgInfo($"Inizio processing plcWriteParams per {currMem.name} | valore richiesto {currMem.value}");
                            lgInfo($"---------------{Environment.NewLine}UPDATED PARAM:{Environment.NewLine}{serObj}{Environment.NewLine}---------------");
                            serObj = JsonConvert.SerializeObject(currMem);
                            lgInfo($"---------------{Environment.NewLine}MEMORY CONTENT:{Environment.NewLine}{serObj}{Environment.NewLine}---------------");

                            switch (currMem.tipoMem)
                            {
                                case plcDataType.Boolean:
                                    break;

                                case plcDataType.Int:
                                case plcDataType.DInt:
                                case plcDataType.Word:
                                case plcDataType.DWord:
                                    int.TryParse(item.reqValue, out valInt);
                                    commWriteVal.Value.Value = valInt;
                                    break;

                                case plcDataType.Real:
                                    double.TryParse(item.reqValue, out valReal);
                                    commWriteVal.Value.Value = valReal;
                                    break;

                                case plcDataType.String:
                                    commWriteVal.Value.Value = item.reqValue;
                                    break;

                                default:
                                    break;
                            }
                            lgInfo($"---------------{Environment.NewLine}OPC-UA data:{Environment.NewLine}NodeId: {commWriteVal.NodeId}{Environment.NewLine}Value: {commWriteVal.Value.Value}{Environment.NewLine}---------------");

                            if (!string.IsNullOrEmpty(memAddrWrite))
                            {
                                nodes2Write.Add(commWriteVal);
                                // 2023.09.19 resetto richiesta
                                item.reqValue = "";
                            }
                            else
                            {
                                lgInfo($"Errore: memAddrWrite vuoto!");
                            }
                        }
                        else
                        {
                            lgInfo($"Errore uid non trovato in area write memory: {item.uid}, ci sono {memMap.mMapWrite.Count} in area write");
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in fase di plcWriteParams per item {item.uid} con valore {item.value}{Environment.NewLine}{exc}");
                    }
                }

                if (nodes2Write.Count > 0)
                {
                    lgInfo($"Richiesta scrittura {nodes2Write.Count} parametri");
                    UA_ref.WriteNodes(nodes2Write);
                }
            }
        }

        #endregion Protected Methods
    }
}