using LFP_Manager.DataStructure;
using LFP_Manager.Function;
using SnmpSharpNet;
using System;
using System.Net;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using LFP_Manager.Utils;
using System.Windows.Forms;
using static DevExpress.Xpo.Helpers.AssociatedCollectionCriteriaHelper;

namespace LFP_Manager.Threads
{
    public delegate void SetResult(string result, bool error);
    public delegate void DataUpdate(object sender, ref DeviceSystemData rData);
    class CsSnmpThread
    {
        #region VARIABLES
        private DeviceSystemData SystemData;
        private CommConfig Config;

        private Thread snmpThread = null;
        private bool snmpThreadHold = true;

        private string targetIP = "";

        private bool snmpDataRecv = false;
        private string snmpResult = "";

        private bool AutoSnmpTx = true;

        private int type = 0;
        private int OidIndex = 0;
        private int OidMax = 0;
        private int OidIndexBase = 0;

        private int CommTimeOut = 0;

        private string[] OidList;

        private int SetSnmpMode = 0;
        private int SetSnmpMode1 = 0;
        private UInt32 SetSnmpValue = 0;
        private string SetSnmpStringData;

        public event DataUpdate OnUpdate = null;
        public event SetResult OnSetResult = null;
        #endregion

        #region CONSTRUCTORS
        public CsSnmpThread()
        {
            snmpThread = new Thread(snmpThreadFuncA);
            snmpThread.Start();
        }

        public CsSnmpThread(CommConfig aConfig, ref DeviceSystemData aSystemData)
        {
            Config = aConfig;

            type = Config.SnmpModelIndex;

            OidIndex = 0;
            OidList = CsSnmpConstData.SnmpOidInfo;
            OidMax = OidList.Length;

            SystemData = aSystemData;

            snmpThread = new Thread(snmpThreadFuncA);
            snmpThread.Start();
        }
        #endregion

        #region PUBLIC FUNCTION
        public void disposeThread()
        {
            snmpThreadHold = true;
            snmpThread.Abort();
        }

        public void Start(string IP, bool autoTx)
        {
            targetIP = IP;
            snmpThreadHold = false;

            AutoSnmpTx = autoTx;
        }

        public void Stop()
        {
            snmpThreadHold = true;
        }

        public bool GetStatus()
        {
            return snmpThreadHold;
        }

        public string GetResult()
        {
            string result = "";

            if (snmpDataRecv)
            {
                result = snmpResult;
                snmpDataRecv = false;
            }
            return result;
        }
        public void SetAutoTx(bool autoTx)
        {
            AutoSnmpTx = autoTx;
        }
        #endregion

        #region COMM. THREAD

        public string GetSnmpOidInfo(int index)
        {
            return OidList[index];
        }

        bool SetCmdbySnmp(UdpTarget sTarget, int mode, UInt32 value, string svalue)
        {
            bool resultError = false;
            Pdu SetPdu = new Pdu(PduType.Set);

            // Set a value to unsigned integer
            SetPdu.VbList.Clear();

            switch (mode)
            {
                case 1:         // Reset Command
                    SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.13.1.0"), new Integer32(Convert.ToInt32(value)));
                    break;
                case 2:         // Module Quantity Set
                    SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.4.0"), new Integer32(Convert.ToInt32(value)));
                    break;
                case 3:         // Alarm Output Mode
                    SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.5.0"), new Integer32(Convert.ToInt32(value)));
                    break;
                case 4:         // Manufacture Date
                    SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.6.0"), new Integer32(Convert.ToInt32(value)));
                    break;
                case 5:         // BMS Serial Number
                    SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.7.0"), new OctetString(svalue));
                    break;
            }

            // Set Agent security parameters
            AgentParameters aparam = new AgentParameters(SnmpVersion.Ver1, new OctetString("public"));

            // Make SNMP request
            try
            {
                // Send request and wait for response
                SnmpV1Packet result = sTarget.Request(SetPdu, aparam) as SnmpV1Packet;

                // If result is null then agent didn't reply or we couldn't parse the reply.
                if (result != null)
                {
                    // ErrorStatus other then 0 is an error returned by 
                    // the Agent - see SnmpConstants for error definitions
                    if (result.Pdu.ErrorStatus != 0)
                    {
                        // agent reported an error with the request
                        snmpResult = String.Format("Error in SNMP reply. Error {0} index {1}",
                            result.Pdu.ErrorStatus,
                            result.Pdu.ErrorIndex);

                        if (OnSetResult != null)
                        {
                            OnSetResult(snmpResult, true);
                        }
                    }
                    else
                    {
                        // Everything is ok. Agent will return the new value for the OID we changed
                        string resultString;
                        if (result.Pdu.VbList[0].Type == AsnType.OCTETSTRING)
                            resultString = result.Pdu.VbList[0].Value.ToString();
                        else
                            resultString = result.Pdu.VbList[0].Value.ToString();

                        snmpResult = String.Format("SNMP Received data. result {0}",
                            resultString);

                        OnSetResult?.Invoke(snmpResult, false);
                    }
                }
                else
                {
                    if (snmpDataRecv == false)
                    {
                        snmpResult = DateTime.Now.ToString("[yyyy-MM-dd hh:mm:ss] ") + String.Format("No response received from SNMP agent.");
                        snmpDataRecv = true;

                        OnSetResult?.Invoke(snmpResult, true);
                    }
                }
            }
            catch (Exception ex)
            {
                if (snmpDataRecv == false)
                {
                    snmpResult = "[" + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + "] " + String.Format("Exception : {0}", ex.Message);
                    snmpDataRecv = true;

                    OnSetResult?.Invoke(snmpResult, true);
                }
                resultError = true;
            }

            return resultError;
        }

        public void SetDataBySnmp(int mode, UInt32 value)
        {
            SetSnmpMode = mode;
            SetSnmpValue = value;
        }

        public void SetDataBySnmp(int mode, string value)
        {
            SetSnmpMode1 = mode;
            SetSnmpStringData = value;
        }

        private void snmpThreadFuncA()
        {
            while (true)
            {
                if (snmpThreadHold == false)
                {
                    // SNMP community name
                    OctetString community = new OctetString("admin");

                    // Define agent parameters class
                    AgentParameters param = new AgentParameters(community);
                    // Set SNMP version to 1 (or 2)
                    param.Version = SnmpVersion.Ver1;
                    // Construct the agent address object
                    // IpAddress class is easy to use here because
                    //  it will try to resolve constructor parameter if it doesn't
                    //  parse to an IP address
                    String dstAddr = targetIP;
                    IpAddress agent = new IpAddress(dstAddr);

                    // Construct target
                    UdpTarget target = new UdpTarget((IPAddress)agent, 161, 1000, 1);

                    // Pdu class used for all requests
                    Pdu pdu = new Pdu(PduType.Get);

                    if (SetSnmpMode != 0)
                    {
                        SetCmdbySnmp(target, SetSnmpMode, SetSnmpValue, SetSnmpStringData);
                        SetSnmpMode = 0;
                    }
                    else if (SetSnmpMode1 != 0)
                    {
                        SetCmdbySnmp(target, SetSnmpMode1, SetSnmpValue, SetSnmpStringData);
                        SetSnmpMode1 = 0;
                    }
                    else
                    {
                        // SNMP GET
                        while (AutoSnmpTx)
                        {
                            snmpResult = "";

                            if ((SetSnmpMode != 0) || (SetSnmpMode1 != 0))
                            {
                                break;
                            }

                            pdu.VbList.Clear();
                            pdu.VbList.Add(GetSnmpOidInfo(OidIndex));

                            // Make SNMP request
                            try
                            {
                                //SnmpV1Packet result = (SnmpV1Packet)target.Request(pdu, param);
                                target.Timeout = 100;
                                // agent reported an error with the request
                                snmpResult = csLog.trx_msg_print(string.Format("SEND {0}: OID [{1}]"
                                        , target.Address.ToString()
                                        , pdu.VbList[0].Oid.ToString()
                                        )
                                    , 0
                                    )
                                    ;
                                OnSetResult?.Invoke(snmpResult, true);

                                SnmpV1Packet result = (SnmpV1Packet)target.Request(pdu, param);
                                // If result is null then agent didn't reply or we couldn't parse the reply.
                                if (result != null)
                                {
                                    // ErrorStatus other then 0 is an error returned by 
                                    // the Agent - see SnmpConstants for error definitions
                                    if (result.Pdu.ErrorStatus != 0)
                                    {
                                        // agent reported an error with the request
                                        snmpResult = csLog.trx_msg_print(string.Format("Error in SNMP reply. Error {0} index {1} - {2}"
                                                , result.Pdu.ErrorStatus
                                                , result.Pdu.ErrorIndex
                                                , result.Pdu.VbList[0].Oid.ToString()
                                                )
                                            , 1
                                            )
                                            ;
                                        OnSetResult?.Invoke(snmpResult, true);
                                    }
                                    else
                                    {
                                        snmpResult = csLog.trx_msg_print(string.Format("RECV {0}: OID [{1}]: [{2}]"
                                                , target.Address.ToString()
                                                , result.Pdu.VbList[0].Oid.ToString()
                                                , result.Pdu.VbList[0].Value.ToString()
                                                )
                                            , 1
                                            )
                                            ;
                                        OnSetResult?.Invoke(snmpResult, true);

                                        String resultTxt = "";
                                        // Reply variables are returned in the same order as they were added
                                        //  to the VbList
                                        //resultTxt = String.Format("[{0}] ({1}): {2}",
                                        //    result.Pdu.VbList[0].Oid.ToString(),
                                        //    SnmpConstants.GetTypeName(result.Pdu.VbList[0].Value.Type),
                                        //    result.Pdu.VbList[0].Value.ToString());

                                        resultTxt = String.Format(".{0}", result.Pdu.VbList[0].Oid.ToString());
                                        uint[] resultInt = (uint[])result.Pdu.VbList[0].Oid;

                                        int len = resultInt.Length;

                                        if ((resultInt[0] == 1)
                                            && (resultInt[1] == 3)
                                            && (resultInt[2] == 6)
                                            && (resultInt[3] == 1)
                                            && (resultInt[4] == 2)
                                            && (resultInt[5] == 1)
                                            && (resultInt[6] == 15)
                                            )
                                        {
                                            //double resultDouble = 0.0;
                                            if (result.Pdu.VbList[0].Value.Type == AsnType.OCTETSTRING)
                                            {
                                                try
                                                {
                                                    //resultDouble = Convert.ToDouble(result.Pdu.VbList[0].Value.ToString());
                                                    csMakeDataFunction.SetSnmpData((int)resultInt[7], result.Pdu.VbList[0].Value.ToString(), ref SystemData);
                                                }
                                                catch (Exception ex)
                                                {
                                                    MessageBox.Show(ex.Message);
                                                }
                                            }
                                            else if (result.Pdu.VbList[0].Type == AsnType.OCTETSTRING)
                                            {
                                                MessageBox.Show(result.Pdu.VbList[0].Value.ToString());
                                            }

                                            CalcAvgTemperatureModule(ref SystemData, (uint)SystemData.tempQty);
                                            CheckShelfCommFail(ref SystemData);

                                            OnUpdate?.Invoke(this, ref SystemData);
                                        }

                                        CommTimeOut = 0;
                                        SystemData.CommFail = false;
                                    }
                                    Thread.Sleep(50);
                                }
                                else
                                {
                                    if (snmpDataRecv == false)
                                    {
                                        snmpResult = DateTime.Now.ToString("[yyyy-MM-dd hh:mm:ss] ") + String.Format("No response received from SNMP agent.");
                                        snmpDataRecv = true;
                                    }
                                    CommTimeOut++;

                                    if (CommTimeOut >= 20)
                                    {
                                        SystemData.CommFail = true;
                                        InitModuleData();
                                        CommTimeOut = 20;
                                    }
                                }

                                OidIndex++;
                                OidIndex %= OidMax;

                                Thread.Sleep(1);
                            }
                            catch (Exception ex)
                            {
                                snmpResult = csLog.trx_msg_print(string.Format("Exception in SNMP reply. Error {0} - {1}"
                                        , pdu.VbList[0].Oid.ToString()
                                        , ex.Message
                                        )
                                    , 1
                                    )
                                    ;
                                OnSetResult?.Invoke(snmpResult, true);
                                CommTimeOut++;

                                if (CommTimeOut >= 10)
                                {
                                    SystemData.CommFail = true;
                                    InitModuleData();
                                    CommTimeOut = 10;
                                }
                                break;
                            }
                        }
                        CheckShelfCommFail(ref SystemData);
                        OnUpdate?.Invoke(this, ref SystemData);
                    }
                    pdu.Clone();
                    target.Close();

                    Thread.Sleep(500);
                }
            }
        }

        private void InitModuleData()
        {
            SystemData.ValueData.voltageOfPack = 0;
            SystemData.ValueData.current = 0;
            SystemData.ValueData.averageCurrent = 0;
            SystemData.ValueData.rSOC = 0;
            SystemData.ValueData.stateOfHealth = 0;
            for (int i = 0; i < SystemData.ValueData.CellVoltage.Length; i++)
            {
                SystemData.ValueData.CellVoltage[i] = 0;
            }
            for (int i = 0; i < SystemData.ValueData.CellTemperature.Length; i++)
            {
                SystemData.ValueData.CellTemperature[i] = 0;
            }
            SystemData.AvgData.avgCellVoltage = 0;
            SystemData.AvgData.diffCellVoltage = 0;
            SystemData.AvgData.maxCellVoltage = 0;
            SystemData.AvgData.minCellVoltage = 0;
            SystemData.AvgData.maxCellNum = 0;
            SystemData.AvgData.minCellNum = 0;

            SystemData.AvgData.avgTemp = 0;
            SystemData.AvgData.diffTemp = 0;
            SystemData.AvgData.maxTemp = 0;
            SystemData.AvgData.minTemp = 0;
            SystemData.AvgData.maxTempNum = 0;
            SystemData.AvgData.minTempNum = 0;
        }

        private void CheckShelfCommFail(ref DeviceSystemData aSystemData)
        {
            if (aSystemData.StatusData.batteryStatus == 0x0003)
                { aSystemData.ShelfCommFail = true; }
            else
                { aSystemData.ShelfCommFail = false; }
        }

        #endregion

        static void CalcAvgTemperatureModule(ref DeviceSystemData aSystemData, UInt32 tSize)
        {
            short i, j;
            int sum;
            TMinMax min, max;

            min = new TMinMax();
            max = new TMinMax();

            sum = 0;
            max.value = 0;
            max.num = 0;
            min.value = 0;
            min.num = 0;

            j = 0;
            for (i = 0; i < tSize; i++)
            {
                if (j == 0)
                {
                    max.value = aSystemData.ValueData.CellTemperature[i];
                    max.num = i;
                    min.value = aSystemData.ValueData.CellTemperature[i];
                    min.num = i;
                }

                if (max.value < aSystemData.ValueData.CellTemperature[i])
                {
                    max.value = aSystemData.ValueData.CellTemperature[i];
                    max.num = i;
                }
                if (min.value > aSystemData.ValueData.CellTemperature[i])
                {
                    min.value = aSystemData.ValueData.CellTemperature[i];
                    min.num = i;
                }

                sum += aSystemData.ValueData.CellTemperature[i];
                j++;
            }

            if (j == 0)
                { aSystemData.AvgData.avgTemp = 0; }
            else
                { aSystemData.AvgData.avgTemp = (short)(sum / j); }

            aSystemData.AvgData.maxTemp = max.value;
            aSystemData.AvgData.maxTempNum = max.num;
            aSystemData.AvgData.minTemp = min.value;
            aSystemData.AvgData.minTempNum = min.num;
        }
    }
}
