using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Plugins;

namespace skyscraper5.Docsis.MacManagement
{
    [SkyscraperPlugin]
	[MacManagementMessageType(4,33)]
    public class MacDomainDescriptor : MacManagementMessage
    {
        public MacDomainDescriptor(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer)
        {
            MemoryStream ms = new MemoryStream(buffer, false);
            ConfigurationChangeCount = ms.ReadUInt8();
            NumberOfFragments = ms.ReadUInt8();
            FragmentSequenceNumber = ms.ReadUInt8();
            CurrentChannelDcid = ms.ReadUInt8();

            while (ms.GetAvailableBytes() >= 2)
            {
                byte type = ms.ReadUInt8();
                byte length = ms.ReadUInt8();
                byte[] v = ms.ReadBytes(length);
                switch (type)
                {
                    case 1:
                        DownstreamActiveChannel downstreamActiveChannel = new DownstreamActiveChannel(v);
                        if (DownstreamActiveChannels == null)
                            DownstreamActiveChannels = new List<DownstreamActiveChannel>();
                        DownstreamActiveChannels.Add(downstreamActiveChannel);
                        break;
                    case 2:
                        DownstreamServiceGroup = new DownstreamServiceGroupObject(v);
                        break;
                    case 3:
						DownstreamAmbiguityResolutionFrequencyList = ParseDownDownstreamAmbiguityResolutionFrequencyList(v);
                        break;
					case 4:
                        ReceiveChannelProfileReportingControl = new ReceiveChannelProfileReportingControlObject(v);
                        break;
                    case 5:
                        IpInitalizationParameters = new IpInitalizationParametersObject(v);
                        break;
                    case 6:
                        EarlyAuthentication = v[0];
                        break;
                    case 7:
                        if (ActiveUpstreamChannelList == null)
                        {
                            ActiveUpstreamChannelList = new List<ActiveUpstreamChannelListObject>();
                        }
                        ActiveUpstreamChannelList.Add(new ActiveUpstreamChannelListObject(v));
                        break;
                    case 8:
                        UpstreamChannelIds = v;
                        break;
                    case 9:
                        UpstreamFrequencyRange = v[0];
                        break;
                    case 10:
                        SymbolClockLocking = v[0] != 0;
                        break;
                    case 11:
                        if (EventControlEncoding == null)
                            EventControlEncoding = new List<EventControlEncodingObject>();
                        EventControlEncoding.Add(new EventControlEncodingObject(v));
                        break;
                    case 12:
                        UpstreamTransmitPowerReporting = v[0];
                        break;
                    case 15:
                        SequenceOutOfRange = (v[1] & 0x08) != 0;
                        CmOperatingOnBatteryBackup = (v[0] & 0x02) != 0;
                        CmReturnedToAcPower = (v[0] & 0x04) != 0;
                        CmMacAddressRemoval = (v[0] & 0x08) != 0;
                        break;
                    case 16:
                        ExtendedUpstreamTransmitPowerSupport = v[0];
                        break;
                    case 17:
                        CmtsDocsisVersion = new CmtsDocsisVersionObject(v);
                        break;
                    case 18:
                        CmPeriodicMaintenanceTimeout = (CmPeriodicMaintenanceTimeoutEnum)v[0];
                        break;
                    case 20:
                        DownstreamOfdmProfileFailure = (v[3] & 0x01) != 0;
                        PrimaryDownstreamChannelChange = (v[3] & 0x02) != 0;
                        DpdMismatch = (v[3] & 0x04) != 0;
                        bool Deprecated = (v[3] & 0x08) != 0;
                        NcpProfileFailure = (v[3] & 0x10) != 0;
                        PlcFailure = (v[3] & 0x20) != 0;
                        NcpProfileRecovery = (v[3] & 0x40) != 0;
                        PlcRecovery = (v[3] & 0x80) != 0;
                        OfdmProfileRecovery = (v[2] & 0x01) != 0;
                        OfdmaProfileFailure = (v[2] & 0x02) != 0;
                        MapStorageOverflowIndicator = (v[2] & 0x04) != 0;
                        MapStorageAlmostFullIndicator = (v[2] & 0x08) != 0;
                        break;
                    default:
                        //CM-SP-MULPIv4.0-I01-190815.pdf see Page 176
                        throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(MacDomainDescriptor), type));
                }
            }
            Valid = true;
        }

		public CmPeriodicMaintenanceTimeoutEnum? CmPeriodicMaintenanceTimeout { get; private set; }
        public enum CmPeriodicMaintenanceTimeoutEnum : byte
        {
            UseUnicastRanging = 0,
            UseProbe = 1,
            UseUnicastOrProbe = 2
        }

        public CmtsDocsisVersionObject CmtsDocsisVersion { get; private set; }
        public class CmtsDocsisVersionObject
        {
            public CmtsDocsisVersionObject(byte[] buffer)
            {
                MemoryStream ms = new MemoryStream(buffer, false);
                while (ms.GetAvailableBytes() >= 2)
                {
                    byte type = ms.ReadUInt8();
                    byte length = ms.ReadUInt8();
                    byte[] v = ms.ReadBytes(length);
                    switch (type)
                    {
                        case 1:
                            MajorVersion = v[0];
                            break;
                        case 2:
                            MinorVersion = v[0];
                            break;
                        default:
                            throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(CmtsDocsisVersionObject), type));
                    }
                }
            }

            public byte? MinorVersion { get; set; }

            public byte? MajorVersion { get; set; }
        }

        public byte? ExtendedUpstreamTransmitPowerSupport { get; set; }

        public bool? CmMacAddressRemoval { get; set; }

        public bool? CmReturnedToAcPower { get; set; }

        public bool? CmOperatingOnBatteryBackup { get; set; }

        public bool? SequenceOutOfRange { get; set; }

        public byte? UpstreamTransmitPowerReporting { get; set; }

        public bool? MapStorageAlmostFullIndicator { get; set; }

        public bool? MapStorageOverflowIndicator { get; set; }

        public bool? OfdmaProfileFailure { get; set; }

        public bool? OfdmProfileRecovery { get; set; }

        public bool? PlcRecovery { get; set; }

        public bool? NcpProfileRecovery { get; set; }

        public bool? PlcFailure { get; set; }

        public bool? NcpProfileFailure { get; set; }

        public bool? DpdMismatch { get; set; }

        public bool? PrimaryDownstreamChannelChange { get; set; }

        public bool? DownstreamOfdmProfileFailure { get; set; }

        public List<EventControlEncodingObject> EventControlEncoding { get; private set; }
        public class EventControlEncodingObject
        {
            public EventControlEncodingObject(byte[] buffer)
            {
                MemoryStream ms = new MemoryStream(buffer, false);
                while (ms.GetAvailableBytes() > 2)
                {
                    byte type = ms.ReadUInt8();
                    byte length = ms.ReadUInt8();
                    byte[] v = ms.ReadBytes(length);
                    switch (type)
                    {
                        case 1:
                            EventTypeCode = v[0]; //CM-SP-MULPIv4.0-I01-190815.pdf Page 482
                            break;
                        case 2:
                            (v[0], v[1]) = (v[1], v[0]);
                            MaximumEventHoldoffTimer = new TimeSpan(0, 0, 0, 0, BitConverter.ToUInt16(v, 0) * 20);
                            break;
                        case 3:
                            MaximumNumberOfReports = v[0];
                            break;
                        default:
                            //CM-SP-MULPIv4.0-I01-190815.pdf page 184
                            throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(EventControlEncodingObject), type));
                    }
                }
            }

            public byte? MaximumNumberOfReports { get; set; }

            public TimeSpan? MaximumEventHoldoffTimer { get; set; }

            public byte? EventTypeCode { get; set; }

            public override string ToString()
            {
                return $"{nameof(EventTypeCode)}: {EventTypeCode}, {nameof(MaximumEventHoldoffTimer)}: {MaximumEventHoldoffTimer}, {nameof(MaximumNumberOfReports)}: {MaximumNumberOfReports}";
            }
        }

        public bool? SymbolClockLocking { get; set; }

        public byte? UpstreamFrequencyRange { get; set; }

        public byte[] UpstreamChannelIds { get; set; }

        public List<ActiveUpstreamChannelListObject> ActiveUpstreamChannelList { get; set; }
        public class ActiveUpstreamChannelListObject
        {
            public ActiveUpstreamChannelListObject(byte[] buffer)
            {
                MemoryStream ms = new MemoryStream(buffer, false);
                while (ms.GetAvailableBytes() > 2)
                {
                    byte type = ms.ReadUInt8();
                    byte length = ms.ReadUInt8();
                    byte[] v = ms.ReadBytes(length);
                    switch (type)
                    {
                        case 1:
                            UpstreamChannelId = v[0];
                            break;
                        case 2:
                            T4Timeout = (v[0] & 0x02) != 0;
                            T3RetriesExceed = (v[0] & 0x01) != 0;
                            SucessfulRangingAfterT3RetriesExceeded = (v[1] & 0x80) != 0;
                            break;
                        case 3:
                            UpstreamChannelPriority = v[0];
                            break;
                        default:
                            //CM-SP-MULPIv4.0-I01-190815.pdf page 182
                            throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(ActiveUpstreamChannelListObject), type));
                    }
                }
            }

            public byte? UpstreamChannelPriority { get; set; }

            public bool? SucessfulRangingAfterT3RetriesExceeded { get; set; }

            public bool? T3RetriesExceed { get; set; }

            public bool? T4Timeout { get; set; }

            public byte? UpstreamChannelId { get; set; }

            public override string ToString()
            {
                return $"{nameof(UpstreamChannelId)}: {UpstreamChannelId}";
            }
        }

        public byte? EarlyAuthentication { get; set; }

        public IpInitalizationParametersObject IpInitalizationParameters { get; private set; }
        public class IpInitalizationParametersObject
        {
            public IpInitalizationParametersObject(byte[] buffer)
            {
                MemoryStream ms = new MemoryStream(buffer, false);
                while (ms.GetAvailableBytes() >= 2)
                {
                    byte type = ms.ReadUInt8();
                    byte length = ms.ReadUInt8();
                    byte[] v = ms.ReadBytes(length);
                    switch (type)
                    {
                        case 1:
                            IpProvisioningMode = (IpProvisioningModeEnum)v[0];
                            break;
                        case 2:
                            byte[] tmp = new byte[4];
                            (tmp[0], tmp[1], tmp[2]) = (v[2], v[1], v[0]);
                            DsidValue = BitConverter.ToUInt32(tmp, 0);
                            break;
                        default:
                            //CM-SP-MULPIv4.0-I01-190815.pdf page 181
                            throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(IpInitalizationParametersObject), type));
                    }
                }
            }

            public uint? DsidValue { get; set; }

            public IpProvisioningModeEnum? IpProvisioningMode { get; private set; }
            public enum IpProvisioningModeEnum : byte
            {
                IPv4 = 0,
                IPv6 = 1,
                Alternate = 2,
                DualStack = 3
            }
        }

        public ReceiveChannelProfileReportingControlObject ReceiveChannelProfileReportingControl { get; private set; }
        public class ReceiveChannelProfileReportingControlObject
        {
            public ReceiveChannelProfileReportingControlObject(byte[] buffer)
            {
                MemoryStream ms = new MemoryStream(buffer, true);
                while (ms.GetAvailableBytes() > 2)
                {
                    byte type = ms.ReadUInt8();
                    byte length = ms.ReadUInt8();
                    byte[] v = ms.ReadBytes(length);
                    switch (type)
                    {
                        case 1:
                            RcpScQamCenterFrequencySpacing = v[0];
                            break;
                        case 2:
                            VerboseRcpReporting = v[0];
                            break;
                        case 3:
                            FragmentRcpTransmission = v[0];
                            break;
                        default:
                            // CM-SP-MULPIv4.0-I01-190815.pdf page 180
                            throw new NotImplementedException(String.Format("{0} TLV Type {1}", nameof(ReceiveChannelProfileReportingControlObject), type));
                    }
                }
            }

            public byte? FragmentRcpTransmission { get; set; }

            public byte? VerboseRcpReporting { get; set; }

            public byte? RcpScQamCenterFrequencySpacing { get; set; }
        }

        public DownstreamServiceGroupObject DownstreamServiceGroup { get; private set; }
        public class DownstreamServiceGroupObject
        {
            public DownstreamServiceGroupObject(byte[] buffer)
            {
                MemoryStream ms = new MemoryStream(buffer, false);
                while (ms.GetAvailableBytes() >= 2)
                {
                    byte type = ms.ReadUInt8();
                    byte length = ms.ReadUInt8();
                    byte[] v = ms.ReadBytes(length);
                    switch (type)
                    {
                        case 1:
                            MD_DS_SG_ID = v[0];
                            break;
                        case 2:
                            DownstreamChannelIds = v;
                            break;
                        default:
                            //CM-SP-MULPIv4.0-I01-190815.pdf, page 179
                            throw new NotImplementedException(String.Format("{0} TLV {1}", nameof(DownstreamServiceGroupObject), type));
                    }
                }
            }

            public byte[] DownstreamChannelIds { get; private set; }

            public byte MD_DS_SG_ID { get; set; }
        }

        public List<DownstreamActiveChannel> DownstreamActiveChannels { get; private set; }

        public class DownstreamActiveChannel
        {
            public DownstreamActiveChannel(byte[] bytes)
            {
                MemoryStream ms = new MemoryStream(bytes, false);
                while (ms.GetAvailableBytes() >= 2)
                {
                    byte type = ms.ReadUInt8();
                    byte length = ms.ReadUInt8();
                    byte[] v = ms.ReadBytes(length);
                    switch (type)
                    {
                        case 1:
                            ChannelId = v[0];
                            break;
                        case 2:
                            (v[3], v[2], v[1], v[0]) = (v[0], v[1], v[2], v[3]);
                            Frequency = BitConverter.ToUInt32(v, 0);
                            break;
                        case 3:
                            Annex = (AnnexEnum)((v[0] & 0xf0) >> 4);
                            ModulationOrder = (ModulationOrderEnum)(v[0] & 0x0f);
                            break;
                        case 4:
                            PrimaryCapable = (PrimaryCapableEnum)(v[0]);
                            break;
                        case 5:
                            (v[1], v[0]) = (v[0], v[1]);
                            ushort uInt16 = BitConverter.ToUInt16(v, 0);
                            bool reserved = (uInt16 & 0x0001) != 0;
                            MddTimeout = (uInt16 & 0x0002) != 0;
                            QamFecLockFailure = (uInt16 & 0x0004) != 0;
                            reserved = (uInt16 & 0x0008) != 0;
                            MddRecovery = (uInt16 & 0x0010) != 0;
                            QamFecLockRecovery = (uInt16 & 0x0020) != 0;
                            break;
                        case 7:
                            _50khzSubcarrierSpacing = (v[0] & 0x40) != 0;
                            CyclicPrefix = (v[0] & 0x38) >> 3;
                            TukeyRaisedCosineWindow = (v[0] & 0x07);
                            break;
                        default:
                            throw new NotImplementedException(String.Format("{0} Sub-TLV Type {1}",
                                nameof(MacDomainDescriptor), type));
                    }
                }
            }

            public int? TukeyRaisedCosineWindow { get; set; }

            public int? CyclicPrefix { get; set; }

            public bool? _50khzSubcarrierSpacing { get; set; }

            public bool? QamFecLockRecovery { get; set; }

            public bool? MddRecovery { get; set; }

            public bool? QamFecLockFailure { get; set; }

            public bool? MddTimeout { get; set; }

            public PrimaryCapableEnum? PrimaryCapable { get; private set; }

            public enum PrimaryCapableEnum : byte
            {
                NotPrimaryCapable = 0,
                PrimaryCapable = 1,
                FdxDownstream = 2
            }

            public ModulationOrderEnum? ModulationOrder { get; private set; }
            public AnnexEnum? Annex { get; private set; }

            public enum ModulationOrderEnum
            {
                _64QAM = 0,
                _256QAM = 1,
                C_DOCSIS = 2
            }

            public enum AnnexEnum
            {
                J83AnnexA = 0,
                J83AnnexB = 1,
                J83AnnexC = 2
            }

            public uint? Frequency { get; set; }

            public byte? ChannelId { get; set; }

            public override string ToString()
            {
                return $"{nameof(ChannelId)}: {ChannelId}, {nameof(Frequency)}: {Frequency}, {nameof(ModulationOrder)}: {ModulationOrder}";
            }
        }

        public byte CurrentChannelDcid { get; set; }

        public byte FragmentSequenceNumber { get; set; }

        public byte NumberOfFragments { get; set; }

        public byte ConfigurationChangeCount { get; set; }
		public uint[] DownstreamAmbiguityResolutionFrequencyList { get; }

        ///TODO: This is useful data! Gets all the frequencies that contain DOCSIS carriers.
		public uint[] ParseDownDownstreamAmbiguityResolutionFrequencyList(byte[] buffer)
        {
            uint[] result = new uint[buffer.Length / 4];
            MemoryStream ms = new MemoryStream(buffer, false);
            for (int i = 0; i < result.Length; i++)
            {
                result[i] = ms.ReadUInt32BE();
            }
            ms.Dispose();
            return result;
        }

	}
}
