using EgwProxy.Ftp;
using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;

namespace IOB_WIN_FTP.Iob
{
    /// <summary>
    /// Classe gestione sync via FTP
    /// </summary>
    public class Ftp : Iob.GenericNext
    {
        #region Public Constructors

        /// <summary>
        /// Estende l'init della classe base, impiegando il pacchetto EgwCoreLib.Ftp
        /// - gestione dei task da svolgere da configurazione json specifica
        /// - specializzazione da conf e non da codice
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public Ftp(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            lgInfo("Init IobFtp Client");
            // imposto
            B_input = 0;
            // init datetime counters
            DateTime adesso = DateTime.Now;
            lastPzCountSend = adesso;
            lastWarnODL = adesso;
            vetoCheckStatus = adesso;
            // 2023.09.05 imposto anche primo ping e check disconnected...
            lastPING = adesso;
            lastDisconnCheck = adesso;
            var VETO_PING_SEC = getOptPar("VETO_PING_SEC");
            if (!string.IsNullOrEmpty(VETO_PING_SEC))
            {
                int.TryParse(VETO_PING_SEC, out vetoPingSec);
            }
            var VETO_CHECKDIR_SEC = getOptPar("VETO_CHECKDIR_SEC");
            if (!string.IsNullOrEmpty(VETO_CHECKDIR_SEC))
            {
                int.TryParse(VETO_CHECKDIR_SEC, out vetoCheckDirSec);
            }
            var POWEROFF_TIMEOUT_SEC = getOptPar("POWEROFF_TIMEOUT_SEC");
            if (!string.IsNullOrEmpty(POWEROFF_TIMEOUT_SEC))
            {
                int.TryParse(POWEROFF_TIMEOUT_SEC, out PoweroffTimeoutSec);
            }
            // carico conf specifica steps FTP
            string ftpConfFile = getOptPar("FTP_PARAM");
            if (!string.IsNullOrEmpty(ftpConfFile))
            {
                loadFtpConfFile(ftpConfFile);
                // mi calcolo ed imposto la ftpClientMan + Remote BaseDir...
                string actKey = "RemoteDir";
                if (currFtpTaskList != null && currFtpTaskList.ListTask.Count > 0)
                {
                    // prendo i task
                    foreach (var fTask in currFtpTaskList.ListTask)
                    {
                        if (string.IsNullOrEmpty(RemoteBaseDir))
                        {
                            foreach (var fAct in fTask.StepsList)
                            {
                                // cerco nei parametri...
                                if (fAct.ParamList.ContainsKey(actKey))
                                {
                                    RemoteBaseDir = fAct.ParamList[actKey];
                                    break;
                                }
                            }
                        }
                        if (!ftpClientMan.IsConfigured)
                        {
                            // setup ftpClientMan!
                            ftpClientMan = new Manager(fTask.ServerAddr, fTask.ConnUser, fTask.ConnPasswd, fTask.RawCert, fTask.SkipCert);
                        }
                    }
                }
            }
            forceMemMap();
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Processo i task richiesti e li elimino dalla coda
        /// </summary>
        /// <param name="task2exe"></param>
        public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
        {
            /*---------------------------------------------------------------------------
            * va creata una folder x ogni ODL (una volta LANCIATO da tablet) APERTO
            * - nella folder scriviamo un file con articolo, qta, commessa
            * - la folder sarà usata x salvare OGNI file necessario e di rilevazione
            * - per farlo si creeano degli ActionStep specifici e vengono poi richiamati...
            *---------------------------------------------------------------------------*/

            // Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti...
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            GenActConf.ActionConfig currAct = new GenActConf.ActionConfig();
            var currActParam = new Dictionary<string, string>();
            string actKey = "RemoteDir";
            string newDir = "";
            bool taskOk = false;
            string taskVal = "";
            string fNameOdl = "ODL_ATTIVO.txt";
            string basePath = System.Windows.Forms.Application.StartupPath;
            string remFile = "";
            string fileContent = "";
            string locFile = "";
            // cerco task specifici: se ho startSetup --> imposto bit DBB701.DBB0.4
            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.setArt:
                    case taskType.setPzComm:
                        // L'articolo ed il numPz li salva nei dati di produzione... SE disponibili
                        // li indicherà nel file ODL ATTIVA...
                        upsertKey(item.Key, item.Value);
                        // salvo nei taskVal il valore acquisito...
                        taskVal = $"Saved {item.Key} = {item.Value} on ProdData";
                        break;

                    case taskType.setComm:
                        /*------------------------------------------------------------------
                         * La commessa è la cartella DI BASE per poter poi procedere con acquisizione dati...
                         * - step 1: check folder
                         * - step 2: creazione folder
                         * - step 3: salva file ODL_attiva
                         * - step 4: salva parametro dell'ODL corrente
                         ------------------------------------------------------------------*/
                        // compongo remDir dai 2 parametri...
                        actKey = "RemoteDir";
                        newDir = $"{RemoteBaseDir}/{item.Value}";
                        currActParam.Add(actKey, newDir);
                        currAct = new GenActConf.ActionConfig()
                        {
                            Id = "01",
                            Description = "Verifica esistenza directory",
                            Action = GenActConf.ActType.CheckDir,
                            ParamList = currActParam
                        };
                        //eseguo step...
                        bool dirOk = doStep(currAct);
                        // se la cartella mancasse
                        if (!dirOk)
                        {
                            // nuovo act x crearla!
                            currAct = new GenActConf.ActionConfig()
                            {
                                Id = "02",
                                Description = "Creazione directory",
                                Action = GenActConf.ActType.CreateDir,
                                ParamList = currActParam
                            };
                            //eseguo step...
                            dirOk = doStep(currAct);
                            if (dirOk)
                            {
                                taskVal = $"DIR Created: {item.Key} --> {item.Value}";
                            }
                        }
                        else
                        {
                            taskVal = $"DIR Already Exists: {item.Key} --> {item.Value}";
                        }
                        // ora creo il file interno...
                        remFile = $"{newDir}/{fNameOdl}";
                        // cerco se disponibili info in prodData x art/pezzi
                        string setArt = getCurrProdData("setArt", "ND");
                        string setPzComm = getCurrProdData("setPzComm", "?");
                        fileContent = $"NEW ODL activated | {item.Value} | ART: {setArt} x {setPzComm} pz | {DateTime.Now}";
                        locFile = Path.Combine(basePath, "temp", fNameOdl);
                        taskOk = SendOdlActFile(locFile, remFile, fileContent);
                        if (taskOk)
                        {
                            taskVal += $"{Environment.NewLine}File uploaded | locFile: {locFile} | remFile: {remFile}";
                        }
                        else
                        {
                            lgError($"Error in startSetup | fileUpload | locFile: {locFile} | remFile: {remFile}");
                        }
                        // salvo in currProd..
                        upsertKey(item.Key, item.Value);

                        // salvo dati scambiati
                        trackExchData(fileContent.LongCount(), 1024);
                        break;

                    case taskType.startSetup:
                    case taskType.stopSetup:
                        //verifico odl x prima cosa...
                        pzCntReload(true);
                        // faccio pulizia preliminare della commessa corrente...
                        bool cleanDone = RemPlaceholder(RemoteBaseDir, fNameOdl);
                        if (cleanDone)
                        {
                            lgInfo($"Cleaned {fNameOdl} during startSetup | remDir: {RemoteBaseDir}");
                        }
                        // verifico di avere già un ODL corrente...
                        if (currIdxODL == 0)
                        {
                            lgTrace($"Manca ODL corrente: non procedo con impostazione start/stop setup");
                        }
                        else
                        {
                            string odlFolder = $"ODL{currIdxODL:00000000}";
                            remFile = $"{RemoteBaseDir}/{odlFolder}/{fNameOdl}";
                            fileContent = item.Value;
                            // scrivo file ultima richiesta setup
                            locFile = Path.Combine(basePath, "temp", fNameOdl);
                            taskOk = SendOdlActFile(locFile, remFile, fileContent);
                            if (taskOk)
                            {
                                taskVal = $"File uploaded: {item.Key} --> {item.Value} | locFile: {locFile} | remFile: {remFile}";
                            }
                            else
                            {
                                lgError($"Error in startSetup | fileUpload | locFile: {locFile} | remFile: {remFile}");
                            }
                        }
                        // salvo dati scambiati
                        trackExchData(fileContent.LongCount(), 1024);

                        break;

                    case taskType.endProd:
                        bool okRemove = RemPlaceholder(RemoteBaseDir, fNameOdl);
                        if (okRemove)
                        {
                            taskVal = $"Cleaned CurrOdlFile: {item.Key} --> {item.Value} | remDir: {RemoteBaseDir} | fName: {fNameOdl}";
                        }
                        else
                        {
                            lgError($"Error in CurrOdlFile | remDir: {RemoteBaseDir} | fName: {fNameOdl}");
                        }
                        break;

                    default:
                        taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
                        lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                        break;
                }
                // aggiungo task SE svolto!
                if (!string.IsNullOrEmpty(taskVal))
                {
                    taskDone.Add(item.Key, taskVal);
                }
            }
            return taskDone;
        }

        public override async Task InitializeAsync()
        {
            // invio conf macchina all'inizio
            await SendMachineConfAsync();
        }

        /// <summary>
        /// Effettua processing CUSTOM x FTP:
        /// - prende ogni conf specifica
        /// - esegue step
        /// - registra eventuali DynData da salvare
        /// </summary>
        public override void processCustomTaskLF()
        {
            lgInfo($"Richiesto processCustomTaskLF");
            // verifico di avere compiti da svolgere...
            if (currFtpTaskList != null && currFtpTaskList.ListTask != null && currFtpTaskList.ListTask.Count > 0)
            {
                foreach (var srvFtp in currFtpTaskList.ListTask)
                {
                    // verifico eventuale veto all'esecuzione...
                    if (ActionEnabled(srvFtp))
                    {
                        // imposto nuovo veto...
                        ActionResetVeto(srvFtp);
                        // ora setup server FTP x item...
                        ftpClientMan = new Manager(srvFtp.ServerAddr, srvFtp.ConnUser, srvFtp.ConnPasswd, srvFtp.RawCert, srvFtp.SkipCert);
                        // test server ok...
                        if (ftpClientMan.ServerOk())
                        {
                            int stepDone = 0;
                            string srvType = ftpClientMan.ServerType();
                            lgTrace($"Connesso a server {srvType} | {srvFtp.ServerAddr} | inizio processing {srvFtp.StepsList.Count} steps");
                            // ciclo tra i vari steps!
                            foreach (var step in srvFtp.StepsList)
                            {
                                bool fatto = doStep(step);
                                stepDone += fatto ? 1 : 0;
                            }
                            lgInfo($"Completata esecuzione steps FTP | {srvFtp.ActionId} | {stepDone}/{srvFtp.StepsList.Count} Done/Req");
                        }
                        else
                        {
                            // FixMe ToDo verificare se necessario...
                            //connectionOk = false;
                            //tryDisconnect();
                            lgError($"Impossibile connettersi al server {srvFtp.ServerAddr}");
                        }
                    }
                    else
                    {
                        lgTrace($"Saltata esecuzione {srvFtp.ActionId} | veto attivo");
                    }
                }
            }
            lastReadPLC = DateTime.Now;
        }

        /// <summary>
        /// Effettua lettura semafori principale <paramref name="currDispData">Parametri da
        /// aggiornare x display in form</paramref>
        /// </summary>
        public override void readSemafori(ref newDisplayData currDispData)
        {
            DateTime adesso = DateTime.Now;
            // procedo solo se ftp è configurato (altrimenti inutile controllare...)ù
            if (ftpClientMan.IsConfigured)
            {
                // salto se fosse attivo il veto ping...
                if (lastPING.AddSeconds(vetoPingSec) < adesso)
                {
                    // lo stato è come ping machine, x ora puntato a IP unico (WiFi?)
                    byte[] MemBlock = new byte[2];
                    try
                    {
                        currDispData.semIn = Semaforo.SV;
                        // in primis salvo data ping comunque...
                        lastPING = adesso;

                        // salvo esito ping
                        bool pingOK = testPingMachine == IPStatus.Success;
                        addTest(pingOK);
                        bool ftpOk = ftpClientMan.ServerOk();
                        // se passa il check ping + folder faccio il resto...
                        if (pingStatusOk() && ftpOk)
                        {
                            // salto se fosse attivo il veto controllo folder...
                            if (lastCheckDir.AddSeconds(vetoCheckDirSec) < adesso)
                            {
                                lastCheckDir = adesso;
                                // verifico SE HO una folder da ODL in currProdData e se è disponibile...
                                string reqFolder = getCurrProdData("setComm", "");
                                if (!string.IsNullOrEmpty(reqFolder))
                                {
                                    // verifico se ci sia...
                                    GenActConf.ActionConfig currAct = new GenActConf.ActionConfig();
                                    var currActParam = new Dictionary<string, string>();
                                    // compongo remDir dai 2 parametri...
                                    currActParam.Add("RemoteDir", $"{RemoteBaseDir}/{reqFolder}");
                                    currAct = new GenActConf.ActionConfig()
                                    {
                                        Id = "01",
                                        Description = "Verifica esistenza directory",
                                        Action = GenActConf.ActType.CheckDir,
                                        ParamList = currActParam
                                    };
                                    //eseguo step...
                                    bool dirOk = doStep(currAct);
                                    if (dirOk)
                                    {
                                        numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0;
                                    }
                                    else
                                    {
                                        lgInfo($"Rilevata mancanza folder ODL: inizio ripristino tramite task setComm");
                                        numErroriCheck++;
                                        // altrimenti eseguo task2exe x crearla...
                                        Dictionary<string, string> task2ExeSetOdl = new Dictionary<string, string>();
                                        task2ExeSetOdl.Add($"setComm", reqFolder);
                                        var res = executeTasks(task2ExeSetOdl, "");
                                    }
                                }
                            }

                            // se supero soglia errori lettura --> disconnetto e resetto
                            if (numErroriCheck > maxErroriCheck)
                            {
                                lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect");

                                numErroriCheck = 0;
                                connectionOk = false;
                                tryDisconnect();
                            }
                            else
                            {
                                connectionOk = true;
                            }

                            lastReadPLC = adesso;
                            lastWatchDog = adesso;
                        }
                        else
                        {
                            connectionOk = false;
                        }

                        if (connectionOk)
                        {
                            B_input = 3;
                            // aggiungo NON emergenza...
                            B_input += (1 << 7);
                        }
                        else
                        {
                            B_input = 0;
                        }
                    }
                    catch
                    {
                        currDispData.semIn = Semaforo.SR;
                    }
                }
            }
        }

        public override void startAdapter(bool resetQueue)
        {
            base.startAdapter(resetQueue);
            // 2023.09.05 imposto anche primo ping e check disconnected...
            DateTime adesso = DateTime.Now;
            lastWatchDog = adesso;
            //lastPING = adesso;
            lastReadPLC = adesso;
            lastDisconnCheck = adesso;
            // faccio un primo check POST ritardo
            tryConnect();
        }

        /// <summary>
        /// Override connessione
        /// </summary>
        public override void tryConnect()
        {
            bool doLog = (verboseLog || periodicLog);
            lgDebug($"FTP: tryConnect step 01 | connectionOk: {connectionOk}");
            if (!connectionOk)
            {
                //// resetto coda...
                //QueuePing = new DataQueue("000", "QueuePing", false);
                // controllo che il ping sia stato tentato almeno pingTestSec fa...
                if (DateTime.Now.Subtract(lastPING).TotalSeconds > vetoPingSec)
                {
                    if (doLog)
                    {
                        lgInfo("FTP: ConnKO - tryConnect");
                    }
                    lgDebug("FTP: tryConnect step 04");

                    lgDebug("FTP: Reset QueuePing");

                    bool pingOK = testPingMachine == IPStatus.Success;
                    addTest(pingOK);

                    // se passa il ping faccio il resto...
                    if (pingStatusOk())
                    {
                        // in primis salvo data ping...
                        lastPING = DateTime.Now;
                        connectionOk = true;
                        queueInEnabCurr = true;
                        lgInfo("FTP - ping OK");
                    }
                    else
                    {
                        // loggo no risposta ping ...
                        lgWarn("FTP - ping KO");
                    }
                }
            }
        }

        /// <summary>
        /// Override disconnessione
        /// </summary>
        public override void tryDisconnect()
        {
            lgInfo("Richiesta disconnessione adapter FTP!");
            connectionOk = false;
            queueInEnabCurr = false;
        }

        #endregion Public Methods

        #region Protected Methods

        /// <summary>
        /// Imposta la memoria PLC in modo forzato (es x oggetti gestiti da FTP come setComm)
        /// </summary>
        protected override void forceMemMap()
        {
            // simulo setup mappa memoria in scrittura...
            memMap = new plcMemMapExt();
            // area READ
            Dictionary<string, dataConfTSVC> ftpMemRead = new Dictionary<string, dataConfTSVC>();
            ftpMemRead.Add("FtpSync", new dataConfTSVC()
            {
                description = "Last FTP sync",
                name = "FtpSync",
                tipoMem = plcDataType.String,
                displOrdinal = 10
            });
            memMap.mMapRead = ftpMemRead;
            // area WRITE
            Dictionary<string, dataConf> ftpMemWrite = new Dictionary<string, dataConf>();
            ftpMemWrite.Add("setComm", new dataConf()
            {
                description = "Commessa",
                name = "setComm",
                tipoMem = plcDataType.String,
                displOrdinal = 1
            });
            ftpMemWrite.Add("setArt", new dataConf()
            {
                description = "Articolo",
                name = "setArt",
                tipoMem = plcDataType.String,
                displOrdinal = 2
            });
            ftpMemWrite.Add("setPzComm", new dataConf()
            {
                description = "Qta Richiesta",
                name = "setPzComm",
                tipoMem = plcDataType.Int,
                displOrdinal = 3
            });
            memMap.mMapWrite = ftpMemWrite;
            // eseguo setup + invio info configurazione...
            setupMemMap();
        }

        #endregion Protected Methods

        #region Private Fields

        /// <summary>
        /// Ultima verifica presenza dir corrente
        /// </summary>
        private DateTime lastCheckDir = DateTime.Now.AddHours(-1);

        private int PoweroffTimeoutSec = 100;

        /// <summary>
        /// Dizionario dei divieti di esecuzione x i vari step
        /// </summary>
        private Dictionary<string, DateTime> StepsVeto = new Dictionary<string, DateTime>();

        /// <summary>
        /// Periodo veto controllo directory FTP corrente
        /// </summary>
        private int vetoCheckDirSec = 5;

        /// <summary>
        /// Veto controllo status x log...
        /// </summary>
        private DateTime vetoCheckStatus = DateTime.Now;

        #endregion Private Fields

        #region Private Properties

        /// <summary>
        /// Oggetto configurazione gestione FTP
        /// </summary>
        private FtpTaskList currFtpTaskList { get; set; } = new FtpTaskList();

        /// <summary>
        /// CLient connessioni FTP
        /// </summary>
        private Manager ftpClientMan { get; set; } = new Manager("", "", "", "", false);

        /// <summary>
        /// Directpry remota di abse
        /// </summary>
        private string RemoteBaseDir { get; set; } = "";

        #endregion Private Properties

        #region Private Methods

        /// <summary>
        /// Verifica se l'azione sia permessa o in stato veto a tempo
        /// </summary>
        /// <param name="currAct"></param>
        /// <returns></returns>
        private bool ActionEnabled(FtpActConf currAct)
        {
            bool enabled = true;
            // se veto presente
            if (StepsVeto.ContainsKey(currAct.ActionId))
            {
                // controllo scadenza
                enabled = StepsVeto[currAct.ActionId] < DateTime.Now;
            }
            return enabled;
        }

        /// <summary>
        /// Imposta veto azione corrente
        /// </summary>
        /// <param name="currAct"></param>
        /// <returns></returns>
        private bool ActionResetVeto(FtpActConf currAct)
        {
            bool fatto = false;
            if (StepsVeto.ContainsKey(currAct.ActionId))
            {
                StepsVeto[currAct.ActionId] = DateTime.Now.AddSeconds(currAct.ReExecVeto);
            }
            else
            {
                StepsVeto.Add(currAct.ActionId, DateTime.Now.AddSeconds(currAct.ReExecVeto));
            }
            return fatto;
        }

        private void addTest(bool pingOk)
        {
            int score = pingOk ? 1 : 0;
            // controllo: se era spenta e risulta ping ok --> reset coda!
            if (B_input == 0 && pingOk)
            {
                B_input = 1;
                QueuePing = new DataQueue(IOBConfFull.General.FilenameIOB, "QueuePing", false, redisMan);
                lgTrace($"QueuePing resetted on addTest");
            }
            QueuePing.Enqueue($"{score}");
            while (QueuePing.Count > maxQueuePing)
            {
                string res = "";
                QueuePing.TryDequeue(out res);
            }
        }

        /// <summary>
        /// Esegue l'azione configurata
        /// </summary>
        /// <param name="step"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private bool doStep(GenActConf.ActionConfig step)
        {
            bool fatto = false;
            string remoteVal = "";
            string localVal = "";
            string sVal = "";
            string actKey = "";
            string actVal = "";
            // faccio switch in base al tipo di azione da eseguire...
            switch (step.Action)
            {
                case GenActConf.ActType.CheckDir:
                    if (step.ParamList != null && step.ParamList.Count > 0)
                    {
                        sw.Restart();
                        remoteVal = step.ParamList["RemoteDir"] ?? "";
                        //verifico dir remota
                        fatto = ftpClientMan.DirExists(remoteVal);
                    }
                    else
                    {
                        lgError("Error: missing parameters!");
                    }
                    sw.Stop();
                    if (fatto)
                    {
                        lgInfo($"Check RemDir: {remoteVal} | {sw.ElapsedMilliseconds:N1} ms");
                    }
                    break;

                case GenActConf.ActType.CheckFile:
                    break;

                case GenActConf.ActType.CreateDir:
                    if (step.ParamList != null && step.ParamList.Count > 0)
                    {
                        sw.Restart();
                        remoteVal = step.ParamList["RemoteDir"] ?? "";
                        //verifico dir remota
                        bool dirExist = ftpClientMan.DirExists(remoteVal);
                        if (dirExist)
                        {
                            lgTrace("Error: Folder already exists!");
                        }
                        else
                        {
                            fatto = ftpClientMan.CreateDir(remoteVal);
                        }
                    }
                    else
                    {
                        lgError("Error: missing parameters!");
                    }
                    sw.Stop();
                    if (fatto)
                    {
                        lgInfo($"Directory {remoteVal} created!| {sw.ElapsedMilliseconds:N1} ms");
                    }
                    break;

                case GenActConf.ActType.DelDir:
                    break;

                case GenActConf.ActType.DelFile:
                    break;

                case GenActConf.ActType.DownloadDir:

                    break;

                case GenActConf.ActType.DownloadFile:
                    break;

                case GenActConf.ActType.GenRandomDir:
                    break;

                case GenActConf.ActType.ListContent:
                    break;

                case GenActConf.ActType.MirrorDirL2R:
                    break;

                case GenActConf.ActType.MirrorDirR2L:
                    // eseguo mirroring directory
                    actKey = "FtpSync";
                    actVal = "SRC --> DEST | Size";
                    long fullSize = 0;
                    if (step.ParamList != null && step.ParamList.Count > 1)
                    {
                        sw.Restart();
                        remoteVal = step.ParamList["RemoteDir"] ?? "";
                        localVal = step.ParamList["LocalDir"] ?? "";
                        // verifico esistenza dir locale...
                        if (!Directory.Exists(localVal))
                        {
                            Directory.CreateDirectory(localVal);
                        }
                        //verifico dir remota
                        var preTest = ftpClientMan.DirExists(remoteVal);
                        if (preTest)
                        {
                            // chiamo metodo MIRROR x calcolare esattamente se ci siano stati
                            // download di sync...
                            var mirResult = ftpClientMan.MirrorRemoteDir(localVal, remoteVal, FluentFTP.FtpFolderSyncMode.Mirror);
                            // ciclo cercando eventuali info da emttere in DynData...
                            foreach (var result in mirResult)
                            {
                                // processo solo se size > 0...
                                if (result.Size > 0 && result.IsDownload && result.IsSuccess && !result.IsSkipped)
                                {
                                    string objSize = MeasureUtils.SizeSuffix(result.Size, 3);
                                    actVal = $"Rem2Loc | {result.Name} | {objSize} | {result.RemotePath}";
                                    sVal = $"{actKey} | {actVal}";
                                    bool sent = accodaFLog(actKey, sVal, qEncodeFLog(actKey, actVal));
                                    if (sent)
                                    {
                                        trackDynData(actKey, actVal);
                                    }
                                }
                                else
                                {
                                    lgTrace($"Skipped sync | {actVal}");
                                }
                                // traccio dimensione
                                fullSize += result.Size;
                            }
                            // risultato sintetico come successi...
                            fatto = mirResult != null && mirResult.Where(x => !x.IsSuccess).Count() == 0;
                            // salvo dati ricevuti
                            trackExchData(fullSize, 1024);

                            if (!fatto)
                            {
                                lgError($"Error: {remoteVal} NOT mirrored!");
                            }
                        }
                        else
                        {
                            lgError($"Dir remota non trovata! RemDir: {remoteVal}");
                        }
                    }
                    else
                    {
                        lgError("Error: missing parameters!");
                    }
                    sw.Stop();
                    if (fatto)
                    {
                        lgInfo($"Mirror Rem2Loc | RemDir: {remoteVal} | {sw.ElapsedMilliseconds:N1} ms");
                    }

                    break;

                case GenActConf.ActType.PingServer:
                    break;

                case GenActConf.ActType.RemoveFileByName:
                    string fName2Del = "";
                    int numRem = 0;
                    if (step.ParamList != null && step.ParamList.Count > 1)
                    {
                        sw.Restart();
                        remoteVal = step.ParamList["RemoteDir"] ?? "";
                        fName2Del = step.ParamList["FileName2Del"] ?? "";
                        //verifico dir remota
                        fatto = ftpClientMan.DirExists(remoteVal);
                        if (fatto)
                        {
                            // recupero elenco di TUTTI i file presenti
                            List<FluentFTP.FtpListItem> resList = ftpClientMan.GetRemoteList(remoteVal, true);
                            List<FluentFTP.FtpListItem> list2del = new List<FluentFTP.FtpListItem>();
                            // cerco tutti i file che indicano ODL attivo e li elimino
                            foreach (var flItem in resList)
                            {
                                if (flItem.Type == FluentFTP.FtpObjectType.File && flItem.Name.EndsWith(fName2Del))
                                {
                                    list2del.Add(flItem);
                                }
                            }
                            // elimino quelli trovati
                            if (list2del.Count > 0)
                            {
                                var fList = list2del.Select(x => x.FullName).ToList();
                                numRem = ftpClientMan.DeleteFileList(fList);
                            }
                        }
                    }
                    else
                    {
                        lgError("Error: missing parameters!");
                    }
                    sw.Stop();
                    if (fatto)
                    {
                        lgInfo($"RemoveFileByName | path: {remoteVal} | name: {fName2Del} | # del: {numRem} | {sw.ElapsedMilliseconds:N1} ms");
                    }

                    break;

                case GenActConf.ActType.UploadDir:
                    break;

                case GenActConf.ActType.UploadFile:
                    if (step.ParamList != null && step.ParamList.Count > 1)
                    {
                        sw.Restart();
                        localVal = step.ParamList["LocalFile"] ?? "";
                        remoteVal = step.ParamList["RemoteFile"] ?? "";

                        //verifico dir remota
                        string remoteDir = remoteVal.Substring(0, remoteVal.LastIndexOf("/"));
                        bool dirExist = ftpClientMan.DirExists(remoteDir);
                        if (!dirExist)
                        {
                            fatto = ftpClientMan.CreateDir(remoteDir);
                        }

                        fatto = ftpClientMan.SendFile(localVal, remoteVal);
                        if (!fatto)
                        {
                            lgError($"Error: {localVal} NOT uploaded!");
                        }
                    }
                    else
                    {
                        lgError("Error: missing parameters!");
                    }
                    sw.Stop();
                    if (fatto)
                    {
                        lgInfo($"Upload File: {remoteVal} | {sw.ElapsedMilliseconds:N1} ms");
                    }
                    break;

                default:
                    break;
            }
            return fatto;
        }

        /// <summary>
        /// Effettuo lettura file di conf
        /// </summary>
        /// <param name="fileName"></param>
        private void loadFtpConfFile(string fileName)
        {
            string jsonFullPath = Path.Combine(System.Windows.Forms.Application.StartupPath, "DATA", "CONF", fileName);
            lgInfo($"Apertura file {jsonFullPath}");
            using (StreamReader reader = new StreamReader(jsonFullPath))
            {
                string jsonData = reader.ReadToEnd().Replace("\n", "").Replace("\r", "");
                if (!string.IsNullOrEmpty(jsonData))
                {
                    lgDebug($"File json composto da {jsonData.Length} caratteri");
                    try
                    {
                        currFtpTaskList = JsonConvert.DeserializeObject<FtpTaskList>(jsonData);
                        lgDebug($"Decodifica aree FtpTaskList: trovati {currFtpTaskList.ListTask.Count} gruppi di task FTP");
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in decodifica conf json FTP:{Environment.NewLine}{exc}");
                    }
                }
                else
                {
                    lgError("Errore in loadFtpConfFile: file json vuoto!");
                }
            }
        }

        /// <summary>
        /// Calcola status ping:
        /// - se ha ‹ 50% coda richiesta --› true
        /// - se ha › 50% coda richiesta --› true se è maggior parte a 1 (true)
        /// </summary>
        /// <returns></returns>
        private bool pingStatusOk()
        {
            bool answ = false;
            long numVal = QueuePing.Count;
            if (numVal > maxQueuePing / 2)
            {
                var listaValori = QueuePing.ToList();
                long numOk = listaValori.Where(x => x == "1").Count();
                long numKo = numVal - numOk;
                answ = numOk >= numKo;
                lgTrace($"PING ok per: {numOk} > {numKo}");
            }
            else
            {
                lgTrace($"PING check: {answ} per mancanza dati minimi test");
            }
            return answ;
        }

        /// <summary>
        /// Rimuove eventuali file placeholder di ODL corrente preesistenti
        /// </summary>
        /// <param name="remDir"></param>
        /// <param name="fNameOdl"></param>
        private bool RemPlaceholder(string remDir, string fNameOdl)
        {
            bool fatto = false;
            try
            {
                Dictionary<string, string> currActParam = new Dictionary<string, string>();
                currActParam.Add("RemoteDir", remDir);
                currActParam.Add("FileName2Del", fNameOdl);
                GenActConf.ActionConfig currAct = new GenActConf.ActionConfig()
                {
                    Id = "01",
                    Description = "Clean Curr ODL Files",
                    Action = GenActConf.ActType.RemoveFileByName,
                    ParamList = currActParam
                };
                // eseguo step...
                fatto = doStep(currAct);
            }
            catch (Exception exc)
            {
                lgError($"Eccezione in RemPlaceholder{Environment.NewLine}{exc}");
            }
            return fatto;
        }

        /// <summary>
        /// Invia il file dell'ODL attivo
        /// </summary>
        /// <param name="locFile"></param>
        /// <param name="remFile"></param>
        /// <param name="fileContent"></param>
        private bool SendOdlActFile(string locFile, string remFile, string fileContent)
        {
            bool taskOk = false;
            Dictionary<string, string> currActParam = new Dictionary<string, string>();
            // creo file locale
            File.WriteAllText(locFile, fileContent);
            // invio file x ODL attivo
            currActParam.Add("LocalFile", locFile);
            currActParam.Add("RemoteFile", remFile);
            GenActConf.ActionConfig currAct = new GenActConf.ActionConfig()
            {
                Id = "01",
                Description = "Upload File",
                Action = GenActConf.ActType.UploadFile,
                ParamList = currActParam
            };
            // eseguo step...
            taskOk = doStep(currAct);
            return taskOk;
        }

        #endregion Private Methods
    }
}