using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.Remoting.Contexts;
using System.Threading.Tasks;
using System.Xml.Serialization;
using static IOB_UT_NEXT.CustomObj;
using static IOB_UT_NEXT.DataModel.UstdData;

namespace IOB_WIN_WS.IobWs
{
    /// <summary>
    /// Adapter specializzato per Taglierine EMMEGI (es Colcom) con le chiamate tramite RestSharp (XML/Soap) al FPWorkshop in ritorno+ invio tramite file CSV al sw Job
    /// </summary>
    public class EmmegiFPW : RestBase
    {
        #region Public Constructors

        /// <summary>
        /// Costruttore dell'IOB SOAP della bilancia EmmegiFPW
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public EmmegiFPW(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            lgInfo($"Richiesto Adapter IobRest.EmmegiFPW con i parametri seguenti | ADDR: {IOBConfFull.Device.Connect.IpAddr} | PORT: {IOBConfFull.Device.Connect.Port}");
            lastPING = DateTime.Now.AddHours(-1);
            redKeyAlarm = redisMan.redHash($"IOB:Status:{IOBConfFull.General.CodIOB}:Alarm:LastRead");
            lastPING = DateTime.Now.AddHours(-1);
            // predispongo configurazione specifica Rest...
            lgInfo("01 - Rest");
            if (!string.IsNullOrEmpty(getOptPar("REST_CONF")))
            {
                setupRestConf(getOptPar("REST_CONF"));
            }

            // sistemo chiavi specifiche redis...
            rKeyConf = $"{redisMan.redIobKey}:Conf:ParamsUSTD";
            rKeyMD5 = $"{redisMan.redIobKey}:FPW:FILE-MD5";
            rKeyJob = $"{redisMan.redIobKey}:JOB";
            lgInfo("02 - Redis");

            // setup variabili custom... file cont in tools
            string FileConfPath = IOBConfFull.OptParGet("FileImportConf");
            if (!string.IsNullOrEmpty(FileConfPath))
            {
                // fix exclToolDirPath
                exclToolDirPath = Path.Combine(fileMover.GetExecutingDirectoryName(), "Tools");
                string fullPath = Path.Combine(exclToolDirPath, FileConfPath);
                if (File.Exists(fullPath))
                {
                    string rawData = File.ReadAllText(fullPath);
                    // carico conf...
                    ExtToolConf ConfigFile = JsonConvert.DeserializeObject<ExtToolConf>(rawData) ?? new ExtToolConf();
                    basePath = ConfigFile.FileOutPath;
                    archivePath = ConfigFile.ArchiveDir;
                    if (!Directory.Exists(archivePath))
                    {
                        Directory.CreateDirectory(archivePath);
                    }

                    lgInfo($"exclToolDirPath: {exclToolDirPath} | fullPath: {fullPath} | basePath: {basePath} | archivePath: {archivePath}");
                    lgInfo("03 - Conf");
                }
            }

            // faccio un fileImport preliminare...
            try
            {
                // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
                Task.Run(async () =>
                {
                    await ProcessFileImportAsync();
                })
                .GetAwaiter()
                .GetResult();
            }
            catch (Exception ex)
            {
                lgError($"Errore in ProcessFileImportAsync{Environment.NewLine}{ex.Message}");
            }

            if (EnableTest)
            {
                PrelimRestTest();
                lgInfo("04 - Test");
            }
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Implementazione custom esecuzione task specifici
        /// </summary>
        /// <param name="task2exe"></param>
        /// <returns></returns>
        public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
        {
            /*---------------------------------------
             * gestione execute task SPECIFICI:
             * - setArt
             * - setComm
             * - setPzComm
             *---------------------------------------*/

            // Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti...
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            if (task2exe != null)
            {
                lgTrace($"executeTasks: richiesta esecuzione {task2exe.Count} task");
                // controllo se memMap != null...
                if (memMap != null)
                {
                    bool taskOk = false;
                    string taskVal = "";
                    // cerco task specifici: qui sono NON standard...
                    foreach (var item in task2exe)
                    {
                        lgInfo($"TASK | {item.Key} --> {item.Value}");
                        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.setComm:
                            case taskType.setPzComm:
                                lgInfo($"Task: {item.Key} | {item.Value}");
                                // salvo valori in memoria prod data
                                upsertKey(item.Key, item.Value);
                                taskVal = $"SET task: {item.Key} --> {item.Value}";
                                // invio una chiamata al sistema con i valori attuali
                                taskOk = SetJobDataEmmegiFPW();
                                break;

                            case taskType.setParameter:
                                // richiedo da URL i parametri WRITE da popolare
                                lgInfo("Chiamata setParameter --> processMemWriteRequests");
                                taskVal = processMemWriteRequests();
                                // se restituiscce "" faccio altra prova...
                                if (string.IsNullOrEmpty(taskVal))
                                {
                                    // i parametri me li aspetto come stringa composta paramName|paramvalue
                                    if (item.Value.Contains("|"))
                                    {
                                        string[] paramsJob = item.Value.Split('|');
                                        taskVal = $"REQUEST SET PARAMETERS: {paramsJob[0]} --> {paramsJob[1]}";
                                    }
                                    else
                                    {
                                        taskVal = $"WRONG REQUEST FOR SET PARAMETERS: {item.Value} doesnt contain pipe for splitting key/value";
                                    }
                                }
                                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!
                        taskDone.Add(item.Key, taskVal);
                    }
                }
                else
                {
                    lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe!");
                }
            }
            return taskDone;
        }

        /// <summary>
        /// Recupera dizionario allarmi (SE PRESENTE)
        /// </summary>
        /// <returns></returns>
        public override Dictionary<string, string> getAlarmData()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            return outVal;
        }

        /// <summary>
        /// Recupero dati DYN, impiegando i valori della LUT...
        /// </summary>
        /// <returns></returns>
        public override Dictionary<string, string> getDynData()
        {
            var answ = RestDataLUT;
            return answ;
        }

        /// <summary>
        /// Recupera il progName in modalità custom...
        /// </summary>
        /// <returns></returns>
        public override string getPrgName()
        {
            return RestLutGet("pName");
        }

        /// <summary>
        /// Override connessione
        /// </summary>
        public override void tryConnect()
        {
            if (!connectionOk)
            {
                // controllo che il ping sia stato tentato almeno pingTestSec fa...
                if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec"))
                {
                    if (verboseLog || periodicLog)
                    {
                        lgInfo("Rest: 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;

                            // chiamo metodo connect
                            var rawData = ExecuteCallGet(GetUrlResource("GetStatus"), true);
                            lgInfo($"GetConnection | {rawData} | EmmegiFPW.tryConnect");

                            if (!string.IsNullOrEmpty(rawData))
                            {
                                connectionOk = true;
                                queueInEnabCurr = true;
                                // salvo lo status con i relativi contenuti...
                                SaveCurrStatus(rawData);
                                if (adpRunning)
                                {
                                    lgInfo("Connessione EmmegiFPW OK");
                                }
                            }
                            else
                            {
                                lgError($"Errore check connessione EmmegiFPW | rawData: {rawData}");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgFatal($"Errore nella connessione all'Adapter IobRest.Base: {szStatusConnection}{Environment.NewLine}{exc}");
                            connectionOk = false;
                            lgInfo($"Eccezione in TryConnect, Adapter IobRest.Base 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: Rest controllo PING fallito per IP {IOBConfFull.Device.Connect.PingIpAddr}");
                        }
                    }
                }
            }
            else
            {
                needRefresh = true;
            }
        }

        #endregion Public Methods

        #region Public Classes

        /// <summary>
        /// Struttura delle risposte alle chiamare REST specifiche
        /// </summary>
        public class EmmegiResp
        {
            #region Public Enums

            public enum MachineStatus
            {
                // manuale
                ON,

                // attrezzaggio = zero assi
                SETUP,

                // pronta, in automatico, in attesa carico pezzi
                READY,

                // posizionamento (teste morse o cambio utensili)
                POSITIONING,

                // attesa (carico/scarico barra, operazioni utente)
                WAIT,

                // in lavorazione effettiva
                WORKING,

                // emergenza
                EMERGENCY,

                // in allarme
                ALARM,

                // offline (poweroff?)
                OFF_LINE,

                OFF,
                ERROR
            }

            #endregion Public Enums

            #region Public Classes

            [XmlRoot("result_workstation_status")]
            public class StatusDTO
            {
                #region Public Properties

                [XmlElement("workstation")]
                public Workstation Workstation { get; set; }

                #endregion Public Properties
            }

            /// <summary>
            /// Obj workstation
            /// </summary>
            public class Workstation
            {
                #region Public Properties

                [XmlElement("id")]
                public WorkstationId Id { get; set; }

                [XmlElement("job_id")]
                public string JobId { get; set; }

                [XmlElement("operations")]
                public int Operations { get; set; }

                [XmlElement("operator")]
                public string Operator { get; set; }

                [XmlElement("status")]
                public MachineStatus Status { get; set; }

                [XmlIgnore]
                public DateTime StatusTimestamp
                {
                    get => DateTime.ParseExact(StatusTimestampRaw, "yyyy-MM-dd HH:mm:ss", null);
                }

                [XmlElement("status_timestamp")]
                public string StatusTimestampRaw { get; set; }

                [XmlElement("tot_operations")]
                public int TotalOperations { get; set; }

                #endregion Public Properties
            }

            /// <summary>
            /// Obj WorkstationId
            /// </summary>
            public class WorkstationId
            {
                #region Public Properties

                [XmlAttribute("id")]
                public string IdValue { get; set; }

                #endregion Public Properties
            }

            #endregion Public Classes

            #region Internal Classes

            /// <summary>
            /// Struttura info JobData da inviare x setup
            /// </summary>
            internal class EmmegiFPWJobData
            {
                #region Public Properties

                public string itemcode { get; set; } = "";
                public string itemdescription { get; set; } = "";
                public string ordercode { get; set; } = "";
                public int piecestodo { get; set; } = 0;
                public string token { get; set; } = "";

                #endregion Public Properties
            }

            #endregion Internal Classes
        }

        #endregion Public Classes

        #region Internal Methods

        /// <summary>
        /// Converte valore raw stato impianto nel formato specifico
        /// </summary>
        /// <returns></returns>
        internal EmmegiResp.StatusDTO GetStatus(string xmlRaw)
        {
            EmmegiResp.StatusDTO answ = new EmmegiResp.StatusDTO();
            // deserializzo
            var serializer = new XmlSerializer(typeof(EmmegiResp.StatusDTO));
            using (var reader = new StringReader(xmlRaw))
            {
                answ = (EmmegiResp.StatusDTO)serializer.Deserialize(reader);
            }
            return answ;
        }

        #endregion Internal Methods

        #region Protected Methods

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO
        /// </summary>
        protected override void decodeToBaseBitmap(ref newDisplayData currDispData)
        {
            // init a zero...
            B_input = 0;
            if (queueInEnabCurr)
            {
                /* -----------------------------------------------------
                * bitmap MAPO STD 60
                * B0: POWER_ON
                * B1: RUN
                * B2: pzCount
                * B3: allarme
                * B4: manuale
                * B5: allarme TCiclo (SLOW)
                * B6: WarmUp_CoolDown
                * B7: EmergArmed (1 = NON emergenza, 0 = emergenza)
                ----------------------------------------------------- */

                // per prima cosa controllo ping e se sia connesso...

                if (connectionOk)
                {
                    B_input = 1;
                    currDispData.semIn = Semaforo.SV;

                    // controllo status...
                    switch (currState)
                    {
                        case EmmegiResp.MachineStatus.ON:
                        case EmmegiResp.MachineStatus.SETUP:
                        case EmmegiResp.MachineStatus.WAIT:
                            B_input += (1 << 4);
                            break;

                        case EmmegiResp.MachineStatus.WORKING:
                            B_input += (1 << 1);
                            break;

                        case EmmegiResp.MachineStatus.EMERGENCY:
                        case EmmegiResp.MachineStatus.ALARM:
                        case EmmegiResp.MachineStatus.ERROR:
                            B_input += (1 << 3);
                            break;

                        case EmmegiResp.MachineStatus.OFF_LINE:
                        case EmmegiResp.MachineStatus.OFF:
                            B_input = 0;
                            break;

                        case EmmegiResp.MachineStatus.READY:
                        case EmmegiResp.MachineStatus.POSITIONING:
                        default:
                            B_input = 1;
                            break;
                    }

                    // accodo NON emergenza ... poi da cercare meglio...
                    B_input += (1 << 7);
                }
                else
                {
                    B_input = 0;
                    currDispData.semIn = Semaforo.SR;
                    lgTrace($"EmmegiFPW.decodeToBaseBitmap | connectionOk: {connectionOk}");
                }
            }
            else
            {
                lgDebug($"[VETO queueInEnabCurr] | veto attivo alle {DateTime.Now:yyyy.MM.dd HH:mm:ss}");
            }
        }

        /// <summary>
        /// Metodo da overridare x scrivere DAVVERO i parametri sul PLC
        /// </summary>
        /// <param name="updatedPar"></param>
        protected override void plcWriteParams(ref List<objItem> updatedPar)
        {
            lgTrace($"plcWriteParams: richiesta per {updatedPar.Count} params");
            foreach (var item in updatedPar)
            {
                lgInfo($"ITEM | {item.uid} | {item.value}");
                // salvo i valori ricevuti
                upsertKey(item.uid, item.reqValue);
                // resetto richiesta!
                item.reqValue = "";
                // chiamo scrittura commessa!
                var taskOk = SetJobDataEmmegiFPW();
            }
        }

        /// <summary>
        /// Effettua eventuale file import del file excel come array json in REDIS
        /// </summary>
        /// <returns></returns>
        protected override async Task<bool> ProcessFileImportAsync()
        {
            bool answ = false;

            /* ------------------------------------------
             * esecuzione cablata, si potrebbe costruire una cosa più flessibile tramite conf,
             * la procedura prevede:
             * - controllo MD5 file sorgente *.xlsx
             * - verifica MD5 precedente
             * - verifica in redis del dict seralizzato
             *
             * in caso di mancanza REDIS o variazione MD5 --> lancio script import dati col relativo file di conf
             * ------------------------------------------*/

            // init condizioni check
            bool cachePresent = false;
            bool changedFile = true;
            string newMd5 = "";
            string oldMd5 = "";
            string rawData = "";

            // cerco il file da controllare
            string FileInPath = IOBConfFull.OptParGet("FileInPath");
            if (!string.IsNullOrEmpty(FileInPath))
            {
                if (File.Exists(FileInPath))
                {
                    // verifico presenza in cache REDIS
                    rawData = redisMan.getRSV(rKeyConf);
                    cachePresent = !string.IsNullOrEmpty(rawData) && rawData.Length > 2;
                    // calcolo MD5
                    newMd5 = baseUtils.GetFileMd5(FileInPath);
                    // verifico MD5 precedente
                    oldMd5 = redisMan.getRSV(rKeyMD5);
                    changedFile = string.IsNullOrEmpty(oldMd5) || !newMd5.Equals(oldMd5);
                    // se c'è una condizione di trigger effettuo import chiamando task esterno + conf file
                    if (changedFile || !cachePresent)
                    {
                        // recupero path file di conf x import
                        string FileConfPath = IOBConfFull.OptParGet("FileImportConf");
                        if (!string.IsNullOrEmpty(FileConfPath))
                        {
                            // avvio proc import tramite exe tool esterno...
                            FileProcMan fpm = new FileProcMan(exclToolDirPath, "ExcImport.exe", rKeyConf, FileConfPath);
                            lgInfo($"ProcessFileImportAsync | FileConfPath: {FileConfPath} | exclToolDirPath: {exclToolDirPath} | FileInPath: {FileInPath}");
                            fpm.doProcess(FileInPath, 0);
                            answ = true;
                            // aggiorno md5...
                            redisMan.setRSV(rKeyMD5, newMd5);
                            lgInfo($"Eseguito caricamento MD5 e DB conf ricette FPW");
                        }
                    }
                }
            }
            // eseguo un SetJobDataEmmegiFPW ogni 60 sec...
            if (DateTime.Now.Subtract(lastSetJob).TotalSeconds > 60)
            {
                await Task.Delay(1);
                SetJobDataEmmegiFPW();
                lastSetJob = DateTime.Now;
            }
            return answ;
        }

        /// <summary>
        /// Esegue lettura dati + salvataggio in LUT
        /// </summary>
        protected override void refreshData()
        {
            DateTime adesso = DateTime.Now;
            // controllo se sia passato il samplePeriod minimo...
            if (adesso.Subtract(lastRestRefreshData.AddMilliseconds(samplePeriod)).TotalSeconds > 0)
            {
                lastRestRefreshData = adesso;
                // in primis testo che sia connessa...
                var rawData = ExecuteCallGet(GetUrlResource("GetStatus"), true);
                lgTrace($"refreshData.GetStatus | {rawData}");

                // proseguo SOLO SE macchina OK, altrimenti disconnetto...
                if (!string.IsNullOrEmpty(rawData))
                {
                    // salvo i dati status aggiornati
                    SaveCurrStatus(rawData);
                    lgInfo($"EmmegiFPW | refreshData | {currStatusDTO.Workstation.StatusTimestamp} | Status: {currStatusDTO.Workstation.Status} | TotCount {currStatusDTO.Workstation.TotalOperations}");
                }
                else
                {
                    // verifico se devo disconnettere
                    if (connErrCur >= connErrMax)
                    {
                        connErrCur = 0;
                        tryDisconnect();
                    }
                    else
                    {
                        connErrCur++;
                    }
                }
            }
        }

        #endregion Protected Methods

        #region Private Fields

        /// <summary>
        ///  folder archivio (relativa a basePath)
        /// </summary>
        private string archivePath = "";

        // path base scambio dati
        private string basePath = "";

        /// <summary>
        /// Dizionario conf parametri taglio x taglierina Emmegi
        /// </summary>
        private Dictionary<string, CutterParam> DictParamsFPW = new Dictionary<string, CutterParam>();

        private DateTime lastRestOtherCount = DateTime.Now;
        private DateTime lastRestProcPzCount = DateTime.Now;
        private DateTime lastRestRefreshData = DateTime.Now;
        private DateTime lastSetJob = DateTime.Now.AddHours(-1);

        /// <summary>
        /// chiave salvataggio conf USTD
        /// </summary>
        private string rKeyConf = "";

        /// <summary>
        ///
        /// </summary>
        private string rKeyJob = "";

        /// <summary>
        ///  chiave check file config md5
        /// </summary>
        private string rKeyMD5 = "";

        #endregion Private Fields

        #region Private Properties

        private EmmegiResp.MachineStatus currState
        {
            get => currStatusDTO.Workstation.Status;
        }

        private EmmegiResp.StatusDTO currStatusDTO { get; set; } = new EmmegiResp.StatusDTO();

        /// <summary>
        /// CHiave redis di salvataggio ultimo set di allarmi scaricato
        /// </summary>
        private string redKeyAlarm { get; set; } = "";

        #endregion Private Properties

        #region Private Methods

        /// <summary>
        /// Test preliminare comunicazione Rest EmmegiFPW
        /// </summary>
        private void PrelimRestTest()
        {
            lgInfo("Rest - 03");
            // test folder salvataggio log
            var appPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
            var fPath = Directory.GetParent(appPath).FullName;
            if (!Directory.Exists(basePath))
            {
                Directory.CreateDirectory(basePath);
            }
            lgInfo("Rest - 04");

            string rawData = TestRawCall("Get?qname=workstation_status&param_1=C129696&complete=true", true);
            lgInfo($"RawData:{Environment.NewLine}{rawData}");
            // deserializzo
            var cStatus = GetStatus(rawData);
            string output = $"Dt: {cStatus.Workstation.StatusTimestamp} | WrkId: {cStatus.Workstation.Id.IdValue} | Operatore: {cStatus.Workstation.Operator} | Job: {cStatus.Workstation.JobId} | Status: {cStatus.Workstation.Status} | # Tagli Job: {cStatus.Workstation.Operations} | # Tot Tagli: {cStatus.Workstation.TotalOperations}";

            lgInfo($"Status:{Environment.NewLine}{output}");
        }

        /// <summary>
        /// Effettua rilettura DB conf ricette FPW SEMPRE da REDIS
        /// </summary>
        private void ReloadConfDb()
        {
            // verifico presenza in cache REDIS
            string rawData = redisMan.getRSV(rKeyConf);
            if (!string.IsNullOrEmpty(rawData) && rawData.Length > 2)
            {
                // deserializzo!
                try
                {
                    DictParamsFPW = JsonConvert.DeserializeObject<Dictionary<string, CutterParam>>(rawData);
                }
                catch (Exception exc)
                {
                    lgError($"Eccezione in reloadConfDb{Environment.NewLine}{exc}");
                }
            }
        }

        /// <summary>
        /// Riceve un valore raw xml dello status e salva in LUT tutti i valori...
        /// </summary>
        /// <param name="rawData"></param>
        private void SaveCurrStatus(string rawData)
        {
            if (!string.IsNullOrEmpty(rawData))
            {
                try
                {
                    // deserializzo
                    currStatusDTO = GetStatus(rawData);
                    RestLutUpsert("serial", currStatusDTO.Workstation.Id.IdValue);
                    RestLutUpsert("currPOdl", currStatusDTO.Workstation.JobId);
                    RestLutUpsert("operator", currStatusDTO.Workstation.Operator);
                    RestLutUpsert("status", currStatusDTO.Workstation.Status);
                    RestLutUpsert("currCount", currStatusDTO.Workstation.Operations);
                    RestLutUpsert("totCount", currStatusDTO.Workstation.TotalOperations);
                    RestLutUpsert("lastStatCh", currStatusDTO.Workstation.StatusTimestamp);

                    // verifico JobID x avvio/chiusura PODL + archiviazione files...
                    if (!string.IsNullOrEmpty(currStatusDTO.Workstation.JobId))
                    {
                        // verifico se cambiato...
                        string lastJobId = getCurrProdData("currJobId", "");
                        //var lastJobId = getCurrProdData("currPODL", "");
                        if (!lastJobId.Equals(currStatusDTO.Workstation.JobId))
                        {
                            startNewJob(lastJobId, currStatusDTO.Workstation.JobId);
                            // --> salvo come valore corrente...
                            upsertKey("currJobId", currStatusDTO.Workstation.JobId);
                        }
                    }
                }
                catch { }
            }
        }

        /// <summary>
        /// Effettua scrittura degli ODL aperti...
        /// </summary>
        /// <returns></returns>
        private bool SetJobDataEmmegiFPW()
        {
            lgTrace("Inizio SetJobDataEmmegiFPW");
            bool answ = false;
            // conf path e file
            //string basePath = @"x:\";
            string srcPath = Path.Combine(exclToolDirPath, "TEMPLATE_ODL_USDT.csv");

            // esegue rilettura DictDB parametri...
            ReloadConfDb();

            // ricerca file csv preesistenti, se fossero stati creati oltre xx Ore prima li considera scaduti
            List<string> listFiles = Directory.GetFiles(basePath, "*.csv").ToList();

            // recupero elenco PODL corrente...
            List<PODLModel> reqPOdlList = MachineNextPodl();

            string rawData = File.ReadAllText(srcPath);

            foreach (var item in reqPOdlList)
            {
                // verifico che NON sia già presente... FARE controllo con età file (max 60 min?)
                if (listFiles.Where(x => x.Contains(item.KeyBCode)).Count() > 0)
                {
                    lgInfo($"Salto file preesistente: {item.KeyBCode}");
                }
                else
                {
                    // scrivo x ogni PODL richiesto un file di processing...
                    string codPOdl = $"PODL{item.IdxPromessa:00000000}";
                    string codExt = item.KeyBCode;
                    // salvo associazione OPR/PODL (e viceversa) x impiego successivo
                    redisMan.setRSV($"{rKeyJob}:PODL:{codPOdl}", codExt);
                    redisMan.setRSV($"{rKeyJob}:OPR:{codExt}", codPOdl);

                    string fileName = Path.Combine(basePath, $"{codExt}.csv");
                    string fileCont = rawData;
                    // cerco item nel DB...
                    if (DictParamsFPW.ContainsKey(item.CodArticolo))
                    {
                        var art = DictParamsFPW[item.CodArticolo];
                        if (art != null)
                        {
                            // effettuo sostituzioni...
                            fileCont = fileCont.Replace("[[ODL]]", codPOdl);
                            fileCont = fileCont.Replace("[[SERIE]]", art.Serie);
                            fileCont = fileCont.Replace("[[CODICE]]", art.Codice);
                            fileCont = fileCont.Replace("[[ALTEZZA]]", $"{art.Altezza.ToString("N1", NumberFormatInfo.InvariantInfo)}");
                            fileCont = fileCont.Replace("[[QUOTA_USCITA]]", $"{art.QuotaUscita.ToString("N1", NumberFormatInfo.InvariantInfo)}");
                            fileCont = fileCont.Replace("[[CODICE_LUNGH]]", $"{art.Codice}_{(art.LungPezzo * 10).ToString("000", NumberFormatInfo.InvariantInfo)}");
                            fileCont = fileCont.Replace("[[QTA]]", $"{item.NumPezzi}");
                            fileCont = fileCont.Replace("[[LUNGH]]", $"{art.LungPezzo.ToString("N1", NumberFormatInfo.InvariantInfo)}");
                            fileCont = fileCont.Replace("[[CLIENTE]]", "COLCOM");

                            // scrivo file...
                            File.WriteAllText(fileName, fileCont);

                            lgTrace($"Generato il file di lavoro: {codExt}.csv per PODL {codPOdl} |fname: {fileName}");
                        }
                    }
                }
                // segno fatto
                answ = true;
            }
            return answ;
        }

        /// <summary>
        /// Esegue setup nuovo Job:
        /// - chiusura ODL precedente
        /// - avvio PODL
        /// - archiviazione file OPR*.csv
        /// - TTL cache redis in disabilitazione
        /// </summary>
        /// <param name="jobIdOld">Vecchio JobId</param>
        /// <param name="jobIdNew">Nuovo JobId</param>
        private void startNewJob(string jobIdOld, string jobIdNew)
        {
            // JobId = OPRxx-yyyyy
            lgInfo($"Richiesta startNewJob: {jobIdOld} --> {jobIdNew}");

            // recupero PODL da chiudere da OPR...
            string codPODL = redisMan.getRSV($"{rKeyJob}:OPR:{jobIdOld}");
            int idxPODL = 0;
            int.TryParse(codPODL.Replace("PODL", ""), out idxPODL);
            //... e lo chiudo
            if (idxPODL > 0)
            {
                // chiudo
                SendClosePOdl(idxPODL, DateTime.Now);
                // svuoto cache REDIS con adeguato TTL a 3 gg...
                redisMan.setRSV($"{rKeyJob}:PODL:{codPODL}", jobIdOld, 3600 * 24 * 3);
                redisMan.setRSV($"{rKeyJob}:OPR:{jobIdOld}", codPODL, 3600 * 24 * 3);
            }

            // recupero dati nuovo PODL
            string newPODL = redisMan.getRSV($"{rKeyJob}:OPR:{jobIdNew}");
            int idxPODLNew = 0;
            int.TryParse(newPODL.Replace("PODL", ""), out idxPODLNew);
            //... e lo chiudo
            if (idxPODLNew > 0)
            {
                // avvio PODL nuovo chiudendo eventuali vecchio ODL
                SendStartPodl(idxPODLNew, DateTime.Now);
                // svuoto cache REDIS con adeguato TTL a 3 gg...
                redisMan.setRSV($"{rKeyJob}:PODL:{newPODL}", jobIdNew, 3600 * 24 * 3);
                redisMan.setRSV($"{rKeyJob}:OPR:{jobIdNew}", newPODL, 3600 * 24 * 3);
            }

            // archivio file csv x anno...
            string filePath = Path.Combine(basePath, $"{jobIdOld}.csv");
            if (File.Exists(filePath))
            {
                // verifico archive dir dell'anno/mese corrente...
                DateTime oggi = DateTime.Today;
                string monthDir = Path.Combine(archivePath, $"{oggi.Year:yyyy}", $"{oggi.Month:MM}");
                if (!Directory.Exists(monthDir))
                {
                    Directory.CreateDirectory(monthDir);
                }
                // sposto!
                string filePathDest = Path.Combine(monthDir, $"{jobIdOld}.csv");
                File.Move(filePath, filePathDest);
            }
        }

        /// <summary>
        /// Test Chiamata EmmegiFPW
        /// </summary>
        /// <param name="urlReq"></param>
        /// <returns></returns>
        private string TestRawCall(string urlReq, bool fastCall = false)
        {
            lgInfo($"TestRawCall: {urlReq}");
            string answ = ExecuteCallGet(urlReq, fastCall);
            lgInfo($"TestRawCall resp: {answ}");
            return answ;
        }

        #endregion Private Methods
    }
}