using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using static IOB_UT_NEXT.CustomObj;

namespace IOB_WIN_OPC_UA.IobOpc
{
    public class OpcUaMBHCimolai : OpcUaMBH
    {
        #region Public Constructors

        /// <summary>
        /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation con la
        /// gestione specifica per MBH (es Cimolai, 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 OpcUaMBHCimolai(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            lgInfo("Init OpcUa MBH versione Cimolai (Baglietto)");
            sendKeyRichiesta = true;
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Processo i task richiesti e li elimino dalla coda 2:2
        /// </summary>
        /// <param name="task2exe"></param>
        public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
        {
            // uso metodo base x salvare esito scrittura
            var writeResult = base.executeTasks(task2exe, codTav);

            // aggiungo comportamento custom: se ho impostato nome ricetta (programma) --> imposto
            // richiesta caricamento se ho richiesto reset o fine lavoro --> imposto azzeramento
            // esco restituendo risultato scrittura iniziali
            if (task2exe != null)
            {
                // controllo se memMap != null...
                if (memMap != null)
                {
                    bool taskOk = false;
                    string taskVal = "";
                    // cerco task specifici x OMP
                    foreach (var item in task2exe)
                    {
                        taskOk = false;
                        taskVal = "";
                        // converto richiesta in enum...
                        taskType tName = taskType.nihil;
                        Enum.TryParse(item.Key, out tName);
                        // controllo sulla KEY...
                        switch (tName)
                        {
                            case taskType.setProg:
                                // recupero dati da memMap...
                                if (memMap != null && memMap.mMapWrite != null)
                                {
                                    if (memMap.mMapWrite.ContainsKey(item.Key))
                                    {
                                        dataConf currMem = memMap.mMapWrite[item.Key];
                                        string addr = currMem.memAddr;
                                        taskVal = $"SET task: {item.Key} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte";
                                        // salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
                                        memMap.mMapWrite[item.Key].value = item.Value;
                                    }
                                    else
                                    {
                                        taskVal = $"NO DATA MEM, SET task: {item.Key} --> {item.Value}";
                                    }
                                }
                                else
                                {
                                    taskVal = $"NO MemMap found, SET task: {item.Key} --> {item.Value}";
                                }
                                // salvo in currProd..
                                upsertKey(item.Key, item.Value);

                                break;

                            case taskType.startSetup:
                                setFineLotto();
                                break;

                            case taskType.stopSetup:
                                setInizioProd();
                                break;

                            case taskType.syncDbData:
                                ProcessDataSync();
                                break;

                            default:
                                taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
                                lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                                break;
                        }
                    }
                }
                else
                {
                    lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe!");
                }
            }

            return writeResult;
        }

        /// <summary>
        /// Effettua vero processing contapezzi
        /// </summary>
        public override void processContapezzi()
        {
            // non fa nulal (non ha contapezzi)
        }

        public override bool resetContapezziPLC(string codTav)
        {
            lgInfo("OpcUaMbhCimolai.resetContapezziPLC - none");
            bool answ = false;
            return answ;
        }

        #endregion Public Methods

        #region Protected Fields

        protected int lastAct = 0;

        protected bool lastIsInCorso = false;

        /// <summary>
        /// Ultimo rum mode rilevato x decidere SE inviare variazione...
        /// </summary>
        protected int lastRunMode = -999;


        protected DateTime vetoSnapshot = DateTime.Now;

        #endregion Protected Fields

        #region Protected Enums

        /// <summary>
        /// Definizione stati travel
        /// </summary>
        protected enum travelState
        {
            Idle = 0,
            StartOdl,
            UpLift,
            Move,
            DownLift,
            WaitClose
        }

        #endregion Protected Enums

        #region Protected Properties

        /// <summary>
        /// Attività corrente (INT) da keyRunMode valore di PLC/DB231/Attivita
        /// </summary>
        protected int currRunMode
        {
            get
            {
                int answ = 0;
                if (!string.IsNullOrEmpty(opcUaParams.keyRunMode))
                {
                    string currRun = getDataItemValue(opcUaParams.keyRunMode);
                    if (!string.IsNullOrEmpty(currRun))
                    {
                        int.TryParse(currRun, out answ);
                    }
                }
                return answ;
            }
        }

        protected travelState currTravelState { get; set; } = travelState.Idle;

        protected DateTime currTravelStateStart { get; set; } = DateTime.Today.AddYears(-1);

        /// <summary>
        /// Lavorazione in CORSO = da keyExeMode valore di PLC/DB231/InCorso
        /// </summary>
        protected bool isInCorso
        {
            get
            {
                bool answ = false;
                if (!string.IsNullOrEmpty(opcUaParams.keyExeMode))
                {
                    string currExe = getDataItemValue(opcUaParams.keyExeMode);
                    if (!string.IsNullOrEmpty(currExe))
                    {
                        bool.TryParse(currExe, out answ);
                    }
                }
                return answ;
            }
        }

        /// <summary>
        /// Indica se abbia stato MANUAL x CIMOLAI
        /// </summary>
        protected bool isManualCimolai
        {
            get
            {
                bool answ = false;
                if (lastAct > 0 && lastAct < 10)
                {
                    answ = (lastAct % 2 == 1);
                }
                return answ;
            }
        }

        /// <summary>
        /// Indica se abbia stato WORKING x CIMOLAI
        /// </summary>
        protected bool isWorkingCimolai
        {
            get
            {
                bool answ = false;
                if (lastAct > 0 && lastAct < 10)
                {
                    answ = (lastAct % 2 == 0);
                }
                return answ;
            }
        }

        #endregion Protected Properties

        #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);

                /* -----------------------------------------------------
                 *  CIMOLAI CUSTOM
                 *------------------------------------------------------
                 * AUX = 2  --> emergenza armata
                 * AUX <> 2 --> emergenza premuta
                 * Aux = 2 + InCorso = 0 --> pronto
                 * Aux = 2 + LastAct in (2,4,6,8) --> LAVORA
                 *
                 *
                 * PLC/DB231/Attivita
                 *  0: Emergenza
                 *  1: Avvio registrazione ricetta
                 *  2: Inizio comando traslazione
                 *  3: Termine comando traslazione
                 *  4: Inizio comando di sterzatura
                 *  5: Termine comando di sterzatura
                 *  6: Inizio comando movimento carrelli
                 *  7: Termine comando movimento carrelli
                 *  8: Inizio comando sollevamento
                 *  9: Termine comando sollevamento
                 *  10: richiesta snapshot parametri
                 *
                 *------------------------------------------------------
                 *  SEMPLIFICAZIONE POST CERTIFICAZIONE
                 *------------------------------------------------------
                 *
                 * Visto che la rete potrebbe saltare ad intermittenza, conviene gestire in modo semplificato il lavora
                 * torno a condizione work base: AUX = 2, marcia = in corso
                 *
                 *
                 ---------------------------------------------------- */

                // controllo emergenza... se zero --> emergenza!
                if (hasEStopArmed)
                {
                    B_input += (1 << 7);
                }
                else
                {
                    // resetto last act...
                    lastAct = 0;
                }

                // verifico se aggiornare stato LAST ACTION
                if (lastAct != currRunMode && currRunMode != 0)
                {
                    // registro solo azioni > 1 e < 10
                    if (currRunMode > 1 && currRunMode < 10)
                    {
                        // escludo le azioni 4 e 5 (che sono anche in concomitanza con 2-3)
                        if (currRunMode < 4 || currRunMode > 5)
                        {
                            lastAct = currRunMode;
                        }
                    }
                }

                // Gestione ODL automatica: se abilitata --> qui con start/stop da impianto...
                if (doProcOdl)
                {
                    lgTrace($"inizio process verifica presa in carico PODL | isInCorso: {isInCorso} | lastIsInCorso: {lastIsInCorso} | currProgName {currProgName}");
                    // se rilevo variazione exe (curr/last)
                    // --> registro richiesta attreazzaggio PODL da info commessa
                    if (lastIsInCorso != isInCorso)
                    {
                        // se 0 --> 1 --> registro
                        if (isInCorso)
                        {
                            lgInfo("--------------------------------------------");
                            // prendo senza stringa PODL
                            string sPODL = currProgName.Replace("PODL", "");
                            int idxPODL = 0;
                            int.TryParse(sPODL, out idxPODL);
                            // chiamo richiesta setup PODL...
                            lgInfo($"Inizio trySetupPODL per {idxPODL}");
                            TrySetupPODL(idxPODL);
                            lgInfo("--------------------------------------------");
                        }
                        else
                        {
                            lgInfo("--------------------------------------------");
                            lgInfo("Inizio TryCloseODLAsync");
                            // registro chiusura ODL..
                            TryCloseCurrODL();
                            lgInfo("--------------------------------------------");
                        }
                        // registro exe mode
                        lastIsInCorso = isInCorso;
                    }
                }

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

                // se ho setup
                if (isWarmUpCoolDown)
                {
                    B_input += (1 << 6);
                }
                // se ho almeno 1 allarme E NON SONO IN AUTO --> ALARM!
                if (hasError)
                {
                    B_input += (1 << 3);
                }
                if (isWorkingCimolai || isWorking || isInCorso)
                {
                    // RUN = LAVORA!
                    B_input += (1 << 1);
                }
                else
                {
                    if (!isReady || isManualCimolai || isManual)
                    {
                        // se NON ready --> manual
                        B_input += (1 << 4);
                    }
                }
            }

            // 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>
        /// Recupera da server set di dati specifici x IOB : qui per compilare files CSV
        /// </summary>
        /// <returns></returns>
        protected override bool IobGetDataFromServer()
        {
            bool answ = false;
            bool okArt = false;
            bool okDoss = false;
            bool okPodl = false;
            bool okLVFasi = false;
            List<PODLModel> listaPODL = new List<PODLModel>();
            List<DossiersModel> listaDoss = new List<DossiersModel>();
            List<AnagArticoli> listaArt = new List<AnagArticoli>();
            List<ListVal> anagLVFasi = new List<ListVal>();
            Dictionary<string, string> dictAF = new Dictionary<string, string>();
            // recupera dati da server tramite chiamate REST a MP/IO/IOB...
            try
            {
                // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
                Task.Run(async () =>
                {
                    var rawListArt = await callUrl(urlGetCurrArt, false);
                    var rawListDOSS =await  callUrl(urlGetCurrDOSS, false);
                    var rawListPODL = await callUrl(urlGetNextPODL, false);
                    var rawLVFasi = await callUrl(urlGetListValFasiPodl, false);
                    if (!string.IsNullOrEmpty(rawListArt))
                    {
                        try
                        {
                            listaArt = JsonConvert.DeserializeObject<List<AnagArticoli>>(rawListArt);
                            okArt = listaArt.Count > 0;
                        }
                        catch (Exception exc)
                        {
                            lgError($"Errore: chiamata elenco ART ha restituito errore{Environment.NewLine}{exc}");
                        }
                    }
                    else
                    {
                        lgError($"Errore: chiamata elenco ART ({urlGetCurrArt}) ha restituito valore vuoto");
                    }
                    if (!string.IsNullOrEmpty(rawListDOSS))
                    {
                        try
                        {
                            listaDoss = JsonConvert.DeserializeObject<List<DossiersModel>>(rawListDOSS);
                            okDoss = listaDoss.Count > 0;
                        }
                        catch (Exception exc)
                        {
                            lgError($"Errore: chiamata elenco DOSSIER ha restituito errore{Environment.NewLine}{exc}");
                        }
                    }
                    else
                    {
                        lgError($"Errore: chiamata elenco DOSSIER ({urlGetCurrDOSS}) ha restituito valore vuoto");
                    }
                    if (!string.IsNullOrEmpty(rawListPODL))
                    {
                        try
                        {
                            listaPODL = JsonConvert.DeserializeObject<List<PODLModel>>(rawListPODL);
                            okPodl = listaPODL.Count > 0;
                        }
                        catch (Exception exc)
                        {
                            lgError($"Errore: chiamata elenco PODL ha restituito errore{Environment.NewLine}{exc}");
                        }
                    }
                    else
                    {
                        lgError($"Errore: chiamata elenco PODL ({urlGetNextPODL}) ha restituito valore vuoto");
                    }
                    if (!string.IsNullOrEmpty(rawLVFasi))
                    {
                        try
                        {
                            anagLVFasi = JsonConvert.DeserializeObject<List<ListVal>>(rawLVFasi);
                            dictAF = anagLVFasi.ToDictionary(x => x.value, x => x.label);
                            okLVFasi = listaArt.Count > 0;
                        }
                        catch (Exception exc)
                        {
                            lgError($"Errore: chiamata elenco ListVal ha restituito errore{Environment.NewLine}{exc}");
                        }
                    }
                    else
                    {
                        lgError($"Errore: chiamata elenco ListVal ({urlGetListValFasiPodl}) ha restituito valore vuoto");
                    }
                })
                .GetAwaiter()
                .GetResult();
            }
            catch (Exception ex)
            {
                lgError($"Errore in IobGetDataFromServer | {Environment.NewLine}{ex.Message}");
            }
            
            answ = okPodl && okDoss && okArt && okLVFasi;
            if (answ)
            {
                // predispongo dati PODL
                ListaJobs = listaPODL
                                .Select(x => new JobRow() { Matricola = x.CodArticolo, Commessa = $"PODL{x.IdxPromessa:00000000}", Articolo = x.CodArticolo, Descrizione = x.CodArticolo, DataIns = $"{x.InsertDate:dd/MM/yyyy}", OraIns = $"{x.InsertDate:HH:mm}", Lavorazione = $"{getLV(dictAF, x.KeyRichiesta)} {x.Note}".Trim() })
                                .ToList();

                // predispongo dati articoli
                ListaArticoli = listaArt
                                .Select(a => new ArtRow() { Matricola = a.CodArticolo, Articolo = a.Disegno, Descrizione = a.DescArticolo, LimiteVel = 100 })
                                .Distinct()
                                .ToList();

                // completo con dati DOSSIER
                foreach (var item in ListaArticoli)
                {
                    string codArt = item.Matricola;
                    var currDoss = listaDoss.Where(x => x.CodArticolo == codArt).FirstOrDefault();
                    if (currDoss != null)
                    {
                        DossierFluxLogDTO resultSet = JsonConvert.DeserializeObject<DossierFluxLogDTO>(currDoss.Valore);
                        // traduco AD MENTULAM...
                        item.Peso_01 = getFluxValInt(resultSet, "OPC_PLC/DB231/peso1");
                        item.Peso_02 = getFluxValInt(resultSet, "OPC_PLC/DB231/peso2");
                        item.Peso_03 = getFluxValInt(resultSet, "OPC_PLC/DB231/peso3");
                        item.Peso_04 = getFluxValInt(resultSet, "OPC_PLC/DB231/peso4");
                        item.PosizCarrello_01 = getFluxValInt(resultSet, "OPC_PLC/DB231/PosCarr1");
                        item.PosizCarrello_02 = getFluxValInt(resultSet, "OPC_PLC/DB231/PosCarr2");
                        item.PosizCarrello_03 = getFluxValInt(resultSet, "OPC_PLC/DB231/PosCarr3");
                        item.PosizCarrello_04 = getFluxValInt(resultSet, "OPC_PLC/DB231/PosCarr4");
                    }
                }
            }
            return answ;
        }

        /// <summary>
        /// Effettua sync dati
        /// </summary>
        protected override void ProcessDataSync()
        {
            lgInfo("--------------------------------------------");
            lgInfo($"executeTasks --> syncDbData --> processDataSync");
            lgInfo("--------------------------------------------");
            // effettua sync fixme todo: test scrittura file csv...
            var taskGet = IobGetDataFromServer();
            var taskWrite = iobWriteLocalCSV();
            var taskSend = iobSendFTP("");
        }

        protected override void procRunMode(ref string currRun)
        {
            // variabili RUN... se richiesto invio runMode
            if (opcUaParams.runModeSend)
            {
                currRun = $"{currRunMode}";
                // effettuo processing SPECIFICO currRunMode x gestione snapshot...
                if (currRunMode == 10)
                {
                    if (DateTime.Now > vetoSnapshot)
                    {
                        lgInfo("--------------------------------------------");
                        try
                        {
                            // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
                            Task.Run(async () =>
                            {
                                await callUrl(urlTakeSnapshot, false);
                            })
                            .GetAwaiter()
                            .GetResult();
                        }
                        catch (Exception ex)
                        {
                            lgError($"Errore in procRunMode{Environment.NewLine}{ex.Message}");
                        }
                        // blocco snapshot x VETO_SEND_SNAPSHOT sec...
                        vetoSnapshot = DateTime.Now.AddSeconds(IOBConfFull.FluxLog.VetoSendSnapshotSec);
                        lgInfo($"Effettuata richiesta salvataggio snapshot, veto nuova chiamata fino a {vetoSnapshot}");
                        lgInfo("--------------------------------------------");
                    }
                    else
                    {
                        lgInfo($"NON effettuo salvataggio snapshot perché veto attivo fino a {vetoSnapshot}");
                    }
                }
                else
                {
                    // aggiorno veto x snapshot
                    vetoSnapshot = DateTime.Now;
                }

                // invio del valore RUN MODE come FLog, SE variato...
                if (!string.IsNullOrEmpty(currRun))
                {
                    if (currReadErrors != lastRunMode)
                    {
                        // aggiorno last...
                        lastRunMode = currRunMode;
                        // se ho valore --> invio
                        string sVal = "";
                        string descr = $"RunModeVal";
                        DateTime locTStamp = DateTime.Now;
                        sVal = $"Change 04: {locTStamp.ToString()} | descr: {descr} | Id: {opcUaParams.keyExeMode} | Val: {currRun}";

                        // cerco se sia un dato/valore in veto
                        bool hasVetoLK = false;
                        bool hasVetoLKV = false;
                        hasVetoLK = opcUaParams.fluxLogKeyValVeto.ContainsKey(descr);
                        if (hasVetoLK)
                        {
                            // se c'è controllo il valore...
                            var valList = opcUaParams.fluxLogKeyValVeto[descr];
                            hasVetoLKV = valList.Contains(currRun);
                        }
                        if (hasVetoLKV)
                        {
                            lgTrace($"NON ACCODATO sample: veto trovato per {descr}/{currRun} in fluxLogKeyValVeto ", false);
                        }
                        else
                        {
                            bool sent = accodaFLog(descr, sVal, qEncodeFLog(descr, currRun));
                            if (sent)
                            {    // traccio valore DynData x analisi
                                trackDynData(descr, currRun);
                            }
                            // se richiesto provo a tradurre
                            if (opcUaParams.runModeTrad)
                            {
                                string RunModeDescr = itemTranslation("RunMode", currRun);
                                descr = $"RunMode";
                                bool sentTrad = accodaFLog(descr, sVal, qEncodeFLog(descr, RunModeDescr));
                                if (sentTrad)
                                {    // traccio valore DynData x analisi
                                    trackDynData(descr, RunModeDescr);
                                }
                            }
                        }
                    }
                }
            }

            //verifica preliminare durata minima stato
            DateTime adesso = DateTime.Now;
            bool canChange = adesso.Subtract(currTravelStateStart).TotalSeconds > opcUaParams.minSecStatusDuration;
            // gestione dei macro-stati del travel... SOLO se in esecuzione da travel... BYPASS x ora!!!
            if (isInCorso || IOBConfFull.Device.ExeModeCheckBypass)
            {
                double pesoTot = 0;
                // può cambiare stato se per ALMENO minSecStatus è rimasto nello status corrente...
                if (canChange)
                {
                    lgTrace($"start checkExe | TRAVEL STATE: {currTravelState}");
                    // STEP 1: verifico se sia iniziato caricamento...
                    if (currTravelState == travelState.Idle)
                    {
                        if (currIdxODL > 0)
                        {
                            currTravelState = travelState.StartOdl;
                            currTravelStateStart = adesso;
                            lgInfo("--------------------------------------------");
                            lgInfo("TRAVEL STATE: StartOdl");
                            lgInfo("--------------------------------------------");
                        }
                    }
                    // sono nell'esecuzione ODL, controllo il resto
                    else
                    {
                        // nb: vedere se usare isWorkingCimolai / isManualCimolai

                        // verifico il valore della variabile calcolata del PESO totale...
                        if (dataItemMem.ContainsKey("PesoTot"))
                        {
                            pesoTot = getDataItemValueDouble("PesoTot");
                        }
                        // ...oppuresommo direttamente
                        else
                        {
                            pesoTot += getDataItemValueDouble("PLC/DB231/peso1");
                            pesoTot += getDataItemValueDouble("PLC/DB231/peso2");
                            pesoTot += getDataItemValueDouble("PLC/DB231/peso3");
                            pesoTot += getDataItemValueDouble("PLC/DB231/peso4");
                        }

                        lgTrace($"TState: Test 03 | pesoTot: {pesoTot}");

                        // STEP 2: verifico se sia iniziato caricamento...
                        if (currTravelState == travelState.StartOdl)
                        {
                            if (pesoTot > 0)
                            {
                                currTravelState = travelState.UpLift;
                                currTravelStateStart = adesso;
                                lgInfo("--------------------------------------------");
                                lgInfo("TRAVEL STATE: UpLift");
                                lgInfo("--------------------------------------------");
                            }
                        }
                        // STEP 3: verifico se sia terminato caricamento...
                        else if (currTravelState == travelState.UpLift)
                        {
                            if (pesoTot == 0)
                            {
                                currTravelState = travelState.Move;
                                currTravelStateStart = adesso;
                                lgInfo("--------------------------------------------");
                                lgInfo("TRAVEL STATE: Move");
                                lgInfo("--------------------------------------------");
                            }
                        }
                        // STEP 4: verifico se sia terminato movimento...
                        else if (currTravelState == travelState.Move)
                        {
                            if (pesoTot > 0)
                            {
                                currTravelState = travelState.DownLift;
                                currTravelStateStart = adesso;
                                lgInfo("--------------------------------------------");
                                lgInfo("TRAVEL STATE: DownLift");
                                lgInfo("--------------------------------------------");
                            }
                        }
                        // STEP 5: verifico se sia terminato scaricamento...
                        else if (currTravelState == travelState.DownLift)
                        {
                            if (pesoTot == 0)
                            {
                                currTravelState = travelState.WaitClose;
                                currTravelStateStart = adesso;
                                lgInfo("--------------------------------------------");
                                lgInfo("TRAVEL STATE: WaitClose");
                                lgInfo("--------------------------------------------");
                            }
                        }
                    }
                }
                else
                {
                    if (canChange)
                    {
                        lgTrace("TRAVEL STATE | Nessuna gestione status travel: inCorso = 0");
                    }
                }

                // ora verifico solo condizione finale x eventuale chiusura...
                bool canSendAskClose = adesso.Subtract(currTravelStateStart).TotalSeconds > opcUaParams.minSecFinalWait;
                if (canSendAskClose && currTravelState == travelState.WaitClose)
                {
                    // STEP 6: verifico se sia tolto ODL e quindi ha già chiuso...
                    if (currIdxODL == 0)
                    {
                        currTravelState = travelState.Idle;
                        currTravelStateStart = adesso;
                        lgInfo("--------------------------------------------");
                        lgInfo("TRAVEL STATE: Idle");
                        lgInfo("--------------------------------------------");
                    }
                    // altrimenti manda richiesta chiusura
                    else
                    {
                        // chiama richiesta chiusura x utente
                        TryAskCloseCurrODL();
                        // resetta contatore x nuova richeista finale eventuale...
                        currTravelStateStart = adesso;
                    }
                }
            }
            else
            {
                if (canChange)
                {
                    // altrimenti forzo a "Idle" state
                    currTravelState = travelState.Idle;
                    currTravelStateStart = adesso;
                    // trace opzionale
                    lgTrace($"TRAVEL STATE: {currTravelState}");
                }
            }
        }

        #endregion Protected Methods

        #region Private Fields

        /// <summary>
        /// abilitata gestione ODL da impianto
        /// </summary>
        private bool doProcOdl = false;

        #endregion Private Fields

        #region Private Methods

        /// <summary>
        /// Azioni specifiche x indicare fine lotto di produzione
        /// </summary>
        /// <returns></returns>
        private bool setFineLotto()
        {
            bool answ = false;
            return answ;
        }

        /// <summary>
        /// Azioni specifiche x iniziare produzione (impostazione ricetta)
        /// </summary>
        /// <returns></returns>
        private bool setInizioProd()
        {
            bool answ = false;
            return answ;
        }

        #endregion Private Methods
    }
}