/*
 * Copyright Copyright 2017, Steamware s.r.l. & CMS/SCM s.p.a.
 *
 * Based on data, code and example by MTC consortium & System Insights, Inc.
 *
 * */
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using NLog;
using MTC;
using NLog.Config;
using NLog.Targets;
using System.Collections;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using MTConnect;

namespace MTC_ADB
{
    public partial class MainForm : Form
    {
        #region variabili ed oggetti globali


        /// <summary>
        /// porta servizio MTC ADB
        /// </summary>
        protected int portMTC = 7879;
        /// <summary>
        /// Oggetto x gestione dell'adapter GENERICO (x poter usare metodi di ognuno...)
        /// </summary>
        AdapterGeneric agObj;
        /// <summary>
        /// timer base in avvio
        /// </summary>
        protected int startTimerMs = 250;
        /// <summary>
        /// timer base (base moltiplica)
        /// </summary>
        protected int timerIntMs = 100; // di norma 100 msec x timer base...
        /// <summary>
        /// contatore veloce
        /// </summary>
        protected int fastCount = 1; // 100ms primo refresh...
        /// <summary>
        /// contatore normale
        /// </summary>
        protected int normCount = 2; // 200ms primo refresh...
        /// <summary>
        /// contatore slow
        /// </summary>
        protected int slowCount = 3; // 300ms primo refresh...
        /// <summary>
        /// contatore lentissimo
        /// </summary>
        protected int verySlowCount = 4; // 400ms primo refresh...
        /// <summary>
        /// ultimo tentativo riavvio...
        /// </summary>
        protected DateTime lastStartTry;
        /// <summary>
        /// tipo di adapter prescelto...
        /// </summary>
        protected tipoAdapter tipoScelto = tipoAdapter.DB;

        /// <summary>
        /// oggetto logging
        /// </summary>
        public static Logger lg;

        #endregion

        public MainForm()
        {
            InitializeComponent();
            myInit();
        }
        /// <summary>
        /// inizializzazione specifica
        /// </summary>
        private void myInit()
        {
#if DEBUG
            // Setup the logging view for Sentinel - http://sentinel.codeplex.com
            var sentinalTarget = new NLogViewerTarget()
            {
                Name = "sentinal",
                Address = "udp://127.0.0.1:9999",
                IncludeNLogData = false
            };
            var sentinalRule = new LoggingRule("*", LogLevel.Trace, sentinalTarget);
            LogManager.Configuration.AddTarget("sentinal", sentinalTarget);
            LogManager.Configuration.LoggingRules.Add(sentinalRule);

#endif
            // avvio logger...
            LogManager.ReconfigExistingLoggers();
            lg = LogManager.GetCurrentClassLogger();
            // inizio
            setDefaults();
            displayTaskAndWait("Starting...");
            lastStartTry = DateTime.Now;
            advProgBar();

            // fix icon!
            Icon = Icon.ExtractAssociatedIcon(defIconFilePath);
            notifyIcon1.Icon = Icon.ExtractAssociatedIcon(defIconFilePath);

            // fix versione!
            lblApp.Text = string.Format("{0}", ConfigurationManager.AppSettings.Get("appName"));
            lblVers.Text = string.Format(" v.{0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);

            startTimerMs = utils.CRI("startTimerMs");
            advProgBar();

            displayTaskAndWait("Starting MainForm");

            // avvio adapter...
            agObj = new AdapterGeneric(this);
            loadPersistLayer(defPersLayerFile);
            lg.Info("PersLayerFile READ");
            agObj.loadPersData();
            lg.Info("PersLayerFile LOADED");

            // Start timer periodico
            gather.Interval = utils.CRI("timerIntMs");
            gather.Enabled = true;

            displayTaskAndWait("Running");
            createTrayMenu();
            displayTaskAndWait("Tray Menu OK");

            // avvio minimizzato se richiesto
            if (utils.CRB("startMinimized"))
            {
                // imposto minimized se necessario!
                if (WindowState != FormWindowState.Minimized)
                {
                    WindowState = FormWindowState.Minimized;
                }
                displayTaskAndWait("Minimized");
            }

            displayTaskAndWait("Main Form OK");

            // SE abilitato provo ad avviare...
            if (utils.CRB("autoStartOnLoad"))
            {
                avviaAdapter();
            }
            checkStarted();
            checkConnected();
        }

        /// <summary>
        /// Lettura conf DB
        /// </summary>
        private void loadServerConf()
        {
            agObj.loadServerConf();
            lblServer.Text = string.Format("{0} | {1}", agObj.ServerType, agObj.ServerAddress);
            displayTaskAndWait("...LOADING DB CONF...");
            checkConnected();
        }
        /// <summary>
        /// Tentativo connessione al DB
        /// </summary>
        private void connectAdapter()
        {
            displayTaskAndWait("...CONNECTIONG DB...");
            agObj.tryConnect();
            checkConnected();
        }
        /// <summary>
        /// Tentativo disconnessione al DB
        /// </summary>
        private void disconnectAdapter()
        {
            displayTaskAndWait("...DISCONNECTIONG DB...");
            agObj.tryDisconnect();
            checkConnected();
        }
        /// <summary>
        /// Verifica se il server sia connesso...
        /// </summary>
        private void checkConnected()
        {
            lblLastData.Text = DateTime.Now.ToString("yy-MM-dd HH:mm:ss");
            if (agObj.connectionOk)
            {
                lblStatus.Text = "CONNECTED";
                lblStatus.BackColor = Color.ForestGreen;
                lblStatus.ForeColor = Color.Yellow;
                btnConnect.Text = "close";
                lblServer.ForeColor = Color.Black;
                lblServer.Text = string.Format("{0} | {1}", agObj.ServerType, agObj.ServerAddress);
                // recupero conteggio degli eventi...
                advProgBar();
                lblNumData.Text = string.Format("{0} new data", agObj.numRecAvail);
                displayTaskAndWait("DB Server CONNECTED");
            }
            else
            {
                lblStatus.Text = "offline";
                lblStatus.BackColor = Color.DimGray;
                lblStatus.ForeColor = Color.White;
                btnConnect.Text = "open";
                lblServer.ForeColor = Color.DimGray;
                lblServer.Text = string.Format("???{0}???", agObj.ServerType, agObj.ServerAddress);
                lblNumData.Text = "#";
                displayTaskAndWait("ERROR connecting to DB Server");
            }
        }
        /// <summary>
        /// Verifica se l'adapter sia partito...
        /// </summary>
        private void checkStarted()
        {
            if (agObj.adpRunning)
            {

                btnStart.Text = "ADP STOP";
                btnStart.ForeColor = Color.DarkRed;
                displayTaskAndWait("ADP STARTED");
            }
            else
            {

                btnStart.Text = "ADP START";
                btnStart.ForeColor = Color.ForestGreen;
                displayTaskAndWait("ADP STOPPED");
            }
        }

        /// <summary>
        /// Verifica finale a fine show...
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Shown(object sender, EventArgs e)
        {
            // avvio minimizzato se richiesto
            if (utils.CRB("startMinimized"))
            {
                // controllo e mando a tray...
                sendToTray();
            }
            displayTaskAndWait("Main Form SHOWN");
        }
        /// <summary>
        /// mostra un testo sulla status bar ed attende startTimerMs
        /// </summary>
        /// <param name="txt2show"></param>
        public void displayTaskAndWait(string txt2show)
        {
            toolStripStatusLabel1.Text = txt2show;
            lg.Info(txt2show);
            // aggiorno componenti principali...
            toolStripStatusLabel1.Invalidate();
            lblServer.Refresh();
            lblStatus.Refresh();
            lblNumData.Refresh();
            advProgBar();
            Thread.Sleep(startTimerMs);
        }
        /// <summary>
        /// crea menù tray x applicazione
        /// </summary>
        private void createTrayMenu()
        {
            // Fix testi menù tray...
            trayMenu.Items.Clear();
            // SE permessa massimizzazione...
            if (utils.CRB("windowCanMax"))
            {
                trayMenu.Items.Add("Show MTC-ADB");
            }
            // se è permesso tray close...
            if (utils.CRB("trayClose"))
            {
                trayMenu.Items.Add("Close MTC-ADB");
            }
            advProgBar();
        }
        /// <summary>
        /// doppio click su tray icon
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void notifyIcon1_DoubleClick(object sender, EventArgs e)
        {
            // SOLO SE PERMESSO mostrare full...
            if (utils.CRB("windowCanMax"))
            {
                Show();
                WindowState = FormWindowState.Normal;
            }
        }
        /// <summary>
        /// evento resize
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Resize(object sender, EventArgs e)
        {
            checkFormVisibility();
        }
        /// <summary>
        /// Verifica stato windows (minimized/normal) e visibilità con tray...
        /// </summary>
        private void checkFormVisibility()
        {
            // se non può massimizzare imposto COMUNQUE a minimized...
            if (!utils.CRB("windowCanMax"))
            {
                WindowState = FormWindowState.Minimized;
            }
            // controllo cosa devo mostrare...
            if (WindowState == FormWindowState.Minimized)
            {
                notifyIcon1.Visible = false;
                sendToTray();
            }
            else
            {
                notifyIcon1.Visible = false;
            }

        }
        /// <summary>
        /// Gestisce "andata nel tray" della form
        /// </summary>
        private void sendToTray()
        {
            if (!notifyIcon1.Visible)
            {
                notifyIcon1.BalloonTipTitle = utils.CRS("appName");
                notifyIcon1.BalloonTipText = string.Format("{0} running on tray", utils.CRS("appName"));
                notifyIcon1.Visible = true;
                notifyIcon1.ShowBalloonTip(100);
            }
            Hide();
        }
        /// <summary>
        /// click su menù contestuale in tray
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void trayMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            if (e.ClickedItem.Text.StartsWith("Close"))
            {
                // stop adapter...
                closeAdapter();
                // chiudo!
                Close();
            }
            else if (e.ClickedItem.Text.StartsWith("Show"))
            {
                if (utils.CRB("windowCanMax"))
                {
                    Show();
                    WindowState = FormWindowState.Normal;
                }
            }

        }

        /// <summary>
        /// File persistenza generale
        /// </summary>
        protected string defPersLayerFile
        {
            get
            {
                return string.Format(@"{0}\{1}", utils.dataDatDir, utils.CRS("defaultPersLayerFile"));
            }
        }
        protected string defIconFilePath
        {
            get
            {
                return string.Format(@"{0}\MTCA.ico", utils.resxDir);
            }
        }
        /// <summary>
        /// impostazione valori defaults
        /// </summary>
        private void setDefaults()
        {
            MainProgrBar.Minimum = 0;
            MainProgrBar.Maximum = 300;
            MainProgrBar.Value = 0;
            MainProgrBar.Step = 1;
            portMTC = utils.CRI("portMTC");
        }
        /// <summary>
        /// Avvio adapter
        /// </summary>
        public void avviaAdapter()
        {
            // avvio adapter vero e proprio...
            displayTaskAndWait("Adapter starting");

            // inizio con la connessione al DB
            if (!agObj.connectionOk)
            {
                loadServerConf();
                connectAdapter();
                advProgBar();
            }
            agObj.startAdapter(portMTC);
            //displayTaskAndWait("Adapter started!");
            btnOpenDump.Enabled = true;
            if (utils.CRB("openDumpOnStart"))
            {
                displayTaskAndWait("Dump Window starting");
                apriDumpAgent();
                displayTaskAndWait("Dump Windows OK");
            }

            displayTaskAndWait("Start Timers");
            // inizializzo contatori fast/mid/slow
            fastCount = utils.CRI("fastCount");
            normCount = utils.CRI("normCount");
            slowCount = utils.CRI("slowCount");
            verySlowCount = utils.CRI("verySlowCount");
            displayTaskAndWait("Adapter Running...");

        }

        /// <summary>
        /// fermata dell'adapter
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void stop_Click(object sender, EventArgs e)
        {
            fermaAdapter(false);
            // salvo che ho fermato adapter
            lg.Info("UNLOAD Adapter");
        }
        /// <summary>
        /// Ferma l'adapter
        /// </summary>
        /// <param name="tryRestart">determina se si debba tentare riavvio automatico (per caduta connessione)</param>
        public void fermaAdapter(bool tryRestart)
        {
            fermaTutto(false, tryRestart);
        }

        private void gather_Tick(object sender, EventArgs e)
        {
            // eseguo cicli attivi SOLO se adapter è in EFFETTIVO running...
            if (agObj.adpRunning)
            {
                // inizio a riportare che sto eseguendo..
                advProgBar();
                if (agObj.connectionOk)
                {
                    // check esecuzione FastTask
                    checkFastTask();
                    // check esecuzione NormTask
                    checkNormTask();
                    // check esecuzione SlowTask
                    checkSlowTask();
                    // check esecuzione VerySlowTask
                    checkVerySlowTask();
                }
                else
                {
                    double currWait = DateTime.Now.Subtract(lastStartTry).TotalMilliseconds;
                    if (agObj.adpTryRestart && currWait > utils.CRI("waitRecMSec"))
                    {
                        // provo a connettermi...
                        lblStatus.Text = "...CONNECTING...";
                        lblStatus.BackColor = Color.Goldenrod;
                        displayTaskAndWait("Server Conf READ");
                        lastStartTry = DateTime.Now;
                        agObj.tryConnect();
                        agObj.loadPersData();
                        checkStarted();
                        checkConnected();
                    }
                }
                // se è arrivato a MAX resetto...
                if (MainProgrBar.Value >= MainProgrBar.Maximum)
                {
                    MainProgrBar.Value = 0;
                }
            }
            else
            {
                // verifico SE debba tentare il riavvio, ovvero NON running ma tryReconn e non ho riprovato x oltre waitRecMSec
                double currWait = DateTime.Now.Subtract(lastStartTry).TotalMilliseconds;
                if (agObj.adpTryRestart && currWait > utils.CRI("waitRecMSec"))
                {
                    lastStartTry = DateTime.Now;
                    avviaAdapter();
                }
            }
            advProgBar();
        }

        public void resetProgBar()
        {
            MainProgrBar.Value = 0;
        }

        private void checkSlowTask()
        {
            slowCount--;
            if (slowCount <= 0)
            {
                slowCount = utils.CRI("slowCount");
                agObj.gaterAndSend(gatherCycle.LF);
                // tenta salvataggio valori ATTUALI dei parametri ove applicabile/aggiornati --> sia file corrente che file "history"
                persistData();
            }
        }
        private void checkVerySlowTask()
        {
            verySlowCount--;
            if (verySlowCount <= 0)
            {
                verySlowCount = utils.CRI("verySlowCount");
                agObj.gaterAndSend(gatherCycle.VLF);
            }
        }

        /// <summary>
        /// file persistenza generale
        /// </summary>
        public string histPersLayerFile
        {
            get
            {
                return string.Format(@"{0}\{1:yyyy}\{1:yyyy-MM-dd}.mtc", utils.dataDatDir, DateTime.Now);
            }
        }


        /// <summary>
        /// file persistenza generale data attuale ANTICIPATA di xx giorni
        /// </summary>
        /// <param name="numDD">num DD di anticipo</param>
        /// <returns></returns>
        public string prevPersLayerFile(int numDD)
        {
            return string.Format(@"{0}\{1:yyyy}\{1:yyyy-MM-dd}.mtc", utils.dataDatDir, DateTime.Now.AddDays(-numDD));
        }
        /// <summary>
        /// salva i valori attuali del file di conf sia in file corrente che in cartella valori storici
        /// </summary>
        public void persistData()
        {
            // salvo ogni "autoSaveSec" secondi dall'ultimo salvataggio...
            TimeSpan tempoMod = new TimeSpan(0);
            if (File.Exists(defPersLayerFile))
            {
                DateTime adesso = DateTime.Now;
                tempoMod = DateTime.Now.Subtract(File.GetLastWriteTime(defPersLayerFile));
            }
            if (tempoMod.TotalSeconds > utils.CRI("autoSaveSec"))
            {
                savePersistLayer(defPersLayerFile);
                savePersistLayer(histPersLayerFile);
            }
        }

        private void checkNormTask()
        {
            // decremento...
            normCount--;
            // se il counter è a zero eseguo...
            if (normCount <= 0)
            {
                normCount = utils.CRI("normCount");
                agObj.gaterAndSend(gatherCycle.MF);
                lblLastData.Text = DateTime.Now.ToString("yy-MM-dd HH:mm:ss");
            }
        }
        /// <summary>
        /// Avanza la barra di stato...
        /// </summary>
        public void advProgBar()
        {
            try
            {
                MainProgrBar.PerformStep();
                if (MainProgrBar.Value >= MainProgrBar.Maximum) resetProgBar();
                MainProgrBar.Invalidate();
            }
            catch
            { }
        }

        private void checkFastTask()
        {
            // decremento...
            fastCount--;
            // se il counter è a zero eseguo...
            if (fastCount <= 0)
            {
                fastCount = utils.CRI("fastCount");
                agObj.gaterAndSend(gatherCycle.HF);
            }
        }


        /// <summary>
        /// apro eseguibile dump
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dump_Click(object sender, EventArgs e)
        {
            apriDumpAgent();
        }
        /// <summary>
        /// apre agent di dump in CMD
        /// </summary>
        private static void apriDumpAgent()
        {
            string path = Application.StartupPath;
            try
            {
                Process.Start(string.Format(@"{0}\..\..\dump\dump.exe", path));
            }
            catch
            {
                Process.Start(string.Format(@"{0}\dump\dump.exe", path));
            }
        }

        /// <summary>
        /// Salva su file l'oggetto di persistenza dati
        /// </summary>
        /// <param name="filePath"></param>
        public void savePersistLayer(string filePath)
        {
            // in primis check semaforo salvataggio...
            if (!agObj.adpSaving)
            {
                // alzo semaforo salvataggio
                agObj.adpSaving = true;
                // se HO dei dati...
                if (agObj.persistenceLayer != null)
                {
                    try
                    {
                        utils.WritePlain(agObj.persistenceLayer, filePath);
                    }
                    catch (Exception exc)
                    {
                        lg.Error(string.Format("Errore salvataggio file{0}{1}", Environment.NewLine, exc));
                    }
                }
                else
                {
                    lg.Info("persistenceLayer null, non salvato...");
                }
                // abbasso semaforo salvataggio
                agObj.adpSaving = false;
            }
        }
        /// <summary>
        /// Carica da file l'oggetto di persistenza dati
        /// </summary>
        /// <param name="filePath"></param>
        public void loadPersistLayer(string filePath)
        {
            // inizializzo prima di leggere...
            agObj.persistenceLayer = new Dictionary<string, string>();
            agObj.persistenceLayer = utils.ReadPlain(filePath);

            // 2017.03.23 check problema files corrotti...
            if (agObj.persistenceLayer.Count == 0)
            {
                // se avesse letto un valore NON coerente (senza righe) PROVA a leggere a ritroso vecchi files... da histPersLayerFile e precedenti...
                int numDD = 0;
                int maxNumDD = utils.CRI("maxNumDD");
                Dictionary<string, string> lastRead = new Dictionary<string, string>();
                // continuo fino a che non leggo almeno 1 riga valida e non ho raggiunto maxDD
                while (numDD < maxNumDD && lastRead.Count == 0)
                {
                    // leggo il file storico alla data anticipata... (ci provo...)
                    try
                    {
                        lastRead = utils.ReadPlain(prevPersLayerFile(numDD));
                    }
                    catch
                    { }
                    numDD++;
                }
                // se sono uscito PROVO a passare il file storico letto buono (oppure vuoto...)
                agObj.persistenceLayer = lastRead;
            }
        }


        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            closeAdapter();
        }

        private void closeAdapter()
        {
            fermaTutto(true, false);
        }
        /// <summary>
        /// Ferma tutti i componenti adapter + update buttons
        /// </summary>
        /// <param name="stopTimer">determina se fermare il timer (gather) principale (solo se non si chiude)</param>
        /// <param name="tryRestart">determina se tentare di riconnettersi</param>
        private void fermaTutto(bool stopTimer, bool tryRestart)
        {
            agObj.stopAdapter(tryRestart);
            // salvo!
            savePersistLayer(defPersLayerFile);
            savePersistLayer(histPersLayerFile);

            if (stopTimer)
            {
                gather.Enabled = false;
                agObj.tryDisconnect();
            }
            // verifica e display
            checkStarted();
            displayTaskAndWait("Adapter Stopped");
        }
        /// <summary>
        /// toggle connessione...
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnConnect_Click(object sender, EventArgs e)
        {
            // blocco button
            btnConnect.Enabled = false;
            btnConnect.Refresh();
            advProgBar();
            // processo
            if (agObj.connectionOk)
            {
                lblStatus.Text = "...CLOSING...";
                lblStatus.BackColor = Color.Goldenrod;
                displayTaskAndWait("Closing DB Connection");
                // CHIUDO!
                disconnectAdapter();
            }
            else
            {
                lblStatus.Text = "...CONNECTING...";
                lblStatus.BackColor = Color.Goldenrod;
                displayTaskAndWait("Opening DB Connection");
                // APRO!
                loadServerConf();
                connectAdapter();
            }
            // sblocco button...
            btnConnect.Enabled = true;
            btnConnect.Refresh();
            advProgBar();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            // blocco button
            btnStart.Enabled = false;
            btnStart.Refresh();
            advProgBar();
            // processo
            if (agObj.adpRunning)
            {
                // CHIUDO!
                fermaTutto(true, false);
            }
            else
            {
                gather.Enabled = true;
                avviaAdapter();
            }
            checkStarted();
            checkConnected();
            // sblocco button...
            btnStart.Enabled = true;
            btnStart.Refresh();
            advProgBar();
        }
    }
}
