using iq2dev.com.IQCommLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace iq2dev.com.iqCommLib.DataTypes
{
    public enum IOLBaudrate
    {
        COM1 = 0,
        COM2 = 1,
        COM3 = 2
    }

    public enum IOLRevisionID
    {
        V10 = 0x10,
        V11 = 0x11
    }

    public enum IOLCycTimeBase
    {
        ms01 = 0,

        ms04 = 1,

        ms16 = 2
    }

    public enum IOLDirectPageParameters
    {
        MinCycTime = 0,
        MseqCap = 1,
        RevID = 2,
        PDIn = 3,
        PDOut = 4,
        VenIDHi = 5,
        VenIDLo = 6,
        DevIDHi = 7,
        DevIDMid = 8,
        DevIDLo = 9,
        FunIDHi = 10,
        FunIDLo = 11
    }

    public enum IOLInspectionLevel
    {
        NoCheck = 0,
        TypeCompatibility = 1,
        Identical = 2
    }

    public enum IOLDSActivationState
    {
        Enabled = 0,
        Disabled = 1,
        Cleared = 2
    }

    public enum IOLPortState
    {
        Inactive = 0,
        DO = 1,
        DI = 2,
        CheckFault = 3,
        Preoperate = 4,
        Operate = 5
    }

    public enum IQDevState
    {
        Inactive = 0,
        SIO = 1,
        Startup = 2,
        Preoperate = 3,
        Operate = 4
    }

    public enum IOLPortOperatingMode
    {
        Inactive = 0,
        DO = 1,
        DI = 2,
        CheckFault = 3,
        Preoperate = 4,
        Operate = 5
    }

    public static class IOLDevIdentByteLength
    {
        public static readonly int DeviceID = 3;
        public static readonly int FunctionID = 2;
        public static readonly int VendorID = 2;
        public static readonly int SerialNumber = 16;
    }

    public enum PDLengthUnit
    {
        Bit,
        Byte
    }

    internal class GenericDeviceConfig
    {
        #region fields

        public readonly double[] ByteTransferTimeMs = { 12000.0 / 4800.0, 12000.0 / 38400.0, 12000.0 / 230400.0 };

        public readonly int[] ODSizes = { 1, 2, 8, 32 };

        public IOLBaudrate Baudrate = IOLBaudrate.COM3;

        public Byte MinCycleTime = 10;

        public Byte MseqCapability = 1;

        public int ODSizeOperate = 1;

        public Byte PDIn = 0x10;

        public Byte PDInLength = 16;

        public Byte PDOut = 0x10;

        public Byte PDOutLength = 16;

        public IOLBaudrate baudrate
        {
            get { return Baudrate; }
            set { Baudrate = value; }
        }

        public UInt32 DeviceID
        {
            get { return deviceID; }
            set
            {
                if (value > 0xFFFFFF)
                    throw new Exception("Device ID must be less than 16777215");
                deviceID = value;
            }
        }

        public UInt16 functionID
        {
            set { if (value != 0) throw new Exception("Function ID can be only null"); }
            get { return 0; }
        }

        public Boolean ISDUSupport
        {
            set
            {
                if (value)
                    MseqCapability |= 1;
                else
                    MseqCapability &= 0xFE;
            }
            get { return (MseqCapability & 1) == 1; }
        }

        public Boolean IsPDInLengthBytes
        {
            set
            {
                if (value)
                    PDIn |= 0x80;
                else
                    PDIn &= 0x7F;
            }
            get
            {
                return (PDIn & 0x80) == 0x80;
            }
        }

        public Boolean IsPDOutLengthBytes
        {
            set
            {
                if (value)
                    PDOut |= 0x80;
                else
                    PDOut &= 0x7F;
            }
            get
            {
                return (PDOut & 0x80) == 0x80;
            }
        }

        public IOLCycTimeBase MinCycleTimeBase
        {
            set
            {
                switch (value)
                {
                    case IOLCycTimeBase.ms01: minCycleTimeBase = 0.1; minCycleTimeAdd = 0; break;
                    case IOLCycTimeBase.ms04: minCycleTimeBase = 0.4; minCycleTimeAdd = 6.4; break;
                    case IOLCycTimeBase.ms16: minCycleTimeBase = 1.6; minCycleTimeAdd = 32; break;
                }
                MinCycleTime &= 0x3F;
                MinCycleTime |= (Byte)((((Byte)value) << 6) & 0xC0);
            }
            get { return (IOLCycTimeBase)((MinCycleTime >> 6) & 3); }
        }

        public Byte MinCycleTimeMult
        {
            set
            {
                if (value > 63)
                    throw new Exception("Min cycle time multiplier can not be more than 63");
                MinCycleTime &= 0xC0;
                MinCycleTime |= value;
            }
            get { return (Byte)(MinCycleTime & 0x3F); }
        }

        public int odSizeOperate
        {
            get { return ODSizeOperate; }
            set { ODSizeOperate = value; }
        }

        public int ODSizePreoperate
        {
            set
            {
                MseqCapability &= 0xCF;
                switch (value)
                {
                    case 1: break;
                    case 2: MseqCapability |= 0x10; break;
                    case 8: MseqCapability |= 0x20; break;
                    case 32: MseqCapability |= 0x30; break;
                    default:
                        throw new Exception("On-request data size can be only 1, 2, 8, 32");
                }
            }
            get
            {
                switch (MseqCapability & 0x30)
                {
                    case 0x00: return 1;
                    case 0x10: return 2;
                    case 0x20: return 8;
                    case 0x30: return 32;
                }
                return 0;
            }
        }

        public Byte pdInLength
        {
            get { return PDInLength; }
            set { PDInLength = value; }
        }

        public Byte PDInLengthBytes { get { return IsPDInLengthBytes ? PDInLength : (PDInLength > 8 ? (Byte)2 : (Byte)1); } }

        public PDLengthUnit PDInLengthUnit
        {
            set { IsPDInLengthBytes = value == PDLengthUnit.Byte; }
            get { return IsPDInLengthBytes ? PDLengthUnit.Byte : PDLengthUnit.Bit; }
        }

        public Byte pdOutLength
        {
            get { return PDOutLength; }
            set { PDOutLength = value; }
        }

        public Byte PDOutLengthBytes { get { return IsPDOutLengthBytes ? PDOutLength : (PDOutLength > 8 ? (Byte)2 : (Byte)1); } }

        public PDLengthUnit PDOutLengthUnit
        {
            set { IsPDOutLengthBytes = value == PDLengthUnit.Byte; }
            get { return IsPDOutLengthBytes ? PDLengthUnit.Byte : PDLengthUnit.Bit; }
        }

        public IOLRevisionID RevisionID
        {
            get { return revID; }
            set { revID = value; }
        }

        public Boolean SIOSupport
        {
            set
            {
                if (value)
                    PDIn |= 0x40;
                else
                    PDIn &= 0xBF;
            }
            get
            {
                return (PDIn & 0x40) == 0x40;
            }
        }

        public UInt16 VendorID
        {
            get { return vendorID; }
            set { vendorID = value; }
        }

        private UInt32 deviceID = 0;
        private double minCycleTimeAdd = 0;
        private double minCycleTimeBase = 0.1;
        private Byte MstCycleTime = 0;
        private IOLRevisionID revID = IOLRevisionID.V11;
        private UInt16 vendorID = 0;

        #endregion fields

        #region Public Methods

        public object Clone()
        {
            return this.MemberwiseClone();
        }

        public void Download(IProtocolCommunicator comm)
        {
            List<byte> data = new List<byte>();

            data.Add(MinCycleTime);
            data.Add(MseqCapability);
            data.Add((byte)revID);
            data.Add(PDIn);
            data.Add(PDOut);
            data.Add((byte)((vendorID >> 8) & 0xFF));
            data.Add((byte)((vendorID) & 0xFF));
            data.Add((byte)((deviceID >> 16) & 0xFF));
            data.Add((byte)((deviceID >> 8) & 0xFF));
            data.Add((byte)((deviceID) & 0xFF));
            data.Add(0);
            data.Add(0);

            comm.writeIQCommPar(IQPar.IQParGenDevSupportedBaudrate, new List<byte>() { (byte)Baudrate });
            comm.writeIQCommPar(IQPar.IQParGenDevDirPage, data);
        }

        public void Submit()
        {
            Byte MseqCapabOper;

            #region pack PD In

            // pack Process Data In
            // bytes
            if (IsPDInLengthBytes && (PDInLength == 2 || PDInLength == 1))
            {
                IsPDInLengthBytes = false;
                PDInLength = (byte)(PDInLength == 2 ? 16 : 8);
            }
            if (IsPDInLengthBytes)
            {
                if (PDInLength == 0)
                {
                    IsPDInLengthBytes = false;
                    PDIn = 0;
                }
                else
                {
                    if (PDInLength < 3)
                        throw new Exception("Process data In length in bytes can not be less than 3 bytes");
                    if (PDInLength > 32)
                        throw new Exception("Process data In length in bytes can not be more than 32 bytes");
                    PDIn &= 0xC0;
                    PDIn |= (Byte)(PDInLength - 1);
                }
            }
            // bits
            else
            {
                if (PDInLength > 16)
                    throw new Exception("Process data In length in bits can not be more than 16 bits");
                else
                {
                    PDIn &= 0xC0;
                    PDIn |= PDInLength;
                }
            }

            #endregion pack PD In

            #region pack PD Out

            // pack Process Data Out
            // bytes
            if (IsPDOutLengthBytes && (PDOutLength == 2 || PDOutLength == 1))
            {
                IsPDOutLengthBytes = false;
                PDOutLength = (byte)(PDOutLength == 2 ? 16 : 8);
            }
            if (IsPDOutLengthBytes)
            {
                if (PDOutLength == 0)
                {
                    IsPDOutLengthBytes = false;
                    PDOut = 0;
                }
                else
                {
                    if (PDOutLength < 3)
                        throw new Exception("Process data Out length in bytes can not be less than 3 bytes");
                    if (PDOutLength > 32)
                        throw new Exception("Process data Out length in bytes can not be more than 32 bytes");
                    PDOut &= 0xC0;
                    PDOut |= (Byte)(PDOutLength - 1);
                }
            }
            // bits
            else
            {
                if (PDOutLength > 16)
                    throw new Exception("Process data Out length in bits can not be more than 16 bits");
                else
                {
                    PDOut &= 0xC0;
                    PDOut |= PDOutLength;
                }
            }

            #endregion pack PD Out

            #region pack Mseq Operate

            if (RevisionID == IOLRevisionID.V10)
            {
                MseqCapabOper = 0;
                if (PDInLength == 0 && PDOutLength == 0)
                    if (ODSizeOperate == 2)
                        MseqCapabOper = 1;
                    else if (ODSizeOperate != 1)
                        throw new Exception("OD size in operate can be only 1 or 2 for revision 1.0");
                    else
                    {
                        if ((PDInLength != 0 && IsPDInLengthBytes) ||
                            (PDOutLength != 0 && IsPDOutLengthBytes))
                        {
                            // interleave mode
                            if (ODSizeOperate != 2)
                                throw new Exception("OD size in operate can be only 2 for revision 1.0, if PD In or Out size more than 2 bytes (interleave mode)");
                        }
                        else if (ODSizeOperate != 1)
                            throw new Exception("OD size in operate can be only 1 for revision 1.0, if PD In and Out size less than 3 bytes");
                    }
            }
            else
            {
                // pack M-Sequence capability Operate
                switch (ODSizeOperate)
                {
                    case 1: MseqCapabOper = (PDInLengthBytes > 2 || PDOutLengthBytes > 2) ? (Byte)4 : (Byte)0; break;
                    case 2: MseqCapabOper = (PDInLengthBytes == 0 && PDOutLengthBytes == 0) ? (Byte)1 : (Byte)5; break;
                    case 8: MseqCapabOper = (Byte)6; break;
                    case 32: MseqCapabOper = (Byte)7; break;
                    default:
                        throw new Exception("On-request data size can be only 1, 2, 8, 32");
                }
            }
            MseqCapability &= 0xF1;
            MseqCapability |= (Byte)((MseqCapabOper << 1) & 0x0E);

            #endregion pack Mseq Operate

            // min cycle time
            if (MinCycleTimeBase == IOLCycTimeBase.ms01 && MinCycleTimeMult < 4)
                throw new Exception("Min cycle time can not be less than 0,4 ms");

            byte MseqLen = (byte)(2 + PDOutLengthBytes + ODSizeOperate + 1 + PDInLengthBytes + 1);
            double MinPossibleCycleTime = Math.Ceiling(MseqLen * ByteTransferTimeMs[(int)Baudrate] * 10) / 10;

            if (minCycleTimeBase * MinCycleTimeMult + minCycleTimeAdd < MinPossibleCycleTime)
                throw new Exception("Min cycle time is too short for selected configuration. It should be more than " + Math.Round(MinPossibleCycleTime, 1) + " ms");
        }

        public void Upload(IProtocolCommunicator comm)
        {
            List<byte> data;

            // baudrate
            data = comm.readIQCommPar(IQPar.IQParGenDevSupportedBaudrate);
            Baudrate = (IOLBaudrate)data[0];

            // dir page device relevant parameters
            data = comm.readIQCommPar(IQPar.IQParGenDevDirPage);

            MinCycleTime = data[(byte)IOLDirectPageParameters.MinCycTime];
            MseqCapability = data[(byte)IOLDirectPageParameters.MseqCap];
            PDIn = data[(byte)IOLDirectPageParameters.PDIn];
            PDOut = data[(byte)IOLDirectPageParameters.PDOut];
            revID = (IOLRevisionID)data[(byte)IOLDirectPageParameters.RevID];
            vendorID = (UInt16)(data[(byte)IOLDirectPageParameters.VenIDHi] * 256 + data[(byte)IOLDirectPageParameters.VenIDLo]);
            deviceID = (UInt32)(data[(byte)IOLDirectPageParameters.DevIDHi] * 256 * 256 + data[(byte)IOLDirectPageParameters.DevIDMid] * 256 + data[(byte)IOLDirectPageParameters.DevIDLo]);

            switch ((MseqCapability >> 1) & 7)
            {
                case 0:
                case 4: ODSizeOperate = (Byte)1; break;
                case 1:
                case 5: ODSizeOperate = (Byte)2; break;
                case 6: ODSizeOperate = (Byte)8; break;
                case 7: ODSizeOperate = (Byte)32; break;
            }

            PDInLength = (Byte)(PDIn & 0x1F);
            if (IsPDInLengthBytes) PDInLength++;
            PDOutLength = (Byte)(PDOut & 0x1F);
            if (IsPDOutLengthBytes) PDOutLength++;

            // master cycle time
            MstCycleTime = (comm.readIQCommPar(IQPar.IQParGenDevMstCycTime))[0];
        }

        #endregion Public Methods
    }
}
