using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using MTConnect.Assets;
using MTConnect.Clients;
using MTConnect.Devices;
using MTConnect.Observations;
using MTConnect.Streams;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;

namespace IOB_WIN_MTC.Iob
{
    public class MTConn : Iob.GenericNext
    {
        #region Public Constructors

        /// <summary>
        /// Estende l'init della classe base, impiegando il pacchetto Nuget TrackHound https://github.com/TrakHound/MTConnect.NET
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public MTConn(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            // gestione parametri da nuova IobConfFull
            enableDataFilter = IobConfFull.FluxLog.EnableFilt;
            MaxSecReload = IobConfFull.FluxLog.MaxSecReload;
            readErrorMax = IobConfFull.Device.ReadErrorMax;
            readErrorSleepTime = IobConfFull.Device.ReadErrorSleepTime;
            enableCliRestart = IobConfFull.Device.Connect.EnableRestart;

            // gestione data unavailable = poweroff...
            if (!string.IsNullOrEmpty(getOptPar("UNAVAIL_POWEROFF")))
            {
                bool.TryParse(getOptPar("UNAVAIL_POWEROFF"), out unavailPoweroff);
            }
            // gestione restart MTC client...
            if (!string.IsNullOrEmpty(getOptPar("ENABLE_MTC_RESTART")))
            {
                bool.TryParse(getOptPar("ENABLE_MTC_RESTART"), out enableMtcRestart);
            }
            // gestione parametri x sendDataItem
            if (!string.IsNullOrEmpty(getOptPar("ENABLE_SEND_DATAITEM")))
            {
                bool.TryParse(getOptPar("ENABLE_SEND_DATAITEM"), out enableSendDataItem);
            }
            if (!string.IsNullOrEmpty(getOptPar("MIN_VETO_SEND_DATAITEM")))
            {
                int.TryParse(getOptPar("MIN_VETO_SEND_DATAITEM"), out minVetoSendDataItem);
            }


            // init datetime counters
            DateTime adesso = DateTime.Now;
            lastPzCountSend = adesso;
            lastWarnODL = adesso;
            lastCurrent = adesso;
            // ora leggo il file di conf specifico....
            string jsonFileName = getOptPar("MTC_PARAM_CONF");
            if (!string.IsNullOrEmpty(jsonFileName))
            {
                // leggo il file...
                loadMtcConf(jsonFileName);
            }

            // verifico SE ho dei bit "extra" configurati allora NON uso la state machine 54 ma 60...
            if (mtcParams != null && mtcParams.bitSpecCond.Count > 0)
            {
                bitEmg = 7;
            }
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Processo i task richiesti document 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>
        /// Recupera uno specifico dataItem
        /// </summary>
        /// <param name="diKey"></param>
        /// <returns></returns>
        public string getDataItemValue(string diKey)
        {
            string answ = "";
            if (queueInEnabCurr)
            {
                try
                {
                    if (dataItemMem.ContainsKey(diKey))
                    {
                        var currDataItem = dataItemMem[diKey];
                        answ = currDataItem.value;
                    }
                }
                catch (Exception exc)
                {
                    Logging.Instance.Error($"Errore in getDataItemValue per {diKey}{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgDebug($"[VETO getDataItemValue] | veto attivo alle {DateTime.Now:yyyy.MM.dd HH:mm:ss}");
            }
            return answ;
        }

        /// <summary>
        /// Recupero dati dinamici...
        /// </summary>
        public override Dictionary<string, string> getDynData()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            return outVal;
        }

        /// <summary>
        /// Effettua vero processing contapezzi
        /// </summary>
        public override void processContapezzi()
        {
            if (enablePzCountByApp)
            {
                if (queueInEnabCurr)
                {
                    // cerco parametro contapezzi...
                    string currPzCount = getDataItemValue(mtcParams.keyPartCount);

                    if (isVerboseLog)
                    {
                        lgInfo($"contapezzi: {mtcParams.keyPartCount} --> {currPzCount}");
                    }

                    // se ho un contapezzi... processo...
                    if (!string.IsNullOrEmpty(currPzCount))
                    {
                        int newVal = -1;
                        Int32.TryParse(currPzCount, out newVal);
                        contapezziPLC = newVal > -1 ? newVal : contapezziPLC;
                    }
                }
                else
                {
                    lgDebug($"[VETO processContapezzi] | veto attivo alle {DateTime.Now:yyyy.MM.dd HH:mm:ss}");
                }
            }
            else
            {
                lgTrace($"processContapezzi escluso: enablePzCountByApp = {enablePzCountByApp} | disablePzCountByIob: {disablePzCountByIob}");
            }
        }

        /// <summary>
        /// Effettua lettura semafori principale di default da FamIngressi 54 ma opzionalmente si può cambiare (es 60)
        /// <paramref name="currDispData">Parametri da
        /// aggiornare x display in form</paramref>
        /// </summary>
        public override void readSemafori(ref newDisplayData currDispData)
        {
            DateTime adesso = DateTime.Now;
            lastReadPLC = adesso;
            // verifico non sia in veto invio iniziale...
            if (queueInEnabCurr)
            {
                try
                {
                    if (verboseLog)
                    {
                        lgInfo("inizio read semafori");
                    }

                    currDispData.semIn = Semaforo.SV;

                    // decodifica document gestione
                    decodeToBaseBitmap();
                    reportRawInput(ref currDispData);

                    // controllo read error ed eventuale disconnect
                    if (readErrorCurr > readErrorMax)
                    {
                        lgError($"Effettuo disconnessione x superamento errori lettura UA_ref.ReadNodeString, sleetp 15 sec");
                        tryDisconnect();
                        Thread.Sleep(readErrorSleepTime);
                        readErrorCurr = 0;
                    }
                    else if (readErrorCurr > 0)
                    {
                        readErrorCurr--;
                    }
                }
                catch (Exception exc)
                {
                    currDispData.semIn = Semaforo.SR;
                    lgError($"Eccezione in readSemafori:{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgDebug($"[VETO readSemafori] | veto attivo alle {adesso:yyyy.MM.dd HH:mm:ss}");
                checkVetoQueueIn();
            }
        }

        /// <summary>
        /// Effettua reset del contapezzi, NON POSSIBILE per MTC (read only)
        /// </summary>
        /// <returns></returns>
        public override bool resetContapezziPLC(string codTav)
        {
            bool answ = true;
            return answ;
        }

        /// <summary>
        /// Effettua IMPOSTAZIONE FORZATA del contapezzi, NON POSSIBILE per MTC (read only)
        /// </summary>
        /// <returns></returns>
        public override bool setcontapezziPLC(int newPzCount, string codTav)
        {
            bool answ = true;
            return answ;
        }

        /// <summary>
        /// Override connessione
        /// </summary>
        public override void tryConnect()
        {
            if (!connectionOk)
            {
                // disattivo eventuali sottoscrizioni
                DeactEvents();
                // controllo che il ping sia stato tentato almeno pingTestSec fa...
                if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec"))
                {
                    if (verboseLog || periodicLog)
                    {
                        lgInfo("MTC: ConnKO - tryConnect");
                    }
                    // in primis salvo data ping...
                    lastPING = DateTime.Now;
                    // se passa il ping faccio il resto...
                    if (testPingMachine == IPStatus.Success)
                    {
                        string szStatusConnection = "";
                        try
                        {
                            // ora provo connessione...
                            parentForm.commPlcActive = true;
                            doConnect();
                            parentForm.commPlcActive = false;
                            // refresh stato allarmi!!!
                            if (connectionOk)
                            {
                                checkVetoQueueIn();
                                //queueInEnabCurr = true;
                                if (adpRunning)
                                {
                                    lgInfo("Connessione OK");
                                }
                            }
                            else
                            {
                                lgError("Impossibile procedere, connessione mancante...");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgFatal($"Errore nella connessione all'adapter MTC: {szStatusConnection}{Environment.NewLine}{exc}");
                            connectionOk = false;
                            lgInfo($"Eccezione in TryConnect, Adapter MTC NON running, pausa di {utils.CRI("waitRecMSec")} msec prima di ulteriori tentativi di riconnessione");
                        }
                    }
                    else
                    {
                        // loggo no risposta ping ...
                        connectionOk = false;
                        if (verboseLog || periodicLog)
                        {
                            lgInfo($"Attenzione: MTC controllo PING fallito per IP {IOBConfFull.Device.Connect.PingIpAddr}");
                        }
                    }
                }
            }
            else
            {
                needRefresh = true;
            }
            // se non è ancora connesso faccio procesisng memoria caso disconnesso...
            if (!connectionOk)
            {
                // processo semafori ed invio...
                processMemoryDiscon();
            }
        }

        /// <summary>
        /// Override disconnessione
        /// </summary>
        public override void tryDisconnect()
        {
            try
            {
                queueInEnabCurr = false;
            }
            catch (Exception exc)
            {
                lg.Error($"eccezione in tryDisconnect | queueInEnabCurr = false{Environment.NewLine}{exc}");
            }
            // verifico che NON sia in fase di avvio MTC...
            if (isConnecting && DateTime.Now.Subtract(startConnecting).TotalSeconds < 60)
            {
                lgInfo("Disconnessione non effettuata: connessione ancora in corso");
            }
            else
            {
                lgTrace("Richiesta tryDisconnect");
                if (connectionOk)
                {
                    string szStatusConnection = "";
                    try
                    {
                        // disattivo sottoscrizioni
                        DeactEvents();
                        // stop componente
                        MTC_ref.Stop();
                        connectionOk = false;
                        isConnecting = false;
                        lgInfo($"conn status: {szStatusConnection}");
                        lgInfo("Effettuata disconnessione adapter MTC!");
                    }
                    catch (Exception exc)
                    {
                        lgFatal(exc, "Errore nella disconnessione dall'adapter MTC");
                    }
                }
                else
                {
                    lgError("IMPOSSIBILE effettuare disconnessione MTC: Connessione non disponibile...");
                }
            }
            queueInEnabCurr = false;
            needRefresh = true;
        }

        #endregion Public Methods

        #region Protected Fields

        /// <summary>
        /// Gestione filtraggio dati
        /// </summary>
        protected bool enableDataFilter = false;

        /// <summary>
        /// Abilitazione restart (da opt par...)
        /// </summary>
        protected bool enableMtcRestart = false;

        /// <summary>
        /// Determina se ha effettuata lettura items in memoria x confronto...
        /// </summary>
        protected bool hasReadItems = false;

        /// <summary>
        /// Ultimo current received x gestione update periodico...
        /// </summary>
        protected DateTime lastCurrent = DateTime.Now;

        /// <summary>
        /// Oggetto MAIN x connessione MTC
        /// </summary>
        protected MTConnectHttpClient MTC_ref;

        /// <summary>
        /// Gestione valori unavailable come POWEROFF (es trevisan)
        /// </summary>
        protected bool unavailPoweroff = false;

        /// <summary>
        /// Veto controllos tatus x log...
        /// </summary>
        protected DateTime vetoCheckStatus = DateTime.Now;

        #endregion Protected Fields

        #region Protected Properties

        /// <summary>
        /// Verifico se abbia ALMENO un errore...
        /// </summary>
        protected bool hasError
        {
            get
            {
                bool answ = false;
                // controllo TUTTE le conditions...
                foreach (var item in dataItemMem)
                {
                    // se NON HO GIA' allarmi attivi...
                    if (!answ)
                    {
                        // se è una condition...
                        if (item.Value.Category == MTConnect.Devices.DataItemCategory.CONDITION)
                        {
                            // se ha valore !="" --> allarmi attivi
                            if (!string.IsNullOrEmpty(item.Value.value))
                            {
                                answ = true;
                            }
                        }
                    }
                }
                return answ;
            }
        }

        /// <summary>
        /// Indica se abbia emergenza premuta
        /// </summary>
        protected bool hasEStopTriggered
        {
            get
            {
                bool answ = false;
                if (queueInEnabCurr)
                {
                    try
                    {
                        if (!string.IsNullOrEmpty(mtcParams.keyEStop))
                        {
                            string currEStop = getDataItemValue(mtcParams.keyEStop);
                            answ = (currEStop == "TRIGGERED");
                        }
                    }
                    catch (Exception exc)
                    {
                        Logging.Instance.Error($"Errore in hasEStopTriggered{Environment.NewLine}{exc}");
                    }
                }
                else
                {
                    lgDebug($"[VETO hasEStopTriggered] | veto attivo alle {DateTime.Now:yyyy.MM.dd HH:mm:ss}");
                }
                return answ;
            }
        }

        /// <summary>
        /// Verifico se abbia unavailable x le condizioni principali condPowerOn keyRunMode
        /// </summary>
        protected bool hasUnavailableData
        {
            get
            {
                bool answ = false;
                if (queueInEnabCurr)
                {
                    // verifico le condizioni: powerOn, run document work...
                    string currRun = getDataItemValue(mtcParams.keyRunMode);
                    bool unavRun = currRun.ToUpper() == "UNAVAILABLE";
                    string currPowerOn = getDataItemValue(mtcParams.condPowerOn.keyName);
                    bool checkPowerOn = (currPowerOn == mtcParams.condPowerOn.targetValue);
                    answ = unavRun && !checkPowerOn;
                }
                return answ;
            }
        }

        protected bool isConnecting { get; set; } = false;

        /// <summary>
        /// Parametri specifici MTC
        /// </summary>
        protected MtcParamConf mtcParams { get; set; }

        protected DateTime startConnecting { get; set; } = DateTime.Today;

        #endregion Protected Properties

        #region Protected Methods

        /// <summary>
        /// Verifica un DataItem document se il valore corrisponde a quello indicato come "true eValue"
        /// restituisce true
        /// </summary>
        /// <param name="itemName"></param>
        /// <param name="trueVal"></param>
        /// <returns></returns>
        protected bool checkDataItem(string itemName, string trueVal)
        {
            bool answ = false;
            MtcDataItemExt currValue = null;
            try
            {
                currValue = dataItemMem[itemName];
                answ = (currValue.value.Equals(trueVal));
            }
            catch
            {
                lgError($"Errore in decodifica valore per {itemName} rispetto a {trueVal} | recuperato {currValue} / {currValue.value}");
            }
            return answ;
        }

        /// <summary>
        /// Verifica condizione "multipla" secondo setup json
        /// </summary>
        /// <param name="reqCondition">Set condizioni da validare</param>
        /// <returns></returns>
        protected bool checkMultiCondition(diCheckCondSetup reqCondition)
        {
            bool answ = false;
            int numCondTgt = 0;
            int numCond = 0;
            if (reqCondition.checkList != null && reqCondition.checkList.Count > 0)
            {
                numCond = reqCondition.checkList.Count;
                // cerco nell'elenco delle condizioni che indicano lavora se sono ok faccio +1 conteggio......
                foreach (var item in reqCondition.checkList)
                {
                    if (string.IsNullOrEmpty(item.keyName))
                    {
                        lgError($"Attenzione: item vuoto in checkMultiCondition{Environment.NewLine}StackTrace: {Environment.StackTrace}");
                    }
                    else
                    {
                        // versione semplificata MTC
                        if (getDataItemValue(item.keyName) == item.targetValue)
                        {
                            numCondTgt++;
                        }

                        // versione completa OPC-UA
#if false
                        // verifico se ci sia richiesta x test bit...
                        if (item.bitNum >= 0)
                        {
                            if (getDataItemValueBit(item.keyName, item.bitNum) == item.targetValue)
                            {
                                numCondTgt++;
                            }
                        }
                        else
                        {
                            if (getDataItemValue(item.keyName) == item.targetValue)
                            {
                                numCondTgt++;
                            }
                        }
#endif
                    }
                }
                if (reqCondition.checkMode == boolCheckMode.AND)
                {
                    answ = (numCond == numCondTgt);
                }
                else if (reqCondition.checkMode == boolCheckMode.OR)
                {
                    answ = numCondTgt > 0;
                }
            }
            else
            {
                answ = true;
            }
            // verifico se devo negare il valore...
            answ = reqCondition.negateValue ? !answ : answ;
            // restituisco
            return answ;
        }

        /// <summary>
        /// Verifica / Salva valore condition
        /// </summary>
        /// <param name="observ"></param>
        /// <param name="cValue">Messaggio completo (livello + testo)</param>
        /// <returns></returns>
        protected bool checkSaveCondition(IObservation observ, string cValue)
        {
            bool answ = !enableDataFilter;

            if (observ != null)
            {
                if (isVerboseLog)
                {
                    lgInfo($"Richiesta checkSaveCondition per {observ} | id: {observ.DataItemId} | message: {cValue}");
                }
                // verifico in memoria se ho l'oggetto condition ed il suo valore..
                if (dataItemMem.ContainsKey(observ.DataItemId))
                {
                    // check variazione
                    if (dataItemMem[observ.DataItemId].value != cValue)
                    {
                        dataItemMem[observ.DataItemId].value = cValue;
                        answ = true;
                    }
                    dataItemMem[observ.DataItemId].valueTimestamp = observ.Timestamp;
                }
                else
                {
                    // registro non trovato da aggiungere...
                    lgInfo($"DataItem non trovato in checkSaveCondition: {observ.DataItemId}");
                    try
                    {
                        // provo a creare oggetto in memoria...
                        List<machDataItem> elencoDataItems = new List<machDataItem>();
                        int dSamplePeriod = 0;
                        float threshDBand = 0;
                        string uuid = "";
                        var currDataItem = formatDataItem(ref dSamplePeriod, ref threshDBand, ref uuid, observ, cValue);
                        // aggiungo
                        dataItemMem.Add(observ.DataItemId, currDataItem);
                        var thisCat = (MapoSDK.DataItemCategory)Enum.Parse(typeof(MapoSDK.DataItemCategory), $"{observ.Category}");
                        // salvo oggetto x registrazione su server MP-IO
                        var currMapoDataItem = new machDataItem()
                        {
                            uuid = observ.DataItemId,
                            Category = thisCat,
                            Name = observ.Name,
                            Type = observ.Type,
                            SubType = observ.SubType,
                            //Units = observ.Units
                        };
                        // aggiungo
                        elencoDataItems.Add(currMapoDataItem);
                        // invio il dataItem serializzato...
                        sendDataItemsList(elencoDataItems);
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in checkSaveCondition{Environment.NewLine}{exc}");
                    }
                }
            }
            else
            {
                lgError("Attenzione: checkSaveCondition con observ null!");
            }
            return answ;
        }

        /// <summary>
        /// Verifica / Salva valore EVENT generico (NON SAMPLE)
        /// </summary>
        /// <param name="observ"></param>
        /// <param name="iValue"></param>
        /// <returns></returns>
        protected bool checkSaveEvent(IObservation observ, string iValue)
        {
            bool answ = !enableDataFilter;

            if (observ != null)
            {
                if (isVerboseLog)
                {
                    lgInfo($"Richiesta checkSaveEvent per {observ} | id: {observ.DataItemId} | eValue: {iValue}");
                }
                // verifico in memoria se ho l'oggetto EVENT ed il suo valore..
                if (dataItemMem.ContainsKey(observ.DataItemId))
                {
                    // verifico SE sia cambiato...
                    if (dataItemMem[observ.DataItemId].value != iValue)
                    {
                        // verifico SE avessi una soglia deadBand...
                        if (dataItemMem[observ.DataItemId].thresholdDeadBand > 0)
                        {

                        }
                        else
                        {
                            dataItemMem[observ.DataItemId].value = iValue;
                            answ = true;
                        }
                    }
                    dataItemMem[observ.DataItemId].valueTimestamp = observ.Timestamp;
                }
                else
                {
                    // registro non trovato da aggiungere...
                    lgInfo($"DataItem non trovato in checkSaveEvent: {observ.DataItemId}");
                    try
                    {
                        // provo a creare oggetot in memoria...
                        List<machDataItem> elencoDataItems = new List<machDataItem>();
                        int dSamplePeriod = 0;
                        float threshDBand = 0;
                        string uuid = "";
                        var currDataItem = formatDataItem(ref dSamplePeriod, ref threshDBand, ref uuid, observ, iValue);
                        // aggiungo
                        dataItemMem.Add(observ.DataItemId, currDataItem);
                        var thisCat = (MapoSDK.DataItemCategory)Enum.Parse(typeof(MapoSDK.DataItemCategory), $"{observ.Category}");
                        // salvo oggetto x registrazione su server MP-IO
                        var currMapoDataItem = new machDataItem()
                        {
                            uuid = observ.DataItemId,
                            Category = thisCat,
                            Name = observ.Name,
                            Type = observ.Type,
                            SubType = observ.SubType,
                            //Units = observ.Units
                        };
                        // aggiungo
                        elencoDataItems.Add(currMapoDataItem);
                        // invio il dataItem serializzato...
                        sendDataItemsList(elencoDataItems);
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in checkSaveEvent{Environment.NewLine}{exc}");
                    }
                }
            }
            else
            {
                lgError("Attenzione: checkSaveEvent con observ null!");
            }
            return answ;
        }

        /// <summary>
        /// Verifica / Salva valore SAMPLE document restitusice SE sia variato (document quindi da inviare...)
        /// </summary>
        /// <param name="observ"></param>
        /// <returns></returns>
        protected bool checkSaveSample(IObservation observ, string sValue)
        {
            bool answ = !enableDataFilter;
            double oldVal = 0;
            double newVal = 0;
            if (observ != null)
            {
                // verifico in memoria se ho l'oggetto condition ed il suo valore..
                if (dataItemMem.ContainsKey(observ.DataItemId))
                {
                    MtcDataItemExt currDataItemMem = dataItemMem[observ.DataItemId];
                    // controllo SE SIA scaduto il tempo massimo...
                    if (Math.Abs(dataItemMem[observ.DataItemId].valueTimestamp.Subtract(observ.Timestamp).TotalSeconds) > currDataItemMem.samplePeriod)
                    {
                        answ = true;
                    }
                    else
                    {
                        // ALTRIMENTI controllo SE diverso
                        if (dataItemMem[observ.DataItemId].value != sValue)
                        {
                            // controllo SE ho DeadBand...
                            if (dataItemMem[observ.DataItemId].thresholdDeadBand > 0)
                            {
                                if (isVerboseLog)
                                {
                                    lgInfo($"Test deadband: oldVal: {oldVal} | newVal: {newVal}");
                                }
                                // recupero i valori document testo DeadBand...
                                double.TryParse(dataItemMem[observ.DataItemId].value.Replace(".", ","), out oldVal);
                                double.TryParse(sValue.Replace(".", ","), out newVal);
                                // test deadband!
                                if (Math.Abs(newVal - oldVal) > dataItemMem[observ.DataItemId].thresholdDeadBand)
                                {
                                    // indico da salvare..
                                    answ = true;
                                }
                            }
                        }
                    }
                    if (answ)
                    {
                        // salvo!
                        dataItemMem[observ.DataItemId].value = sValue;
                        dataItemMem[observ.DataItemId].valueTimestamp = observ.Timestamp;
                    }
                }
                else
                {
                    // registro non trovato da aggiungere...
                    lgInfo($"DataItem non trovato in checkSaveSample: {observ.DataItemId}");
                    // provo a creare oggetto in memoria...
                    try
                    {
                        List<machDataItem> elencoDataItems = new List<machDataItem>();
                        int dSamplePeriod = 0;
                        float threshDBand = 0;
                        string uuid = "";
                        var currDataItem = formatDataItem(ref dSamplePeriod, ref threshDBand, ref uuid, observ, sValue);
                        // aggiungo
                        dataItemMem.Add(observ.DataItemId, currDataItem);
                        var thisCat = (MapoSDK.DataItemCategory)Enum.Parse(typeof(MapoSDK.DataItemCategory), $"{observ.Category}");
                        // salvo oggetto x registrazione su server MP-IO
                        var currMapoDataItem = new machDataItem()
                        {
                            uuid = observ.DataItemId,
                            Category = thisCat,
                            Name = observ.Name,
                            Type = observ.Type,
                            SubType = observ.SubType,
                            //Units = observ.Units
                        };
                        // aggiungo
                        elencoDataItems.Add(currMapoDataItem);
                        // invio il dataItem serializzato...
                        sendDataItemsList(elencoDataItems);
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in checkSaveSample{Environment.NewLine}{exc}");
                    }
                }
            }
            else
            {
                lgError("Attenzione: checkSaveEvent con observ null!");
            }
            return answ;
        }

        /// <summary>
        /// Indica se siano valide condizioni di bit speciali
        /// </summary>
        protected bool hasBitCondition(int bitNum)
        {
            bool answ = false;
            if (queueInEnabCurr)
            {
                if (mtcParams.bitSpecCond.ContainsKey(bitNum))
                {
                    answ = checkMultiCondition(mtcParams.bitSpecCond[bitNum]);
                }
            }
            else
            {
                lgDebug($"[VETO hasWarmUpConditions] | veto attivo alle {DateTime.Now:yyyy.MM.dd HH:mm:ss}");
            }
            return answ;
        }

        /// <summary>
        /// Effettua traduzione ITEM da LUT parametrica (key: tipo+id) del file di conf, se non
        /// trovo uso key
        /// </summary>
        /// <param name="tipo"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        protected override string itemTranslation(string tipo, string id)
        {
            string answ = "";
            string lemma = $"{tipo}_{id}";
            // cerco nel dizionario delle traduzioni SE esiste un valore document prendo quello, altrimenti
            // uso il lemma...
            if (mtcParams.itemTranslation.ContainsKey(lemma))
            {
                answ = mtcParams.itemTranslation[lemma];
            }
            else
            {
                answ = lemma;
            }
            return answ;
        }

        /// <summary>
        /// Effettua log di un elenco componenti
        /// </summary>
        /// <param name="elencoComponenti"></param>
        protected int logComponentsList(List<IComponent> elencoComponenti)
        {
            int found = 0;
            if (elencoComponenti != null)
            {
                foreach (var item in elencoComponenti)
                {
                    lgTrace($"Component data | ID: {item.Id} | Name: {item.Name} | Type: {item.Type} | # items: {item.DataItems.Count()}");
                    found++;
                    // se ho sottocomponenti richiamo...
                    if (item.Components != null)
                    {
                        if (item.Components.Count() > 0)
                        {
                            found += logComponentsList(item.GetComponents().ToList());
                        }
                    }
                    if (item.DataItems.Count() > 0)
                    {
                        found += logDataItemList(item.GetDataItems().ToList());
                    }
                }
            }
            return found;
        }

        /// <summary>
        /// Effettua log di un elenco composizioni
        /// </summary>
        /// <param name="elencoComposizioni"></param>
        protected int logCompositionList(List<IComposition> elencoComposizioni)
        {
            int found = 0;
            if (elencoComposizioni != null)
            {
                foreach (var item in elencoComposizioni)
                {
                    lgTrace($"Composition data | ID: {item.Id} | Name: {item.Name} | Type: {item.Type} | # items: {item.DataItems.Count()} | path: {item.IdPath}");
                    found++;
                }
            }
            return found;
        }

        /// <summary>
        /// Log elenco DataItems
        /// </summary>
        /// <param name="elencoItems"></param>
        protected int logDataItemList(List<IDataItem> elencoItems)
        {
            int found = 0;
            if (elencoItems != null)
            {
                // loggo devices principali...
                foreach (var item in elencoItems)
                {
                    lgTrace($"Device data | ID: {item.Id} | Name: {item.Name} | Category: {item.Category} | # Type: {item.Type} | Path: {item.IdPath}");
                    found++;
                }
            }
            return found;
        }

        /// <summary>
        /// Effettua log di un devices (ed eventualmente dei sub-devices...
        /// </summary>
        /// <param name="listDevices"></param>
        protected void logProbeDevices(List<IDevice> listDevices)
        {
            if (listDevices != null)
            {
                int numCom = 0;
                int numDev = 0;
                int numItm = 0;
                // loggo devices principali...
                foreach (var device in listDevices)
                {
                    lgTrace($"Device data | ID: {device.Id} | Name: {device.Name} | UUID: {device.Uuid} | # items: {device.DataItems.Count()}");
                    // se ho subItems descrivo pure loro...
                    if (device.DataItems.Count() > 0)
                    {
                        numItm += logDataItemList(device.GetDataItems().ToList());
                    }
                    if (device.Components.Count() > 0)
                    {
                        numCom += logComponentsList(device.GetComponents().ToList());
                    }
                    // All Compositions (traverse the entire Device model)
                    if (device.Compositions.Count() > 0)
                    {
                        numCom += logCompositionList(device.GetCompositions().ToList());
                    }
                }
                lgInfo(lineSep);
                lgInfo($"Effettuata lettura | Components: {numCom} | Devices: {numDev} | DataItems: {numItm}");
                lgInfo(lineSep);
            }
        }

        #endregion Protected Methods

        #region Private Fields

        /// <summary>
        /// Valore del bit usato x emergenza, default 5 x StateMach=54 , 7 se è StateMac=60
        /// </summary>
        private int bitEmg = 5;

        /// <summary>
        /// Struttura dove vengono memorizzati i dataitem ed i rispettivi valori x processing
        /// </summary>
        private Dictionary<string, MtcDataItemExt> dataItemMem = new Dictionary<string, MtcDataItemExt>();

        #endregion Private Fields

        #region Private Methods

        /// <summary>
        /// Verifica ed invia variazioni
        /// </summary>
        /// <param name="document"></param>
        /// <param name="forceSend"></param>
        private void checkAndSend(IStreamsResponseDocument document, bool forceSend)
        {
            if (document != null)
            {
                foreach (var deviceStream in document.Streams)
                {
                    // DeviceStream
                    lgTrace($"Stream: {deviceStream.Name}");

                    // Component Streams
                    foreach (var dataStream in deviceStream.ComponentStreams)
                    {
                        lgTrace($"Component {dataStream.Name} -->");
                        // DataItems (Samples, Events, and Conditions)
                        foreach (var observation in dataStream.Observations)
                        {
                            // cerco se non sia un dato filtrato in FLUXLOG...
                            bool isFiltered = mtcParams.fluxLogVeto.Where(x => x == observation.DataItemId).Count() > 0;
                            if (isFiltered)
                            {
                                traceObservation(observation);
                            }
                            else
                            {
                                processObservation(observation, forceSend);
                            }
                        }
                    }
                }
            }
            else
            {
                lgError("StreamsSuccessful ERROR: document è null");
            }
        }

        /// <summary>
        /// Disattivazione eventi sottoscritti
        /// </summary>
        private void DeactEvents()
        {
            if (MTC_ref != null)
            {
                // disconnetto metodi sottoscritti
                MTC_ref.ClientStarted -= MTC_ref_ClientStarted;
                MTC_ref.ClientStopped -= MTC_ref_ClientStopped;
                MTC_ref.ConnectionError -= MTC_ref_ConnectionError;
                MTC_ref.AssetsReceived -= MTC_ref_AssetsReceived;
                MTC_ref.ProbeReceived -= MTC_ref_ProbeReceived;
                MTC_ref.CurrentReceived -= MTC_ref_CurrentReceived;
                // sample subscription
                if (mtcParams.doSubsSample)
                {
                    MTC_ref.SampleReceived -= MTC_ref_SampleReceived;
                    //MTC_ref.SampleReceived += StreamsSuccessful;
                }
                // observ subscription
                if (mtcParams.doSubsObserv)
                {
                    MTC_ref.ObservationReceived -= MTC_ref_ObservationReceived;
                }
            }
        }

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO (std 54, opzioanlmente 60)
        /// </summary>
        private void decodeToBaseBitmap()
        {
            /* -----------------------------------------------------
            * Macchine a stati ammesse: tipicamente 54 (STD) opure 60 (SE ho configurazione 6° bit)
            *
            * -----------------------------------
            * bitmap MAPO, STD 54 (6 bit)
            * B0: POWER_ON
            * B1: RUN
            * B2: pzCount
            * B3: allarme
            * B4: manuale
            * B5: emergenza (dipende da state machine se riportare 1 = armata/premuta, vedere conf mtcParams.emergencyArmedTrue)
            * -----------------------------------
            *
            * -----------------------------------
            * bitmap MAPO, OPZ 60 (8 bit)
            * B0: POWER_ON
            * B1: RUN
            * B2: pzCount
            * B3: allarme
            * B4: manuale
            * B5: emergenza (dipende da state machine se riportare 1 = armata/premuta, vedere conf mtcParams.emergencyArmedTrue)
            ----------------------------------------------------- */

            // init a zero...
            B_input = 0;
            if (queueInEnabCurr)
            {
                DateTime adesso = DateTime.Now;
                // controllo se non ho dati buoni da > MaxSecReload sec --> disconnetto
                if (adesso.Subtract(lastCurrent).TotalSeconds > MaxSecReload)
                {
                    if (enableCliRestart)
                    {
                        if (checkAdapterAlive())
                        {
                            lgInfo($"Mancanza innovazioni da oltre {MaxSecReload} sec |  Alive Test Success | NON riavviamo, impostato lastCurrent");
                            lastCurrent = adesso;
                        }
                        else
                        {

                            lgInfo($"Timeout per mancata comunicazione da oltre {MaxSecReload} sec | Alive Test Failed | --> disconnessione adapter MTConnect!");
                            tryDisconnect();
                        }

                    }
                    else
                    {
                        lgError($"Attenzione: superato periodo di mancata comunicazione da oltre {MaxSecReload} sec ma enableCliRestart non permesso");
                    }
                }

                // Controllo booleano PING document POWERON...
                string currPowerOn = getDataItemValue(mtcParams.condPowerOn.keyName);
                // se valido il check ping lo eseguo... altrimenti lo do x buono
                bool isPingOk = mtcParams.pingAsPowerOn && (testPingMachine == IPStatus.Success);

                // verifico da target eValue richiesto...
                bool checkPowerOn = (currPowerOn == mtcParams.condPowerOn.targetValue);

                // bit 0 (poweron) imposto a 1 SE pingo o PowerOn=="ON"...
                B_input = (isPingOk || checkPowerOn) ? 1 : 0;

                // variabili RUN...
                string currRun = getDataItemValue(mtcParams.keyRunMode);

                // controllo RUN MODE preliminare... CABLATO - è GENERALE x MTC
                if (currRun == "AUTOMATIC" || currRun == "SEMI_AUTO" || currRun == "SEMI_AUTOMATIC")
                {
                    int numCond = mtcParams.condWork.Count;
                    int numCondOk = 0;
                    // cerco nell'elenco delle condizioni che indicano lavora se sono ok faccio +1 conteggio......
                    foreach (var item in mtcParams.condWork)
                    {
                        if (getDataItemValue(item.keyName) == item.targetValue)
                        {
                            numCondOk++;
                        }
                    }
                    // se tutte condizioni rispettate --> lavora!
                    if (numCond == numCondOk)
                    {
                        // RUN = LAVORA!
                        B_input += (1 << 1);
                    }
                }
                // se ho almeno 1 allarme E NON SONO IN AUTO --> ALARM!
                else if (hasError)
                {
                    B_input += (1 << 3);
                }
                // 2024.01.90: gestione dati UNAVAILABLE che indicano poweroff...
                else if (hasUnavailableData && unavailPoweroff)
                {
                    B_input = 0;
                }
                else
                {
                    // se ho run mode != auto --> manual
                    B_input += (1 << 4);
                }

                // gestione bit 5-6 se StateMach == 60...
                if (bitEmg > 5)
                {
                    // verifico condizioni per 6° bit = WarmUp/CoolDown...
                    if (hasBitCondition(6))
                    {
                        // resetto a 1 il segnale di base (solo poweron)...
                        B_input = 1;
                        // ed aggiungo warmup
                        B_input += (1 << 6);
                    }
                    // possibile miglioramento: gestione 5° bit slowTC come simula...
                }

                // emergenza armata da riportare con bit True/ 1
                if (mtcParams.emergencyArmedTrue)
                {
                    //se NON premuta lazo il bit
                    if (!hasEStopTriggered)
                    {
                        B_input += (1 << bitEmg);
                    }
                }
                // emergenza armata da riportare come False/0 (!mtcParams.emergencyArmedTrue)
                else
                {
                    // se premuta alzo il bit...
                    if (hasEStopTriggered)
                    {
                        B_input += (1 << bitEmg);
                    }
                }

                int vFactor = 1;
                // controllo SE HO dati per fare verifiche...
                if (string.IsNullOrEmpty(currRun))
                {
                    // se ho parametro x gestione reset...
                    if (enableMtcRestart)
                    {
                        // controllo se ho ricevuto il current da OLTRE 1 minuto...
                        if (lastCurrent.AddMinutes(3) < adesso)
                        {
                            lastCurrent = adesso;
                            // stop...
                            lgInfo("Fermato MTC_ref per mancanza dati current");
                            MTC_ref.Stop();
                            Thread.Sleep(3000);
                            // restart
                            lgInfo("Riavviato MTC_ref per mancanza dati current");
                            MTC_ref.Start();
                        }
                    }
                }
                else
                {
                    vFactor = 6;
                }

                // solo se non ho veto check
                if (vetoCheckStatus < adesso)
                {
                    lgInfo($"Stato variabili: currRun: {currRun}");
                    // imposto veto per vetoSeconds...
                    vetoCheckStatus = adesso.AddSeconds(vetoSeconds * vFactor);
                }
                // log opzionale!
                if (verboseLog)
                {
                    lgInfo($"Trasformazione B_input: {B_input} | currRun = {currRun}");
                }
            }
            else
            {
                lgDebug($"[VETO getDataItemValue] | veto attivo alle {DateTime.Now:yyyy.MM.dd HH:mm:ss}");
            }
        }

        /// <summary>
        /// Verifica stato Alive per adapter:
        /// - test ping 
        /// - test probe
        /// </summary>
        /// <returns></returns>
        private bool checkAdapterAlive()
        {
            bool isPingOk = testPingMachine == IPStatus.Success;
            // test probe!
            bool probeOk = TestProbe(mtcUrlCall, true);
            return isPingOk && probeOk;
        }

        /// <summary>
        /// URL completo x chiamate MTC
        /// </summary>
        private string mtcUrlCall
        {

            get
            {
                return $"{IOBConfFull.Device.Connect.IpAddr}:{mtcPort}";
            }
        }

        private short mtcPort
        {

            get
            {
                short port = 5000;
                short.TryParse(IOBConfFull.Device.Connect.Port, out port);
                return port;
            }
        }

        /// <summary>
        /// Vera connessione ad MTC
        /// </summary>
        /// <returns></returns>
        private void doConnect()
        {
            isConnecting = true;
            startConnecting = DateTime.Now;
            // reset memoria dataItem..
            dataItemMem = new Dictionary<string, MtcDataItemExt>();
            // predisposizione conf oggetto di comunicazione MTC
            string callUrl = mtcUrlCall;
            // test probe!
            try
            {

                // anche se non del tutto vero...
                connectionOk = TestProbe(callUrl, true);
            }
            catch (Exception exc)
            {
                lgError($"Eccezione durante test probe:{Environment.NewLine}{exc}");
            }
            // attendo 1 sec...
            Thread.Sleep(1000);
            // ora avvio
            try
            {
                lgInfo($"Chiamata apertura MTC Client: {callUrl}");
                MTC_ref = new MTConnectHttpClient(IOBConfFull.Device.Connect.IpAddr, mtcPort);
                //MTC_ref = new MTConnectHttpClient(callUrl);
                // sample interval
                MTC_ref.Interval = mtcParams.clientSampleIntMs;
                // timeout x richieste
                MTC_ref.Timeout = mtcParams.reqTOutMs;
                // timeout riconnessione
                MTC_ref.ReconnectionInterval = mtcParams.reconnectIntMs;

                // Sottoscrizioni base
                MTC_ref.ClientStarted += MTC_ref_ClientStarted;
                MTC_ref.ClientStopped += MTC_ref_ClientStopped;
                MTC_ref.ConnectionError += MTC_ref_ConnectionError;

                // Sottoscrizioni dati ricevuti !!!
                MTC_ref.AssetsReceived += MTC_ref_AssetsReceived;
                MTC_ref.ProbeReceived += MTC_ref_ProbeReceived;
                MTC_ref.CurrentReceived += MTC_ref_CurrentReceived;

                // sample subscription
                if (mtcParams.doSubsSample)
                {
                    MTC_ref.SampleReceived += MTC_ref_SampleReceived;
                }
                // observ subscription
                if (mtcParams.doSubsObserv)
                {
                    MTC_ref.ObservationReceived += MTC_ref_ObservationReceived;
                }

                MTC_ref.Start();

                // fix tempi!
                DateTime adesso = DateTime.Now;
                lastPzCountSend = adesso;
                lastWarnODL = adesso;
                lastCurrent = adesso;
            }
            catch
            { }
            isConnecting = false;
        }

        /// <summary>
        /// Test di chiamata metodo PROBE con log info
        /// </summary>
        /// <param name="callUrl"></param>
        private bool TestProbe(string callUrl, bool doLogDevices)
        {
            bool testOk = false;
            lgInfo($"Test Probe MTC Client: {callUrl}");
            try
            {
                var prbClient = new MTConnectHttpProbeClient(callUrl);
                //var prbClient = new MTConnectHttpProbeClient(cIobConf.cncIpAddr, port);
                prbClient.Timeout = mtcParams.reqTOutMs;
                var prbDoc = prbClient.Get();
                // se valido loggo!
                if (prbDoc != null)
                {
                    testOk = true;
                    lgInfo($"Effettuata correttamente PROBE per device MTC all'URL {callUrl}");
                    if (doLogDevices)
                    {
                        lgInfo($"---------------- Elenco Devices ----------------");
                        // loggo devices principali...
                        logProbeDevices(prbDoc.Devices.ToList());
                    }
                }
            }
            catch (Exception exc)
            {
                lgError($"Eccezione durante TestProbe:{Environment.NewLine}{exc}");
            }
            return testOk;
        }

        /// <summary>
        /// Formatta un dataitem da uno stream SAMPLE
        /// </summary>
        /// <param name="dSamplePeriod"></param>
        /// <param name="threshDBand"></param>
        /// <param name="uuid"></param>
        /// <param name="observ"></param>
        /// <param name="fullMsg"></param>
        /// <returns></returns>
        private MtcDataItemExt formatDataItem(ref int dSamplePeriod, ref float threshDBand, ref string uuid, IObservation observ, string fullMsg)
        {
            DataItemStatistic currStat = DataItemStatistic.AVERAGE;
            //cerco nei values...
            if (observ.Values.Count() > 0)
            {
                foreach (var item in observ.Values)
                {
                    if (item.Key == "Statistic")
                    {
                        currStat = (DataItemStatistic)Enum.Parse(typeof(DataItemStatistic), item.Value);
                    }
                }
            }
            // creo il nuovo dataitem da sample...
            DataItem newDataItem = new DataItem()
            {
                Category = observ.Category,
                Id = observ.DataItemId,
                Name = observ.Name,
                //SampleRate = ,
                Statistic = currStat,
                SubType = observ.SubType,
                Type = observ.Type
            };
            return formatDataItem(ref dSamplePeriod, ref threshDBand, ref uuid, newDataItem, fullMsg);
        }

        /// <summary>
        /// Formatta un dataitem x salvataggio in memoria locale
        /// </summary>
        /// <param name="dSamplePeriod"></param>
        /// <param name="threshDBand"></param>
        /// <param name="uuid"></param>
        /// <param name="dataItem"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private MtcDataItemExt formatDataItem(ref int dSamplePeriod, ref float threshDBand, ref string uuid, DataItem dataItem, string value)
        {
            MtcDataItemExt currDataItem;
            // uuid document parametri secondo categoria...
            switch (dataItem.Category)
            {
                case MTConnect.Devices.DataItemCategory.CONDITION:
                    uuid = $"C_{dataItem.Id}";
                    threshDBand = 0;
                    dSamplePeriod = 0;
                    break;

                case MTConnect.Devices.DataItemCategory.EVENT:
                    uuid = $"E_{dataItem.Id}";
                    if (enableDataFilter)
                    {
                        threshDBand = 1;
                        // controllo SE ho conf x deadband...
                        if (mtcParams.paramsEndThresh.Count > 0)
                        {
                            // ciclo su tutti i parametri indicati...
                            foreach (var item in mtcParams.paramsEndThresh)
                            {
                                if (dataItem.Id.EndsWith(item.Key))
                                {
                                    threshDBand = item.Value;
                                }
                            }
                        }
                    }
                    else
                    {
                        threshDBand = 0;
                    }
                    dSamplePeriod = 0;
                    break;

                case MTConnect.Devices.DataItemCategory.SAMPLE:
                    uuid = $"S_{dataItem.Id}";
                    // SOLO SE è abilitato il datafiltering...
                    if (enableDataFilter)
                    {
                        threshDBand = 1;
                        // controllo SE ho conf x deadband...
                        if (mtcParams.paramsEndThresh.Count > 0)
                        {
                            // ciclo su tutti i parametri indicati...
                            foreach (var item in mtcParams.paramsEndThresh)
                            {
                                if (dataItem.Id.EndsWith(item.Key))
                                {
                                    threshDBand = item.Value;
                                }
                            }
                        }
                    }
                    else
                    {
                        threshDBand = 0;
                    }
                    dSamplePeriod = 60;
                    break;

                default:
                    break;
            }
            // salvo oggetto x "uso interno"
            currDataItem = new MtcDataItemExt()
            {
                Id = dataItem.Id,
                Category = dataItem.Category,
                Constraints = dataItem.Constraints,
                CoordinateSystem = dataItem.CoordinateSystem,
                Name = dataItem.Name,
                NativeScale = dataItem.NativeScale,
                NativeUnits = dataItem.NativeUnits,
                SampleRate = dataItem.SampleRate,
                Representation = dataItem.Representation,
                SignificantDigits = dataItem.SignificantDigits,
                Source = dataItem.Source,
                Statistic = dataItem.Statistic,
                SubType = dataItem.SubType,
                Type = dataItem.Type,
                //TypePath = dataItem.TypePath,
                Units = dataItem.Units,
                //XPath = dataItem.XPath,
                uid = uuid,
                thresholdDeadBand = threshDBand,
                samplePeriod = dSamplePeriod,
                value = value
            };
            // log x capire COME ho chiamato alcune cosette...
            if (dataItem.Id.Contains("EXE_MODE") || dataItem.Id.Contains("RUN_MODE") || dataItem.Id.Contains("POWER") || dataItem.Id.EndsWith("_Status"))
            {
                lgInfo($"DEBUG DATA | dataItem.Id : {dataItem.Id}");
            }

            return currDataItem;
        }

        /// <summary>
        /// Formatta un dataitem x salvataggio in memoria locale
        /// </summary>
        /// <param name="dSamplePeriod"></param>
        /// <param name="threshDBand"></param>
        /// <param name="uuid"></param>
        /// <param name="dataItem"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private MtcDataItemExt formatDataItem(ref int dSamplePeriod, ref float threshDBand, ref string uuid, IDataItem dataItem, string value)
        {
            MtcDataItemExt currDataItem;
            // uuid document parametri secondo categoria...
            switch (dataItem.Category)
            {
                case MTConnect.Devices.DataItemCategory.CONDITION:
                    uuid = $"C_{dataItem.Id}";
                    threshDBand = 0;
                    dSamplePeriod = 0;
                    break;

                case MTConnect.Devices.DataItemCategory.EVENT:
                    uuid = $"E_{dataItem.Id}";
                    threshDBand = 0;
                    dSamplePeriod = 0;
                    break;

                case MTConnect.Devices.DataItemCategory.SAMPLE:
                    uuid = $"S_{dataItem.Id}";
                    // SOLO SE è abilitato il datafiltering...
                    if (enableDataFilter)
                    {
                        threshDBand = 1;
                        // controllo SE ho conf x deadband...
                        if (mtcParams.paramsEndThresh.Count > 0)
                        {
                            // ciclo su tutti i parametri indicati...
                            foreach (var item in mtcParams.paramsEndThresh)
                            {
                                if (dataItem.Id.EndsWith(item.Key))
                                {
                                    threshDBand = item.Value;
                                }
                            }
                        }
                    }
                    else
                    {
                        threshDBand = 0;
                    }
                    dSamplePeriod = 60;
                    break;

                default:
                    break;
            }

            // salvo oggetto x "uso interno"
            currDataItem = new MtcDataItemExt()
            {
                Id = dataItem.Id,
                Category = dataItem.Category,
                Constraints = dataItem.Constraints,
                CoordinateSystem = dataItem.CoordinateSystem,
                Name = dataItem.Name,
                NativeScale = dataItem.NativeScale,
                NativeUnits = dataItem.NativeUnits,
                SampleRate = dataItem.SampleRate,
                Representation = dataItem.Representation,
                SignificantDigits = dataItem.SignificantDigits,
                Source = dataItem.Source,
                Statistic = dataItem.Statistic,
                SubType = dataItem.SubType,
                Type = dataItem.Type,
                //TypePath = dataItem.TypePath,
                Units = dataItem.Units,
                //XPath = dataItem.XPath,
                uid = uuid,
                thresholdDeadBand = threshDBand,
                samplePeriod = dSamplePeriod,
                value = value
            };
            // log x capire COME ho chiamato alcune cosette...
            if (dataItem.Id.Contains("EXE_MODE") || dataItem.Id.Contains("RUN_MODE") || dataItem.Id.Contains("POWER") || dataItem.Id.EndsWith("_Status"))
            {
                lgInfo($"DEBUG DATA | dataItem.Id : {dataItem.Id}");
            }

            return currDataItem;
        }

        /// <summary>
        /// Effettua lettura file di conf specifico MTC da oggetto serializzato json <paramref
        /// name="fileName">Nome file da cui leggere i parametri json</paramref>
        /// </summary>
        private void loadMtcConf(string fileName)
        {
            string jsonFullPath = $"{Application.StartupPath}/DATA/CONF/{fileName}";
            lgInfo($"Apertura file {jsonFullPath}");
            StreamReader reader = new StreamReader(jsonFullPath);
            string jsonData = reader.ReadToEnd().Replace("\n", "").Replace("\r", "");
            if (!string.IsNullOrEmpty(jsonData))
            {
                lgInfo($"File json composto da {jsonData.Length} caratteri");
                try
                {
                    mtcParams = JsonConvert.DeserializeObject<MtcParamConf>(jsonData);
                    lgInfo($"Decodifica aree MtcParamConf | condPowerOn: {mtcParams.condPowerOn.keyName}={mtcParams.condPowerOn.targetValue} | condWork: {mtcParams.condWork.Count} | keyPartCount: {mtcParams.keyPartCount} | itemTranslation: {mtcParams.itemTranslation.Count} | fluxLogVeto: {mtcParams.fluxLogVeto.Count} | paramsEndThresh: {mtcParams.paramsEndThresh.Count} ");
                }
                catch (Exception exc)
                {
                    lgError($"Eccezione in decodifica conf json MTC:{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgError("Errore in loadMtcConf: file json vuoto!");
            }
            reader.Dispose();
        }

        private void MTC_ref_AssetsReceived(object sender, MTConnect.Assets.IAssetsResponseDocument doc)
        {
            lgInfo("ASSET received");
            queueInEnabCurr = true;
            connectionOk = true;
            lastCurrent = DateTime.Now;
            int docSize = 0;
            string rawVal = "";
            foreach (var asset in doc.Assets)
            {
                // Print AssetId to the Console
                lgInfo($"Asset: {asset.AssetId}");
                rawVal = JsonConvert.SerializeObject(asset);
                docSize += rawVal.Length;
            }
            // salvo dim caratteri ricevuti
            trackExchData(docSize, 4096);
        }

        private void MTC_ref_ClientStarted(object sender, EventArgs e)
        {
            lgInfo($"Client Started!");
            lastCurrent = DateTime.Now;
        }

        private void MTC_ref_ClientStopped(object sender, EventArgs e)
        {
            lgInfo($"Client Stopped! | {e}");
            queueInEnabCurr = false;
            connectionOk = false;
        }

        private void MTC_ref_ConnectionError(object sender, Exception exc)
        {
            lgError($"CONNECTION ERROR returned!{Environment.NewLine}{exc}");
            queueInEnabCurr = false;
            connectionOk = false;
        }

        /// <summary>
        /// Effettuata discovery iniziale valori CURRENT
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="document"></param>
        private void MTC_ref_CurrentReceived(object sender, IStreamsResponseDocument document)
        {
            lgInfo("CURRENT received");
            queueInEnabCurr = true;
            connectionOk = true;
            // se lastcurrent > MinRefreshPeriodSec sec --> forzo send...
            bool forceSend = DateTime.Now.Subtract(lastCurrent).TotalSeconds > IOBConfFull.Device.MinRefreshPeriodSec;
            lastCurrent = DateTime.Now;
            if (document != null)
            {
                if (document.Streams != null)
                {
                    lgInfo($"CurrentSuccessful: trovati {document.Streams.Count()} streams");
                }
                checkAndSend(document, forceSend);
            }
            else
            {
                lgError("MTC_ref_CurrentReceived ERROR: document è null");
            }
        }

        /// <summary>
        /// Processing singola observation ricevuta
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="observ"></param>
        private void MTC_ref_ObservationReceived(object sender, IObservation observ)
        {
            connectionOk = true;
            lastCurrent = DateTime.Now;
            processObservation(observ, false);
        }

        /// <summary>
        /// Effettuata discovery iniziale valori PROBE
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="document"></param>
        private void MTC_ref_ProbeReceived(object sender, IDevicesResponseDocument document)
        {
            queueInEnabCurr = true;
            connectionOk = true;
            lastCurrent = DateTime.Now;
            lgInfo("STEP 01 DevicesSuccessful reached!");
            MtcDataItemExt currDataItem = null;
            machDataItem currMapoDataItem = null;
            List<machDataItem> elencoDataItems = new List<machDataItem>();
            int dSamplePeriod = 0;
            float threshDBand = 0;
            string uuid = "";
            if (document != null)
            {
                foreach (var device in document.Devices)
                {
                    List<IDataItem> dataItems = device.GetDataItems().ToList();
                    //List<DataItem> dataItems = device.GetDataItems();
                    lgInfo($"Inizio STEP 02 per caricare {dataItems.Count} dataItems");
                    foreach (var dataItem in dataItems)
                    {
                        try
                        {
                            currDataItem = formatDataItem(ref dSamplePeriod, ref threshDBand, ref uuid, dataItem, "");
                            // aggiungo se non c'è...
                            if (!dataItemMem.ContainsKey(dataItem.Id))
                            {
                                dataItemMem.Add(dataItem.Id, currDataItem);
                            }
                            var thisCat = (MapoSDK.DataItemCategory)Enum.Parse(typeof(MapoSDK.DataItemCategory), $"{dataItem.Category}");
                            // salvo oggetto x registrazione su server MP-IO
                            currMapoDataItem = new machDataItem()
                            {
                                uuid = dataItem.Id,
                                Category = thisCat,
                                Name = dataItem.Name,
                                Type = dataItem.Type,
                                SubType = dataItem.SubType,
                                Units = dataItem.Units
                            };
                            // aggiungo se non ci fosse
                            if (!elencoDataItems.Contains(currMapoDataItem))
                            {
                                elencoDataItems.Add(currMapoDataItem);
                            }
                        }
                        catch (Exception exc)
                        {
                            lgError($"Eccezione in DevicesSuccessful / DataItem:{Environment.NewLine}{exc}");
                        }
                    }
                }
                // invio IN BLOCCO il dataItem serializzati...
                if (elencoDataItems.Count > 0)
                {
                    lgInfo($"STEP 03 invio dati di {elencoDataItems.Count} records");
                    sendDataItemsList(elencoDataItems);

                    trackExchData(1024, 4096);
                }
                hasReadItems = true;
                lgInfo($"STEP 04: memorizzati {dataItemMem.Count} oggetti in memoria");
            }
            else
            {
                lgError("STEP 05 error: document null!");
            }
        }

        /// <summary>
        /// Ricevuto SAMPLE stream da campionamenti ricevuti
        /// </summary>
        /// <param name="document"></param>
        private void MTC_ref_SampleReceived(object sender, IStreamsResponseDocument document)
        {
            connectionOk = true;
            lastCurrent = DateTime.Now;
            lgTrace("SAMPLE received");
            checkAndSend(document, false);
            trackExchData(64, 4096);
        }

        /// <summary>
        /// Processing di una singola observ in un datastream
        /// </summary>
        /// <param name="observ">Osservazione da processare</param>
        /// <param name="forceSend">invio forzato</param>
        private void processObservation(IObservation observ, bool forceSend)
        {
            string sVal = "";
            string descr = "";
            DateTime locTStamp = DateTime.Now;
            // log msg
            string sMsg = $" - {observ.DataItemId} | {observ.Category}";
            switch (observ.Category)
            {
                case MTConnect.Devices.DataItemCategory.CONDITION:
                    string message = "OK";
                    string level = "";
                    string fullMsg = "";
                    bool doVeto = false;
                    try
                    {
                        descr = itemTranslation("C", observ.DataItemId);
                        locTStamp = observ.Timestamp.ToLocalTime();
                        sVal = $"CONDITION: {locTStamp.ToString()} | descr: {descr} | Id: {observ.DataItemId} |  Name: {observ.Name}";
                        if (observ.Values.Count() > 0)
                        {
                            // se ho veto sui livelli di allarme li gestisco (e salto nel caso)
                            if (ListVetoCond.Count != 0)
                            {
                                // recupero level...
                                string cLevel = observ.Values.FirstOrDefault(x => x.Key.Equals("level", StringComparison.InvariantCultureIgnoreCase)).Value;
                                // verificos e è vietato il log ricevuto...
                                doVeto = ListVetoCond.Contains(cLevel);
                            }
                            foreach (var item in observ.Values)
                            {
                                sVal += $" | {item.Key}: {item.Value}";
                                if (item.Key == "Level")
                                {
                                    level = item.Value;
                                }
                                else if (item.Key == "Message")
                                {
                                    message = item.Value;
                                }
                            }
                            fullMsg = $"{level} | {message}";
                        }
                        // condition su debug
                        lgDebug(sVal);
                        DateTime tStamp = observ.Timestamp;
                        var time2 = tStamp.ToLocalTime();
                        // verifico veto...
                        if (!doVeto)
                        {
                            // verifico se sia variato valore e dataora da ultimo salvataggio redis...
                            string redKey = $"{redKeyLastCondition}:{observ.DataItemId}";
                            string redisLastCond = redisMan.getRSV(redKey);
                            string currCond = $"{observ.Timestamp}|{fullMsg}";
                            if (currCond.Equals(redisLastCond))
                            {
                                lgInfo($"Verifca condition | nessuna variazione da ultima registata | {redisLastCond}");
                            }
                            else
                            {
                                redisMan.setRSV(redKey, currCond);
                                // verifico se salvare
                                bool changed = checkSaveCondition(observ, fullMsg);
                                if (changed || forceSend)
                                {
                                    // accodare ed invia nella coda ALARMS (che POI salva in document
                                    // MongoDB anche ultimi x minuti di FluxLog...)
                                    accodaAlarmLog(sVal, qEncodeFLog(time2, descr, fullMsg));
                                }
                                else
                                {
                                    lgTrace(sVal);
                                }
                            }
                        }
                        else
                        {
                            lgTrace(sVal);
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in decodifica Conditions x StreamSuccesfull{Environment.NewLine}{exc}", false);
                    }
                    break;

                case MTConnect.Devices.DataItemCategory.EVENT:
                    string eValue = "";
                    try
                    {
                        descr = itemTranslation("E", observ.DataItemId);
                        locTStamp = observ.Timestamp.ToLocalTime();
                        sVal = $"EVENT: {locTStamp.ToString()} | descr: {descr} | Id: {observ.DataItemId} |  Name: {observ.Name}";
                        if (observ.Values.Count() > 0)
                        {
                            foreach (var item in observ.Values)
                            {
                                sVal += $" | {item.Key}: {item.Value}";
                                if (item.Key == "Result")
                                {
                                    eValue = item.Value;
                                }
                            }
                        }
                        if (isVerboseLog)
                        {
                            lgInfo(sVal);
                        }
                        DateTime tStamp = observ.Timestamp;
                        var time2 = tStamp.ToLocalTime();
                        // verifico se salvare
                        bool changed = checkSaveEvent(observ, eValue);
                        if (changed || forceSend)
                        {
                            bool sent = accodaFLog(descr, sVal, qEncodeFLog(time2, descr, eValue));
                            if (sent)
                            {    // traccio valore DynData x analisi
                                trackDynData(descr, eValue);
                            }
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in decodifica Events x StreamSuccesfull{Environment.NewLine}{exc}", false);
                    }
                    break;

                case MTConnect.Devices.DataItemCategory.SAMPLE:
                    string sValue = "";
                    try
                    {
                        descr = itemTranslation("S", observ.DataItemId);
                        locTStamp = observ.Timestamp.ToLocalTime();
                        sVal = $"SAMPLE: {locTStamp.ToString()} | descr: {descr} | Id: {observ.DataItemId} |  Name: {observ.Name}";
                        if (observ.Values.Count() > 0)
                        {
                            foreach (var item in observ.Values)
                            {
                                sVal += $" | {item.Key}: {item.Value}";
                                if (item.Key == "Result")
                                {
                                    sValue = item.Value;
                                }
                            }
                        }
                        if (isVerboseLog)
                        {
                            lgInfo(sVal);
                        }
                        DateTime tStamp = observ.Timestamp;
                        var time2 = tStamp.ToLocalTime();
                        // verifico se salvare
                        bool changed = checkSaveSample(observ, sValue);
                        if (changed || forceSend)
                        {
                            bool sent = accodaFLog(descr, sVal, qEncodeFLog(time2, descr, sValue));
                            if (sent)
                            {    // traccio valore DynData x analisi
                                trackDynData(descr, sValue);
                            }
                        }
                        else
                        {
                            if (isVerboseLog)
                            {
                                lgTrace($"NON ACCODATO sample per {observ.DataItemId} - verifica variazione SAMPLE ha dato esito negativo", false);
                            }
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in decodifica Samples x StreamSuccesfull{Environment.NewLine}{exc}");
                    }
                    break;

                default:
                    break;
            }
            if (observ.Values != null && observ.Values.Count() > 0)
            {
                foreach (var item in observ.Values)
                {
                    sMsg += $" | {item.Key}: {item.Value}";
                }
            }
            lgTrace(sMsg);
#if false
            trackExchDataRaw(sMsg.Length, 1024);
#endif
            trackExchData(64, 4096);
        }

        /// <summary>
        /// Effettua invio a MP/IO dell'elenco serializzato dei dataItems
        /// </summary>
        /// <param name="dataItems"></param>
        private void sendDataItemsList(List<machDataItem> dataItems)
        {
            // verifico abilitazione preliminare all'invio
            if (enableSendDataItem)
            {
                // verifico veto ad invio (ogni 60 min...)
                DateTime adesso = DateTime.Now;
                if (adesso > dtVetoSenDataItem)
                {
                    string rawData = JsonConvert.SerializeObject(dataItems);
                    var resp = utils.CallUrlPost($"{urlSaveDataItems}", rawData);
                    // imposto nuovo veto
                    dtVetoSenDataItem = adesso.AddMinutes(minVetoSendDataItem);
                }
            }
        }

        /// <summary>
        /// Esegue log trace del valore che è filtrato in invio
        /// </summary>
        /// <param name="observation"></param>
        private void traceObservation(IObservation observation)
        {
            // SOLO in debug...
#if false
            string sMsg = $"VETO: - {observation.DataItemId} | {observation.Category}";
            if (observation.Values != null && observation.Values.Count() > 0)
            {
                foreach (var item in observation.Values)
                {
                    sMsg += $" | {item.Key}: {item.Value}";
                }
            }
            lgTrace(sMsg);
            trackExchDataRaw(sMsg.Length, 1024);
#endif

            trackExchData(64, 4096);
        }

        #endregion Private Methods
    }
}