using NLog;
using RestSharp;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Security.Policy;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using YamlDotNet.Core.Tokens;

namespace IOB_UT_NEXT
{
    public class baseUtils
    {
        #region Public Fields

        /// <summary>
        /// Indicazione VETO PING a server sino alla data-ora indicata
        /// </summary>
        public static DateTime dtVetoPing = DateTime.Now;

        /// <summary>
        /// Indicazione VETO accodamento INGRESSI/EVENTI sino alla data-ora indicata
        /// </summary>
        public static DateTime dtVetoQueueIN = DateTime.Now;

        /// <summary>
        /// Indicazione VETO invio a server sino alla data-ora indicata
        /// </summary>
        public static DateTime dtVetoSend = DateTime.Now;

        /// <summary>
        /// Status IOB
        /// </summary>
        public static bool IOB_Online = false;

        /// <summary>
        /// Status MP_IO
        /// </summary>
        public static bool MPIO_Online = false;

        #endregion Public Fields

        #region Public Properties

        /// <summary>
        /// Calcola una pausa con errore casuale x il prossimo send programmato verso SERVER
        /// </summary>
        public static int nextPauseSendMSec
        {
            get
            {
                // parto dal dato std di veto per pauseSendMSec
                int value = CRI("pauseSendMSec");
                Random rnd = new Random();
                // NextDouble() ∈ [0.0, 1.0) → fattore ∈ [-1.0, 1.0)
                double factor = rnd.NextDouble() * 2.0 - 1.0;
                // ±10%
                return (int)(value * (1.0 + 0.20 * factor));
            }
        }

        #endregion Public Properties

        #region Public Methods

        /// <summary>
        /// formatta un numero in forma binaria 0/1
        /// </summary>
        /// <param name="valore"></param>
        /// <returns></returns>
        public static string binaryForm(int valore)
        {
            string answ = "";
            try
            {
                answ = string.Format(new BinaryUtils(), "{0:B}", valore);
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// formatta un numero in forma binaria 0/1
        /// </summary>
        /// <param name="valore"></param>
        /// <returns></returns>
        public static string binaryForm(uint valore)
        {
            string answ = "";
            try
            {
                answ = string.Format(new BinaryUtils(), "{0:B}", valore);
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Effettua chiamata URL e restituisce risultato, SE NON E' in veto send (x mitigare chiamate...)
        /// </summary>
        /// <param name="URL"></param>
        /// <returns></returns>
        public static string callUrl(string URL)
        {
            string answ = "";
            // controllo se ho un VETO all'invio...
            if (dtVetoSend < DateTime.Now)
            {
                answ = CallUrlGet(URL);
            }
            return answ;
        }

        /// <summary>
        /// Effettua chiamata URL e restituisce risultato
        /// </summary>
        /// <param name="URL"></param>
        /// <param name="payload"></param>
        /// <returns></returns>
        public static string callUrl(string URL, string payload)
        {
            string answ = "";
            // controllo se ho un VETO all'invio...
            if (dtVetoSend < DateTime.Now)
            {
                answ = CallUrlPost(URL, payload);
            }
            // restituisco valore!
            return answ;
        }

        /// <summary>
        /// Metodo chiamata URL con POST async
        /// </summary>
        /// <param name="URL"></param>
        /// <param name="payload"></param>
        /// <param name="ct"></param>
        /// <returns></returns>
        public static async Task<string> callUrlAsync(string URL, string payload, CancellationToken ct = default)
        {
            try
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();

                // Se hai un payload, probabilmente è una POST
                var content = new StringContent(payload, Encoding.UTF8, "application/json");

                // Qui il thread viene RILASCIATO. La CPU dell'istanza va a 0%
                // mentre aspetta la risposta dal server.
                HttpResponseMessage response = await _httpClient.PostAsync(URL, content, ct);

                response.EnsureSuccessStatusCode();

                sw.Stop();
                TrackUrlCall(URL, sw.Elapsed);

                return await response.Content.ReadAsStringAsync();
            }
            catch (OperationCanceledException)
            {
                // Gestisci la cancellazione (quando chiudi l'app o scade il timeout)
                return "Canceled";
            }
            catch (Exception ex)
            {
                // Gestisci errori di rete
                return $"Error: {ex.Message}";
            }
        }

        /// <summary>
        /// Metodo chiamata URL con GET async
        /// </summary>
        /// <param name="URL"></param>
        /// <param name="ct"></param>
        /// <returns></returns>
        public static async Task<string> callUrlAsync(string URL, CancellationToken ct = default)
        {
            try
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();

                // Qui il thread viene RILASCIATO. La CPU dell'istanza va a 0%
                // mentre aspetta la risposta dal server.
                HttpResponseMessage response = await _httpClient.GetAsync(URL, ct);

                response.EnsureSuccessStatusCode();

                sw.Stop();
                TrackUrlCall(URL, sw.Elapsed);
                return await response.Content.ReadAsStringAsync();
            }
            catch (OperationCanceledException)
            {
                // Gestisci la cancellazione (quando chiudi l'app o scade il timeout)
                return "Canceled";
            }
            catch (Exception ex)
            {
                // Gestisci errori di rete
                return $"Error: {ex.Message}";
            }
        }

        /// <summary>
        /// Effettua chiamata URL IMMEDIATAMENTE e restituisce risultato
        /// </summary>
        /// <param name="URL"></param>
        /// <returns></returns>
        public static string CallUrlGet(string URL)
        {
            try
            {
                // Gestione certificati self-signed
                HttpClientHandler handler = new HttpClientHandler();
                if (URL.Contains("10.74.82."))
                {
                    handler.ServerCertificateCustomValidationCallback =
                        HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
                }

                using (var client = new HttpClient(handler))
                {
                    Stopwatch sw = new Stopwatch();
                    sw.Start();
                    // Timeout personalizzabile
                    client.Timeout = TimeSpan.FromMilliseconds(5000);

                    // Header custom
                    client.DefaultRequestHeaders.UserAgent.ParseAdd(CRS("appName"));

                    // GET sincrona
                    var response = client.GetAsync(URL).Result;

                    string answ = response.Content.ReadAsStringAsync().Result;

                    sw.Stop();
                    TrackUrlCall(URL, sw.Elapsed);

                    return answ;
                }
            }
            catch (Exception exc)
            {
                int pauseSendMSec = nextPauseSendMSec;
                dtVetoSend = DateTime.Now.AddMilliseconds(pauseSendMSec);

                if (logValuePermit(URL))
                {
                    lg.Error($"CallUrlGet | Errore chiamando {URL} | wait {pauseSendMSec} ms{Environment.NewLine}Eccezione:{Environment.NewLine}{exc}");
                }

                return "";
            }
        }

        /// <summary>
        /// Effettua chiamata IMMEDIATAMENTE URL e restituisce risultato
        /// </summary>
        /// <param name="URL"></param>
        /// <param name="payload"></param>
        /// <returns></returns>
        public static string CallUrlPost(string URL, string payload)
        {
            string answ = "";
            Stopwatch sw = new Stopwatch();
            sw.Start();

            try
            {
                // Gestione certificati self-signed
                HttpClientHandler handler = new HttpClientHandler();
                if (URL.Contains("10.74."))
                {
                    handler.ServerCertificateCustomValidationCallback =
                        HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
                }

                using (var client = new HttpClient(handler))
                {
                    // Timeout personalizzabile
                    client.Timeout = TimeSpan.FromMilliseconds(5000);

                    // Header custom
                    client.DefaultRequestHeaders.UserAgent.ParseAdd($"{CRS("appName")}-PAYLOAD");

                    // Corpo JSON
                    var content = new StringContent(payload, Encoding.UTF8, "application/json");

                    // POST sincrono
                    var response = client.PostAsync(URL, content).Result;

                    answ = response.Content.ReadAsStringAsync().Result;

                    // Log identico al tuo
                    if (answ != "OK")
                    {
                        if (logValuePermit($"{URL}|[{answ}]"))
                        {
                            lg.Error($"CallUrlPost | POST fallito | ans: [{answ}]:{Environment.NewLine}- URL{Environment.NewLine}{URL}{Environment.NewLine}- payload{Environment.NewLine}{payload}");
                        }
                    }
                }
            }
            catch (Exception exc)
            {
                int pauseSendMSec = nextPauseSendMSec;
                dtVetoSend = DateTime.Now.AddMilliseconds(pauseSendMSec);

                if (logValuePermit(URL))
                {
                    lg.Error($"CallUrlPost | Errore chiamando {URL} | wait {pauseSendMSec} ms | Dump Payload:{Environment.NewLine}{payload}{Environment.NewLine}Eccezione:{Environment.NewLine}{exc}");
                }
            }

            sw.Stop();
            TrackUrlCall(URL, sw.Elapsed);
            return answ;
        }

        /// <summary>
        /// Test esistenza/creazione directory
        /// </summary>
        /// <param name="dirPath"></param>
        public static void checkDir(string dirPath)
        {
            if (!Directory.Exists(dirPath))
            {
                lg.Info($"Dir not found: {dirPath}");
                Directory.CreateDirectory(dirPath);
                lg.Info($"reated local dir {dirPath}");
            }
        }

        /// <summary>
        /// Conteggio valori true in un array di boolean
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public static int CountTrue(params bool[] args)
        {
            return args.Count(t => t);
        }

        /// <summary>
        /// legge conf in formato BOOLean
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool CRB(string key)
        {
            bool answ = false;
            try
            {
                answ = Convert.ToBoolean(CRS(key));
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// legge conf in formato char
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static char CRC(string key)
        {
            char answ = '-';
            try
            {
                answ = ConfigurationManager.AppSettings[key].ToCharArray()[0];
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// legge conf in formato INT
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static Int32 CRI(string key)
        {
            int answ = 0;
            try
            {
                answ = Convert.ToInt32(CRS(key));
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// legge conf in formato stringa
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string CRS(string key)
        {
            return ConfigurationManager.AppSettings[key] ?? string.Empty;
        }

        /// <summary>
        /// Effettua chiamata URL tramite RestSharp e restituisce risultato, SE NON E' in veto send (x mitigare chiamate...)
        /// </summary>
        /// <param name="url">URI da chiamare</param>
        /// <param name="fastCall">Chiamata rapida (timeout rapido = 5sec)</param>
        /// <returns></returns>
        public static string ExecCallGet(string url, bool fastCall = false)
        {
            string answ = "";
            // divido key tra base e opzionale...
            Uri uri = new Uri(url);
            string baseUrl = $"{uri.Scheme}://{uri.Host}{(uri.IsDefaultPort ? "" : $":{uri.Port}")}";
            string resource = uri.AbsolutePath;
            RestClientOptions restOptStd = new RestClientOptions
            {
                BaseUrl = new Uri(baseUrl),
                Timeout = TimeSpan.FromSeconds(60)
            };
            if (!string.IsNullOrEmpty(url))
            {
                if (fastCall)
                {
                    restOptStd.Timeout = TimeSpan.FromSeconds(5);
                }
                try
                {
                    // client chiamate rest
                    using (var client = new RestClient(restOptStd))
                    {
                        var actReq = new RestRequest(resource, Method.Get);
                        // effettuo vera chiamata
                        var currResp = client.Get(actReq);
                        if (currResp.StatusCode == System.Net.HttpStatusCode.OK && currResp.Content != null)
                        {
                            answ = currResp.Content ?? "";
                        }
                    }
                }
                catch (Exception exc)
                {
                    lg.Error($"Eccezione in callRestUrl{Environment.NewLine}{exc}");
                }
            }
            return answ;
        }

        /// <summary>
        /// Effettua chiamata URL tramite RestSharp e restituisce risultato, SE NON E' in veto send (x mitigare chiamate...)
        /// </summary>
        /// <param name="url">URI da chiamare</param>
        /// <param name="payload">Payload da allegare (già serializzato)</param>
        /// <param name="fastCall">Chiamata rapida (timeout rapido = 5sec)</param>
        /// <returns></returns>
        public static string ExecCallPostJson(string url, string payload, bool fastCall = false)
        {
            string answ = "";

            // divido key tra base e opzionale...
            Uri uri = new Uri(url);
            string baseUrl = $"{uri.Scheme}://{uri.Host}{(uri.IsDefaultPort ? "" : $":{uri.Port}")}";
            string resource = uri.AbsolutePath;
            RestClientOptions restOptStd = new RestClientOptions
            {
                BaseUrl = new Uri(baseUrl),
                Timeout = TimeSpan.FromSeconds(60)
            };
            Stopwatch sw = new Stopwatch();
            sw.Start();

            if (!string.IsNullOrEmpty(url))
            {
                if (fastCall)
                {
                    restOptStd.Timeout = TimeSpan.FromSeconds(5);
                }
                try
                {
                    // client chiamate rest
                    using (var client = new RestClient(restOptStd))
                    {
                        var actReq = new RestRequest(resource, Method.Post);
                        actReq.AddJsonBody(payload);
                        // effettuo vera chiamata
                        var currResp = client.Post(actReq);
                        if (currResp.StatusCode == System.Net.HttpStatusCode.OK && currResp.Content != null)
                        {
                            answ = currResp.Content ?? "";
                        }
                    }
                }
                catch (Exception exc)
                {
                    lg.Error($"Eccezione in callRestUrlJson{Environment.NewLine}{exc}");
                }
            }
            return answ;
        }

        /// <summary>
        /// Effettua chiamata URL tramite RestSharp e restituisce risultato, SE NON E' in veto send (x mitigare chiamate...)
        /// </summary>
        /// <param name="url">URI da chiamare</param>
        /// <param name="payload">Payload da allegare (già serializzato)</param>
        /// <param name="fastCall">Chiamata rapida (timeout rapido = 5sec)</param>
        /// <returns></returns>
        public static string ExecCallPostPlain(string url, string payload, bool fastCall = false)
        {
            string answ = "";
            // divido key tra base e opzionale...
            Uri uri = new Uri(url);
            string baseUrl = $"{uri.Scheme}://{uri.Host}{(uri.IsDefaultPort ? "" : $":{uri.Port}")}";
            string resource = uri.AbsolutePath;
            RestClientOptions restOptStd = new RestClientOptions
            {
                BaseUrl = new Uri(baseUrl),
                Timeout = TimeSpan.FromSeconds(60)
            };
            Stopwatch sw = new Stopwatch();
            sw.Start();

            if (!string.IsNullOrEmpty(url))
            {
                if (fastCall)
                {
                    restOptStd.Timeout = TimeSpan.FromSeconds(5);
                }
                try
                {
                    // client chiamate rest
                    using (var client = new RestClient(restOptStd))
                    {
                        var actReq = new RestRequest(resource, Method.Post);
                        actReq.AddStringBody(payload, ContentType.Plain);
                        // effettuo vera chiamata
                        var currResp = client.Post(actReq);
                        if (currResp.StatusCode == System.Net.HttpStatusCode.OK && currResp.Content != null)
                        {
                            answ = currResp.Content ?? "";
                        }
                    }
                }
                catch (Exception exc)
                {
                    lg.Error($"Eccezione in callRestUrlJson | rawUrl: {url} {Environment.NewLine}{exc}");
                }
            }
            sw.Stop();
            TrackUrlCall(url, sw.Elapsed);
            return answ;
        }

        /// <summary>
        /// Calcolo MD5 del fine indicato
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static string GetFileMd5(string fileName)
        {
            string answ = "";
            using (var md5 = MD5.Create())
            {
                using (var stream = File.OpenRead(fileName))
                {
                    var hash = md5.ComputeHash(stream);
                    answ = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                }
            }
            return answ;
        }

        /// <summary>
        /// Restituisce la prima cifra (a sx) da un numero (es x decodere quale memoria modbus sia)
        /// </summary>
        /// <param name="num">Numero di cui trovare la priam cifra</param>
        /// <returns></returns>
        public static int getFirstInt(int num)
        {
            if (num >= 100000000) num /= 100000000;
            if (num >= 10000) num /= 10000;
            if (num >= 100) num /= 100;
            if (num >= 10) num /= 10;

#if false
            // formulazione alternativa con ciclo...
            while (num >= 10)
                num /= 10;
#endif

            return num;
        }

        /// <summary>
        /// IP della macchina
        /// </summary>
        /// <returns></returns>
        public static string GetIP()
        {
            NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
            String sIpAddr = string.Empty;
            try
            {
                foreach (NetworkInterface adapter in nics)
                {
                    if (sIpAddr == String.Empty)// only return IP Address from first card
                    {
                        IPInterfaceProperties properties = adapter.GetIPProperties();
                        foreach (var item in properties.UnicastAddresses)
                        {
                            if (item.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                            {
                                sIpAddr = item.Address.ToString();
                            }
                        }
                    }
                }
            }
            catch (Exception exc)
            {
                // controllo log permesso...
                if (logValuePermit("GetIP"))
                {
                    lg.Error(exc);
                }
            }
            return sIpAddr;
        }

        /// <summary>
        /// Macaddress della macchina
        /// </summary>
        /// <returns></returns>
        public static string GetMACAddress()
        {
            NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
            String sMacAddress = string.Empty;
            foreach (NetworkInterface adapter in nics)
            {
                if (sMacAddress == String.Empty)// 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;
        }

        /// <summary>
        /// Restituisce una stringa di soli caratteri numerici (stripe caratteri alfabetici)
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static string GetNumbers(string input)
        {
            return new string(input.Where(c => char.IsDigit(c)).ToArray());
        }

        /// <summary>
        /// Method to convert an integer to a string containing the number in binary. A negative
        /// number will be formatted as a 32-character binary number in two's compliment.
        /// </summary>
        /// <param name="theNumber">self-explanatory</param>
        /// <param name="minimumDigits">
        /// if binary number contains fewer characters leading zeros are added
        /// </param>
        /// <returns>string as described above</returns>
        public static string IntToBinStr(int theNumber, int minimumDigits)
        {
            return Convert.ToString(theNumber, 2).PadLeft(minimumDigits, '0');
        }

        /// <summary>
        /// verifica se un dato bit sia alzato (come flag di strobe)
        /// </summary>
        /// <param name="value">valore da testare</param>
        /// <param name="flag">
        /// valore cercato, può essere un singolo valore o un insieme in modalità AND
        /// </param>
        /// <returns></returns>
        public static bool IsSetAll(StFlag32 value, StFlag32 flag)
        {
            return ((value & flag) == flag);
        }

        /// <summary>
        /// verifica se un dato bit sia alzato (come flag di strobe)
        /// </summary>
        /// <param name="value">valore da testare</param>
        /// <param name="flag">
        /// valore cercato, può essere un singolo valore o un insieme in modalità OR
        /// </param>
        /// <returns></returns>
        public static bool IsSetAny(StFlag32 value, StFlag32 flag)
        {
            return ((value & flag) != 0);
        }

        /// <summary>
        /// Test ping x indirizzo indicato
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static bool pingAddress(IPAddress address)
        {
            bool answ = false;
            try
            {
                Ping pingSender = new Ping();
                PingReply reply = pingSender.Send(address, 100);
                // se passa il ping do OK...
                if (reply.Status == IPStatus.Success)
                {
                    answ = true;
                }
            }
            catch (Exception exc)
            {
                // controllo log permesso...
                if (logValuePermit("pingAddr"))
                {
                    lg.Error(exc);
                }
            }
            return answ;
        }

        /// <summary>
        /// Lettura dictionary da file
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static Dictionary<string, string> ReadBin(string file)
        {
            var result = new Dictionary<string, string>();
            // verifico file esista...
            if (!File.Exists(file))
            {
                FileStream fs = File.Create(file);
                fs.Close();
            }
            using (FileStream fs = File.OpenRead(file))
            using (BinaryReader reader = new BinaryReader(fs))
            {
                // Get count.
                int count = 0;
                try
                {
                    count = reader.ReadInt32();
                }
                catch
                { }
                // Read in all pairs.
                for (int i = 0; i < count; i++)
                {
                    string key = reader.ReadString();
                    string value = reader.ReadString();
                    result[key] = value;
                }
            }
            return result;
        }

        /// <summary>
        /// Lettura dictionary da file
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static Dictionary<string, string> ReadPlain(string file)
        {
            // inizializzo num righe lette...
            int numRow = 0;
            var result = new Dictionary<string, string>();
            // verifico file esista...
            if (!File.Exists(file))
            {
                FileStream fs = File.Create(file);
                fs.Close();
            }
            try
            {
                string[] lines = File.ReadAllLines(file);
                result = lines.Select(l => l.Split(':')).ToDictionary(a => a[0], a => a[1]);
                numRow = result.Count;
            }
            catch
            { }
            // se leggesse un valore NON coerente (senza righe) restituisce un file vuoto...
            if (numRow == 0)
            {
                result = new Dictionary<string, string>();
            }
            return result;
        }

        public static void resetWebClients()
        {
#if false
            // resetto i webclients...
            client = new WebClientWT();
            clientPayload = new WebClientWT();
#endif
        }

        /// <summary>
        /// Effettua reverse della stringa
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static string reverseStr(string s)
        {
            char[] arr = s.ToCharArray();
            Array.Reverse(arr);
            return new string(arr);
        }

        /// <summary>
        /// imposta un bit al valore richiesto duplicando il valore IN come OUT
        /// </summary>
        /// <param name="original">valore originale da aggiornare</param>
        /// <param name="bitBool">valore richiesto x il bit (0/1)</param>
        /// <param name="bitIndex">indice bit, 0 based (es: 0..31 per 32bit)</param>
        /// <returns></returns>
        public static byte[] setBitOnStFlag(byte[] original, bool bitBool, int bitIndex)
        {
            int bitVal = 0;
            if (bitBool)
            {
                bitVal = 1;
            }
            // risposta è identica ad originale...
            byte[] answ = original;
            // verifico se il bit è 0/1b
            if (bitVal <= 1 && bitVal >= 0)
            {
                // verifico se si possa aggiornare il bit richiesto (<= al totale dei bit...)
                if (bitIndex <= original.Length * 8 - 1)
                {
                    // calcolo byte
                    int byteIndex = bitIndex / 8;
                    // bit nel byte
                    int bitInByteIndex = bitIndex % 8;
                    // bit richiesto
                    byte mask = (byte)(bitVal << bitInByteIndex);
                    // imposto!
                    answ[byteIndex] |= mask;
                }
            }
            return answ;
        }

        /// <summary>
        /// provvede a verificare la dim della cartella dei log e cancella i + vecchi fino a restare
        /// a dim inferiori a _logMaxMb
        /// </summary>
        public static void shrinkDir(string dirPath)
        {
            // obj filemover...
            fileMover.obj.setDirectory(dirPath);
            float dirSizeMb = fileMover.obj.totalMb();
            lg.Info("Inizio shrinkDir LOG folder: {0} Mb", dirSizeMb);

            // ottengo elenco files *.txt
            FileInfo[] _fis = fileMover.obj.elencoFiles_FI("*.log");
            int numDdMax = 2;
            try
            {
                numDdMax = CRI("zipLogOldDay");
            }
            catch
            { }
            foreach (FileInfo _file in _fis)
            {
                if (_file.LastWriteTime < DateTime.Now.AddDays(-1)) // zippo files + vecchi di 2 gg...
                {
                    fileMover.obj.zippaSingoloFile(_file);
                    // cancello l'originale...
                    fileMover.obj.eliminaFile(_file);
                }
            }
            // inizio con eliminare file + vecchi della data indicata...
            int maxLogDays = CRI("maxLogDays");
            fileMover.obj.deleteOlderThan(maxLogDays);
            // ora controllo SE sia superata la dim max della directory --> in tal caso cancello dal
            // + vecchio...
            dirSizeMb = fileMover.obj.totalMb();
            int maxLogDirSize = CRI("maxLogDirSize");
            int maxTry = 100;
            // controllo se serva eliminare...
            if (dirSizeMb > maxLogDirSize)
            {
                lg.Info("Continuo shrinkDir LOG folder: {0} Mb --> ELIMINAZIONE FILES", dirSizeMb);
                while (dirSizeMb > maxLogDirSize)
                {
                    fileMover.obj.deleteOldest();
                    maxTry--;
                    if (maxTry > 0)
                    {
                        dirSizeMb = fileMover.obj.totalMb();
                    }
                    else
                    {
                        // per uscire fingo di aver ridotto...
                        dirSizeMb = maxLogDirSize - 1;
                    }
                }
                dirSizeMb = fileMover.obj.totalMb();
                lg.Info("Completata shrinkDir LOG folder: {0} Mb", dirSizeMb);
            }
        }

        /// <summary>
        /// Restitusice la stringa prima dell'ultima occorrenza del char richiesto
        /// </summary>
        /// <param name="origString">string originale</param>
        /// <param name="sepChar">char separatore da cercare</param>
        /// <returns></returns>
        public static string StringBeforeLastChar(string origString, char sepChar)
        {
            int index = origString.LastIndexOf(sepChar);
            string answ = (index == -1) ? origString : origString.Substring(0, index);
            return answ;
        }

        /// <summary>
        /// Rimuove query string e fragment in modo ottimizzato.
        /// Compatibile .NET 4.7 / C# 7.3
        /// </summary>
        public static string StripQueryAndFragment(string url)
        {
            if (string.IsNullOrEmpty(url)) return url;

            // ? è il separator standard per le query string
            int qIndex = url.IndexOf('?');
            if (qIndex >= 0) return url.Substring(0, qIndex);

            // Gestione opzionale di fragment # (es. #section)
            int hIndex = url.IndexOf('#');
            return hIndex >= 0 ? url.Substring(0, hIndex) : url;
        }

        /// <summary>
        /// Converte un bitarray a byte[]
        /// </summary>
        /// <param name="bits"></param>
        /// <returns></returns>
        public static byte[] ToByteArray(BitArray bits)
        {
            int numBytes = bits.Count / 8;
            if (bits.Count % 8 != 0)
            {
                numBytes++;
            }

            byte[] bytes = new byte[numBytes];
            int byteIndex = 0, bitIndex = 0;

            for (int i = 0; i < bits.Count; i++)
            {
                if (bits[i])
                {
                    bytes[byteIndex] |= (byte)(1 << (7 - bitIndex));
                }

                bitIndex++;
                if (bitIndex == 8)
                {
                    bitIndex = 0;
                    byteIndex++;
                }
            }

            return bytes;
        }

        /// <summary>
        /// Scrittura dictionary su file
        /// </summary>
        /// <param name="dictionary"></param>
        /// <param name="file"></param>
        public static void WriteBin(Dictionary<string, string> dictionary, string file)
        {
            using (FileStream fs = File.OpenWrite(file))
            using (BinaryWriter writer = new BinaryWriter(fs))
            {
                // Put count.
                writer.Write(dictionary.Count);
                // Write pairs.
                foreach (var pair in dictionary)
                {
                    writer.Write(pair.Key);
                    writer.Write(pair.Value);
                }
            }
        }

        /// <summary>
        /// Scrittura dictionary su file
        /// </summary>
        /// <param name="dictionary"></param>
        /// <param name="file"></param>
        public static void WritePlain(Dictionary<string, string> dictionary, string file)
        {
            string dirPath = file.Substring(0, file.LastIndexOf('\\'));
            // verifico directory
            checkDir(dirPath);
            string[] lines = dictionary.OrderBy(i => i.Key).Select(kvp => kvp.Key + ":" + kvp.Value).ToArray();
            //string[] lines = dictionary.OrderBy(i => i.Key).Select(kvp => kvp.Key + ":" + kvp.Value).ToArray();
            try
            {
                File.WriteAllLines(file, lines);
            }
            catch (Exception exc)
            {
                lg.Info(exc, string.Format("Errore in scrittura file {0}", file));
            }
        }

        #endregion Public Methods

        #region Protected Fields

        /// <summary>
        /// Classe logger
        /// </summary>
        protected static Logger lg = LogManager.GetCurrentClassLogger();

        #endregion Protected Fields

        #region Private Fields

        /// <summary>
        /// Client HTTP statico e riutilizzabile per tutto il ciclo di vita dell'app
        /// </summary>
        private static readonly HttpClient _httpClient = new HttpClient();

        /// <summary>
        /// Ultima ora registrata x statistiche track urlCall
        /// </summary>
        private static string LastHourCurr = "";

        /// <summary>
        /// Dizionario dei valori bloccati x evitare log eccessivo
        /// </summary>
        private static Dictionary<string, DateTime> vetoLogError = new Dictionary<string, DateTime>();

        /// <summary>
        /// Periodo di veto log in minuti
        /// </summary>
        private static int vetoPeriodMin = 30;

        #endregion Private Fields

        #region Private Methods

        /// <summary>
        /// Verifica se il log di un dato errore sia permesso
        /// </summary>
        /// <param name="logKey">ID del valore log da loggare/verificare</param>
        /// <returns></returns>
        private static bool logValuePermit(string logKey)
        {
            bool doLog = false;
            if (vetoLogError.ContainsKey(logKey))
            {
                // verifico se veto scaduto...
                if (DateTime.Now > vetoLogError[logKey])
                {
                    doLog = true;
                    vetoLogError[logKey] = DateTime.Now.AddMinutes(vetoPeriodMin);
                }
            }
            else
            {
                doLog = true;
                vetoLogError.Add(logKey, DateTime.Now.AddMinutes(vetoPeriodMin));
            }

            return doLog;
        }

        /// <summary>
        /// Traccia le chiamate URL (in ram) dall'ultimo riavvio, ogni ora scrive log
        /// </summary>
        /// <param name="rawUrl">URL chiamato</param>
        /// <param name="elapsed">durata chiamata</param>
        private static void TrackUrlCall(string rawUrl, TimeSpan elapsed)
        {
            // per l'URL tengo solo fino a prima dei parametri (prima di ?)
            string url = StripQueryAndFragment(rawUrl);
            // attenzione: x ora cablato 1 h per log pareto
            DateTime adesso = DateTime.Now;
            string HourCurr = $"{adesso:ddHH}";
            // se cambiata ora --> forzo scrittura senza aggiunte
            if (!LastHourCurr.Equals(HourCurr))
            {
                // recupero pareto chiamate...
                var callList = CallMetricsCollector.LogPareto();
                if (callList != null)
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine();
                    sb.AppendLine("------------------- CALL STATS ---------------------");
                    foreach (var item in callList)
                    {
                        sb.AppendLine($"num: {item.Count} | CumSum: {item.CumulativeParetoPct:P2} | avg: {item.AvgElapsed.TotalMilliseconds:N1} ms | {item.Key}");
                    }
                    sb.AppendLine("------------------- CALL STATS ---------------------");
                    lg.Info(sb.ToString());
                }
                LastHourCurr = HourCurr;
            }
            // accumulo stat oraria
            CallMetricsCollector.AddCall(url, elapsed);
        }

        #endregion Private Methods
    }

    /// <summary>
    /// Override metodo WebClient con gesitone TimeOut corto
    /// </summary>
    public class WebClientWT : WebClient
    {
        #region Protected Properties

        /// <summary>
        /// timeout da conf
        /// </summary>
        protected int urlCallTOut
        {
            get
            {
                int answ = 5000;
                answ = baseUtils.CRI("urlCallTOut");
                if (answ < 0 || answ > 10000)
                {
                    answ = 3000;
                }
                return answ;
            }
        }

        #endregion Protected Properties

        #region Protected Methods

        protected override WebRequest GetWebRequest(Uri address)
        {
            WebRequest wr = base.GetWebRequest(address);
            wr.Timeout = urlCallTOut; // timeout in milliseconds (ms)
            return wr;
        }

        #endregion Protected Methods
    }
}