using MapoSDK;
using NLog;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading.Tasks;

namespace IOB_UT
{

  /// <summary>
  /// Override metodo WebClient con gesitone TimeOut corto
  /// </summary>
  public class WebClientWT : WebClient
  {
    /// <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;
      }
    }
    protected override WebRequest GetWebRequest(Uri address)
    {
      WebRequest wr = base.GetWebRequest(address);
      wr.Timeout = urlCallTOut; // timeout in milliseconds (ms)
      return wr;
    }
  }

  public class baseUtils
  {

    /// <summary>
    /// Oggetto connessione REDIS
    /// </summary>
    public static RedisIobCache redisMan = new RedisIobCache();
    /// <summary>
    /// Indicazione VETO PING a server sino alla data-ora indicata
    /// </summary>
    public static DateTime dtVetoPing = DateTime.Now;
#if false
    {
      get
      {
        return redisMan.servStatus.dtVetoPing;
      }
      set
      {
        var currData = redisMan.servStatus;
        currData.dtVetoPing = value;
        redisMan.servStatus = currData;
      }
    } 
#endif
    /// <summary>
    /// Indicazione VETO invio a server sino alla data-ora indicata
    /// </summary>
    public static DateTime dtVetoSend = DateTime.Now;
    /// <summary>
    /// Status MP_IO
    /// </summary>
    public static bool MPIO_Online = false;
    /// <summary>
    /// Status IOB
    /// </summary>
    public static bool IOB_Online = false;

    /// <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 answ = CRI("pauseSendMSec");
        // aggiungo NOISE... +/- 33%    
        Random rnd = new Random();
        int noise = rnd.Next(1, answ / 3);
        answ += noise - (answ / 6);
        return answ;
      }
    }
    /// <summary>
    /// Classe logger
    /// </summary>
    public static Logger lg = LogManager.GetCurrentClassLogger();
    /// <summary>
    /// Fattore di riduzione dei log
    /// </summary>
    public static int logReduxFactor = 10;
    /// <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 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 stringa
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public static string CRS(string key)
    {
      string answ = "";
      try
      {
        answ = ConfigurationManager.AppSettings[key].ToString();
      }
      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 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>
    /// 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>
    /// 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 BinaryFormatter(), "{0:B}", valore);
      }
      catch
      { }
      return answ;
    }
    /// <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>
    /// 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>
    /// 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>
    /// 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>
    /// 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
      if (!Directory.Exists(dirPath))
      {
        Directory.CreateDirectory(dirPath);
      }
      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));
      }
    }
    /// <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;
    }
    /// <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)
      {
        lg.Error(exc);
      }
      return answ;
    }
    /// <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)
      {
        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>
    /// Oggetto WebClient BASE da riutilizzare
    /// </summary>
    protected static WebClientWT client { get; set; }
    /// <summary>
    /// Oggetto WebClient con PAYLOAD da riutilizzare
    /// </summary>
    protected static WebClientWT clientPayload { get; set; }

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

    /// <summary>
    /// Effettua chiamata URL IMMEDIATAMENTE e restituisce risultato
    /// </summary>
    /// <param name="URL"></param>
    /// <returns></returns>
    public static string callUrlNow(string URL)
    {
      string answ = "";
      if (client == null)
      {
        client = new WebClientWT();
      }
      client.Headers.Add("user-agent", $"{CRS("appName")}");
#if false
      WebClientWT client = new WebClientWT(); 
      client.Headers.Add("user-agent", CRS("appName"));
#endif
      try
      {
        answ = client.DownloadString(URL);
      }
      catch (Exception exc)
      {
        // imposto veto
        int pauseSendMSec = nextPauseSendMSec;
        dtVetoSend = DateTime.Now.AddMilliseconds(pauseSendMSec);


        // log ogni 
        lg.Info($"Errore in callURL verso {URL}: impostato attesa di {pauseSendMSec} ms prima della prossima chiamata");
        logReduxFactor--;
        if (logReduxFactor <= 0)
        {
          lg.Error($"Eccezione in esecuzione callURL: {URL}{Environment.NewLine}{exc}");
          logReduxFactor = 10;
        }
      }
      // restituisco valore!
      return answ;
    }
    /// <summary>
    /// Effettua chiamata IMMEDIATAMENTE URL e restituisce risultato
    /// </summary>
    /// <param name="URL"></param>
    /// <param name="payload"></param>
    /// <returns></returns>
    public static string callUrlNow(string URL, string payload)
    {
      string answ = "";
      if (clientPayload == null)
      {
        clientPayload = new WebClientWT();
      }
      clientPayload.Headers.Add("user-agent", $"{CRS("appName")}-PAYLOAD");
#if false
      WebClientWT clientPayload = new WebClientWT();
      clientPayload.Headers.Add("user-agent", CRS("appName"));
#endif
      try
      {
        answ = clientPayload.UploadString(URL, payload);
      }
      catch (Exception exc)
      {
        // imposto veto
        int pauseSendMSec = nextPauseSendMSec;
        dtVetoSend = DateTime.Now.AddMilliseconds(pauseSendMSec);

        // log ogni 
        lg.Info($"Errore in callURL con PAYLOAD verso {URL}: impostato attesa di {pauseSendMSec} ms prima della prossima chiamata");
        logReduxFactor--;
        if (logReduxFactor <= 0)
        {
          lg.Error($"Eccezione in esecuzione callURL: {URL}{Environment.NewLine}con payload: {payload}{Environment.NewLine}{exc}");
          logReduxFactor = 10;
        }
      }
      // restituisco valore!
      return answ;
    }
    /// <summary>
    /// Effettua chiamata URL e restituisce risultato, SE NON E' in veto send (x mmitigare 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 = callUrlNow(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)
      {
        callUrlNow(URL, payload);
      }
      // restituisco valore!
      return answ;
    }
    /// <summary>
    /// Versione async della chiamata ad URL
    /// </summary>
    /// <param name="URL"></param>
    /// <returns></returns>
    public static string callUrlAsync(string URL)
    {
      // Chiamo in modalità task...
      var resp = TaskEx.Run(() => callUrl(URL));
      return resp.Result;
    }
    /// <summary>
    /// Versione async della chiamata ad URL
    /// </summary>
    /// <param name="URL"></param>
    /// <param name="payload"></param>
    /// <returns></returns>
    public static string callUrlAsync(string URL, string payload)
    {
      // Chiamo in modalità task...
      var resp = TaskEx.Run(() => callUrl(URL, payload));
      return resp.Result;
    }


    /// <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);
        }
      }
      // 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);
      }
    }
  }

}