using CndexLinkDotNet;
using IOB_UT;
using MapoSDK;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using EgwProxy.OsaiCncLib;

namespace IOB_WIN
{
    public class IobOSAI : IobGeneric
    {
        #region Private Fields

        /// <summary>
        /// LookUpTable di decodifica da CNC a segnali tipo bitmap MAPO
        /// </summary>
        private Dictionary<string, string> signLUT = new Dictionary<string, string>();

        #endregion Private Fields

        #region Protected Fields

        /// <summary>
        /// Struttura dati principale in continuo update...
        /// </summary>
        protected Cndex.GETINFO1DATA oData;

        /// <summary>
        /// Oggetto MAIN x connessione OSAI
        /// </summary>
        protected ComCNOsai OSAI_ref;

        #endregion Protected Fields

        #region Public Fields

        /// <summary>
        /// Vettore degli allarmi CNC attivi
        /// </summary>
        public Dictionary<string, string> allarmiCNC = new Dictionary<string, string>();

        /// <summary>
        /// Stato corrente (da classe ENUM)
        /// </summary>
        public CNC_STATUS_OSAI currStatus;

        #endregion Public Fields

        #region Public Constructors

        /// <summary>
        /// estende l'init della classe base...
        /// </summary>
        /// <param name="caller"></param>
        /// <param name="adpConf"></param>
        public IobOSAI(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf)
        {
            // gestione invio ritardato contapezzi
            pzCountDelay = utils.CRI("pzCountDelay");
            lastPzCountSend = DateTime.Now;
            lastWarnODL = DateTime.Now;
            if (IOBConf != null)
            {
                // inizializzo correttamente aree memoria secondo CONF - iniFileName
                if (!string.IsNullOrEmpty(IOBConf.iniFileName))
                {
                    IniFile fIni = new IniFile(IOBConf.iniFileName);

                    // fix enable prgName
                    enablePrgName = fIni.ReadBoolean("CNC", "GETPRGNAME", true);

                    // effettuo lettura della conf sigLUT... cercando 1:1 i bit...
                    string currBit = "";
                    string memArea = "";
                    for (int i = 0; i < 8; i++)
                    {
                        currBit = string.Format("BIT{0}", i);
                        memArea = fIni.ReadString("MEMORY", currBit, "");
                        // se trovo un valore...
                        if (!string.IsNullOrEmpty(memArea))
                        {
                            signLUT.Add(currBit, memArea);
                        }
                    }
                }
            }

            // è little endian (NON serve conversione)
            hasBigEndian = false;
            lgInfo("Start init Adapter OSAI, tipo all'IP/NOME {0}, variante {1} per IOB {2}", IOBConf.cncIpAddr, IOBConf.tipoIob, IOBConf.codIOB);

            // Creo oggetto x gestione connessione/comunicazione NC: secondo il tipo creo CNDEX o OPEN
            parentForm.commPlcActive = true;
            if (IOBConf.tipoIob == tipoAdapter.OSAI_OPEN)
            {
                OSAI_ref = new Open_Series(IOBConf.cncIpAddr, false);
            }
            else if (IOBConf.tipoIob == tipoAdapter.OSAI_VB6)
            {
                OSAI_ref = new ComCNOSAIVB6(IOBConf.cncIpAddr, false);
            }
            else
            {
                OSAI_ref = new ComCNOsai(IOBConf.cncIpAddr, false);
            }
            parentForm.commPlcActive = false;

            if (isVerboseLog)
            {
                lgInfo(string.Format("INIT OSAI_ref da EgwProxy.MultiCncLib come {0}", IOBConf.tipoIob));
            }

            // disconnetto e connetto...
            if (isVerboseLog)
            {
                lgInfo("OSAI: tryDisconnect");
            }

            tryDisconnect();
            lgInfo("OSAI: tryConnect");
            tryConnect();

            // recupero machine status e mode da cui decodificare info sul PLC...
            byte machineStatus = OSAI_ref.GetMachineStatus();
            byte modeSelected = OSAI_ref.GetModeSelected();
            lgInfo(string.Format("Lettura preliminare: machineStatus={0} | modeSelected={1}", machineStatus, modeSelected));

            // possiamo leggere tutto da qui che contiene status, mode e last_nc_error
            oData = new Cndex.GETINFO1DATA();
            var ncInfo = OSAI_ref.NcInfo1(ref oData);

            lgInfo(string.Format("Lettura START completa NCINFO1DATA{0} lastNcError={1}{0}status={2}{0}substatus={9}{0}mode_select={3}{0}main_progr_name={4}{0}speed_ov={5}{0}progr_speed={6}{0}real_speed={7}{0}real_feed={8}{0}", Environment.NewLine, oData.last_nc_error, oData.status, oData.mode_select, oData.main_progr_name, oData.speed_ov, oData.progr_speed, oData.real_speed, oData.real_feed, oData.substatus));

            // inizio il calcolo dello status semaforico
            short bitStatus = 0;
            if (OSAI_ref.Connected)
            {
                bitStatus += 1;
            }

            if (utils.CRB("enableContapezzi"))
            {
                lgInfo("OSAI: inizio gestione contapezzi");
                try
                {
                    // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con indicazione area)
                    if (cIobConf.optPar.Count > 0 && !string.IsNullOrEmpty(getOptPar("PZCOUNT_MODE")))
                    {
                        if (getOptPar("PZCOUNT_MODE").StartsWith("OVAR"))
                        {
                            lgInfo("Init contapezzi OSAI: pzCntReload(true)");
                            pzCntReload(true);
                            // refresh associazione Macchina - IOB
                            sendM2IOB();
                            // per adesso imposto lettura dal CNC == contapezzi (poi farà vera lettura...)
                            contapezziPLC = contapezziIOB;
                        }
                        else
                        {
                            contapezziIOB = 0;
                            lgInfo("Contapezzi STD disabilitato: modalità {0}", getOptPar("PZCOUNT_MODE"));
                        }
                    }
                    else
                    {
                        contapezziIOB = 0;
                        lgInfo("Parametro mancante PZCOUNT_MODE");
                    }
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in contapezzi OSAI");
                }
            }
            // finisco INIT ADAPTER
            lgInfo("End init Adapter OSAI");
        }

        #endregion Public Constructors

        #region Private Methods

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO
        /// </summary>
        private void decodeToBitmap()
        {
            // init a zero...
            B_input = 0;
            // SE SI E' CONNESSO al CNC allora è 1=powerON...
            if (OSAI_ref.Connected)
            {
                B_input += 1 << 0;
            }

            // decodifico impiegando dictionary... cercando il TIPO di memoria & co...
            string bKey = "";
            string bVal = "";
            for (int i = 0; i < 8; i++)
            {
                bKey = string.Format("BIT{0}", i);
                // cerco se ci sia in LUT
                if (signLUT.ContainsKey(bKey))
                {
                    // recupero nome variabile...
                    bVal = signLUT[bKey];
                    // se l'area è PZCOUNT... processo PUNTUALMENTE il CONTAPEZZI...
                    if (bVal.StartsWith("PZCOUNT"))
                    {
                        // procedo SOLO SE è enabled IOB
                        if (IobOnline)
                        {
                            try
                            {
                                currODL = utils.callUrl(urlGetCurrODL);
                                // solo SE HO un ODL...
                                if (string.IsNullOrEmpty(currODL) || currODL == "0")
                                {
                                    if (periodicLog)
                                    {
                                        lgInfo(string.Format("Osai | Lettura ODL andata a vuoto: currODL: {0}", currODL));
                                    }
                                }
                                else
                                {
                                    lgInfo(string.Format("Osai | Lettura ODL, currODL: {0} --> currIdxODL prec: {1}", currODL, currIdxODL));
                                    // provo a salvare nuovo ODL
                                    int.TryParse(currODL, out currIdxODL);
                                }
                            }
                            catch (Exception exc)
                            {
                                if (DateTime.Now.Subtract(lastWarnODL).TotalSeconds > 15)
                                {
                                    lgError(exc, "Errore in fase di chiamata URL x ODL corrente | URL chiamato: {0}", urlGetCurrODL);
                                    lastWarnODL = DateTime.Now;
                                }
                            }
                        }
                        else
                        {
                            // imposto currODL a vuoto!
                            currODL = "";
                            if (periodicLog)
                            {
                                lgInfo($"OSAI | Lettura ODL non effettuata: IobOnline: {IobOnline} | currODL impostato a vuoto");
                            }
                        }
                        if (!string.IsNullOrEmpty(currODL) && currODL != "0")
                        {
                            // controllo se è passato intervallo minimo tra 2 controlli/elaborazioni x distanziare invio e ridurre letture
                            if (DateTime.Now >= lastPzCountSend.AddMilliseconds(pzCountDelay))
                            {
                                // se sono differenti MOSTRO...
                                if (contapezziPLC != contapezziIOB)
                                {
                                    // registro contapezzi
                                    lgInfo($"Differenza Contapezzi: contapezziPLC: {contapezziPLC} | contapezziIOB {contapezziIOB}");
                                }
                                // verifico se variato contapezzi in area STD PAR6711... e se passato ritardo minimo...
                                if (contapezziPLC > contapezziIOB)
                                {
                                    // salvo nuovo contapezzi (incremento di 1...) + richiesta refresh conteggio
                                    contapezziIOB++;
                                    needRefreshPzCount = true;
                                    // salvo in semaforo!
                                    B_input += 1 << 2;
                                    // registro contapezzi
                                    lgInfo($"contapezziPLC OSAI: {contapezziPLC} | contapezziIOB {contapezziIOB}");
                                }

                                // invio a server contapezzi (aggiornato)
                                string retVal = utils.callUrl(urlSetPzCount + contapezziIOB.ToString());
                                // verifica se tutto OK
                                if (retVal.Trim() != $"{contapezziIOB}")
                                {
                                    // errore salvataggio contapezzi
                                    lgInfo($"Errore salvataggio Contapezzi OSAI: urlSetPzCount: {urlSetPzCount} | contapezziPLC {contapezziPLC} | contapezziIOB {contapezziIOB} | risposta: {retVal}");
                                    // rileggo il counter pezzi da server
                                    pzCntReload(true);
                                }
                                // resetto timer...
                                lastPzCountSend = DateTime.Now;
                            }
                        }
                        else
                        {
                            if (DateTime.Now >= lastPzCountSend.AddMilliseconds(pzCountDelay))
                            {
                                lgInfo($"Attenzione: mancanza ODL non procedo con gestione contapezzi. contapezziPLC OSAI: {contapezziPLC} | contapezziIOB {contapezziIOB}");
                                // resetto timer...
                                lastPzCountSend = DateTime.Now;
                            }
                        }
                    }
                    // controllo le condizioni x costruire il bit1 RUN!
                    if (oData.mode_select == 2 && oData.status == 2)
                    {
                        B_input += 1 << 1;
                    }
                    // errore su bit3
                    if (oData.mode_select == 6)
                    {
                        B_input += 1 << 3;
                    }
                    // auto su bit4
                    if (oData.mode_select != 2)
                    {
                        B_input += 1 << 4;
                    }
                    // emergenza su bit5
                    if (oData.mode_select == 9)
                    {
                        B_input += 1 << 5;
                    }
                }
            }
            // log opzionale!
            if (verboseLog)
            {
                lgInfo(string.Format("Trasformazione B_input: {0}", B_input));
            }
        }

        #endregion Private Methods

        #region Protected Methods

        /// <summary>
        /// Decodifica del MODE selezionato
        /// </summary>
        /// <param name="mode"></param>
        /// <returns></returns>
        protected static CNC_MODE decodeModeOsai(int mode)
        {
            CNC_MODE answ = CNC_MODE.ND;
            switch (mode)
            {
                case 1:
                    answ = CNC_MODE.MDI;
                    break;

                case 2:
                    answ = CNC_MODE.AUTO;
                    break;

                case 3:
                    answ = CNC_MODE.SEMI;
                    break;

                case 4:
                    answ = CNC_MODE.JOG_MAN;
                    break;

                case 5:
                    answ = CNC_MODE.JOG_INC;
                    break;

                case 6:
                    answ = CNC_MODE.PROFILE;
                    break;

                case 7:
                    answ = CNC_MODE.HOME;
                    break;

                case 8:
                    answ = CNC_MODE.HANDLE_INC;
                    break;

                default:
                    break;
            }
            return answ;
        }

        #endregion Protected Methods

        #region Public Methods

        /// <summary>
        /// Recupera e processa allarmi CNC...
        /// </summary>
        public override Dictionary<string, string> getCncAlarms()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            // se ho allarmi li accodo...
            if (oData.last_nc_error != 0)
            {
                try
                {
                    outVal.Add("CNC_ALARM", (oData.last_nc_error).ToString());
                }
                catch (Exception exc)
                {
                    lg.Error(exc, "Eccezione in getCncAlarms");
                }
            }
            return outVal;
        }

        /// <summary>
        /// Recupero dati dinamici...
        /// </summary>
        public override Dictionary<string, string> getDynData()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            stopwatch.Restart();
            try
            {
                string actf = oData.real_feed.ToString();
                string acts = oData.real_speed.ToString();
                outVal.Add("DYNDATA", string.Format("FEED {0}#SPEED_RPM {1}", actf, acts));

                if (utils.CRB("SendFeedSpeed"))
                {
                    outVal.Add("FEED", actf);
                    outVal.Add("SPEED_RPM", acts);
                    //outVal.Add("NUM_ALARM", numAlarm);
                    outVal.Add("ACT_TOOL", oData.actual_tool.ToString());
                }
                if (utils.CRB("SendAxPos"))
                {
                    // salvo info assi...
                    outVal.Add("AX_SEL", oData.ax_sel.ToString());
                    outVal.Add("NUM_AX_SEL", oData.num_ax_sel.ToString());
                }
            }
            catch (Exception exc)
            {
                lgError(exc, "Errore in getDynData");
            }
            stopwatch.Stop();
            return outVal;
        }

        /// <summary>
        /// Recupero dati override (da area G che è già stata letta...)
        /// </summary>
        /// <returns></returns>
        public override Dictionary<string, string> getOverrides()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            outVal.Add("FEED_OVER", (oData.feed_ov).ToString());
            outVal.Add("RAPID_OVER", (oData.rapid_override).ToString());
            return outVal;
        }

        /// <summary>
        /// Recupero programma in lavorazione
        /// </summary>
        /// <returns></returns>
        public override string getPrgName()
        {
            string prgName = "";
            // recupero NUOVO prgName...
            try
            {
                // recupero nome programma MAIN
                prgName = System.Text.Encoding.Default.GetString(oData.main_progr_name);
                // trimmo path del programma, ovvero "CNCMEMUSERPATH1"
                prgName = prgName.Replace(utils.CRS("basePrgMemPath"), "");
                lgInfo("Current PROG: {0}", prgName);
            }
            catch (Exception exc)
            {
                lgError(string.Format("Eccezione in recupero PRG NAME MAIN:{0}{1}", Environment.NewLine, exc));
                connectionOk = false;
            }
            return prgName;
        }

        /// <summary>
        /// Effettua vero processing contapezzi appoggiato ad area specifica da CONF
        /// </summary>
        public override void processContapezzi()
        {
            if (utils.CRB("enableContapezzi"))
            {
                try
                {
                    // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con indicazione area)
                    if (cIobConf.optPar.Count > 0 && !string.IsNullOrEmpty(getOptPar("PZCOUNT_MODE")))
                    {
                        string memAddr = getOptPar("PZCOUNT_MODE");
                        if (memAddr.StartsWith("STD"))
                        {
                            // inizio verifica area memoria/parametro levando prima parte codice
                            memAddr = memAddr.Replace("STD.", "");
                            // var di appoggio
                            int cntAddr = 0;
                            // verifico se si tratta di lettura MEMORIA... formato tipo STD.MEM.6711
                            if (memAddr.StartsWith("MEM."))
                            {
                                // recupero parametro...
                                int.TryParse(memAddr.Replace("MEM.", ""), out cntAddr);
                                if (cntAddr == 0)
                                {
                                    cntAddr = 29;
                                }
                                // processo parametro contapezzi (lavorati)
                                stopwatch.Restart();
                                // vera lettura area memoria...
                                int resVal = 0;
                                // lettura variabili (es contapezzi)
                                resVal = (int)OSAI_ref.ReadVarSN((short)cntAddr);

                                if (utils.CRB("recTime"))
                                {
                                    TimingData.addResult(cIobConf.codIOB, string.Format("R{0}-MEM", 2), stopwatch.ElapsedTicks);
                                }
                                // aggiungo in visualizzazione SE variata...
                                if (resVal != contapezziPLC)
                                {
                                    string mCount = string.Format("MEM{0}", cntAddr);
                                    string sVal = string.Format("[PZCOUNT]{0}|{1}", mCount, resVal);
                                    // chiamo accodamento...
                                    accodaFLog(sVal, qEncodeFLog(mCount, resVal.ToString()));
                                }
                                // salvo ultimo conteggio rilevato
                                contapezziPLC = resVal;
                            }
                            stopwatch.Stop();
                        }
                    }
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in contapezzi CNC");
                    connectionOk = false;
                }
            }
        }

        /// <summary>
        /// Esegue processing MODE (e nel contempo recupera altri dati dell'area G)
        /// </summary>
        public override void processMode()
        {
            if (utils.CRB("enableMode"))
            {
                try
                {
                    // verifico modo con valore corrente, se cambia aggiorno...
                    CNC_MODE newMode = decodeModeOsai(oData.mode_select);
                    if (newMode != currMode)
                    {
                        // aggiorno!
                        currMode = newMode;
                        // conversione NUM MODE in descrizione da ENUM
                        string descrMode = Enum.GetName(typeof(CNC_MODE), currMode);
                        // accodo x invio
                        string sVal = string.Format("[CNC_MODE]{0}", descrMode);
                        // chiamo accodamento...
                        accodaFLog(sVal, qEncodeFLog("CNC_MODE", descrMode));
                    }
                }
                catch (Exception exc)
                {
                    lgError(exc, string.Format("Errore in process Mode OSAI: {0}{1}", Environment.NewLine, exc));
                    connectionOk = false;
                    stopwatch.Stop();
                }
            }
            // lo status lo processo SEMPRE
            try
            {
                // verifico modo con valore corrente, se cambia aggiorno...
                CNC_STATUS_OSAI newStatus = (CNC_STATUS_OSAI)(oData.status);
                if (newStatus != currStatus)
                {
                    // aggiorno!
                    currStatus = newStatus;
                    // accodo x invio
                    string sVal = string.Format("[CNC_STATUS]{0}", currStatus.ToString());
                    // chiamo accodamento...
                    accodaFLog(sVal, qEncodeFLog("CNC_STATUS", currStatus.ToString()));
                }
            }
            catch (Exception exc)
            {
                lgError(exc, string.Format("Errore in process Mode OSAI: {0}{1}", Environment.NewLine, exc));
                connectionOk = false;
                stopwatch.Stop();
            }
        }

        /// <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);
            try
            {
                if (verboseLog)
                {
                    lgInfo("inizio read semafori");
                }

                currDispData.semIn = Semaforo.SV;

                stopwatch.Restart();
                // possiamo leggere tutto da qui che contiene tutto: status, mode, last_nc_error
                var ncInfo = OSAI_ref.NcInfo1(ref oData);
                // time rec
                if (utils.CRB("recTime"))
                {
                    TimingData.addResult(cIobConf.codIOB, "GETINFO1DATA", stopwatch.ElapsedTicks);
                }
                stopwatch.Stop();

                // salvo il solo BYTE dell'input decifrando il semaforo...
                decodeToBitmap();
            }
            catch (Exception exc)
            {
                lgError(string.Format("Eccezione in readSemafori:{0}{1}", Environment.NewLine, exc));
                connectionOk = false;
                currDispData.semIn = Semaforo.SR;
            }
        }

        /// <summary>
        /// Override connessione
        /// </summary>
        public override void tryConnect()
        {
            if (!connectionOk)
            {
                // controllo che il ping sia stato tentato almeno pingTestSec fa...
                if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec"))
                {
                    if (verboseLog || periodicLog)
                    {
                        lgInfo("OSAI: ConnKO - tryConnect");
                    }
                    // PING SOLO x OPEN!!!
                    bool needPing = (cIobConf.tipoIob == tipoAdapter.OSAI_OPEN);
                    bool pingOk = false;
                    Ping pingSender = new Ping();
                    PingReply reply = pingSender.Send("127.0.0.1", 100);
                    // se serve PING...
                    if (needPing)
                    {
                        // in primis salvo data ping...
                        lastPING = DateTime.Now;
                        // ora PING!!!
                        IPAddress address = IPAddress.Loopback;
                        IPAddress.TryParse(cIobConf.cncIpAddr, out address);
                        reply = pingSender.Send(address, 100);
                        pingOk = reply.Status == IPStatus.Success;
                    }
                    // se passa il ping faccio il resto...
                    if (pingOk || !needPing)
                    {
                        string szStatusConnection = "";
                        try
                        {
                            // ora provo connessione...
                            parentForm.commPlcActive = true;
                            bool fatto = OSAI_ref.OpenSession();
                            if (!fatto)
                            {
                                // log errore!
                                lgInfo("Impossibile effettuare apertura sessione: " + OSAI_ref.ErrMsg);
                            }
                            parentForm.commPlcActive = false;
                            lgInfo("szStatusConnection: " + szStatusConnection);
                            connectionOk = true;
                            // refresh stato allarmi!!!
                            if (connectionOk)
                            {
                                dtAvvioAdp = DateTime.Now;
                                if (adpRunning)
                                {
                                    lgInfo("Connessione OK");
                                }
                            }
                            else
                            {
                                lgError("Impossibile procedere, connessione mancante...");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgFatal(string.Format("Errore nella connessione all'adapter OSAI: {0}{1}{2}", szStatusConnection, Environment.NewLine, exc));
                            connectionOk = false;
                            lgInfo(string.Format("Eccezione in TryConnect, Adapter NON running, pausa di {0} msec prima di ulteriori tentativi di riconnessione", utils.CRI("waitRecMSec")));
                        }
                    }
                    else
                    {
                        // loggo no risposta ping ...
                        connectionOk = false;
                        if (needPing)
                        {
                            if (verboseLog || periodicLog)
                            {
                                lgInfo(string.Format("Attenzione: controllo PING fallito per IP {0} - {1}", cIobConf.cncIpAddr, reply.Status));
                            }
                        }
                    }
                }
            }
            // se non è ancora connesso faccio procesisng memoria caso disconnesso...
            if (!connectionOk)
            {
                // processo semafori ed invio...
                processMemoryDiscon();
            }
        }

        /// <summary>
        /// Override disconnessione
        /// </summary>
        public override void tryDisconnect()
        {
            if (connectionOk)
            {
                string szStatusConnection = "";
                try
                {
                    OSAI_ref.CloseSession();
                    connectionOk = false;
                    // resetto timing!
                    TimingData.resetData();
                    lgInfo(szStatusConnection);
                    lgInfo("Effettuata disconnessione adapter OSAI!");
                }
                catch (Exception exc)
                {
                    lgFatal(exc, "Errore nella disconnessione dall'adapter OSAI");
                }
            }
            else
            {
                lgError("IMPOSSIBILE effettuare disconnessione: Connessione non disponibile...");
            }
        }

        #endregion Public Methods
    }
}