using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Opc.Ua;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;

namespace IOB_WIN_OPC_UA.IobOpc
{
    public enum IcoelStatus
    {
        Allarme = 0,
        Stop = 1,
        Manuale = 2,
        Automatico = 3
    }

    public class DatiMesIcoel
    {
        #region Public Constructors

        /// <summary>
        /// Inizializzazione classe con dati RAW da byte[]
        /// </summary>
        /// <param name="rawData">Flusso di dati RAW da tradurre...</param>
        public DatiMesIcoel(byte[] rawData)
        {
            // solo se i dati sono esattamente 115...

            if (rawData.Length >= 115)
            {
                Calibratrice_L1 = new MesItemStatus(rawData.Skip(0).Take(7).ToArray());
                Calibratrice_L2 = new MesItemStatus(rawData.Skip(7).Take(7).ToArray());
                Dewatering_L1 = new MesItemStatus(rawData.Skip(14).Take(7).ToArray());
                Dewatering_L2 = new MesItemStatus(rawData.Skip(21).Take(7).ToArray());
                Precalibro_L1 = new MesItemStatus(rawData.Skip(28).Take(7).ToArray());
                Precalibro_L2 = new MesItemStatus(rawData.Skip(35).Take(7).ToArray());
                Taglierina_L1 = new MesItemStatus(rawData.Skip(42).Take(7).ToArray());
                Taglierina_L2 = new MesItemStatus(rawData.Skip(49).Take(7).ToArray());
                NastroTaglierina_L1 = new MesItemStatus(rawData.Skip(56).Take(7).ToArray());
                NastroTaglierina_L2 = new MesItemStatus(rawData.Skip(63).Take(7).ToArray());
                Elevatore_L1 = new MesItemStatus(rawData.Skip(70).Take(7).ToArray());
                Elevatore_L2 = new MesItemStatus(rawData.Skip(77).Take(7).ToArray());
                ImmergitoreBins_L1 = new MesItemStatus(rawData.Skip(84).Take(7).ToArray());
                ImmergitoreBins_L2 = new MesItemStatus(rawData.Skip(91).Take(7).ToArray());
                ImmergitoreCasse_L1 = new MesItemStatus(rawData.Skip(98).Take(7).ToArray());
                ImmergitoreCasse_L2 = new MesItemStatus(rawData.Skip(105).Take(7).ToArray());
                Varie = new PlantStatus(rawData.Skip(112).Take(3).ToArray());
            }
        }

        #endregion Public Constructors

        #region Public Properties

        public MesItemStatus Calibratrice_L1 { get; set; }
        public MesItemStatus Calibratrice_L2 { get; set; }
        public MesItemStatus Dewatering_L1 { get; set; }
        public MesItemStatus Dewatering_L2 { get; set; }
        public MesItemStatus Elevatore_L1 { get; set; }
        public MesItemStatus Elevatore_L2 { get; set; }
        public MesItemStatus ImmergitoreBins_L1 { get; set; }
        public MesItemStatus ImmergitoreBins_L2 { get; set; }
        public MesItemStatus ImmergitoreCasse_L1 { get; set; }
        public MesItemStatus ImmergitoreCasse_L2 { get; set; }
        public MesItemStatus NastroTaglierina_L1 { get; set; }
        public MesItemStatus NastroTaglierina_L2 { get; set; }
        public MesItemStatus Precalibro_L1 { get; set; }
        public MesItemStatus Precalibro_L2 { get; set; }
        public MesItemStatus Taglierina_L1 { get; set; }
        public MesItemStatus Taglierina_L2 { get; set; }
        public PlantStatus Varie { get; set; }

        #endregion Public Properties

        #region Public Methods

        public override bool Equals(object obj)
        {
            // Object is not a GaugeModel instance
            if (!(obj is DatiMesIcoel item))
                return false;

            if (Calibratrice_L1 != item.Calibratrice_L1)
                return false;
            if (Calibratrice_L2 != item.Calibratrice_L2)
                return false;
            if (Dewatering_L1 != item.Dewatering_L1)
                return false;
            if (Dewatering_L2 != item.Dewatering_L2)
                return false;
            if (Precalibro_L1 != item.Precalibro_L1)
                return false;
            if (Precalibro_L2 != item.Precalibro_L2)
                return false;
            if (Taglierina_L1 != item.Taglierina_L1)
                return false;
            if (Taglierina_L2 != item.Taglierina_L2)
                return false;
            if (NastroTaglierina_L1 != item.NastroTaglierina_L1)
                return false;
            if (NastroTaglierina_L2 != item.NastroTaglierina_L2)
                return false;
            if (Elevatore_L1 != item.Elevatore_L1)
                return false;
            if (Elevatore_L2 != item.Elevatore_L2)
                return false;
            if (ImmergitoreBins_L1 != item.ImmergitoreBins_L1)
                return false;
            if (ImmergitoreBins_L2 != item.ImmergitoreBins_L2)
                return false;
            if (ImmergitoreCasse_L1 != item.ImmergitoreCasse_L1)
                return false;
            if (ImmergitoreCasse_L2 != item.ImmergitoreCasse_L2)
                return false;
            if (Varie != item.Varie)
                return false;

            return true;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        #endregion Public Methods
    }

    public class MesItemStatus
    {
        #region Public Constructors

        public MesItemStatus(byte[] rawData)
        {
            Stato = (IcoelStatus)BitConverter.ToUInt16(rawData, 0);
            Velocita = BitConverter.ToUInt16(rawData, 2);
            Termico = !rawData.Skip(4).Take(1).FirstOrDefault().Equals(0);
            MagnetoTermico = !rawData.Skip(5).Take(1).FirstOrDefault().Equals(0);
            AvariaInverter = !rawData.Skip(6).Take(1).FirstOrDefault().Equals(0);
        }

        #endregion Public Constructors

        #region Public Properties

        public bool AvariaInverter { get; set; } = false;
        public bool MagnetoTermico { get; set; } = false;
        public IcoelStatus Stato { get; set; } = 0;
        public bool Termico { get; set; } = false;
        public UInt16 Velocita { get; set; } = 0;

        #endregion Public Properties

        #region Public Methods

        public override bool Equals(object obj)
        {
            // Object is not a GaugeModel instance
            if (!(obj is MesItemStatus item))
                return false;

            if (Stato != item.Stato)
                return false;
            if (Velocita != item.Velocita)
                return false;
            if (Termico != item.Termico)
                return false;
            if (MagnetoTermico != item.MagnetoTermico)
                return false;
            if (AvariaInverter != item.AvariaInverter)
                return false;

            return true;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Converte un singolo item in un array di byte per scrittura su PLC S7
        /// </summary>
        /// <returns></returns>
        public byte[] serialize()
        {
            byte[] answ = new byte[7];
            Buffer.BlockCopy(BitConverter.GetBytes((short)Stato), 0, answ, 0, 2);
            Buffer.BlockCopy(BitConverter.GetBytes(Velocita), 0, answ, 2, 2);
            Buffer.BlockCopy(BitConverter.GetBytes(Termico), 0, answ, 4, 1);
            Buffer.BlockCopy(BitConverter.GetBytes(MagnetoTermico), 0, answ, 5, 1);
            Buffer.BlockCopy(BitConverter.GetBytes(AvariaInverter), 0, answ, 6, 1);
            return answ;
        }

        #endregion Public Methods
    }

    public class OpcUaOmronIcoel : OpcUaOmron
    {
        #region Public Constructors

        /// <summary>
        /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation con la
        /// gestione specifica per Omron (es ICOEL) https://github.com/OPCFoundation/UA-.NETStandard
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IOBConf">Configurazione (legacy)</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public OpcUaOmronIcoel(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            // inizializzo classe base...
            sendKeyRichiesta = true;
            doByteRead = true;
            lgInfo($"Avviato IobOpcUaOmronIcoel | encodeReadData: {doByteRead}");
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Processo i task richiesti e li elimino dalla coda 1:1
        /// </summary>
        /// <param name="task2exe"></param>
        public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
        {
            // uso metodo base x ora
            return base.executeTasks(task2exe, codTav);
        }

        /// <summary>
        /// Effettua vero processing contapezzi
        /// </summary>
        public override void processContapezzi()
        {
        }

        /// <summary>
        /// Effettua reset del contapezzi, NON POSSIBILE in questa versione
        /// </summary>
        /// <returns></returns>
        public override bool resetContapezziPLC(string codTav)
        {
            lgInfo("OpcUaOmronIcoel.resetContapezziPLC - none");
            bool answ = false;
            return answ;
        }

        /// <summary>
        /// Effettua IMPOSTAZIONE FORZATA del contapezzi, NON POSSIBILE in questa versione
        /// </summary>
        /// <returns></returns>
        public override bool setcontapezziPLC(int newPzCount, string codTav)
        {
            bool answ = false;
            return answ;
        }

        #endregion Public Methods

        #region Internal Methods

        /// <summary>
        /// Verifica ed invia variazioni DAL FORMATO RAW data (byte[]) --&gt; esplode oggetti e li
        /// testa 1:1
        /// </summary>
        /// <param name="MonIt"></param>
        /// <param name="NotifyValue"></param>
        /// <param name="forceSend"></param>
        internal override bool checkAndSendRaw(Opc.Ua.Client.MonitoredItem MonIt, byte[] NotifyValue, bool forceSend)
        {
            bool changed = false;
            if (MonIt != null)
            {
                if (NotifyValue != null && NotifyValue.Length > 0)
                {
                    // verifico variazione "globale"
                    if (!lastData.Equals(currData))
                    {
                        // effettuo test/invio x ogni info
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Calibratrice_L1", lastData.Calibratrice_L1, currData.Calibratrice_L1, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Calibratrice_L2", lastData.Calibratrice_L2, currData.Calibratrice_L2, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Dewatering_L1", lastData.Dewatering_L1, currData.Dewatering_L1, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Dewatering_L2", lastData.Dewatering_L2, currData.Dewatering_L2, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Elevatore_L1", lastData.Elevatore_L1, currData.Elevatore_L1, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Elevatore_L2", lastData.Elevatore_L2, currData.Elevatore_L2, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreBins_L1", lastData.ImmergitoreBins_L1, currData.ImmergitoreBins_L1, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreBins_L2", lastData.ImmergitoreBins_L2, currData.ImmergitoreBins_L2, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreCasse_L1", lastData.ImmergitoreCasse_L1, currData.ImmergitoreCasse_L1, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreCasse_L2", lastData.ImmergitoreCasse_L2, currData.ImmergitoreCasse_L2, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "NastroTaglierina_L1", lastData.NastroTaglierina_L1, currData.NastroTaglierina_L1, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "NastroTaglierina_L2", lastData.NastroTaglierina_L2, currData.NastroTaglierina_L2, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Precalibro_L1", lastData.Precalibro_L1, currData.Precalibro_L1, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Precalibro_L2", lastData.Precalibro_L2, currData.Precalibro_L2, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Taglierina_L1", lastData.Taglierina_L1, currData.Taglierina_L1, forceSend);
                        changed = changed || testSendDataBlock(MonIt.StartNodeId, "Taglierina_L2", lastData.Taglierina_L2, currData.Taglierina_L2, forceSend);
                        // salvo lastData...
                        lastData = currData;
                    }
                }
                else
                {
                    lgError($"checkAndSend ERROR | MonIt: {MonIt.DisplayName} | NotifyValue Null!!!");
                }
            }
            else
            {
                lgError("checkAndSend ERROR: MonIt null");
            }
            return changed;
        }

        internal override void UA_ref_eh_MonItChange(object sender, opcUaMonitItemChange e)
        {
            base.UA_ref_eh_MonItChange(sender, e);
        }

        #endregion Internal Methods

        #region Protected Properties

        /// <summary>
        /// Valore corrente dei dati ICOEL (traduzione JIT da byte[])
        /// </summary>
        protected DatiMesIcoel currData
        {
            get
            {
                byte[] rawByte = new byte[115];
                // se ho dati raw decodifico...
                if (byteRawData != null)
                {
                    if (byteRawData.Length >= 115)
                    {
                        rawByte = byteRawData;
                    }
                }
                DatiMesIcoel answ = new DatiMesIcoel(rawByte);
                return answ;
            }
        }

        /// <summary>
        /// Indica se abbia stato POWER ON (multicondizione)
        /// </summary>
        protected override bool hasPowerOn
        {
            get
            {
                // da rivedere
                return true;
                //return checkMultiCondition(opcUaParams.condPowerOn);
            }
        }

        /// <summary>
        /// Indica se abbia stato MANUAL (condizioni varie, es stopped)
        /// </summary>
        protected override bool isManual
        {
            get
            {
                return false;
                //return checkMultiCondition(opcUaParams.condManual);
            }
        }

        /// <summary>
        /// Indica se abbia stato READY (condizioni varie, es ausiliari OK)
        /// </summary>
        protected override bool isReady
        {
            get
            {
                return false;
                //return checkMultiCondition(opcUaParams.condReady);
            }
        }

        /// <summary>
        /// Ultima versione validata delle info x confronto
        /// </summary>
        protected DatiMesIcoel lastData { get; set; } = new DatiMesIcoel(new byte[115]);

        #endregion Protected Properties

        #region Protected Methods

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO
        /// </summary>
        protected override void decodeToBaseBitmap()
        {
            DateTime adesso = DateTime.Now;
            CheckLiveData(adesso);
            // init a zero...
            B_input = 0;

            /* -----------------------------------------------------
             *  STATE MACHINE 60 STD / SIMULA
             *------------------------------------------------------
             * bitmap MAPO
             * B0: POWER_ON
             * B1: RUN
             * B2: pzCount
             * B3: allarme
             * B4: manuale
             * B5: SlowTC (NON gestito qui)
             * B6: warm-up / cool-down / setup
             * B7: emergenza ARMATA (1=ok, 0 = premuta)
             ---------------------------------------------------- */

            // se valido il check ping lo eseguo... altrimenti lo do x buono
            bool checkPing = !opcUaParams.pingAsPowerOn;
            string currRun = "N.A.";
            if (!checkPing)
            {
                checkPing = (testPingMachine == IPStatus.Success);
            }
            // bit 0 (poweron) imposto a 1 SE pingo + PowerOn=="ON"...
            bool powerOnOk = checkPing && hasPowerOn;
            // procedo SOLO SE mi da ping OK...
            if (checkPing)
            {
                B_input = powerOnOk ? 1 : 0;

                // decodifico da currData
                if (!currData.Varie.InEmergenza)
                {
                    B_input += (1 << 7);
                }
                if (currData.Varie.InMarcia)
                {
                    B_input += (1 << 1);
                }
                if (currData.Varie.InStop)
                {
                    B_input += (1 << 3);
                }
            }

            // controllo se sono poweroff e se non ho dati buoni da > 2 minuti --> disconnetto
            if (!powerOnOk && adesso.Subtract(lastCurrent).TotalMinutes > 2)
            {
                tryDisconnect();
            }

            // solo se non ho veto check
            int vFactor = 2;
            if (vetoCheckStatus < adesso)
            {
                lgDebug($"Stato variabili checkPing: {testPingMachine}");
                // imposto veto per vetoSeconds...
                vetoCheckStatus = adesso.AddSeconds(vetoSeconds * vFactor);
            }

            // log opzionale!
            if (verboseLog)
            {
                lgDebug($"Trasformazione checkPing: {checkPing} | hasPowerOn: {hasPowerOn} | B_input: {B_input} | currRun = {currRun}");
            }
        }

        /// <summary>
        /// Effettua vera scrittura parametri
        /// </summary>
        /// <param name="updatedPar"></param>
        protected override void plcWriteParams(ref List<objItem> updatedPar)
        {
        }

        /// <summary>
        /// effettua verifica del datablock icoel inviando eventualmente i dati variati
        /// </summary>
        /// <param name="startNodeId"></param>
        /// <param name="blockName"></param>
        /// <param name="currBlock"></param>
        /// <param name="newBlock"></param>
        /// <param name="forceSend"></param>
        /// <returns></returns>
        protected bool testSendDataBlock(NodeId startNodeId, string blockName, MesItemStatus currBlock, MesItemStatus newBlock, bool forceSend)
        {
            bool changed = false;
            // verifica globale blocchi old/new...
            if (!currBlock.Equals(newBlock))
            {
                // creo un nuovo monitoredItem se non ci fosse x ogni variabile dell'oggetto...
                changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Stato", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Stato}", forceSend);
                changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Velocita", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Velocita}", forceSend);
                changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Termico", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Termico}", forceSend);
                changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_MagnetoTermico", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.MagnetoTermico}", forceSend);
                changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_AvariaInverter", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.AvariaInverter}", forceSend);

                // spostare sotto in checkAndSendRaw ??? FIXME todo
                if (changed)
                {
                    lgTrace($"Invio variazione dataitem per {dataItemMem.Count} elementi");
                    sendDataItemListToServer(startNodeId);
                }
                else
                {
                    lgTrace("Nessuna variazione DataItem da trasmettere");
                }
            }
            return changed;
        }

        /// <summary>
        /// Verifico se salvare e inviare proprietà specificata
        /// </summary>
        /// <param name="MonIt"></param>
        protected bool testSendProperty(Opc.Ua.Client.MonitoredItem MonIt, string NotifyValue, bool forceSend)
        {
            bool changed = false;
            DateTime locTStamp = DateTime.Now;
            string sVal = "";
            string descr = "";
            string uuid = calcID($"{MonIt.StartNodeId.Identifier}", $"{MonIt.DisplayName}");
            descr = itemTranslation("OPC", uuid);
            sVal = $"Change: {locTStamp.ToString()} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {NotifyValue}";
            lgDebug($"TSP | {sVal}");

            changed = checkSaveValue(MonIt, NotifyValue, false);
            // cerco se non sia un dato filtrato in FLUXLOG...
            bool isFiltered = opcUaParams.fluxLogVeto.Contains(uuid);
            if (isFiltered)
            {
                lgTrace($"TSP | NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto", false);
            }
            else
            {
                if (changed || forceSend)
                {
                    bool sent = accodaFLog(descr, sVal, qEncodeFLog(descr, $"{NotifyValue}"));
                    if (sent)
                    {    // traccio valore DynData x analisi
                        trackDynData(descr, $"{NotifyValue}");
                    }
                }
                else
                {
                    lgTrace($"TSP | NON ACCODATO sample per {uuid} - verifica variazione ha dato esito negativo", false);
                }
            }
            return changed;
        }

        #endregion Protected Methods
    }

    public class PlantStatus
    {
        #region Public Constructors

        public PlantStatus(byte[] rawData)
        {
            InMarcia = !rawData.Skip(0).Take(1).FirstOrDefault().Equals(0);
            InEmergenza = !rawData.Skip(1).Take(1).FirstOrDefault().Equals(0);
            InStop = !rawData.Skip(2).Take(1).FirstOrDefault().Equals(0);

            //var valore = BitConverter.ToUInt16(rawData, 0);

            //byte b = rawData.Skip(0).Take(1).FirstOrDefault();
            //InMarcia = (b & (1 << (1 - 1))) != 0;
            //InEmergenza = (b & (1 << (2 - 1))) != 0;
            //InStop = (b & (1 << (3 - 1))) != 0;
        }

        #endregion Public Constructors

        #region Public Properties

        public bool InEmergenza { get; set; } = false;
        public bool InMarcia { get; set; } = false;
        public bool InStop { get; set; } = false;

        #endregion Public Properties

        #region Public Methods

        public override bool Equals(object obj)
        {
            // Object is not a GaugeModel instance
            if (!(obj is PlantStatus item))
                return false;

            if (InMarcia != item.InMarcia)
                return false;
            if (InEmergenza != item.InEmergenza)
                return false;
            if (InStop != item.InStop)
                return false;

            return true;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        #endregion Public Methods
    }
}