using System;
using System.Linq;
using System.Text;
using SCMCncLib;
using System.Windows.Forms;
using System.Threading;
using System.IO;

namespace MTC_Adapter
{
    public class AdapterESA : AdapterGeneric
    {
        /// <summary>
        /// oggetto onnessione ESA
        /// </summary>
        protected thdNcEsaGvKvara ncDevice;
        /// <summary>
        /// thread del processo comunicazione esa
        /// </summary>
        protected Thread thdDevice;

        /// <summary>
        /// estende l'init della classe base...
        /// </summary>
        /// <param name="caller"></param>
        /// <param name="adpConf"></param>
        public AdapterESA(MainForm caller, AdapterConf adpConf) : base(caller, adpConf)
        {
            string iniPath = string.Format(@"{0}\{1}", utils.confDir, utils.CRS("defaultEsaFile"));

            if (utils.CRB("verbose")) lg.Info("Start init Adapter ESA dal file {0}", iniPath);

            IniFiles.IniFile EsaIni = new IniFiles.IniFile(iniPath);

            if (utils.CRB("verbose")) lg.Info("step 01: impostato INI File {0}", iniPath);

            ncDevice = new thdNcEsaGvKvara(EsaIni);

            if (utils.CRB("verbose")) lg.Info("step 02: avviato thdNcEsaGvKvara da INI File {0}", iniPath);

            // inizializzo posizioni assi...
            prevPosAxis = new double[adpConf.nAxis];
            prevDirAxis = new int[adpConf.nAxis];

            // verifica avvio...
            if (utils.CRB("verbose")) lg.Info("ESA: tryConnect");
            tryConnect();
            if (utils.CRB("verbose")) lg.Info("End init Adapter ESA");
        }

        public override void tryDisconnect()
        {
            base.tryDisconnect();
            // disconnetto
            ncDevice.Disconnect();
            connectionOk = false;
        }

        public override void tryConnect()
        {
            base.tryConnect();

            // se non già connesso provo a connettermi...
            if (!ncDevice.Connected)
            {
                // provo a collegarmi
                if (!ncDevice.Connect())
                {
                    //altrimenti disconnette...
                    ncDevice.Disconnect();
                    connectionOk = false;
                }
                else
                {
                    connectionOk = true;
                }
            }
        }

        /// <summary>
        /// Verifico connessione ESA...
        /// </summary>
        /// <returns></returns>
        public override bool connectionOk
        {
            get
            {
                return ncDevice.Connected;
            }
        }

        /// <summary>
        /// Effettuo lettura memorie strobe/status
        /// </summary>
        public override void getStrobeAndAckStatus()
        {
            base.getStrobeAndAckStatus();
        }

        public override void getGlobalData()
        {
            base.getGlobalData();

            // accodo dati path in DataMonitor......
            StringBuilder sb = new StringBuilder();

            if (connectionOk)
            {
                // leggo TUTTO il blocco di memoria
                inizio = DateTime.Now;
                ncDevice.ReadBuffer();
                if (utils.CRB("recTime")) TimingData.addResult(string.Format("R-PLC_FullMemoryRead", ncDevice.PLC_MemoryRead.Length), DateTime.Now.Subtract(inizio).Ticks);
            }
            else
            {
                lg.Error("Errore connessione mancante in getStrobeAndAckStatus");
            }

            // dati override feed/speed...
            Byte v82 = ncDevice.PLC_MemoryAreaV[14];
            Byte v83 = ncDevice.PLC_MemoryAreaV[15];
            // 2 byte x speed da copiare... 
            byte[] tmpByte = new byte[2];
            Buffer.BlockCopy(ncDevice.PLC_MemoryAreaV, 16, tmpByte, 0, 2);
            short v84 = BitConverter.ToInt16(tmpByte, 0);
            // lista allarmi PLC/CNC
            Byte v87 = ncDevice.PLC_MemoryAreaV[19]; // Allarme CN (almeno 1?!?)

            // -------------------------------------------
            // recupero dati Feed/Speed/override
            // FeedOver: 100% = 213 (il pannello è 0-120 --> 0-255, quindi 100% è 100/120*255=213)
            FeedRateOver = Convert.ToInt16((decimal)(v82 * 100) / 213);
            sb.AppendLine(string.Format("FeedRateOver: {0} %", FeedRateOver));
            // SpeedOver: 50% = 128
            SpeedRateOver = Convert.ToInt16((decimal)(v83 * 100) / 255);
            sb.AppendLine(string.Format("SpeedRateOver: {0} %", SpeedRateOver));
            // Speed S5000 OK!!!
            SpeedRate = v84;
            sb.AppendLine(string.Format("SpeedRate: {0} rpm", SpeedRate));
            // -------------------------------------------

            // da recuperare da qualche parte?!?
            UnOpLoad = 0;
            sb.AppendLine(string.Format("Load: {0}", UnOpLoad));

            // -------------------------------------------
            // recupero dati dai contatori EOK
            bool needSave = false;

            // ore totali accensione
            int MinMaccOn = (int)ncDevice.PLC_MemoryAreaEOK[0];
            int OreMaccOn = (int)ncDevice.PLC_MemoryAreaEOK[1];
            istOreMaccOn = OreMaccOn + (double)MinMaccOn / 60;
            needSave = procOreMaccOn(needSave);

            // ore totali lavoro
            int MinMaccLav = (int)ncDevice.PLC_MemoryAreaEOK[2];
            int OreMaccLav = (int)ncDevice.PLC_MemoryAreaEOK[3];
            istOreMaccLav = OreMaccLav + (double)MinMaccLav / 60;
            needSave = procOreMaccLav(needSave);

            // giri totali degli elettromandrini!
            for (int i = 0; i < currAdpConf.nUnOp; i++)
            {
                istGiriElettrom[i] = (uint)ncDevice.PLC_MemoryAreaEOK[4 + i];
            }
            needSave = procGiriTotUnOp(needSave);
            needSave = procNumCU(needSave);

            // per ESA calcolo a mano se ci sia stato impiego assi... salvo le istantanee mov precedenti...
            double precVal=0;
            DateTime adesso = DateTime.Now;
            TimeSpan accumTime = adesso.Subtract(lastChekAccumTimeAxis);

            // spostamento totale assi!
            for (int i = 0; i < 3; i++)
            {
                precVal = istDistMovAssi[i];
                // primo è metri, secondo km (moltiplico x 1000)
                istDistMovAssi[i] = ncDevice.PLC_MemoryAreaEOK[8 + 2 * i] + ncDevice.PLC_MemoryAreaEOK[9 + 2 * i] * 1000;
                // verifico SE devo aggiungere tempo lavoro sui singoli assi (se si sono spostati...) -- è a maglie LARGHE poiché leggiamo lo "scatto ogni metro" x cui se campiono velocemente ma assi muovono lenti prendo MENO tempo di movimento del reale...
                if (precVal != istDistMovAssi[i])
                {
                    istAccTimeAssi[i] += accumTime.TotalHours;
                }
            }
            // salvo valore lastChekAccumTimeAxis
            lastChekAccumTimeAxis = adesso;

            needSave = procMovTotAssi(needSave);
            needSave = procAccTimeAssi(needSave);
            needSave = procProgrName(needSave);
            needSave = procPartId(needSave);
            needSave = procPzProd(needSave);
            needSave = procNumInvAssi(needSave);
            needSave = procVacPump(needSave);
            needSave = procVacAct(needSave);
            needSave = procLubro(needSave);



            // salvo se necessario!
            if (needSave) parentForm.persistData();
            // -------------------------------------------


            // copio allarmi in vettore generale AlarFlags (dove lo gestisce)...
            Buffer.BlockCopy(ncDevice.PLC_MemoryAreaAllarmi, 0, AlarmFlags, 0, ncDevice.PLC_MemoryAreaAllarmi.Length);
            //Buffer.BlockCopy(ncDevice.PLC_MemAreaAlarm_tmp, 0, AlarmFlags, 0, ncDevice.PLC_MemAreaAlarm_tmp.Length);

            parentForm.dataMonitor = sb.ToString();
        }
        /// <summary>
        /// Path percorso file prod
        /// </summary>
        protected string ScmProdFileName
        {
            get
            {
                return string.Format(@"{0}\{1}", utils.confDir, utils.CRS("ScmProdFile"));
            }
        }
        /// <summary>
        /// Oggetto elenco dati produzione
        /// </summary>
        public datiProdSCM[] elencoDatiProd;

        /// <summary>
        /// leggo altri file:
        /// - dati produzione
        /// </summary>
        public override void getSlowChangingData()
        {
            base.getSlowChangingData();

            // carico dati x Maintenance
            if (utils.CRB("verbose")) lg.Info(string.Format("Inizio caricamento file dati produzione ({0})", ScmProdFileName));
            int totRighe = 0;
            string linea;
            // controllo se file esista, altrimenti loggo errore!
            if (!File.Exists(ScmProdFileName))
            {
                lg.Error(string.Format("ATTENZIONE! file dati produzione ({0}) non trovato!", ScmProdFileName));
            }
            else
            {
                totRighe = File.ReadLines(ScmProdFileName).Count();
                // creo un vettore della dimensione corretta... conta anche commenti tanto poi riduco...
                datiProdSCM[] elencoDatiProdNew = new datiProdSCM[File.ReadLines(ScmProdFileName).Count()];
                // carica da file...
                System.IO.StreamReader file = new System.IO.StreamReader(ScmProdFileName);
                // leggo 1 linea alla volta...
                int rumRiga = 0;
                while ((linea = file.ReadLine()) != null)
                {
                    // SE non è un valore totale... lungh > 50 e 22 virgole (23 valori)
                    if (linea.Length > 50)
                    {
                        // comma separated!
                        elencoDatiProdNew[rumRiga] = decodeScmProdLine(linea, ',');
                        // se !=null conto!
                        if (elencoDatiProdNew != null) rumRiga++;
                    }
                }
                // chiudo file
                file.Close();
                // ora trimmo vettore al solo numero VERO degli allarmi caricati...
                Array.Resize<datiProdSCM>(ref elencoDatiProdNew, rumRiga);

                /*--------------------------------------------------
                 * Gestione verifica dati prod (naive)
                 * - se file curr vuoto --> inizio a popolare
                 * - se i 2 file hanno stessa lunghezza --> verifico + update last row
                 * - se nNew > nCurr --> accodo
                 * - se nCurr > nNew e nNew ==0 --> aggiorno last row giorno prec
                 * - se nCurr > nNew e nNew > 0 --> svuoto file curr e riparto
                 *--------------------------------------------------*/


                // verifica preliminare dati da lung vettori
                int nRecCur = 0;
                int nRecIst = 0;
                if (elencoDatiProd != null)
                {
                    try
                    {
                        nRecCur = elencoDatiProd.Length;
                    }
                    catch
                    { }
                }
                if (elencoDatiProdNew != null)
                {
                    try
                    {
                        nRecIst = elencoDatiProdNew.Length;
                    }
                    catch
                    { }
                }

                // uso un INT x delta qta da sommare...
                int deltaQta = 0;
                // in primis devo verificare che sia > 0 num oggetti su entrambi i vettori
                if (nRecCur + nRecIst > 0)
                {
                    // verifico se ho meno valori CURR e quindi devo accodare
                    if (nRecCur < nRecIst)
                    {
                        // resize vettore!
                        Array.Resize<datiProdSCM>(ref elencoDatiProd, nRecCur + 1);
                        // carico nuovo record
                        elencoDatiProd[nRecCur] = elencoDatiProdNew[nRecCur];
                        deltaQta = elencoDatiProd[nRecCur].qta;
                        nRecCur++;
                    }
                    else if (nRecCur == nRecIst)
                    {
                        // devo confrontare ULTIMO record Curr con equivalente record NEW x verificare se sia variato.. verifico PRG + start
                        bool checkName = elencoDatiProd[nRecCur - 1].fileName == elencoDatiProdNew[nRecCur - 1].fileName;
                        bool checkStart = elencoDatiProd[nRecCur - 1].start == elencoDatiProdNew[nRecCur - 1].start;
                        if (checkName && checkStart)
                        {
                            // salvo se variata quantità...
                            deltaQta = elencoDatiProdNew[nRecCur - 1].qta - elencoDatiProd[nRecCur - 1].qta;
                            elencoDatiProd[nRecCur - 1] = elencoDatiProdNew[nRecCur - 1];
                        }
                    }
                    else
                    {
                        // inizio svuotando elenco eventi CURR (migliorabile leggendo da data prec?!?)
                        elencoDatiProd = null;
                        nRecCur = 0;
                    }

                    // riporto comunque ultima riga del vettore CURR (sia perché variato sia perché aggiunto)
                    if (nRecCur > 0)
                    {
                        string prgName = elencoDatiProd[nRecCur - 1].fileName;
                        prgName = prgName.Substring(prgName.LastIndexOf(@"\") + 1);
                        // se il mio articolo NON cambia...
                        if (istPathPartId[0] == prgName)
                        {
                            istPathPartCount[0] += (uint)deltaQta;
                        }
                        else
                        {
                            istPathPartId[0] = prgName;
                            // 2016.07.27 riporto puntualmente qta nuovo record...
                            istPathPartCount[0] = (uint)deltaQta;
                            // old sostituito
                            //istPathPartCount[0] = Convert.ToUInt32(elencoDatiProd[nRecCur - 1].qta);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Processo stti macchina...
        /// </summary>
        public override void processStatus()
        {

            // HARD CODE: forzo path 1 (indice 0...)
            int idxPath = 0;

            // verifica macchina accesa...
            Byte V73 = ncDevice.PLC_MemoryAreaV[5];
            if (((StFlag8)V73).HasFlag(StFlag8.B2))
            {
                mPower.Value = "ON";
            }
            else
            {
                mPower.Value = "OFF";
            }

            // switch su EXE mode...
            /*
             * MODO_X V[70/71] --> byte (2-3)
             * V70.2 = Modo RUN  
             * V70.3 = Modo FeedHold
             * V71.5 = Ready 
             * */
            Byte V70 = ncDevice.PLC_MemoryAreaV[2];
            Byte V71 = ncDevice.PLC_MemoryAreaV[3];
            if (((StFlag8)V70).HasFlag(StFlag8.B3))
            {
                vettPath[idxPath].mPathExeMode.Value = "FEED_HOLD"; //"FEEDHOLD";
            }
            else if (((StFlag8)V70).HasFlag(StFlag8.B2))
            {
                vettPath[idxPath].mPathExeMode.Value = "ACTIVE";
            }
            else if (((StFlag8)V71).HasFlag(StFlag8.B5))
            {
                vettPath[idxPath].mPathExeMode.Value = "READY";
            }
            //// appunto: modi da creare
            //STOPPED/HOLD(da creare)
            //INTERRUPTED(da creare)
            //OPTIONAL_STOP(da creare)
            //PROGRAM_STOPPED(da creare)
            //PROGRAM_COMPLETED(da creare)


            // switch su run mode...
            /*
             * MODO_X V[74].W --> byte (6)
             * 0 = Modo NESSUNO 
             * 1 = Modo MANUALE  
             * 2 = Modo AUTOMATICO  
             * 3 = Modo POM  
             * 4 = Modo MDI  
             * 5 = Modo SEMIAUTOMATICO  
             * 6 = Modo RAP  
             * 7 = Modo TES
             * */
            uint V74 = ncDevice.PLC_MemoryAreaV[6];
            switch (V74)
            {
                case 1:
                    vettPath[idxPath].mPathRunMode.Value = "EDIT";
                    break;
                case 2:
                    vettPath[idxPath].mPathRunMode.Value = "AUTOMATIC";
                    break;
                case 3:
                    vettPath[idxPath].mPathRunMode.Value = "MANUAL";
                    break;
                case 4:
                    vettPath[idxPath].mPathRunMode.Value = "MANUAL_DATA_INPUT";
                    break;
                case 5:
                    vettPath[idxPath].mPathRunMode.Value = "SEMI_AUTOMATIC"; //"SEMIAUTO";
                    break;
                case 6:
                    vettPath[idxPath].mPathRunMode.Value = "MANUAL"; //"RAP";
                    break;
                case 7:
                    vettPath[idxPath].mPathRunMode.Value = "MANUAL"; //"TES";
                    break;
                case 0:
                default:
                    vettPath[idxPath].mPathRunMode.Value = "NA";
                    break;
            }
        }

        public override void getUnOp()
        {
            base.getUnOp();

            // cicl su UnOp
            for (int i = 0; i < currAdpConf.nUnOp; i++)
            {
                vettUnOp[i].mUnOpToolId.Value = (int)ncDevice.PLC_MemoryAreaEOK[31];
            }
        }

        public override void getPath()
        {
            base.getPath();
        }

        public override void getAxis()
        {
            base.getAxis();

            // mostro assi in DataMonitor......
            StringBuilder sb = new StringBuilder();

            // nuova posizione (per calcoli)
            double newPos = 0;
            double distPerc = 0;
            int newDir = 0;
            string tipoAsse = "";
            string direzione = "";

            // leggo in modo "cablato" i dati dei vari assi...
            for (int i = 0; i < currAdpConf.nAxis; i++)
            {

                // in base a indice scelgo valore posizione e load
                switch (i)
                {
                    case 0:
                        newPos = (double)BitConverter.ToInt32(BitConverter.GetBytes(ncDevice.PLC_MemoryRead[240]), 0);
                        break;
                    case 1:
                        newPos = (double)BitConverter.ToInt32(BitConverter.GetBytes(ncDevice.PLC_MemoryRead[241]), 0);
                        break;
                    case 2:
                        newPos = (double)BitConverter.ToInt32(BitConverter.GetBytes(ncDevice.PLC_MemoryRead[242]), 0);
                        break;
                    case 3:
                        newPos = (double)BitConverter.ToInt32(BitConverter.GetBytes(ncDevice.PLC_MemoryRead[243]), 0);
                        break;
                    case 4:
                        newPos = (double)BitConverter.ToInt32(BitConverter.GetBytes(ncDevice.PLC_MemoryRead[244]), 0);
                        break;
                    case 5:
                        newPos = (double)BitConverter.ToInt32(BitConverter.GetBytes(ncDevice.PLC_MemoryRead[245]), 0);
                        break;
                    default:
                        //    valPos = posAssi.p1;
                        //    valLoad = loadAssi.svload1;
                        break;
                }

                // popolo valori...
                //vettAxis[i].mAxLoad.Value = (double)(valLoad.data) / Math.Pow(10, valLoad.dec);
                vettAxis[i].mAxPosAct.Value = newPos;
                //vettAxis[i].mAxPosTgt.Value = newPos + (double)(valPos.dist.data) / Math.Pow(10, valPos.dist.dec);

                vettAxis[i].mAxFeedAct.Value = FeedRate;

                // calcolo distanza e salvo valore...
                distPerc = newPos - prevPosAxis[i];

                // sistemo direzione +/- (POS/NEG se lineari, CCW/CW se rotativi)
                if (distPerc != 0)
                {
                    newDir = Convert.ToInt32(distPerc / Math.Abs(distPerc));
                }
                else
                {
                    newDir = prevDirAxis[i];
                }


                // verifico tipo direzione da tipo asse...
                tipoAsse = vettAxis[i].mAxType.Value.ToString();
                if (tipoAsse == "LINEAR")
                {
                    // ?: conditional operator.  
                    direzione = (newDir > 0) ? "POSITIVE" : "NEGATIVE";
                }
                else if (tipoAsse == "ROTARY")
                {
                    direzione = (newDir > 0) ? "CLOCKWISE" : "COUNTER_CLOCKWISE";
                }

                vettAxis[i].mAxDir.Value = direzione;

                // se la direzione è variata salvo il cambio direzione...
                if (newDir != prevDirAxis[i])
                {
                    // salvo "+1" come cambi direzione
                    istNumInvAssi[i]++;
                }

                if (utils.CRB("verbose"))
                {
                    sb.AppendLine(string.Format("Asse {0}: PosAct:{1:N3}, ToGo:{2:N3}{3} | {4}", i, (double)(newPos) / utils.CRI("fattdecimale"), i, 0, direzione));
                }

                // salvo valori vettore prec...
                prevPosAxis[i] = newPos;
                prevDirAxis[i] = newDir;

                //vettAxis[i].mAxMainProc.Value = AxData.AxisMainProc;
                //vettAxis[i].mAxIsMaster.Value = AxData.AxisIsMaster;
                //vettAxis[i].mAxMastId.Value = AxData.AxisMastId;
                //vettAxis[i].mAxFeedOver.Value = AxData.AxisFeedOver;
                //vettAxis[i].mAxAccelAct.Value = AxData.AxisAccel;
                //vettAxis[i].mAxBattery.Value = AxData.AxisBattery;
            }

            parentForm.dataMonitor += sb.ToString();
        }

        public override void processAlarm()
        {
            base.processAlarm();

            // ora controllo "allarmi speciali" se si sono...
            if (AlarmFlags != null)
            {
                // variabili helper
                StFlag32 AlarmBlock = 0;
                int eStopCode = utils.CRI("eStopCode") - 1; // è base zero memoria, ma allarmi base 1...

                int memStart = (int)Math.Floor((double)eStopCode / 8);
                // leggo 32bit alla volta...
                AlarmBlock = (StFlag32)BitConverter.ToUInt32(AlarmFlags, memStart);
                int eStopIndex = eStopCode - memStart * 8;
                // converto! e aggiungo allarmi sollevati al corretto controller allarmi...
                if (AlarmBlock.HasFlag((StFlag32)Math.Pow(2, eStopIndex)))
                {
                    mEStop.Value = "TRIGGERED";
                }
                else
                {
                    mEStop.Value = "ARMED";
                }
            }
        }
    }
}
