using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Firefox;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace IOB_WIN_WPS.IobNet
{
    /// <summary>
    /// Generica classe per implementare WebPageScraping (scaricamento web pages anche js-based x
    /// recupero informazioni)
    /// </summary>
    public class WebPageScrap : Iob.GenericNext, IDisposable
    {
        /* --------------------------------------------------------------------------------
         * Controlli dotati di GENERICA pagina WEB in cui cercare e recuperare informazioni
         *
         * - il file di conf deve contenere l'URL di base
         * - il file di conf deve contenere COSA cercare (es il "contenitore" del dato da estrarre)
         *
         * -------------------------------------------------------------------------------- */

        #region Public Constructors

        /// <summary>
        /// Estende l'init della classe base...
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public WebPageScrap(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            lgInfo("INIT IobWPS");
            //reloadAdapterConf();
        }

        #endregion Public Constructors

        #region Public Methods

        public void Dispose()
        {
            driver.Dispose();
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Recupero dati dinamici in formato dictionary
        /// </summary>
        /// <returns></returns>
        public override Dictionary<string, string> getDynData()
        {
            lgInfo("Chiamata getDynData x IOB WebPageScraping!");
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            if (driver != null)
            {
                try
                {
                    /* ----------------------------------------------------------
                    *   Recupero dalla TUTTE le chiavi richieste...
                    *   */

                    string cKey = "";
                    string cVal = "";
                    // processo tutti i DynData...
                    foreach (var item in monitoredItems.DynData)
                    {
                        try
                        {
                            // cerco elemento indicato
                            var element = driver.FindElement(By.Id(item.val));
                            cVal = element.Text;
                            // verifico nome o key...
                            if (!string.IsNullOrEmpty(item.name))
                            {
                                cKey = item.name;
                            }
                            else
                            {
                                element = driver.FindElement(By.Id(item.key));
                                cKey = element.Text;
                            }
                            // controllo se devo inviare (per tipo di dato, x scadenza)
                            if (monItem2Send(cVal, item))
                            {
                                item.actVal = cVal;
                                item.DTScad = DateTime.Now.AddSeconds(item.sPeriod);
                                // accodo!
                                outVal.Add(cKey, cVal);
                            }
                        }
                        catch (Exception exc)
                        {
                            lgError($"Errore in FindElement | key: {item.key} | val: {item.val}{Environment.NewLine}{exc}");
                        }
                    }
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in getDynData x IOB WPS");
                }
            }
            else
            {
                lgError("Errore: driver null");
            }
            return outVal;
        }

        /// <summary>
        /// Effettua processing del recupero delle OVERRIDE (spindle, feedrate, rapid)
        /// </summary>
        public override void processOverride()
        {
        }

        /// <summary>
        /// Effettua lettura semafori principale
        /// <paramref name="currDispData">Parametri da aggiornare x display in form</paramref>
        /// </summary>
        public override void readSemafori(ref newDisplayData currDispData)
        {
            base.readSemafori(ref currDispData);
            // init a zero...
            B_input = 0;
            // ciclo!
            try
            {
                // controllo SE il driver SIA attivo...
                if (driver != null)
                {
                    string cKey = "";
                    string cVal = "";
                    // IPOTESI: un UNICO oggetto decodifica status
                    if (monitoredItems.Status.Count == 1)
                    {
                        var item = monitoredItems.Status[0];
                        // cerco elemento indicato
                        element = driver.FindElement(By.Id(item.val));
                        cKey = element.Text;
                        // verifico se mancasse il mapping...
                        if (!item.codeMapping.ContainsKey(cKey))
                        {
                            processUnknStatus(cKey);
                        }
                        else
                        {
                            // ora decodifico da variabile status a valore secondo impostazione "codeMapping"
                            cVal = item.codeMapping[cKey];
                            B_input = int.Parse(cVal, System.Globalization.NumberStyles.HexNumber);
                            if (currDispData != null)
                            {
                                currDispData.semIn = Semaforo.SV;
                            }
                        }
                    }
                }
                else
                {
                    lgError("Errore: driver non pronto (null)");
                }
                // riporto bitmap...
                reportRawInput(ref currDispData);
            }
            catch (Exception exc)
            {
                lgError(exc, "Errore in readSemafori x IOB WPS");
                if (currDispData != null)
                    currDispData.semIn = Semaforo.SR;
            }
        }

        public override void startAdapter(bool resetQueue)
        {
            // in primis RICARICO conf specifica...
            reloadAdapterConf();
            // continuo con start...
            base.startAdapter(resetQueue);
        }

        /// <summary>
        /// Override x chiusura driver...
        /// </summary>
        /// <param name="tryRestart"></param>
        /// <param name="forceDequeue"></param>
        public override async Task stopAdapter(bool tryRestart, bool forceDequeue)
        {
            try
            {
                // in primis chiudo driver...
                if (driver != null)
                {
                    driver.Quit();
                    driver.Dispose();
                }
            }
            catch (Exception exc)
            {
                lgError(exc, "Eccezione in tryDisconnect");
                // chiude tutti i processi firefox/geckodriver /...
                ForceKillByName(monitoredItems.SrvData.driverName);
                ForceKillByName("geckodriver");
            }

            // continuo
            await base.stopAdapter(tryRestart, forceDequeue);
        }

        /// <summary>
        /// Connessione
        /// </summary>
        public override void tryConnect()
        {
            // controllo ping --> segno connected...
            connectionOk = (testPingMachine == IPStatus.Success);
            if (connectionOk)
            {
                queueInEnabCurr = true;
                try
                {
                    startDriver();
                    lgInfo("Completato start driver");
                }
                catch (Exception exc)
                {
                    connectionOk = false;
                    lgError($"Eccezione in tryConnect{Environment.NewLine}{exc}");
                }
            }
            else
            {
                // aspetto prima di riprovare...
                Thread.Sleep(200);
            }
        }

        /// <summary>
        /// Disconnessione
        /// </summary>
        public override void tryDisconnect()
        {
            connectionOk = false;
            queueInEnabCurr = false;
            try
            {
                // in primis chiudo driver...
                if (driver != null)
                //if (driver != null && driver.WindowHandles.Count > 0)
                {
                    driver.Quit();
                }
            }
            catch (Exception exc)
            {
                lgError(exc, "Eccezione in tryDisconnect");
            }
        }

        #endregion Public Methods

        #region Protected Fields

        /// <summary>
        /// Pagina web da scaricare
        /// </summary>
        protected string baseUri = "http://127.0.0.1";

        /// <summary>
        /// Array di configurazione degli oggetti da cercare x decodifica e recupero info
        /// </summary>
        protected Dictionary<string, string> dataLocatorLUT;

        /// <summary>
        /// Driver per gestione chiamate crawling/scraping
        /// </summary>
        protected IWebDriver driver;

        /// <summary>
        /// Elementi da recuperare nella apgina web
        /// </summary>
        protected IWebElement element;

        /// <summary>
        /// Vettore della frequenza di ogni status trovato... invio ogni 100 rilevazioni (modulo
        /// 100, resto == 1)
        /// </summary>
        protected Dictionary<string, int> freqUnknStatus = new Dictionary<string, int>();

        /// <summary>
        /// Oggetti decodificati da pagina
        /// </summary>
        protected MonitoredItemsConf monitoredItems;

        #endregion Protected Fields

        #region Private Methods

        /// <summary>
        /// Processo stati unknown...
        /// </summary>
        /// <param name="cKey"></param>
        private void processUnknStatus(string cKey)
        {
            // cerco se avevo già una key nella dictionary...
            if (freqUnknStatus.ContainsKey(cKey))
            {
                freqUnknStatus[cKey]++;
                // se è 1 ogni 100 (%100, resto ==1) --> loggo...
                if (freqUnknStatus[cKey] % 100 == 1)
                {
                    lgInfo($"Errore in decodifica status: MAPPING non trovato per {cKey} | freq: {freqUnknStatus[cKey]}");
                    // accodo come invio di tipo FLOG...
                    string sVal = string.Format("[UnknStatus] {0}, freq: {1}", cKey, freqUnknStatus);
                    // chiamo accodamento...
                    bool sent = accodaFLog("UnknStatus", sVal, qEncodeFLog("UnknStatus", sVal));
                    if (sent)
                    {    // traccio valore DynData x analisi
                        trackDynData("UnknStatus", sVal);
                    }
                }
            }
            else
            {
                // creo chiave con freq = 1
                freqUnknStatus.Add(cKey, 1);
                // log iniziale
                lgInfo($"Errore in decodifica status: MAPPING non trovato per {cKey}");
            }
        }

        /// <summary>
        /// Ricarica conf adapter...
        /// </summary>
        private void reloadAdapterConf()
        {
            // init obj display
            newDisplayData currDispData = new newDisplayData();
            lgInfo("BEGIN reloadAdapterConf");
            // inizializzo LUT decodifica
            string jsonConf = getOptPar("LUT_CONF");
            if (!string.IsNullOrEmpty(jsonConf))
            {
                string jsonFullPath = $"{Application.StartupPath}/DATA/CONF/{jsonConf}";
                lgInfo($"Apertura file {jsonFullPath}");
                StreamReader reader = new StreamReader(jsonFullPath);
                string jsonData = reader.ReadToEnd();
                if (!string.IsNullOrEmpty(jsonData))
                {
                    try
                    {
                        monitoredItems = JsonConvert.DeserializeObject<MonitoredItemsConf>(jsonData);
                        // salvo baseUri
                        baseUri = monitoredItems.SrvData.baseUri;
                        lgInfo($"baseUri = {baseUri}");
                        // imposto a zero la bitmap x riavvio!
                        B_input = 0;
                        // FORZO invio dati...
                        accodaSigIN(ref currDispData);
                        // loggo!
                        lgInfo($"init input bitmap to zero: {B_input}");
                    }
                    catch (Exception exc)
                    {
                        lgError(exc, "Eccezione in decodifica conf json");
                    }
                }
                reader.Dispose();
            }
            lgInfo("DONE reloadAdapterConf");
            raiseRefresh(currDispData);
        }

        /// <summary>
        /// Avvio del WebDriver
        /// </summary>
        private void startDriver()
        {
            bool startOk = false;
            if (monitoredItems.SrvData.driverName == "CHROME")
            {
                lgInfo("Starting CHROME Driver");
                // preparo opzione headless x CHROME
                var o = new ChromeOptions();
                o.AddArgument("headless");
                ChromeDriverService service = ChromeDriverService.CreateDefaultService(Application.StartupPath);
                service.HideCommandPromptWindow = true;
                driver = new ChromeDriver(service, o);
                startOk = true;
            }
            else if (monitoredItems.SrvData.driverName == "EDGE")
            {
                lgInfo("Starting EDGE Driver");
                // preparo opzione headless x CHROME
                var o = new EdgeOptions();
                o.AddArgument("headless");
                EdgeDriverService service = EdgeDriverService.CreateDefaultService(Application.StartupPath);
                service.HideCommandPromptWindow = true;
                driver = new EdgeDriver(service, o);
                startOk = true;
            }
            else if (monitoredItems.SrvData.driverName == "FIREFOX")
            {
                lgInfo("Starting FIREFOX Driver");
                // preparo opzione headless x FIREFOX
                var o = new FirefoxOptions();
                o.AddArgument("-headless");
                o.SetPreference("app.update.auto", false);
                o.SetPreference("app.update.enabled", false);
                FirefoxDriverService service = FirefoxDriverService.CreateDefaultService(Application.StartupPath);
                service.HideCommandPromptWindow = true;
                driver = new FirefoxDriver(service, o);
                startOk = true;
            }
            else
            {
                lgError("ERRORE: manca indicazione driver: CHROME/EDGE/FIREFOX");
            }
            if (startOk)
            {
                // aggiungo timeout x JScripts
                driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);
                driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(20);
                // imposto pagina di acquisizione
                driver.Navigate().GoToUrl(baseUri);
            }
        }

        #endregion Private Methods
    }
}