using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;
using MTC;

namespace MTC_ADB
{
    using MTConnect;
    using System.Configuration;
    using System.Data.Common;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Threading;
    using System.Windows.Forms;


    public class AdapterGeneric
    {
        /// <summary>
        /// wrapper di log
        /// </summary>
        public static Logger lg;
        /// <summary>
        /// valore booleano di check se sia in fase di COMUNICAZIONE ATTIVA con il DB
        /// </summary>
        protected bool adpCommAct;
        /// <summary>
        /// valore booleano di check se sia stato AVVIATO l'adapter (Running)
        /// </summary>
        public bool adpRunning;
        /// <summary>
        /// valore booleano di check se l'adapter STIA SALVANDO
        /// </summary>
        public bool adpSaving;
        /// <summary>
        /// valore booleano (richiesta di riavvio automatico)
        /// </summary>
        public bool adpTryRestart;
        /// <summary>
        /// ultimo ID letto
        /// </summary>
        protected uint lastId = 0;
        /// <summary>
        /// DataOra ultimo avvio adapter x watchdog
        /// </summary>
        protected DateTime adpStartRun;
        /// <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>
        /// vettore gestione cronometraggi
        /// </summary>
        public DateTime inizio;

        /// <summary>
        /// adapter globale
        /// </summary>
        public Adapter mAdapter = new Adapter();
        /// <summary>
        /// Form chiamante
        /// </summary>
        protected MainForm parentForm;
        /// <summary>
        /// Numero tentativi di riconnesiosne prima di chiamare "fermaAdapter" nel parent...
        /// </summary>
        protected int numRetryConnDb = 10;
        /// <summary>
        /// Tipo server DB
        /// </summary>
        public string ServerType = "ND";
        /// <summary>
        /// Indirizzo server DB
        /// </summary>
        public string ServerAddress = "";
        /// <summary>
        /// Nome DB
        /// </summary>
        public string DbName = "";
        /// <summary>
        /// User connessione
        /// </summary>
        protected string DbUser = "";
        /// <summary>
        /// Pwd connessione
        /// </summary>
        protected string DbPwd = "";
        /// <summary>
        /// Oggetto connessione al DB
        /// </summary>
        protected DbConnection DbConn;
        /// <summary>
        /// testo della query da eseguire...
        /// </summary>
        public string Query { get; set; }

        /// <summary>
        /// Singola istanza valore tipo DbTimeseries
        /// </summary>
        public class DbTS
        {
            /// <summary>
            /// Vettore della serie storica
            /// </summary>
            public Sample mSampleData;
            /// <summary>
            /// Classe gestione dati POWER
            /// </summary>
            /// <param name="ident">Nome (univoco) variabile da pubblicare</param>
            public DbTS(string ident)
            {
                mSampleData = new Sample(ident);
            }
        }

        #region DataItems Globali

        /// <summary>
        /// Disponibilità (del DB)
        /// </summary>
        public Event mAvail = new Event("AVAIL");
        /// <summary>
        /// Status (ADP Running = ON / stopped INACTIVE)
        /// </summary>
        public Event mStatus = new Event("STATUS");
        /// <summary>
        /// Orologio
        /// </summary>
        public Sample mClock = new Sample("CLOCK");
        /// <summary>
        /// Messaggi (generico)
        /// </summary>
        public MTConnect.Message mMessage = new MTConnect.Message("MESSAGE");

        /// <summary>
        /// vettore dati da lettura DB
        /// </summary>
        public DbTS[] vettDbData;

        #endregion


        public event EventHandler eh_refreshed;

        /// <summary>
        /// inizializzo l'oggetto
        /// </summary>
        /// <param name="caller"></param>
        /// <param name="adpConf"></param>
        public AdapterGeneric(MainForm caller)
        {
            lg = LogManager.GetCurrentClassLogger();
            lg.Info("Avvio AdapterGeneric");

            // salvo al form chiamante
            parentForm = caller;

            numRetryConnDb = utils.CRI("numRetryConnDb");

            adpRunning = false;
            adpSaving = false;

            // item base
            mAdapter.AddDataItem(mAvail);
            mAdapter.AddDataItem(mStatus);
            mAdapter.AddDataItem(mClock);
            mAdapter.AddDataItem(mMessage);
            mAvail.Value = "ND";
            mStatus.Value = "ND";
            mClock.Value = string.Format("{0:yyyy-MM-dd} {0:HH:mm:ss}", DateTime.Now);
            mMessage.Code = "ND";
            mMessage.Value = "INIT";

            createDbConn();

            // aggiunto 1 valore x la gestione timeseries...
            vettDbData = new DbTS[1];
            for (int i = 0; i < vettDbData.Length; i++)
            {
                vettDbData[i] = new DbTS(string.Format("DbVal_E_{0:00}", i + 1));
                mAdapter.AddDataItem(vettDbData[i].mSampleData);
            }

            // concluso!
            lg.Info("Istanziata classe ADB");
        }


        /// <summary>
        /// Inizializzazione oggetti connessione al DB...
        /// </summary>
        private void createDbConn()
        {
            DbProviderFactory factory = DbProviderFactories.GetFactory("MySql.Data.MySqlClient");
            DbConn = factory.CreateConnection();
        }
        /// <summary>
        /// Lettura conf DB
        /// </summary>
        public void loadServerConf()
        {
            // disconnetto e riconnetto SE fosse già connesso...
            if (!connectionOk)
            {
                ServerType = utils.CRS("ServerType");
                ServerAddress = utils.CRS("ServerAddress");
                DbName = utils.CRS("DbName");
                DbUser = utils.CRS("DbUser");
                DbPwd = utils.CRS("DbPwd");
                DbConn.ConnectionString = string.Format("SERVER={0};database={1};uid={2};password={3}", ServerAddress, DbName, DbUser, DbPwd);
            }
        }

        /// <summary>
        /// Caricamento altri file necessari epr adapter all'avvio
        /// </summary>
        protected virtual void loadOtherFile()
        {

        }

        #region metodi adapter

        public void loadPersData()
        {
            // nuova lettura valori da file persistenza...
            lastId = getStoredValUInt("LAST_ID");
        }

        /// <summary>
        /// Avvia l'adapter sulla porta richiesta
        /// </summary>
        /// <param name="port"></param>
        public virtual void startAdapter(int port)
        {
            lg.Info("Starting adapter...");
            adpRunning = true;
            dtAvvioAdp = DateTime.Now;
            // inizializzo vettori di utility..
            loadOtherFile();

            // salvo porta!
            mAdapter.Port = port;
            // avvio!
            mAdapter.Start();

            // setto status a ON
            mStatus.Value = "ON";

            // resetto running flag...
            adpCommAct = false;


            //mAlarmGeneral.Normal();
            adpTryRestart = true;
            parentForm.displayTaskAndWait("Adapter Started!");
        }

        /// <summary>
        /// ferma l'adapter...
        /// </summary>
        /// <param name="tryRestart">indica se si debba tentare di riavviare l'adapter (con caduta connessione viene fermato in automatico)</param>
        public void stopAdapter(bool tryRestart)
        {
            parentForm.displayTaskAndWait("Stopping adapter...");
            adpTryRestart = false;
            mStatus.Value = "INACTIVE";
            // faccio una chiamata extra ad ogni metodo di check...
            gaterAndSend(gatherCycle.HF);
            gaterAndSend(gatherCycle.MF);
            gaterAndSend(gatherCycle.LF);
            gaterAndSend(gatherCycle.VLF);
            parentForm.displayTaskAndWait("Stopping adapter - last periodic data read...");

            // chiudo la connessione all'adapter...
            tryDisconnect();
            // Stop everything...
            try
            {
                mAdapter.Stop();
            }
            catch (Exception exc)
            {
                lg.Error(exc, "Eccezione in chiusura Adapter");
            }
            dtStopAdp = DateTime.Now;
            adpTryRestart = tryRestart;
            adpRunning = false;
            // chiudo!
            parentForm.resetProgBar();
            parentForm.displayTaskAndWait("Adapter Stopped.");
        }

        /// <summary>
        ///  effettua recupero dati ed invio valori modificati...
        /// </summary>
        /// <param name="ciclo"></param>
        public void gaterAndSend(gatherCycle ciclo)
        {
            // controllo connessione/connettività
            if (connectionOk)
            {
                // controllo non sia già in esecuzione...
                if (!adpCommAct)
                {
                    // provo ad avviare
                    try
                    {
                        // avvio fase raccolta dati
                        mAdapter.Begin();
                        // imposto flag adapter running..
                        adpCommAct = true;
                        adpStartRun = DateTime.Now;
                    }
                    catch (Exception exc)
                    {
                        parentForm.displayTaskAndWait(string.Format("Adapter NOT STARTED!!!{0}{1}", Environment.NewLine, exc));
                        adpCommAct = false;
                        adpStartRun = DateTime.Now;
                    }
                    if (adpCommAct)
                    {
                        // try / catch generale altrimenti segno che è disconnesso...
                        try
                        {

                            // ciclo HF (nulla)
                            if (ciclo == gatherCycle.HF)
                            {
                                // gestisco dati stato adapter
                                getGlobalData();
                            }
                            else if (ciclo == gatherCycle.MF)
                            {
                                getMainData();
                            }
                            // ciclo lento
                            else if (ciclo == gatherCycle.LF)
                            {
                                // invio dati "lenti"
                                getSlowChangingData();
                            }
                            // ciclo lentissimo
                            else if (ciclo == gatherCycle.VLF)
                            {
                                // eventuale log!
                                if (utils.CRB("recTime")) logTimeResults();
                            }
                            // INVIO dati variati!
                            mAdapter.SendChanged();
                        }
                        catch (Exception exc)
                        {
                            // segnalo eccezione e indico disconnesso...
                            DbConn.Close();
                            numRetryConnDb--;
                            if (numRetryConnDb < 0)
                            {
                                numRetryConnDb = utils.CRI("numRetryConnDb");
                                lg.Error(exc, string.Format("Errore in gestione ciclo principale ADP, richiesto reset completo{0}{1}", Environment.NewLine, exc));
                                mMessage.Code = "ERR-ADP-RESET";
                                mMessage.Value = "Effettuato Reset Adapter";
                                parentForm.fermaAdapter(true);
                            }
                            else
                            {
                                lg.Error(exc, string.Format("Errore in gestione ciclo principale ADP, tentativo reset{0}{1}", Environment.NewLine, exc));
                                mMessage.Code = "ERR-DB-RESET";
                                mMessage.Value = "Reset Connessione DB";
                            }
                        }
                        // tolgo flag running
                        adpCommAct = false;
                    }
                    else
                    {
                        lg.Info("ADP not running...");
                    }
                }
                else
                {
                    // log ADP running
                    lg.Error("Non eseguo chiamata: ADP ancora in running");
                    // se è bloccato da oltre maxSec lo sblocco...
                    if (DateTime.Now.Subtract(adpStartRun).TotalSeconds > utils.CRI("maxAdapterLockSec"))
                    {
                        // tolgo flag running
                        adpCommAct = false;
                        adpStartRun = DateTime.Now;
                    }
                }
            }
            else
            {
                // log connessione KO
                lg.Error("ADP - Connessione non disponibile, provo a riconnettere");
                mMessage.Code = "ERR-DB-CONN";
                mMessage.Value = "Connessione DB non disponibile, tentativo riconnessione";

                // provo a riconnettere SE abilitato tryRestart...
                if (adpTryRestart && !connectionOk)
                {
                    lg.Info("ConnKO - tryConnect");
                    tryConnect();
                }
            }
            if (eh_refreshed != null)
            {
                eh_refreshed(this, new EventArgs());
            }
        }
        /// <summary>
        /// salvo e controllo dati globali...
        /// </summary>
        public virtual void getGlobalData()
        {
            string currAvail = "";
            if (connectionOk)
            {
                currAvail = "AVAILABLE";
            }
            else
            {
                currAvail = "DISCONNECTED";
            }
            if (mAvail.Value.ToString() != currAvail)
            {
                mAvail.Value = currAvail;
            }
        }

        /// <summary>
        /// riporta il log di tutti i dati di results temporali registrati
        /// </summary>
        public void logTimeResults()
        {
            if (TimingData.results.Count > 0)
            {
                lg.Info("{0}--------------- START TIMING DATA ---------------", Environment.NewLine);
                int globNumCall = 0;
                TimeSpan globAvgMsec = new TimeSpan(0);
                foreach (TimeRec item in TimingData.results)
                {
                    lg.Info("Chiamate {0}: effettuate {1}, tempo medio {2:N2} msec", item.codCall, item.numCall, item.avgMsec);
                    globNumCall += item.numCall;
                    globAvgMsec += item.totMsec;
                }
                // riporto conteggio medio al secondo...
                lg.Info("Chiamate GLOBALI: {0}, periodo: {1:N2} minuti.cent, tempo medio {2:N2} msec, impegno MEDIO del canale {3:P3}", globNumCall, DateTime.Now.Subtract(dtAvvioAdp).TotalMinutes, globAvgMsec.TotalMilliseconds / globNumCall, globAvgMsec.TotalSeconds / DateTime.Now.Subtract(dtAvvioAdp).TotalSeconds);
                lg.Info("{0}--------------- STOP TIMING DATA ---------------{0}", Environment.NewLine);
            }
        }

        /// <summary>
        /// Metodo base connessione...
        /// </summary>
        public virtual void tryConnect()
        {
            if (!connectionOk)
            {
                try
                {
                    DbConn.Open();
                    mMessage.Code = "DB-CONN";
                    mMessage.Value = "DB Connesso";
                }
                catch (Exception exc)
                {
                    mMessage.Code = "DB-DISC";
                    mMessage.Value = "DB Disconnesso";
                    lg.Error(exc, string.Format("Errore apertura connessione DB: {0}{1}", Environment.NewLine, exc));
                    mAdapter.SendChanged();
                }
            }
            else
            {
                lg.Info("Exit tryConnect (ADP connected)");
            }
        }
        /// <summary>
        /// Metodo base disconnessione...
        /// </summary>
        public virtual void tryDisconnect()
        {
            try
            {
                // CHIUDO!
                DbConn.Close();
                mMessage.Code = "DB-DISC";
                mMessage.Value = "ADP - Disconnessione DB";
            }
            catch (Exception exc)
            {
                lg.Error(exc, string.Format("Errore in tryDisconnect da DB: {0}{1}", Environment.NewLine, exc));
                mMessage.Code = "ERR-DB-DISC";
                mMessage.Value = "Errore durante disconnessione DB";
            }
        }
        /// <summary>
        /// Salva verifica stato connessione OK
        /// </summary>
        /// <returns></returns>
        public virtual bool connectionOk
        {
            get
            {
                return DbConn.State == System.Data.ConnectionState.Open;
            }
        }
        /// <summary>
        /// Restituisce conteggio record disponibili e lo restituisce
        /// </summary>
        /// <returns></returns>
        public string numRecAvail
        {
            get
            {
                string answ = "NA";
                if (connectionOk)
                {
                    // recupero conteggio degli eventi...
                    Query = string.Format("SELECT COUNT(id) AS num FROM tracelog WHERE resource = 'E' AND device=1 AND id > {0}", lastId);
                    DbCommand qrycmd = DbConn.CreateCommand();
                    qrycmd.CommandText = Query;
                    qrycmd.Connection = DbConn;
                    using (DbDataReader reader = qrycmd.ExecuteReader())
                    {
                        try
                        {
                            while (reader.Read())
                            {
                                answ = reader["num"].ToString();
                            }
                            // chiudo reader!
                            reader.Close();

                        }
                        catch (Exception exc)
                        {
                            lg.Error(exc, string.Format("Errore in recupero conteggio eventi: {0}{1}", Environment.NewLine, exc));
                            answ = "ERR";
                            mMessage.Code = "ERR-DB-COUNT";
                            mMessage.Value = "Errore conteggio dati da DB";
                        }
                    }
                }
                return answ;
            }
        }
        /// <summary>
        /// Recupero dati principali (DB)
        /// </summary>
        public virtual void getMainData()
        {
            // recupero da DB ultimo valore NON inviato e se c'è lo invio...
            string valore = "-999";
            bool valFound = false;
            if (connectionOk)
            {
                // recupero conteggio degli eventi... se in debug rileggo da inizio altrimenti SOLO ultimo valore...
#if DEBUG
                Query = string.Format("SELECT id, value FROM tracelog WHERE resource = 'E' AND device=1 AND id > {0} ORDER BY id LIMIT 1", lastId);
#else
                Query = string.Format("SELECT id, value FROM tracelog WHERE resource = 'E' AND device=1 AND id > {0} ORDER BY id DESC LIMIT 1", lastId);
#endif
                DbCommand qrycmd = DbConn.CreateCommand();
                qrycmd.CommandText = Query;
                qrycmd.Connection = DbConn;
                using (DbDataReader reader = qrycmd.ExecuteReader())
                {
                    try
                    {
                        while (reader.Read())
                        {
                            valore = reader["value"].ToString();
                            lastId = Convert.ToUInt32(reader["id"].ToString());
                            valFound = true;
                        }
                        // chiudo reader!
                        reader.Close();
                    }
                    catch (Exception exc)
                    {
                        lg.Error(exc, string.Format("Errore in recupero dati da DB: {0}{1}", Environment.NewLine, exc));
                        valore = "-99";
                        mMessage.Code = "ERR-DB-READ";
                        mMessage.Value = "Errore recupero dati da DB";
                    }
                }
                if (valFound)
                {
                    // salvo in sample SE trovato...!
                    vettDbData[0].mSampleData.Value = valore;
                    // salvo lastID...
                    updateValUInt(0, lastId, "LAST_ID");
                }
            }
        }

        /// <summary>
        /// dati "lenti" relativi al device
        /// </summary>
        public virtual void getSlowChangingData()
        {
            // dati da PC
            mClock.Value = string.Format("{0:yyyy-MM-dd} {0:HH:mm:ss}", DateTime.Now);
        }


        #endregion


        #region layer persistenza dati

        /// <summary>
        /// Dizionario di persistenza per i valori da salvare da/su file
        /// </summary>
        public Dictionary<string, string> persistenceLayer;

        /// <summary>
        /// recupera valore salvato in persistence layer (se non c'è crea...)
        /// </summary>
        /// <param name="keyVal"></param>
        /// <returns></returns>
        private string getStoredVal(string keyVal)
        {
            string value = "";
            try
            {
                if (!persistenceLayer.TryGetValue(keyVal, out value))
                {
                    persistenceLayer.Add(keyVal, "0");
                }
            }
            catch
            { }
            return value;
        }
        /// <summary>
        /// recupera valore salvato in persistence layer (se non c'è crea...) come UINT
        /// </summary>
        /// <param name="keyVal"></param>
        /// <returns></returns>
        private uint getStoredValUInt(string keyVal)
        {
            uint answ = 0;
            try
            {
                answ = Convert.ToUInt32(getStoredVal(keyVal));
            }
            catch
            { }
            return answ;
        }
        /// <summary>
        /// recupera valore salvato in persistence layer (se non c'è crea...) come INT
        /// </summary>
        /// <param name="keyVal"></param>
        /// <returns></returns>
        private long getStoredValLong(string keyVal)
        {
            long answ = 0;
            try
            {
                answ = Convert.ToInt64(getStoredVal(keyVal));
            }
            catch
            { }
            return answ;
        }
        /// <summary>
        /// recupera valore salvato in persistence layer (se non c'è crea...) come double
        /// </summary>
        /// <param name="keyVal"></param>
        /// <returns></returns>
        private double getStoredValDouble(string keyVal)
        {
            double answ = 0;
            try
            {
                answ = Convert.ToDouble(getStoredVal(keyVal));
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Aggiorna un valore del dizionario in SOSTITUZIONE
        /// </summary>
        /// <param name="i"></param>
        /// <param name="newVal"></param>
        /// <param name="searchString"></param>
        /// <returns>Nuovo valore incrementato</returns>
        private void updateValString(int i, string newVal, string searchString)
        {
            // stringa da cercare..
            string keyVal = string.Format(searchString, i + 1);
            // salvo in ram!
            persistenceLayer[keyVal] = newVal;
        }
        /// <summary>
        /// Aggiorna un valore del dizionario in SOSTITUZIONE e lo restituisce
        /// </summary>
        /// <param name="i"></param>
        /// <param name="newVal"></param>
        /// <param name="searchString"></param>
        /// <returns>Nuovo valore incrementato</returns>
        private void updateValUInt(int i, uint newVal, string searchString)
        {
            // stringa da cercare..
            string keyVal = string.Format(searchString, i + 1);
            // salvo in ram!
            persistenceLayer[keyVal] = newVal.ToString();
        }

        #endregion
    }
}
