using IOB_UT_NEXT.Config;
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;
using static IOB_UT_NEXT.BaseAlarmConf;

namespace IOB_UT_NEXT.Iob
{
    /// <summary>
    /// Classe di base per IOB
    /// </summary>
    public class BaseObj
    {
        #region Public Fields

        /// <summary>
        /// valore booleano di check se sia stato AVVIATO l'adapter (Running)
        /// </summary>
        public bool adpRunning = false;

        /// <summary>
        /// valore booleano di check se l'adapter STIA SALVANDO
        /// </summary>
        public bool adpSaving = false;

        /// <summary>
        /// valore booleano (richiesta di riavvio automatico)
        /// </summary>
        public bool adpTryRestart;

        /// <summary>
        /// Livello minimo allarmi da considerare x invio
        /// </summary>
        public AlarmLevel alarmLevelMin = AlarmLevel.Alarm;

        /// <summary>
        /// Struttura allarmi mappati
        /// </summary>
        public List<BaseAlarmConf> alarmMaps = new List<BaseAlarmConf>();

        /// <summary>
        /// Tipo di allarmi gestiti
        /// rif: BaseAlarmConf.AlarmBlockType.[Bitmap/ActiveList]
        /// </summary>
        public AlarmBlockType alarmType = AlarmBlockType.Bitmap;

#if false
        /// <summary>
        /// Conf adapter corrente
        /// </summary>
        public IobConfiguration cIobConf;
#endif

        /// <summary>
        /// Conteggio ATTUALE ore macchina IN LAVORO
        /// </summary>
        public double contOreMaccLav;

        /// <summary>
        /// Conteggio ATTUALE ore macchina ON
        /// </summary>
        public double contOreMaccOn;

        /// <summary>
        /// contatore x simulazione valori input
        /// </summary>
        public int countSim = 0;

        /// <summary>
        /// ODL attualmente sulla macchina
        /// </summary>
        public Int32 currIdxODL = 0;

        /// <summary>
        /// Modo corrente (da classe ENUM)
        /// </summary>
        public CNC_MODE currMode;

        /// <summary>
        /// ODL corrente caricato sulla macchina (stringa, da chiamata MP/IO)
        /// </summary>
        public string currODL = "";

        /// <summary>
        /// Indica se sia richiesto campionamento memoria PERIODICO
        /// </summary>
        public bool doSampleMemory;

        /// <summary>
        /// Indica se si debba leggere e fare DUMP delle aree di memoria (1 volta solo all'avvio x debug...)
        /// </summary>
        public bool doStartMemDump;

        /// <summary>
        /// Data/ora ultimo avvio adapter
        /// </summary>
        public DateTime dtAvvioAdp = DateTime.Now;

        /// <summary>
        /// Data/ora ultimo spegnimento adapter
        /// </summary>
        public DateTime dtStopAdp = DateTime.Now;

        /// <summary>
        /// Indicazione VETO check status IOB x evitare loop troppo stretti...
        /// </summary>
        public DateTime dtVetoCheckIOB = DateTime.Now.AddDays(-1);

        /// <summary>
        /// Indicazione VETO check sync ricette x evitare loop troppo stretti...
        /// </summary>
        public DateTime dtVetoCheckSyncRecipe = DateTime.Now.AddHours(-1);

        /// <summary>
        /// Abilitazione lettura PrgName
        /// </summary>
        public bool enablePrgName = true;

        /// <summary>
        /// Abilitazione invio pezzi "in blocco" per recupero contapezzi
        /// </summary>
        public bool enableSendPzCountBlock = false;

        /// <summary>
        /// Determina se sia encessario convertire valori little/big endian (SIEMENS=true, OSAI=FALSE)
        /// </summary>
        public bool hasBigEndian = false;

        /// <summary>
        /// Configurazione gerarchica completa (v 4.x.x.x)
        /// </summary>
        public IobConfTree IOBConfFull;

        /// <summary>
        /// dataOra ultima verifica CNC disconnesso...
        /// </summary>
        public DateTime lastDisconnCheck;

        /// <summary>
        /// Data/ora ultima volta che IOB è stato dichiarato online
        /// </summary>
        public DateTime lastIobOnline = DateTime.Now.AddHours(-1);

        /// <summary>
        /// dataOra ultimo log periodico...
        /// </summary>
        public DateTime lastPeriodicLog;

        /// <summary>
        /// dataOra ultimo PING inviato verso il PLC...
        /// </summary>
        public DateTime lastPING = DateTime.Now.AddHours(-1);

        /// <summary>
        /// DataOra ultima lettura da PLC
        /// </summary>
        public DateTime lastReadPLC;

        /// <summary>
        /// ULtimo valore inviato (in caso di disconnessione lo reinvia x garantire watchdog...)
        /// </summary>
        public string lastSignInVal = "";

        /// <summary>
        /// DateTime Ultimo valore simulazione generato
        /// </summary>
        public DateTime lastSim;

        /// <summary>
        /// Ultima verifica status IOB x forzare display status SRV
        /// </summary>
        public DateTime lastIobStatusDisplUpdate = DateTime.Now;

        /// <summary>
        /// dataOra ultimo segnale inviato al SERVER...
        /// </summary>
        public DateTime lastWatchDog;

        /// <summary>
        /// dataOra ultimo segnale inviato a macchina/PLC...
        /// </summary>
        public DateTime lastWatchDogPLC = DateTime.Now;

        /// <summary>
        /// Massimo numero di px da inviare in blocco
        /// </summary>
        public int maxSendPzCountBlock = 10;

        /// <summary>
        /// Struttura memoria PLC x lettura/scrittura da JSON file
        /// </summary>
        public plcMemMapExt memMap;

        /// <summary>
        /// Minimo numero di px da inviare in blocco
        /// </summary>
        public int minSendPzCountBlock = 1;

        /// <summary>
        /// Variabile booleana che indica se sia necessario fare refresh del contapezzi
        /// </summary>
        public bool needRefreshPzCount = true;

        /// <summary>
        /// Coda degli esiti di ping x calcolo stato macchina
        /// </summary>
        public DataQueue QueuePing;

        /// <summary>
        /// Determina se utilizzare blocchi di memoria IOT contigui (e quindi processing
        /// "monoblocco" semplificato"=
        /// </summary>
        public bool procIotMem = false;

        /// <summary>
        /// Coda valori ALLARMI ove gestiti...
        /// </summary>
        public DataQueue QueueAlarm;// = new DataQueue("000", "QueueAlarm", false);

        /// <summary>
        /// Oggetto della coda degli elementi letti di tipo FluxLog (e non ancora trasmessi)
        /// </summary>
        public DataQueue QueueFLog;// = new DataQueue("000", "QueueFLog", false);

        /// <summary>
        /// Oggetto della coda degli elementi letti (e non ancora trasmessi)
        /// </summary>
        public DataQueue QueueIN;// = new DataQueue("000", "QueueIN", false);

        /// <summary>
        /// Coda valori MESSAGGI/EVENTI (da non sottocampionare come samples)...
        /// </summary>
        public DataQueue QueueMessages;// = new DataQueue("000", "QueueMessages", false);

        /// <summary>
        /// Oggetto della coda degli elementi di tipo RawTransf (e non ancora trasmessi)
        /// NB: sono salvati serializzati come stringhe
        /// </summary>
        public DataQueue QueueRawTransf;// = new DataQueue("000", "QueueRawTransf", false);

        /// <summary>
        /// Coda delle richieste dal server (Task2Exe)
        /// </summary>
        public DataQueue QueueSrvReq;

        /// <summary>
        /// Coda delle risposte al server (Task2Exe)
        /// </summary>
        public DataQueue QueueSrvResp;

        /// <summary>
        /// Coda valori LOG UTENTE (da non sottocampionare come samples)...
        /// </summary>
        public DataQueue QueueULog;// = new DataQueue("000", "QueueULog", false);

        /// <summary>
        /// alias booleano false = R
        /// </summary>
        public bool R = false;

        /// <summary>
        /// 32 byte input base (es strobe, 8 word da 32 bit di flags...)
        /// </summary>
        public byte[] RawInput = new byte[32];

        /// <summary>
        /// 32 byte output base (es ack, 8 word da 32 bit di flags...)
        /// </summary>
        public byte[] RawOutput = new byte[32];

        /// <summary>
        /// Oggetto connessione REDIS
        /// </summary>
        public RedisIobCache redisMan;

        /// <summary>
        /// Oggetto cronometro x campionamento durate chiamate
        /// </summary>
        public Stopwatch sw = new Stopwatch();

        /// <summary>
        /// Oggetto gestione TempiCiclo e contapezzi
        /// </summary>
        public TCMan tcMan = new TCMan(0.5, 1.3, 5);

        /// <summary>
        /// Imposta veto lettura dati (es per DB a 2 sec)
        /// </summary>
        public DateTime vetoDataRead = DateTime.Now;

        /// <summary>
        /// Imposta veto SYNC dati (es per DB 2 DB a 10 sec)
        /// </summary>
        public DateTime vetoDataSync = DateTime.Now;

        /// <summary>
        /// Veto (in sec) a nuovi ping (x IOB PING, FTP...)
        /// </summary>
        public int vetoPingSec = 1;

        /// <summary>
        /// Veto specifico per la gestione (svuotamento) coda contapezzi (es in fase di attrezzaggio e reset lenti)
        /// Tipicamente impostato a DelayReadPzCountSetup dopo attrezzaggio...
        /// </summary>
        public DateTime vetoQueuePzCount = DateTime.Now;

        /// <summary>
        /// Imposta veto chiamata split (durante chiamata, per 60 sec)
        /// </summary>
        public DateTime vetoSplit = DateTime.Now.AddMinutes(1);

        /// <summary>
        /// alias booleano true = W
        /// </summary>
        public bool W = true;

        #endregion Public Fields

        #region Public Properties

        /// <summary>
        /// Verifica se sia in modalità DEMO avanzata (campionamento da set di valori ammessi...)
        /// </summary>
        public static bool DemoInSample
        {
            get
            {
                return baseUtils.CRB("DemoInSample");
            }
        }

        /// <summary>
        /// Verifica se sia in modalità DEMO x dati OUTPUT
        /// </summary>
        public static bool DemoOut
        {
            get
            {
                return utils.CRB("DemoOut");
            }
        }

        /// <summary>
        /// Indicazione VETO PING a server sino alla data-ora indicata
        /// </summary>
        public static DateTime dtVetoPing
        {
            get
            {
                return utils.dtVetoPing;
            }
            set
            {
                utils.dtVetoPing = value;
            }
        }

        /// <summary>
        /// Indicazione VETO accodamento valori INGRESSI/EVENTI sino alla data-ora indicata
        /// </summary>
        public static DateTime dtVetoQueueIN
        {
            get
            {
                return utils.dtVetoQueueIN;
            }
            set
            {
                utils.dtVetoQueueIN = value;
            }
        }

        /// <summary>
        /// Indicazione VETO invio a server sino alla data-ora indicata
        /// </summary>
        public static DateTime dtVetoSend
        {
            get
            {
                return utils.dtVetoSend;
            }
            set
            {
                utils.dtVetoSend = value;
            }
        }

        /// <summary>
        /// Verifica se sia abilitato test lettura blocchi memoria all'avvio
        /// </summary>
        public static bool EnableTest
        {
            get
            {
                return baseUtils.CRB("enableTest");
            }
        }

        /// <summary>
        /// stato Online/Offline del server MP IO (su REDIS)
        /// </summary>
        public static bool MPOnline
        {
            get
            {
                return utils.MPIO_Online;
            }
            set
            {
                utils.MPIO_Online = value;
            }
        }

        #endregion Public Properties

        #region Public Methods

        /// <summary>
        /// Effettua chiamata URL e restituisce risultato
        /// </summary>
        /// <param name="URL"></param>
        /// <param name="doAsync">invio in modalità async (NON GARANTITO ordine...)</param>
        /// <returns></returns>
        public static async Task<string> callUrl(string URL, bool doAsync)
        {
            string answ = "";
            // verifica URL preliminare
            if (URL.StartsWith("http://ND"))
            {
                lg.Warn($"callUrl | wrong URL data: {URL}");
            }
            else
            {
                if (doAsync)
                {
                    answ = await utils.callUrlAsync(URL);
                }
                else
                {
                    answ = await Task.Run(() => utils.callUrl(URL));
                }

                if (urlRandWait > 0)
                {
                    // NON blocca il thread, libera risorse per le altre 20 istanze
                    await Task.Delay(rnd.Next(urlRandWait / 10, urlRandWait));
                }
            }
            return answ;
        }

        /// <summary>
        /// Effettua chiamata URL e restituisce risultato
        /// </summary>
        /// <param name="URL"></param>
        /// <param name="payload"></param>
        /// <param name="doAsync">invio in modalità async (NON GARANTITO ordine...)</param>
        /// <returns></returns>
        public static async Task<string> callUrlWithPayloadAsync(string URL, string payload, bool doAsync)
        {
            string answ = "";
            if (doAsync)
            {
                answ = await utils.callUrlAsync(URL, payload);
            }
            else
            {
                answ = await Task.Run(() => utils.callUrl(URL, payload));
            }

            if (urlRandWait > 0)
            {
                // NON blocca il thread, libera risorse per le altre 20 istanze
                await Task.Delay(rnd.Next(urlRandWait / 10, urlRandWait));
            }
            return answ;
        }

        /// <summary>
        /// processa dataLayer e se necessario salva/mostra
        /// </summary>
        public static void checkSavePersDataLayer()
        {
        }

        public static string GetMACAddress()
        {
            NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
            String sMacAddress = string.Empty;
            foreach (NetworkInterface adapter in nics)
            {
                if (string.IsNullOrEmpty(sMacAddress))// only return MAC Address from first card
                {
                    IPInterfaceProperties properties = adapter.GetIPProperties();
                    //sMacAddress = adapter.GetPhysicalAddress().ToString();
                    sMacAddress = string.Join(":", (from z in adapter.GetPhysicalAddress().GetAddressBytes() select z.ToString("X2")).ToArray());
                }
            }
            return sMacAddress;
        }

        public static void resetDebugConsole()
        {
        }

        /// <summary>
        /// Reset dei webclients
        /// </summary>
        public static void resetWebClients()
        {
            utils.resetWebClients();
        }

        #endregion Public Methods

        #region Protected Fields

        /// <summary>
        /// Valore di attesa (random) dopo ogni invio x evitare congestione send...
        /// </summary>
        protected static int urlRandWait = 0;

        /// <summary>
        /// Disabilitazione gestione ODL (lettura e gestione)
        /// </summary>
        protected bool disableOdl = false;

        //protected static Logger lg = LogManager.GetCurrentClassLogger();
        /// <summary>
        /// Abilitazione invio conf macchine
        /// </summary>
        protected bool enabSendMachineConf = true;

        /// <summary>
        /// Ultimo LOG registrazione avvio (x ridurre log notturni...)
        /// </summary>
        protected DateTime lastLogStartup = DateTime.Today.AddHours(-1);

        /// <summary>
        /// Dimensione coda di ping x valutazione
        /// </summary>
        protected int maxQueuePing = 11;

        /// <summary>
        /// Tempo minimo ammissibile di risposta (es x errori ModBus che risponde troppo in fretta) in millisec
        /// </summary>
        protected int minRespTimeMs = 5;

        /// <summary>
        /// Indica se resettare allarmi all'avvio e inviare il reset appena parte adapter
        /// </summary>
        protected bool resetAlarmOnStart = false;

        /// <summary>
        /// Veto per registrazione completa log di startup (minuti)
        /// </summary>
        protected int vetoLogStartupDuration = 60;

        #endregion Protected Fields

        #region Protected Properties

        /// <summary>
        /// Dizionario condizioni di veto log x ogni tipo di messaggio (periodo differente secondo livello)
        /// </summary>
        protected Dictionary<string, DateTime> VetoLog { get; set; } = new Dictionary<string, DateTime>();

        /// <summary>
        /// Dizionario conteggio numero volte che si fa veto log x ogni tipo di messaggio
        /// </summary>
        protected Dictionary<string, int> VetoLogCount { get; set; } = new Dictionary<string, int>();

        #endregion Protected Properties

        #region Protected Methods

        /// <summary>
        /// Verifica se ci sia veto log attivo e gestisce casistiche
        /// </summary>
        /// <param name="vetoSec"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        protected bool checkLogVeto(int vetoSec, ref string message)
        {
            // verifico SE si debba fare log, altrimenti metto in coda...
            bool doVeto = true;
            try
            {
                DateTime adesso = DateTime.Now;
                if (VetoLog.ContainsKey(message))
                {
                    // conteggio num veto a +1...
                    if (VetoLogCount.ContainsKey(message))
                    {
                        VetoLogCount[message]++;
                    }
                    else
                    {
                        VetoLogCount.Add(message, 1);
                    }
                    // controllo scadenza, quando superata soglia aggiorno messaggio con {n} x {messaggio}
                    if (adesso.Subtract(VetoLog[message]).TotalSeconds > vetoSec)
                    {
                        doVeto = false;
                        string newMessage = $"{VetoLogCount[message]} x {message}";
                        VetoLog.Remove(message);
                        VetoLogCount.Remove(message);
                        message = newMessage;
                    }
                }
                else
                {
                    // primo --> loggo
                    doVeto = false;
                    VetoLog.Add(message, adesso.AddSeconds(vetoSec));
                    if (VetoLogCount.ContainsKey(message))
                    {
                        VetoLogCount[message]++;
                    }
                    else
                    {
                        VetoLogCount.Add(message, 0);
                    }
                }
            }
            catch (Exception exc)
            {
                lg.Error($"Eccezione in checkLogVeto: reset contatori VetoLog/VetoLogCount | message: {message}");
                lg.Error($"Raised Exc:{Environment.NewLine}{exc}");
                doVeto = false;
                // svuoto tutti i log veto...
                VetoLog = new Dictionary<string, DateTime>();
                VetoLogCount = new Dictionary<string, int>();
                lg.Error($"Effettuato reset contatori VetoLog/VetoLogCount");
            }
            // restituisco esito!
            return doVeto;
        }

        /// <summary>
        /// Setup di tutti gli oggetti Queue, ma solo alcuni hanno coda REDIS (quelli senza sono "sacrificabili"
        /// </summary>
        protected void SetupQueue()
        {
            string codIob = IOBConfFull.General.FilenameIOB;
            bool useRedis = IOBConfFull.General.EnabRedisQue;
            // valutare se portare di nuovo in redis...
            QueueIN = new DataQueue(codIob, "QueueIN", useRedis, redisMan);
            QueueSrvResp = new DataQueue(codIob, "QueueServResp", useRedis, redisMan);
            // fix a NON redis
            QueueAlarm = new DataQueue(codIob, "QueueAlarm", false, redisMan);
            QueueFLog = new DataQueue(codIob, "QueueFLog", false, redisMan);
            //QueueFLog = new DataQueue(codIob, "QueueFLog", useRedis, redisMan);
            QueueMessages = new DataQueue(codIob, "QueueMessages", false, redisMan);
            QueuePing = new DataQueue(codIob, "QueuePing", false, redisMan);
            QueueRawTransf = new DataQueue(codIob, "QueueRawTransf", false, redisMan);
            QueueSrvReq = new DataQueue(codIob, "QueueServReq", false, redisMan);
            QueueULog = new DataQueue(codIob, "QueueULog", false, redisMan);
        }

        #endregion Protected Methods

        #region Private Fields

        /// <summary>
        /// wrapper di log
        /// </summary>
        private static readonly Logger lg = LogManager.GetCurrentClassLogger();

        private static Random rnd = new Random();

        #endregion Private Fields
    }
}