using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace arinc_1553_CSharpGUI
{
    using U64BIT = System.UInt64;
    using U32BIT = System.UInt32;
    using U16BIT = System.UInt16;
    using U8BIT = System.Byte;
    using S64BIT = System.Int64;
    using S32BIT = System.Int32;
    using S16BIT = System.Int16;
    using S8BIT = System.SByte;

    public class MsgArinc429
    {
        public U8BIT labelValue;
        public string labelName;

        /* The duration at which this message is to be repeatedly transmitted */
        //public ushort frequency;

        public string type;
        public string channel;

        /* List of engg unit parameters in this message */
        public List<EguParameterArinc429> eguParams = new List<EguParameterArinc429>();

        /* The Message last received from the Arinc429 controller */
        U32BIT msgRunTimeData;

        U64BIT combinedTT = 0;

        ushort timeTagResolution = 4;

        U8BIT arincChan = 0xFF;

        public void AddNewEguParameter(EguParameterArinc429 param)
        {
            /* Create a new EguParameter object, fill it with values from the param argument and add to the list */
            EguParameterArinc429 newParam = new EguParameterArinc429();
            newParam = param;
            eguParams.Add(newParam);
        }

        /* Returns a pointer to the EguParameter object with name "paraName" if found, else returns nullptr */
        public EguParameterArinc429 GetParameter(string paraName)
        {
            foreach (EguParameterArinc429 eguParam in eguParams)
            {
                if (eguParam.name == paraName)
                {
                    return eguParam;
                }
            }

            //qDebug() << __FUNCTION__ << "No parameter found with name " << paraName << " in message " << name;
            return null;
        }

        public void setMsgRuntimeData(U32BIT msg, U64BIT timeStamp, U8BIT u8Chan)
        {
            // Consume new run time data
            msgRunTimeData = msg;

            combinedTT = timeStamp;

            arincChan = u8Chan;
        }

        public U32BIT getMsgRunTimeData()
        {
            return msgRunTimeData;
        }

        public MsgArinc429()
        {
            // Reset
            ResetRunTimeData();
        }

        /* Copy constructor */
        public MsgArinc429(ref MsgArinc429 msg)
        {
            CopyObject(ref msg);
        }

        void CopyObject(ref MsgArinc429 msg)
        {
            // Reset
            ResetRunTimeData();

            labelValue = msg.labelValue;
            labelName = msg.labelName;
            type = msg.type;

            setMsgRuntimeData(msg.msgRunTimeData, msg.combinedTT, msg.arincChan);

            foreach (EguParameterArinc429 e in msg.eguParams)
            {
                AddNewEguParameter(e);
            }
        }

        /* "=" operator */
        /*        Msg1553& Msg1553::operator=(const Msg1553& msg)
                {
                    CopyObject(msg);
                    return *this;
                }*/

        ~MsgArinc429()
        {
            /* Clean up all the dynamically created EguParameter objects */
            for (int i = 0; i < eguParams.Count; i++)
            {
                /* Delete the dynamically created object */
                /*                if (eguParams[i] != null)
                                    delete eguParams[i];*/

                /* Remove it from the list */
                eguParams.RemoveAt(i);
            }
        }

        bool DoEguConversionForParam(ref EguParameterArinc429 eguParam, List<CodedType> codedTypes)
        {
            bool isSignBit = false;

            if (eguParam.dataType.ToUpper() == "INTEGER")
            {
                if( ((U32BIT)(msgRunTimeData & 0x10000000)) == 0x10000000 ) // Is 29th bit (sign bit) 1?
                    isSignBit = true;
            }

            /* Extract only the bits corresponding to this parameter */
            U32BIT countValue = (U32BIT)(msgRunTimeData >> (int)eguParam.startBit);
            U32BIT mask = (U32BIT)(0xFFFFFFFF >> (int)(32 - eguParam.length));
            countValue &= mask;

            // Assume EGU conversion will succeed and mark this parameter as valid
            eguParam.isValid = true;

            if (eguParam.dataType.ToUpper() == "UNSIGNED")
            {
                eguParam.value.fValue = (float)(((U32BIT)countValue) * eguParam.scaleFactor + eguParam.offset);
            }
            else if (eguParam.dataType.ToUpper() == "INTEGER")
            {
                eguParam.value.fValue = (float)(((S32BIT)countValue) * eguParam.scaleFactor + eguParam.offset);

                if(isSignBit)
                {
                    eguParam.value.fValue = -Math.Abs(eguParam.value.fValue);
                }
            }
            else if (eguParam.dataType.ToUpper() == "BCD")
            {
                if(!TryBcd8ToUInt32(countValue, out eguParam.value.u19Value))
                {
                    // Mark parameter as invalid so everyone knows that the value is not the latest valid one
                    eguParam.isValid = false;
                }
                else
                {
                    // BCD converted to binary. Scale and store
                    eguParam.value.fValue = (float)(eguParam.value.u19Value * eguParam.scaleFactor);
                }
            }
            //else if ((eguParam.dataType == "UNSIGNED_REAL") || (eguParam.dataType == "SIGNED_REAL"))
            else if (eguParam.dataType.ToUpper() == "REALIEEE754")
            {
                U32BIT extCountValue;

                if ((eguParam.dataType == "SIGNED_REAL") &&
                    (((countValue >> (int)(eguParam.length - 1)) & 1) != 0)
                    )  /* Is sign bit 1? */
                {
                    //Make sure to extend the sign information
                    extCountValue = (U32BIT)(countValue | (0xFFFFFFFF << (int)eguParam.length));
                }
                else
                {
                    extCountValue = countValue;
                }

                eguParam.value.fValue = (float)(extCountValue * eguParam.scaleFactor + eguParam.offset);
            }
            else if (eguParam.dataType.ToUpper() == "CODED")
            {
                string tempStr;

                /* For coded types, we will not only populate the string value but also the raw value in u16Value field. This is to enable
                    utilities like "plot" to plot coded parameters */
                eguParam.value.u19Value = countValue;

                // Use the computed value as an index into the coded string list
                List<string> codedValues = GetCodedValues(eguParam.codedType, codedTypes);

                if (codedValues.Count > eguParam.value.u19Value)
                {
                    tempStr = codedValues[(int)eguParam.value.u19Value];
                }
                else
                {
                    tempStr = "!VAL_OOR";
                }

                eguParam.value.strValue = tempStr;
            }
            else
            {
                //qDebug() << __FUNCTION__ << "Unknown data type encountered";
            }

            return true;
        }


        /// <summary>
        /// Converts a 4-digit BCD (packed in a 16-bit value) into a numeric value.
        /// E.g., if bcd = 0x1234 (i.e., digits “1,2,3,4”), returns 1234.
        /// Returns false if any nibble > 9 (i.e., invalid BCD).
        /// </summary>
        public static bool TryBcd4ToUInt16(ushort bcd, out ushort result)
        {
            result = 0;
            ushort factor = 1;

            for (int i = 0; i < 4; i++)
            {
                ushort digit = (ushort)((bcd >> (i * 4)) & 0x0F);
                if (digit > 9)
                {
                    // invalid BCD digit
                    return false;
                }
                result += (ushort)(digit * factor);
                factor *= 10;
            }

            return true;
        }

        /// <summary>
        /// Converts an 8-digit packed BCD (32-bit value, each nibble = one decimal digit)
        /// into a numeric value (uint). Returns false if any nibble > 9.
        /// Example: bcd = 0x12345678 → decimal 12 345 678 → hex 0x00BC614E.
        /// </summary>
        public static bool TryBcd8ToUInt32(uint bcd32, out uint result)
        {
            result = 0u;
            uint factor = 1u;

            // Process 8 digits (8 nibbles)
            for (int i = 0; i < 8; i++)
            {
                uint digit = (bcd32 >> (i * 4)) & 0x0Fu;
                if (digit > 9u)
                {
                    // invalid BCD digit found
                    return false;
                }
                result += digit * factor;
                factor *= 10u;
            }

            return true;
        }
        List<string> GetCodedValues(string codedType, List<CodedType> listCodedTypes)
        {
            List<string> codedValues = new List<string>();

            foreach (CodedType ct in listCodedTypes)
            {
                if (ct.type == codedType)
                {
                    codedValues = ct.values;
                    break;
                }
            }

            return codedValues;
        }


        public bool DoEguConversionForAllParams(List<CodedType> codedTypes)
        {
            //foreach (EguParameter eguParam in eguParams)
            for (int i = 0; i < eguParams.Count; i++)
            {
                EguParameterArinc429 eguParam = new EguParameterArinc429();
                eguParam = eguParams[i];
                if (DoEguConversionForParam(ref eguParam, codedTypes) == false)  // Doubt
                {
                    return false;
                }

                eguParams.RemoveAt(i);
                eguParams.Insert(i, eguParam);
            }

            return true;
        }

        public bool DoEguConversionForParam(string paraName, List<CodedType> codedTypes)
        {
            EguParameterArinc429 eguParam = GetParameter(paraName);
            bool retVal = false;
            if (eguParam != null)
            {
                retVal = DoEguConversionForParam(ref eguParam, codedTypes);
            }
            else
            {
                retVal = false;
                //qDebug() << __FUNCTION__ << "GetParameter returned nullptr for parameter " << paraName;
            }
            return retVal;
        }

        public U64BIT getCombinedTT()
        {
            return combinedTT;
        }

        public U8BIT getChannelNum()
        {
            return arincChan;
        }

        public string getFormattedTT()
        {
            TimeSpan ts = TimeSpan.FromTicks((long)combinedTT);

            return string.Format("{0}:{1:D2}:{2:D2}:{3:D2}.{4:D3}", ts.Days, ts.Hours,
                ts.Minutes, ts.Seconds, ts.Milliseconds);
        }

        public void LoadDefaultData()
        {
            foreach (EguParameterArinc429 eguParam in eguParams)
            {
                eguParam.LoadDefaultData();
            }
        }

        public void setTimeTagResolution(ushort newTimeTagResolution)
        {
            timeTagResolution = newTimeTagResolution;
        }

        public void ResetRunTimeData()
        {
            //memset(&msgRunTimeData, 0, sizeof(MSGSTRUCT));
            msgRunTimeData = 0;
        }
    }
}
