using IOB_UT;
using MapoSDK;
using Newtonsoft.Json;
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows.Forms;

namespace IOB_WIN
{
    public partial class AdapterForm : Form
    {
        #region Protected Fields

        /// <summary>
        /// contatore veloce
        /// </summary>
        protected int fastCount;

        /// <summary>
        /// Data-Ora prima apertura FORM...
        /// </summary>
        protected DateTime firstStart;

        /// <summary>
        /// Oggetto ultimo inviato stato IOB x REDIS
        /// </summary>
        protected IobWinStatus lastIobStatus = new IobWinStatus();

        /// <summary>
        /// Oggetto ultimo inviato stato MP-IO x REDIS
        /// </summary>
        protected ServerMpStatus lastSrvStatus = new ServerMpStatus();

        /// <summary>
        /// ultimo tentativo riavvio...
        /// </summary>
        protected DateTime lastStartTry;

        /// <summary>
        /// contatore normale
        /// </summary>
        protected int normCount;

        /// <summary>
        /// Contatore campionamento memoria
        /// </summary>
        protected int sampleMemCount;

        /// <summary>
        /// contatore lento
        /// </summary>
        protected int slowCount;

        /// <summary>
        /// contatore sync allarmi
        /// </summary>
        protected int verySlowCount;

        /// <summary>
        /// Temnpo attesa std in MS
        /// </summary>
        protected int waitRecMSec = 30000;

        #endregion Protected Fields

        #region Public Fields

        /// <summary>
        /// oggetto logging
        /// </summary>
        public static Logger lg;

        /// <summary>
        /// Modello macchina
        /// </summary>
        public string curModel = "";

        /// <summary>
        /// Vendor macchina
        /// </summary>
        public string curVendor = "";

        /// <summary>
        /// configurazione caricata
        /// </summary>
        public IobConfiguration IOBConf;

        /// <summary>
        /// Oggetto x gestione dell'adapter GENERICO (x poter usare metodi di ognuno...)
        /// </summary>
        public IobGeneric iobObj;

        /// <summary>
        /// tipo di adapter prescelto...
        /// </summary>
        public tipoAdapter tipoScelto = tipoAdapter.SIMULA;

        #endregion Public Fields

        #region Public Constructors

        /// <summary>
        /// Avvio MainForm
        /// </summary>
        /// <param name="codIOB"></param>
        public AdapterForm(string codIOB)
        {
            CurrIOB = codIOB;
            // continuo avvio...
            InitializeComponent();
            myGraphInitForm();

            checkEditMes2Plc();

            // inizializzo orologi
            firstStart = DateTime.Now;
            lastStartTry = DateTime.Now;

            initDatamonitor();

            waitRecMSec = utils.CRI("waitRecMSec");

            LogManager.ReconfigExistingLoggers();

            lg = LogManager.GetCurrentClassLogger();
            displayTaskAndLog("MainForm Starting");

            // se abilitato autoload conf leggo file corretto...
            if (utils.CRB("autoLoadConf"))
            {
                try
                {
                    loadIniFile(defConfFilePath);
                    lgInfo("INI LOADED");
                }
                catch (Exception exc)
                {
                    displayTaskAndLog(string.Format("Eccezione in autoLoadConf: {0}", exc));
                }
            }

            if (IOBConf == null || !utils.CRB("autoLoadConf"))
            {
                // definisco e avvio tipo adapter generico
                tipoScelto = tipoAdapter.ND;
                curVendor = "ACME";
                curModel = "NONE";
                IOBConf = new IobConfiguration();
                loadIobType();
                displayTaskAndLog("Waiting for config file selection");
            }

            // cerco tra i parametri opzionali se ho il parametro di
            int timerIntMs = utils.CRI("timerIntMs");
            if (IOBConf.optPar.ContainsKey("timerIntMs"))
            {
                // recupero
                string rawVal = IOBConf.optPar["timerIntMs"];
                if (!string.IsNullOrEmpty(rawVal))
                {
                    int.TryParse(rawVal, out timerIntMs);
                    lgInfo($"Impostato timerIntMs da IOB.ini: {timerIntMs}");
                }
            }
            // Start timer periodico comunicazione
            gather.Interval = timerIntMs;
            gather.Enabled = true;
            displayTaskAndLog(string.Format("Main timer set: {0}ms", gather.Interval));

            // Start timer periodico interfaccia
            displTimer.Interval = utils.CRI("timerIntMs");
            displTimer.Enabled = true;

            displayTaskAndLog("Program Running");

            // check oggetto not null
            if (iobObj != null)
            {
                try
                {
                    // verifico server online...
                    if (iobObj.checkServerAlive)
                    {
                        // segnalo reboot (programma)...
                        IobGeneric.callUrl(iobObj.urlReboot, true);
                    }
                    else
                    {
                        displayTaskAndLog("AdapterForm: Server OFFLINE");
                    }
                }
                catch (Exception exc)
                {
                    lgError(string.Format("AdapterForm: EXCEPTION in fase di chiamata URL di reboot:{0}{1}{2}", iobObj.urlReboot, Environment.NewLine, exc));
                }
            }
            displayTaskAndLog("Main Form OK");
        }

        #endregion Public Constructors

        #region Private Delegates

        private delegate void SafeCallDelegate(string text);

        #endregion Private Delegates

        #region Protected Properties

        /// <summary>
        /// Valore protected comunicazione PLC
        /// </summary>
        protected bool _commPlcActive { get; set; } = false;

        /// <summary>
        /// Valore protetto stato comunicazione
        /// </summary>
        protected int _commSrvActive { get; set; } = 0;

        /// <summary>
        /// Valore protected semaforo IN
        /// </summary>
        protected Semaforo _sIN { get; set; } = Semaforo.ND;

        /// <summary>
        /// Valore protected semaforo OUT
        /// </summary>
        protected Semaforo _sOUT { get; set; } = Semaforo.ND;

        /// <summary>
        /// Codice IOB della macchina cui connettersi (x scegliere corretto file di conf...)
        /// </summary>
        protected string CurrIOB { get; set; }

        protected int delayShowLogMs { get; set; } = utils.CRI("delayShowLogMs");

        /// <summary>
        /// Dictionary dei divieti del datamonitor
        /// </summary>
        protected Dictionary<int, DateTime> dMonDisplVetoVeto { get; set; } = new Dictionary<int, DateTime>();

        /// <summary>
        /// array degli oggetti datamonitor da mostrare
        /// </summary>
        protected Dictionary<int, string> dMonValues { get; set; } = new Dictionary<int, string>();

        /// <summary>
        /// Veto a NUOVE scritture in COUNTER...
        /// </summary>
        protected DateTime logCounterVeto { get; set; } = DateTime.Now;

        /// <summary>
        /// Stringa corrente di log...
        /// </summary>
        protected string logWatchString { get; set; } = "";

        /// <summary>
        /// Veto a NUOVE scritture in logWatch...
        /// </summary>
        protected DateTime logWatchWriteVeto { get; set; } = DateTime.Now;

        protected int maxAlQueue { get; set; }

        protected int maxEvQueue { get; set; }

        protected int maxFlQueue { get; set; }

        protected int maxMsQueue { get; set; }

        protected int qAlLen { get; set; }

        protected int qEvLen { get; set; }

        protected int qFlLen { get; set; }

        protected int qMsLen { get; set; }

        protected int totQueue
        {
            get
            {
                return qEvLen + qFlLen + qAlLen + qMsLen;
            }
        }

        #endregion Protected Properties

        #region Public Properties

        public int alQueueLen
        {
            set
            {
                qAlLen = value;
                lblQueueAlarmLen.Text = qAlLen.ToString();
                showQueueData();
                // se supero max precedente, ed è > 10... loggo!
                if (qAlLen > maxAlQueue && qAlLen > 10)
                {
                    maxAlQueue = qAlLen;
                    lgInfo($"[WARN] Coda FLog di {value} record");
                }
                else
                {
                    maxAlQueue--;
                    maxAlQueue = maxAlQueue < qAlLen ? qAlLen : maxAlQueue;
                }
            }
            get
            {
                return qAlLen;
            }
        }

        /// <summary>
        /// Visualizzazione stato di comunicazione attiva con PLC
        /// </summary>
        public bool commPlcActive
        {
            get
            {
                return _commPlcActive;
            }
            set
            {
                _commPlcActive = value;
                // se true --> comunica/verde, altrimenti grigio
                lblCNC.ForeColor = value ? Color.SeaGreen : Color.Black;
                statusStrip1.Refresh();
            }
        }

        /// <summary>
        /// Visualizzazione stato di comunicazione attiva con PLC:
        /// 0 = NO PING
        /// 1 = PING OK
        /// 2 = IOB enabled
        /// </summary>
        public int commSrvActive
        {
            get
            {
                return _commSrvActive;
            }
            set
            {
                _commSrvActive = value;
                switch (value)
                {
                    case 0:
                        lblSrvUrl.ForeColor = Color.Red;
                        break;

                    case 1:
                        lblSrvUrl.ForeColor = Color.OrangeRed;
                        break;

                    case 2:
                        lblSrvUrl.ForeColor = Color.SeaGreen;
                        break;

                    default:
                        break;
                }
                statusStrip1.Refresh();
            }
        }

        public string counterIob
        {
            get
            {
                return lblPzCountIob.Text;
            }
            set
            {
                lblPzCountIob.Text = value;
            }
        }

        public string counterMac
        {
            get
            {
                return lblPzCountMac.Text;
            }
            set
            {
                lblPzCountMac.Text = value;
            }
        }

        /// <summary>
        /// Stringa dati monitoraggio mostrata (1 SX)...
        /// </summary>
        public string dataMonitor_0
        {
            get
            {
                return dMonValues[0];
            }
            set
            {
                DateTime adesso = DateTime.Now;
                // salvo nell'array...
                dMonValues[0] = value;
                if (dMonDisplVetoVeto[0] < adesso)
                {
                    // se scaduto veto --> display!
                    lblRawData.Text = value;
                    // update veto!
                    dMonDisplVetoVeto[0] = adesso.AddMilliseconds(delayShowLogMs);
                }
            }
        }

        /// <summary>
        /// Stringa dati monitoraggio mostrata (1 SX)...
        /// </summary>
        public string dataMonitor_1
        {
            get
            {
                return dMonValues[1];
            }
            set
            {
                DateTime adesso = DateTime.Now;
                // salvo nell'array...
                dMonValues[1] = value;
                if (dMonDisplVetoVeto[1] < adesso)
                {
                    // se scaduto veto --> display!
                    lblOutMessage.Text = value;
                    // update veto!
                    dMonDisplVetoVeto[1] = adesso.AddMilliseconds(delayShowLogMs);
                }
            }
        }

        /// <summary>
        /// Stringa dati monitoraggio mostrata (2 centro)...
        /// </summary>
        public string dataMonitor_2
        {
            get
            {
                return dMonValues[2];
            }
            set
            {
                DateTime adesso = DateTime.Now;
                // salvo nell'array...
                dMonValues[2] = value;
                if (dMonDisplVetoVeto[2] < adesso)
                {
                    // se scaduto veto --> display!
                    lblOutMessage2.Text = value;
                    // update veto!
                    dMonDisplVetoVeto[2] = adesso.AddMilliseconds(delayShowLogMs);
                }
            }
        }

        /// <summary>
        /// Stringa dati monitoraggio mostrata (3 dx)...
        /// </summary>
        public string dataMonitor_3
        {
            get
            {
                return dMonValues[3];
            }
            set
            {
                DateTime adesso = DateTime.Now;
                // salvo nell'array...
                dMonValues[3] = value;
                if (dMonDisplVetoVeto[3] < adesso)
                {
                    // se scaduto veto --> display!
                    lblOutMessage3.Text = value;
                    // update veto!
                    dMonDisplVetoVeto[3] = adesso.AddMilliseconds(delayShowLogMs);
                }
            }
        }

        /// <summary>
        /// label del numero di record processati (libera)
        /// </summary>
        public string dataProcLabel
        {
            get
            {
                return lblDataProc.Text;
            }
            set
            {
                lblDataProc.Text = value;
            }
        }

        /// <summary>
        /// File configurazione default
        /// </summary>
        public string defConfFilePath
        {
            get
            {
                return string.Format(@"{0}\{1}.ini", utils.confDir, CurrIOB);
            }
        }

        public bool enableEditMes2Plc { get; set; }

        public int evQueueLen
        {
            set
            {
                qEvLen = value;
                lblQueueLen.Text = qEvLen.ToString();
                showQueueData();
                // se supero max precedente, ed è > 10... loggo!
                if (qEvLen > maxEvQueue && qEvLen > 10)
                {
                    maxEvQueue = qEvLen;
                    lgInfo($"[WARN] Coda EV di {value} record");
                }
                else
                {
                    maxEvQueue--;
                    maxEvQueue = maxEvQueue < qEvLen ? qEvLen : maxEvQueue;
                }
            }
            get
            {
                return qEvLen;
            }
        }

        public int flQueueLen
        {
            set
            {
                qFlLen = value;
                lblQueueFLogLen.Text = qFlLen.ToString();
                showQueueData();
                // se supero max precedente, ed è > 10... loggo!
                if (qFlLen > maxFlQueue && qFlLen > 10)
                {
                    maxFlQueue = qFlLen;
                    lgInfo($"[WARN] Coda FLog di {value} record");
                }
                else
                {
                    maxFlQueue--;
                    maxFlQueue = maxFlQueue < qFlLen ? qFlLen : maxFlQueue;
                }
            }
            get
            {
                return qFlLen;
            }
        }

        /// <summary>
        /// Log verboso da configurazione (SOLO CHAIVE "verbose"...
        /// </summary>
        public bool isVerboseLog { get; set; } = utils.CRB("verbose");

        /// <summary>
        /// Logwatcher (in modalità "accodamento in testa" ultimi messaggi...)
        /// </summary>
        public string logWatcher
        {
            get
            {
                return lblLogfile.Text;
            }
            set
            {
                try
                {
                    logWatchString = limitLine2show($"{value}{Environment.NewLine}{logWatchString}");
                    DateTime adesso = DateTime.Now;
                    if (logWatchWriteVeto < adesso)
                    {
                        lblLogfile.Text = logWatchString;
                        lblLogfile.Refresh();
                        logWatchWriteVeto = adesso.AddMilliseconds(delayShowLogMs);
                    }
                }
                catch (Exception exc)
                {
                    lgError($"Errore in esecuzione logWatcher{Environment.NewLine}--> {value}");
                    if (isVerboseLog)
                    {
                        lgError($"{exc}");
                    }
                }
            }
        }

        public int msQueueLen
        {
            set
            {
                qMsLen = value;
                lblQueueMessLen.Text = qMsLen.ToString();
                showQueueData();
                // se supero max precedente, ed è > 10... loggo!
                if (qMsLen > maxMsQueue && qMsLen > 10)
                {
                    maxMsQueue = qMsLen;
                    lgInfo($"[WARN] Coda FLog di {value} record");
                }
                else
                {
                    maxMsQueue--;
                    maxMsQueue = maxMsQueue < qMsLen ? qMsLen : maxMsQueue;
                }
            }
            get
            {
                return qMsLen;
            }
        }

        /// <summary>
        /// Numero max linee da mostrare (da controllo)...
        /// </summary>
        public int nLine2show
        {
            get
            {
                int answ = 5;
                try
                {
                    Int32.TryParse(nLines.Text, out answ);
                }
                catch
                { }
                return answ;
            }
            set
            {
                nLines.Text = value.ToString();
            }
        }

        /// <summary>
        /// Imposta COLORE SFONDO x Semaforo IN
        /// </summary>
        public Semaforo sIN
        {
            get
            {
                return _sIN;
            }
            set
            {
                _sIN = value;
                var newColor = decSemaforo(value);
                if (newColor != bIN.BackColor)
                {
                    bIN.BackColor = newColor;
                    bIN.Refresh();
                }
            }
        }

        /// <summary>
        /// Imposta COLORE SFONDO x Semaforo OUT
        /// </summary>
        public Semaforo sOUT
        {
            get
            {
                return _sOUT;
            }
            set
            {
                _sOUT = value;
                var newColor = decSemaforo(value);
                if (newColor != bOUT.BackColor)
                {
                    bOUT.BackColor = newColor;
                    bOUT.Refresh();
                }
            }
        }

        /// <summary>
        /// Task watcher (in modalità "accodamento in testa" ultimi messaggi...)
        /// </summary>
        public string taskWatcher
        {
            get
            {
                return lblTaskLog.Text;
            }
            set
            {
                try
                {
                    lblTaskLog.Text = limitLine2show($"{value}{Environment.NewLine}{lblTaskLog.Text}");
                    lblTaskLog.Refresh();
                }
                catch (Exception exc)
                {
                    lgError($"Errore in esecuzione taskWatcher{Environment.NewLine}--> {value}");
                    if (isVerboseLog)
                    {
                        lgError($"{exc}");
                    }
                }
            }
        }

        #endregion Public Properties

        #region Private Methods

        private void BtnOpenLog_Click(object sender, EventArgs e)
        {
            try
            {
                string logPath = $"{System.AppDomain.CurrentDomain.BaseDirectory}logs\\{CurrIOB}\\{DateTime.Today:yyyy-MM-dd}.log";
                Process.Start(logPath);
            }
            catch (Exception exc)
            {
                lgError($"Eccezione in show log (MAIN):{Environment.NewLine}{exc}");
            }
        }

        private void BtnSendPLC_Click(object sender, EventArgs e)
        {
            // invia a MES il parametro selezionato (es ART / ODL / PRG NAME, come task2exe..)
            Dictionary<string, string> forcedTask = new Dictionary<string, string>();
            // guardo i parametri...
            if (!string.IsNullOrEmpty(txtValue.Text))
            {
                forcedTask.Add(cmbParamValues.SelectedValue.ToString(), txtValue.Text);
            }
            // chiedo esecuzione task!
            iobObj.processTask(forcedTask);
            chkEdit.Checked = false;
            toggleEditMes2Plc();
        }

        private void checkEditMes2Plc()
        {
            cmbParamValues.Enabled = enableEditMes2Plc;
            txtValue.Enabled = enableEditMes2Plc;
            if (enableEditMes2Plc)
            {
                // aggiorno (se possibile) i parametri selezionabili...
                fixComboParameters();
            }
        }

        private void checkFastTask()
        {
            // decremento...
            fastCount--;
            // se il counter è a zero eseguo...
            if (fastCount <= 0)
            {
                fastCount = utils.CRI("fastCount");

                // avvio fase raccolta dati e invio con adapter
                iobObj.getAndSend(gatherCycle.HF);
            }
        }

        private void checkNormTask()
        {
            // decremento...
            normCount--;
            // se il counter è a zero eseguo...
            if (normCount <= 0)
            {
                normCount = utils.CRI("normCount");

                // avvio fase raccolta dati e invio con adapter
                iobObj.getAndSend(gatherCycle.MF);
            }
        }

        private void checkSampleMem()
        {
            // decremento contatore...
            sampleMemCount--;
            if (sampleMemCount <= 0)
            {
                sampleMemCount = utils.CRI("sampleMemCount");
                // avvio fase raccolta dati e invio con adapter
                iobObj.saveMemDump(dumpType.SAMPLE);
            }
        }

        private void checkSendTask()
        {
            // avvio fase invio con adapter (code MST)
            iobObj.getAndSend(gatherCycle.VHF);
        }

        private void checkSlowTask()
        {
            slowCount--;
            if (slowCount <= 0)
            {
                slowCount = utils.CRI("slowCount");

                // avvio fase raccolta dati e invio con adapter
                iobObj.getAndSend(gatherCycle.LF);
            }
        }

        private void checkVerySlowData()
        {
            verySlowCount--;
            if (verySlowCount <= 0)
            {
                verySlowCount = utils.CRI("verySlowCount");
                // avvio fase raccolta dati e invio con adapter
                iobObj.getAndSend(gatherCycle.VLF);
            }
        }

        private void ChkEdit_CheckedChanged(object sender, EventArgs e)
        {
            toggleEditMes2Plc();
        }

        /// <summary>
        /// Chiusura adapter
        /// </summary>
        private void closeAdapter()
        {
            fermaTutto(true, false, true, false);
        }

        private void displTimer_Tick(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// Ferma tutti i componenti adapter + update buttons
        /// </summary>
        /// <param name="stopTimer">indica se fermare il timer (gather) principale (solo se non si chiude)</param>
        /// <param name="tryRestart">indica se tentare di riconnettersi</param>
        /// <param name="forceDequeue">indica se sia richeisto di SVUOTARE le code delel info</param>
        /// <param name="updateForm">indica se si debba aggiornare la form (no se si sta chiudendo...)</param>
        private void fermaTutto(bool stopTimer, bool tryRestart, bool forceDequeue, bool updateForm)
        {
            iobObj.stopAdapter(tryRestart, forceDequeue);
            // salvo!
            savePersistLayer(utils.defPersLayerFile);
            savePersistLayer(utils.histPersLayerFile);

            stop.Enabled = false;
            start.Enabled = true;
            restart.Enabled = false;

            if (stopTimer)
            {
                gather.Enabled = false;
                iobObj.tryDisconnect();
            }

            newDisplayData currDispData = new newDisplayData();
            currDispData.semIn = Semaforo.SS;
            currDispData.semOut = Semaforo.SS;
            if (updateForm)
            {
                updateFormDisplay(currDispData);
            }
        }

        private void fixComboParameters()
        {
            if (iobObj != null)
            {
                if (iobObj.memMap != null)
                {
                    if (iobObj.memMap.mMapWrite != null)
                    {
                        if (iobObj.memMap.mMapWrite.Count > 0)
                        {
                            var oldParams = cmbParamValues.DataSource;
                            List<string> parametri = new List<string>();
                            foreach (var item in iobObj.memMap.mMapWrite)
                            {
                                parametri.Add(item.Key);
                            }
                            // salvo selezione
                            int oldIdx = cmbParamValues.SelectedIndex;
                            // riassegno e ri-seleziono
                            cmbParamValues.DataSource = parametri;
                            cmbParamValues.SelectedIndex = oldIdx >= 0 ? oldIdx : 0; ;
                        }
                    }
                }
            }
        }

        private void gather_Tick(object sender, EventArgs e)
        {
            bool doLog = false;
            if (iobObj != null)
            {
                if (iobObj.periodicLog)
                {
                    doLog = true;
                }
                try
                {
                    refreshFormData();
                    // check esecuzione SendTask (VHF) COMUNQUE...
                    checkSendTask();
                    // eseguo cicli attivi SOLO se adapter è in EFFETTIVO running...
                    if (iobObj.adpRunning)
                    {
                        if (iobObj.connectionOk)
                        {
                            // se richiesto faccio memory DUMP INIZIALE!
                            if (iobObj.doStartMemDump)
                            {
                                lgInfo("Inizio dump memoria");
                                iobObj.saveMemDump(dumpType.STARTUP);
                                // fatto! non ripeto...
                                iobObj.doStartMemDump = false;
                                lgInfo("Finito dump memoria");
                            }
                            // controllo se sia abilitato sampleDump della meoria (periodico)
                            if (iobObj.doSampleMemory)
                            {
                                checkSampleMem();
                            }
                            // check esecuzione FastTask
                            checkFastTask();
                            // check esecuzione NormTask
                            checkNormTask();
                            // check esecuzione SlowTask
                            checkSlowTask();
                            // check esecuzione AlarmSync
                            checkVerySlowData();
                            if (utils.CRI("waitEndCycle") > 0)
                            {
                                Thread.Sleep(utils.CRI("waitEndCycle"));
                            }
                        }
                        else
                        {
                            DateTime dtVeto = lastStartTry.AddMilliseconds(waitRecMSec);
                            if (iobObj.adpTryRestart && (DateTime.Now > dtVeto))
                            {
                                if (doLog)
                                {
                                    lgInfo($"Retry Time Elapsed ({waitRecMSec} ms)--> tryConnect");
                                }
                                lastStartTry = DateTime.Now;
                                iobObj.tryConnect();
                            }
                        }
                    }
                    else
                    {
                        if (doLog)
                        {
                            lgInfo("Adapter stopped");
                        }
                        // verifico SE debba tentare il riavvio, ovvero NON running ma adpTryRestart e non ho riprovato x oltre waitRecMSec
                        DateTime dtVeto = lastStartTry.AddMilliseconds(waitRecMSec);
                        if (iobObj.adpTryRestart && (DateTime.Now > dtVeto))
                        {
                            lastStartTry = DateTime.Now;
                            avviaAdapter(chkForceDequeue.Checked);
                        }
                    }
                }
                catch (Exception exc)
                {
                    lgError(string.Format("Eccezione in fase di gatherTick: {0}{1}", Environment.NewLine, exc));
                }
            }
        }

        /// <summary>
        /// Init oggetti datamonitor
        /// </summary>
        private void initDatamonitor()
        {
            for (int i = 0; i < 4; i++)
            {
                dMonDisplVetoVeto.Add(i, DateTime.Now);
                dMonValues.Add(i, "");
            }
        }

        /// <summary>
        /// GEstione evento refresh
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void IobObj_eh_refreshed(object sender, iobRefreshedEventArgs e)
        {
            // aggiorno!
            updateFormDisplay(e.DisplayDataObject);
        }

        /// <summary>
        /// Carica file ini della configurazione richiesta
        /// </summary>
        /// <param name="iniConfFile"></param>
        private void loadIniFile(string iniConfFile)
        {
            // out di cosa faccio...
            displayTaskAndLog($"[STARTUP] Loading iniConfFile: {iniConfFile}");
            // leggo file
            IniFile fIni = new IniFile(iniConfFile);

            // leggo vendor e modello...
            curVendor = fIni.ReadString("MACHINE", "VENDOR", "ACME");
            curModel = fIni.ReadString("MACHINE", "MODEL", "NONE");
            // verifico tipo adapter
            try
            {
                tipoScelto = (tipoAdapter)Enum.Parse(typeof(tipoAdapter), fIni.ReadString("IOB", "CNCTYPE", "DEMO"));
            }
            catch (Exception exc)
            {
                lgError($"Eccezione in conversione tipo adapter: richiesto un tipo non codificato...{Environment.NewLine}{exc}");
                tipoScelto = tipoAdapter.ND;
            }
            // carivo vettore parametri opzionai
            Dictionary<string, string> optParRead = new Dictionary<string, string>();
            string[] optParRows = fIni.ReadSection("OPTPAR");
            if (optParRows.Length > 0)
            {
                try
                {
                    string[] kvp;
                    foreach (var item in optParRows)
                    {
                        kvp = item.Split('=');
                        optParRead.Add(kvp[0], kvp[1]);
                    }
                    lgInfo($"Caricati {optParRead.Count} parametri opzionali da OPTPAR");
                }
                catch (Exception exc)
                {
                    lgError(string.Format("EXCEPTION in fase di lettura OPTPAR: {0}{1}", Environment.NewLine, exc));
                }
            }
            // inizializzio conf IOB
            IOBConf = new IobConfiguration
            {
                pingMsTimeout = fIni.ReadInteger("IOB", "PING_MS_TIMEOUT", 500),
                vendor = curVendor,
                model = curModel,
                tipoIob = tipoScelto,
                optPar = optParRead,
                versIOB = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(),
                codIOB = CurrIOB,
                cncIpAddr = fIni.ReadString("CNC", "IP", "::1"),
                cncPort = fIni.ReadString("CNC", "PORT", "0"),
                iniFileName = iniConfFile,
                cpuType = fIni.ReadString("CNC", "CPUTYPE", ""),
                rack = (short)fIni.ReadInteger("CNC", "RACK", 0),
                slot = (short)fIni.ReadInteger("CNC", "SLOT", 0),
                serverData = new serverMapo(fIni.ReadString("SERVER", "MPIP", "::1"), fIni.ReadString("SERVER", "MPURL", "/"), fIni.ReadString("SERVER", "CMDBASE", "/"), fIni.ReadString("SERVER", "CMDFLOG", "/"), fIni.ReadString("SERVER", "CMDALIVE", "/"), fIni.ReadString("SERVER", "CMDENABLED", "/"), fIni.ReadString("SERVER", "CMDREBO", "/"), fIni.ReadString("SERVER", "CMD_ODL_STARTED", "/IOB/getCurrOdlStart/"), fIni.ReadString("SERVER", "CLI_INST", "SW_CLI"), fIni.ReadString("SERVER", "CMD_FORCLE_SPLIT_ODL", "/IOB/forceSplitOdlFull/")),
                MAX_COUNTER_BLINK = Convert.ToInt32(fIni.ReadString("BLINK", "MAX_COUNTER_BLINK", "1")),
                BLINK_FILT = Convert.ToInt32(fIni.ReadString("BLINK", "BLINK_FILT", "0")),
                TCMaxDelayFactor = Convert.ToDouble(fIni.ReadString("OPTPAR", "TC_MAX_TC_FACTOR", "1.2").Replace(".", ",")),
                TCLambda = Convert.ToDouble(fIni.ReadString("OPTPAR", "TC_LAMBDA", "0.5").Replace(".", ",")),
                TCMaxIncrPz = Convert.ToDouble(fIni.ReadString("OPTPAR", "TC_MAX_INCR", "5").Replace(".", ","))
            };
            lgInfo($"Creato IOBConf!");

            // salvo serializzando json... nella folder x Cliente oppure Vendor_Model
            string confJson = JsonConvert.SerializeObject(IOBConf, Formatting.Indented);
            string path = fileMover.GetExecutingDirectoryName(); // Directory.GetCurrentDirectory();
            string fileName = Path.GetFileName(iniConfFile).Replace(".ini", ".iob");
            string dirPath = $"{path}\\DATA\\{IOBConf.serverData.ClientInstall}";
            string fullPath = $"{dirPath}\\{fileName}";
            // verifica directory
            Directory.CreateDirectory(dirPath);
            // salvataggio
            File.WriteAllText(fullPath, confJson);

            loadIobType();
            // avvio macchina con adapter specificato...
            if (utils.CRB("autoStartOnLoad"))
            {
                displayTaskAndLog("Auto Starting...");
                // avvio!
                avviaAdapter(chkForceDequeue.Checked);
                displayTaskAndLog("Auto Started!");
            }
            try
            {
                // segnalo reboot (programma)... metto in coda invio...
                //iobObj.sendToMoonPro(urlType.FLog, "IOB INI Loaded");
                //iobObj.QueueFLog.Enqueue(iobObj.qEncodeFLog("IOB-STATUS", "IOB INI Loaded"));
            }
            catch (Exception exc)
            {
                lgError(string.Format("EXCEPTION in fase di chiamata URL di segnalazione caricamento INI file:{0}{1}", Environment.NewLine, exc));
            }
        }

        /// <summary>
        /// carica IOB richiesto
        /// </summary>
        private void loadIobType()
        {
            if (IOBConf != null)
            {
                switch (tipoScelto)
                {

                    case tipoAdapter.OSAI_OPEN:
                    case tipoAdapter.OSAI_CNDEX:
                    case tipoAdapter.OSAI_VB6:
                        // versione EgwProxy.OsaiCncLib
                        iobObj = new IobOSAI(this, IOBConf);
                        start.Enabled = true;
                        break;

                 

                    case tipoAdapter.ND:
                    default:
                        iobObj = new IobSimula(this, IOBConf);
                        start.Enabled = false;
                        break;
                }
                lblCNC.Text = $"CNC: {IOBConf.tipoIob} [{IOBConf.cncIpAddr}:{IOBConf.cncPort}]";
                lblSrvUrl.Text = $"SRV: {IOBConf.serverData.MPIP} | URL: {IOBConf.serverData.MPURL}{IOBConf.serverData.CMDBASE}";

                // aggancio evento refresh
                iobObj.eh_refreshed += IobObj_eh_refreshed;

                // carico i default values su interfaccia
                setDefaults();

                displayTaskAndLog(string.Format("Caricata conf per adapter {0}", tipoScelto));
            }
        }

        /// <summary>
        /// Fase chiusura Form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            closeAdapter();
        }

        /// <summary>
        /// Completato resize form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Resize(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// Mostrata form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Shown(object sender, EventArgs e)
        {
            displayTaskAndLog("Main Form SHOWN (Adapter)");
        }

        private void mLoadConf_Click(object sender, EventArgs e)
        {
            // mostro selettore file x leggere adapter..
            OpenFileDialog openFileDial = new OpenFileDialog();

            // directory iniziale
            openFileDial.InitialDirectory = utils.confDir; // string.Format(@"{0}\{1}", Application.StartupPath, utils.CRS("dataConfPath"));
                                                           // Set filter options and filter index.
            openFileDial.Filter = "INI Files (.ini)|*.ini|All Files (*.*)|*.*";
            openFileDial.FilterIndex = 1;
            // altre opzioni
            openFileDial.Multiselect = false;

            // Call the ShowDialog method to show the dialog box.
            DialogResult userClickedOK = openFileDial.ShowDialog();

            // Process input if the user clicked OK.
            if (userClickedOK == DialogResult.OK)
            {
                string iniConfFile = openFileDial.FileName;
                loadIniFile(iniConfFile);
                lgInfo("INI LOADED");
            }
        }

        private void refreshFormData()
        {
            // aggiorno visualizzazioni varie in form...
            evQueueLen = iobObj.QueueIN.Count;
            flQueueLen = iobObj.QueueFLog.Count;
            alQueueLen = iobObj.QueueAlarm.Count;
            msQueueLen = iobObj.QueueMessages.Count;
            // aggiorno labels counters...
            counterIob = $"pz IOB {iobObj.contapezziIOB}";
            counterMac = $"pz PLC {iobObj.contapezziPLC}";
            // verifico IOB status
            IobWinStatus currIobStatus = new IobWinStatus()
            {
                CodIob = iobObj.cIobConf.codIOB,
                queueEvLen = evQueueLen,
                queueFlLen = flQueueLen,
                queueAlLen = alQueueLen,
                queueMsLen = msQueueLen,
                counterIOB = iobObj.contapezziIOB,
                counterMAC = iobObj.contapezziPLC,
                lastUpdate = lastIobStatus.lastUpdate,
                online = utils.IOB_Online,
                lastDataIn = iobObj.lastReadPLC
            };
            // se diverso SALVO!
            if (lastIobStatus.online != currIobStatus.online || lastIobStatus.lastDataIn != currIobStatus.lastDataIn || lastIobStatus.counterIOB != currIobStatus.counterIOB || lastIobStatus.counterMAC != currIobStatus.counterMAC || lastIobStatus.queueEvLen != currIobStatus.queueEvLen || lastIobStatus.queueFlLen != currIobStatus.queueFlLen || lastIobStatus.queueAlLen != currIobStatus.queueAlLen || lastIobStatus.queueMsLen != currIobStatus.queueMsLen)
            {
                // aggiorno data
                currIobStatus.lastUpdate = DateTime.Now;
                // salvo su redis e in obj corrente
                iobObj.redisMan.iobStatus = currIobStatus;
                lastIobStatus = currIobStatus;
            }
            // se diverso SALVO MP IO status
            if (lastSrvStatus.online != utils.MPIO_Online)
            {
                // aggiorno
                ServerMpStatus currSrvStatus = iobObj.redisMan.servStatus;
                currSrvStatus.online = utils.MPIO_Online;
                currSrvStatus.lastUpdate = DateTime.Now;
                // salvo su redis e in obj corrente
                iobObj.redisMan.servStatus = currSrvStatus;
                lastSrvStatus = currSrvStatus;
            }
        }

        /// <summary>
        /// Button restart
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void restart_Click(object sender, EventArgs e)
        {
            string message = "Attenzione: con l'operazione di reset veranno persi (e non inviati) i dati eventualmente accumulati (indipendentemente dalla selezione del checkbox 'svuota coda').";
            string caption = "Conferma Reset Dati IOB";
            MessageBoxButtons buttons = MessageBoxButtons.YesNo;
            DialogResult result;

            // verifica con messagebox
            result = MessageBox.Show(message, caption, buttons);
            // solo se ho conferma da utente
            if (result == DialogResult.Yes)
            {
                // faccio stop... SENZA inviare
                fermaAdapter(false, false, true);
                displayTaskAndLog("RESTARTING: Adapter Stopped");
                // rileggo INI
                loadIniFile(defConfFilePath);
                displayTaskAndLog("RESTARTING: Ini File Reloaded");
                // faccio start... CON RESET delel code
                avviaAdapter(true);
                displayTaskAndLog("RESTARTING: Adapter Started");
                // resetto i data monitor...
                dataMonitor_0 = "";
                dataMonitor_1 = "";
                dataMonitor_2 = "";
                dataMonitor_3 = "";
            }
        }

        /// <summary>
        /// impostazione valori defaults
        /// </summary>
        private void setDefaults()
        {
            stop.Enabled = false;

            evQueueLen = 0;
            flQueueLen = 0;
            nLine2show = utils.CRI("numRowConsole");
        }

        private void splitContainer1_Panel1_Paint(object sender, PaintEventArgs e)
        {
        }

        /// <summary>
        /// Avvio dell'adapter
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void start_Click(object sender, EventArgs e)
        {
            avviaAdapter(chkForceDequeue.Checked);
            // salvo che ho avviato adapter
            lgInfo("Completato LOAD Adapter");
        }

        /// <summary>
        /// fermata dell'adapter
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void stop_Click(object sender, EventArgs e)
        {
            fermaAdapter(false, chkForceDequeue.Checked, true);
            // salvo che ho fermato adapter
            lgInfo("UNLOAD Adapter");
        }

        private void TabData_Selected(object sender, TabControlEventArgs e)
        {
        }

        private void toggleEditMes2Plc()
        {
            // abilita i campi --> PLC per editing
            enableEditMes2Plc = !enableEditMes2Plc;
            checkEditMes2Plc();
        }

        #endregion Private Methods

        #region Protected Methods

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        /// <summary>
        /// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="txt2log"></param>
        protected void lgError(string txt2log)
        {
            if (!string.IsNullOrEmpty(txt2log))
            {
                lg.Factory.Configuration.Variables["codIOB"] = this.CurrIOB;
                lg.Error(txt2log);
                // salvo anche in logwatcher... SE non si dimostra ricorsivo...
                if (!txt2log.Contains("logWatcher"))
                {
                    newDisplayData currDispData = new newDisplayData();
                    currDispData.newLiveLogData = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | ERROR | {txt2log}";
                    updateFormDisplay(currDispData);
                }
            }
        }

        /// <summary>
        /// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="txt2log"></param>
        protected void lgInfo(string txt2log)
        {
            lg.Factory.Configuration.Variables["codIOB"] = this.CurrIOB;
            lg.Info(txt2log);
            // salvo anche in logwatcher...
            newDisplayData currDispData = new newDisplayData();
            currDispData.newLiveLogData = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | INFO | {txt2log}";
            updateFormDisplay(currDispData);
        }

        /// <summary>
        /// Init form (grafica) con helper x testi e varie
        /// </summary>
        protected void myGraphInitForm()
        {
            lblStatus.Text = "Loading";

            // aggiunta tooltip x send forzato
            ToolTip ttForceSend = new ToolTip();

            // imposto delay.
            ttForceSend.AutoPopDelay = 3000;
            ttForceSend.InitialDelay = 500;
            ttForceSend.ReshowDelay = 500;
            // sempre visibile (che sia o meno attiva la form).
            ttForceSend.ShowAlways = true;

            // imposto tooltip
            ttForceSend.SetToolTip(this.chkForceDequeue, "Forza invio eventi allo stop");
        }

        /// <summary>
        /// MOstra info su coda complessiva
        /// </summary>
        protected void showQueueData()
        {
            lblQueueLenTop.Text = totQueue == 0 ? "realtime" : $"ev: {qEvLen} | flog: {qFlLen} | tot: {totQueue}";
        }

        #endregion Protected Methods

        #region Public Methods

        /// <summary>
        /// Decodifica colore da valore semaforico
        /// </summary>
        /// <param name="valore"></param>
        /// <returns></returns>
        public static Color decSemaforo(Semaforo valore)
        {
            Color colore = Color.LightGray;
            switch (valore)
            {
                case Semaforo.SV:
                    colore = Color.LightGreen;
                    break;

                case Semaforo.SG:
                    colore = Color.LightGoldenrodYellow;
                    break;

                case Semaforo.SR:
                    colore = Color.Red;
                    break;

                case Semaforo.SS:
                    colore = Color.DarkGray;
                    break;

                default:
                    colore = Color.LightGray;
                    break;
            }
            return colore;
        }

        /// <summary>
        /// Avvio l'adapter
        /// </summary>
        /// <param name="resetQueue">indica se sia richeisto di SVUOTARE le code delel info</param>
        public void avviaAdapter(bool resetQueue)
        {
            displayTaskAndLog("Adapter starting");
            // se NON sta girando...
            if (!iobObj.adpRunning)
            {
                iobObj.startAdapter(resetQueue);
                displayTaskAndLog("Adapter started!");

                // fix buttons start/stop/dump
                start.Enabled = false;
                stop.Enabled = true;
                restart.Enabled = true;

                displayTaskAndLog("Start Timers");
                // inizializzo contatori fast/mid/slow
                fastCount = utils.CRI("fastCount");
                normCount = utils.CRI("normCount");
                slowCount = utils.CRI("slowCount");
                verySlowCount = utils.CRI("verySlowCount");
                displayTaskAndLog("Adapter Running...");
                // init max queue
                maxEvQueue = 1;
                maxFlQueue = 1;

                try
                {
                    // segnalo reboot (programma)...
                    iobObj.QueueFLog.Enqueue(iobObj.qEncodeFLog("IOB-STATUS", "IOB Started"));
                }
                catch (Exception exc)
                {
                    lgError(string.Format("EXCEPTION in fase di chiamata URL di segnalazione AVVIO IOB:{0}{1}", Environment.NewLine, exc));
                }
            }
            else
            {
                displayTaskAndLog("Adapter STILL Running...");
            }
        }

        /// <summary>
        /// mostra un testo sulla status bar + LOG
        /// </summary>
        /// <param name="txt2show"></param>
        public void displayTaskAndLog(string txt2show)
        {
            lblStatus.Text = txt2show;
            lblStatus.Invalidate();
            lgInfo(txt2show);
        }

        /// <summary>
        /// Ferma l'adapter
        /// </summary>
        /// <param name="tryRestart">determina se si debba tentare riavvio automatico (per caduta connessione)</param>
        /// <param name="forceDequeue">indica se sia richeisto di SVUOTARE le code delel info</param>
        /// <param name="updateForm">indica se aggiornare la form prima di fermare</param>
        public void fermaAdapter(bool tryRestart, bool forceDequeue, bool updateForm)
        {
            fermaTutto(false, tryRestart, forceDequeue, updateForm);
        }

        /// <summary>
        /// Effettua un trim della stringa al numero max di linee da mostrare a video
        /// </summary>
        /// <param name="newString"></param>
        /// <returns></returns>
        public string limitLine2show(string newString)
        {
            if (!string.IsNullOrEmpty(newString))
            {
                // se num righe superiore a limite trimmo...
                if (newString.Split('\n').Length > nLine2show)
                {
                    //int idx = newString.LastIndexOf('\r');
                    int idx = newString.LastIndexOf(Environment.NewLine);
                    newString = newString.Substring(0, idx);
                }
            }
            return newString;
        }

        /// <summary>
        /// Salva su file l'oggetto di persistenza dati
        /// </summary>
        /// <param name="filePath"></param>
        public void savePersistLayer(string filePath)
        {
            // in primis check semaforo salvataggio...
            if (!iobObj.adpSaving)
            {
                // alzo semaforo salvataggio
                iobObj.adpSaving = true;
                // se HO dei dati...
                if (iobObj.persistenceLayer != null)
                {
                    try
                    {
                        utils.WritePlain(iobObj.persistenceLayer, filePath);
                    }
                    catch (Exception exc)
                    {
                        lgError(string.Format("Errore salvataggio file{0}{1}", Environment.NewLine, exc));
                    }
                }
                else
                {
                    lgInfo("persistenceLayer null, non salvato...");
                }
                // abbasso semaforo salvataggio
                iobObj.adpSaving = false;
            }
        }

        /// <summary>
        /// Mostra update delle statistiche di comunicazione (numero chiamate, tempo medio...)
        /// </summary>
        /// <param name="txt2show"></param>
        public void updateComStats(string txt2show)
        {
            lblComStats.Text = string.Format("{0} | ", txt2show);
        }

        /// <summary>
        /// Effettua update form una volta ricevuto OBJ che contiene le varie innovazioni...
        /// </summary>
        /// <param name="currDispData"></param>
        public void updateFormDisplay(newDisplayData currDispData)
        {
            // ciclo x ogni oggetto x caricare le innovazioni...
            if (currDispData != null && currDispData.hasData)
            {
                DateTime adesso = DateTime.Now;
                // RealTime display
                if (!string.IsNullOrWhiteSpace(currDispData.newInData))
                {
                    dataMonitor_0 = limitLine2show($"{currDispData.newInData}{Environment.NewLine}{dataMonitor_0}");
                }
                if (!string.IsNullOrWhiteSpace(currDispData.newSignalData))
                {
                    dataMonitor_1 = limitLine2show($"{currDispData.newSignalData}{Environment.NewLine}{dataMonitor_1}");
                }
                if (!string.IsNullOrWhiteSpace(currDispData.newFLogData))
                {
                    dataMonitor_3 = limitLine2show($"{currDispData.newFLogData}{Environment.NewLine}{dataMonitor_3}");
                }
                if (!string.IsNullOrWhiteSpace(currDispData.newUrlCallData))
                {
                    dataMonitor_2 = limitLine2show($"{currDispData.newUrlCallData}{Environment.NewLine}{dataMonitor_2}");
                }

                // Bitmap lettura attuale
                if (currDispData.counter >= 0)
                {
                    if (logCounterVeto < adesso || lblCounter.Text != $"{currDispData.counter}")
                    {
                        lblCounter.Text = $"{currDispData.counter}";
                        lblCounter.Refresh();
                        logCounterVeto = adesso.AddMilliseconds(delayShowLogMs);
                    }
                }
                // Bitmap lettura attuale
                if (!string.IsNullOrWhiteSpace(currDispData.currBitmap))
                {
                    lblBitmap.Text = currDispData.currBitmap;
                    lblBitmap.Refresh();
                }
                // LiveLog
                if (!string.IsNullOrWhiteSpace(currDispData.newLiveLogData))
                {
                    logWatcher = currDispData.newLiveLogData;
                }
                // semafori
                if (currDispData.semOut != Semaforo.ND)
                {
                    // aggiorno SE diverso
                    if (sOUT != currDispData.semOut)
                    {
                        sOUT = currDispData.semOut;
                    }
                }
                if (currDispData.semIn != Semaforo.ND)
                {
                    //aggiorno SE diverso
                    if (sIN != currDispData.semIn)
                    {
                        sIN = currDispData.semIn;
                    }
                }
            }
        }

        public void WriteTextSafe(string text)
        {
            if (lblOutMessage3.InvokeRequired)
            {
                var d = new SafeCallDelegate(WriteTextSafe);
                lblOutMessage3.Invoke(d, new object[] { text });
            }
            else
            {
                lblOutMessage3.Text = text;
            }
        }

        #endregion Public Methods
    }
}