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.Dvb.Descriptors;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Plugins;

namespace skyscraper5.Docsis.MacManagement
{
	[SkyscraperPlugin]
	[MacManagementMessageType(3, 29)]
    [MacManagementMessageType(5, 51)]
    public class UpstreamChannelDescriptor : MacManagementMessage
    {
        public UpstreamChannelDescriptor(PhysicalAddress source, PhysicalAddress destination, byte[] buffer) : base(source, destination, buffer)
        {
            MemoryStream ms = new MemoryStream(buffer, false);
            UpstreamChannelID = ms.ReadUInt8();
            ConfigurationChangeCount = ms.ReadUInt8();
            MinislotSize = ms.ReadUInt8();
            DownstreamChannelId = ms.ReadUInt8();
            
            while (ms.GetAvailableBytes() >= 2)
            {
                byte type = ms.ReadUInt8();
                byte length = ms.ReadUInt8();
                if (ms.GetAvailableBytes() < length)
                {
                    Valid = false;
                    return;
                }
                byte[] v = ms.ReadBytes(length);
                switch (type)
                {
                    //Found in CM-SP-MULPIv3.1-I03-1406101.pdf on page 107
                    case 1:
                        ModulationRate = v[0];
                        break;
                    case 2:
                        (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]);
                        Frequency = BitConverter.ToUInt32(v);
                        break;
                    case 3:
                        PreamblePattern = v;
                        break;
                    case 5:
                        if (BurstDescriptors == null)
                            BurstDescriptors = new List<BurstDescriptor>();
                        BurstDescriptors.Add(new BurstDescriptor(v));
                        break;
                    case 6:
                        ExtendedPreamblePattern = v;
                        break;
                    case 7:
                        if (v[0] == 1) S_CDMAMode = true;
                        else if (v[0] == 2) S_CDMAMode = false;
                        else break;
                        break;
                    case 15:
                        //Undocumented as of DOCSIS 4.0 ?
                        break;
                    case 16:
                        RangingRequired = (RangingRequiredEnum)v[0];
                        break;
                    case 23:
                        goto case 5;
                    case 24:
                        (v[0], v[1]) = (v[1], v[0]);
                        UcdChangeIndicator = new UcdChangeIndicatorBitmask(BitConverter.ToUInt16(v, 0));
                        break;
                    case 25:
                        OfdmaTimestampSnapshot = v[0] & 0x0f;   
                        OfdmaTimestampSnapshot <<= 8;           //4 bits
                        OfdmaTimestampSnapshot += v[1];
                        OfdmaTimestampSnapshot <<= 8;           //12 bits
                        OfdmaTimestampSnapshot += v[2];
                        OfdmaTimestampSnapshot <<= 8;           //20 bits
                        OfdmaTimestampSnapshot += v[3];
                        OfdmaTimestampSnapshot <<= 4;           //28 bits
                        OfdmaTimestampSnapshot += ((v[4] & 0xf0) >> 4);
                        OfdmaTimestampSnapshotDivideBy20 = v[4] & 0x0f;
                        break;
                    case 26:
                        OfdmaCyclicPrefixSize = v[0];
                        break;
                    case 27:
                        OfdmaRolloffPeriodSize = v[0];
                        break;
                    case 28:
                        SubcarrierSpacing = v[0];
                        break;
                    case 29:
                        (v[0], v[1], v[2], v[3]) = (v[3], v[2], v[1], v[0]);
                        CenterFrequencyOfSubcarrier0 = BitConverter.ToUInt32(v);
                        break;
                    case 30:
                        SubcarrierExclusionBand = new Tuple<ushort, ushort>[length / 4];
                        for (int i = 0; i < length / 4; i++)
                        {
                            (v[(i * 4) + 0], v[(i * 4) + 1]) = (v[(i * 4) + 1], v[(i * 4) + 0]);
                            (v[(i * 4) + 2], v[(i * 4) + 3]) = (v[(i * 4) + 3], v[(i * 4) + 2]);
                            SubcarrierExclusionBand[i] = new Tuple<ushort, ushort>(BitConverter.ToUInt16(v, (i * 4) + 0), BitConverter.ToUInt16(v, (i * 4) + 2));
                        }
                        break;
                    case 32:
                        SymbolsInOfdmaFrame = v[0];
                        break;
                    case 33:
                        byte[] v2 = new byte[4];
                        (v2[0], v2[1], v2[2]) = (v[2], v[1], v[0]);
                        RandomizationSeed = BitConverter.ToUInt32(v2, 0);
                        break;
                    default:
                        if (type > 34)
                        {
                            Valid = false;
                            return;
                        }
                        throw new NotImplementedException(String.Format("{0} TLV type {1}", nameof(UpstreamChannelDescriptor), type));

                }
            }
            Valid = true;
        }

        public uint RandomizationSeed { get; set; }

        public byte? SymbolsInOfdmaFrame { get; set; }

        public Tuple<ushort, ushort>[] SubcarrierExclusionBand { get; private set; }

        public uint? CenterFrequencyOfSubcarrier0 { get; set; }

        public byte? SubcarrierSpacing { get; set; }

        public byte? OfdmaRolloffPeriodSize { get; set; }

        public byte? OfdmaCyclicPrefixSize { get; set; }

        public int? OfdmaTimestampSnapshotDivideBy20 { get; set; }

        public int? OfdmaTimestampSnapshot { get; set; }

        public UcdChangeIndicatorBitmask UcdChangeIndicator { get; private set; }

        public List<BurstDescriptor> BurstDescriptors { get; private set; }

        public bool? S_CDMAMode { get; set; }

        public byte[] PreamblePattern { get; set; }

        public uint? Frequency { get; set; }

        public byte? ModulationRate { get; set; }

        public byte DownstreamChannelId { get; set; }

        public byte MinislotSize { get; set; }

        public byte ConfigurationChangeCount { get; set; }

        public byte UpstreamChannelID { get; set; }

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

        public RangingRequiredEnum RangingRequired { get; private set; }

        public enum RangingRequiredEnum : byte
        {
            NoRangingRequired = 0,
            UnicastInitialRangingRequired = 1,
            BroadcastInitialRangingRequired = 2,
            ProbingRequired = 3
        }
        

        public class BurstDescriptor
        {
            public BurstDescriptor(byte[] buffer)
            {
                MemoryStream ms = new MemoryStream(buffer, false);
                IntervalUsageCode = ms.ReadUInt8();
                while (ms.GetAvailableBytes() > 2)
                {
                    byte type = ms.ReadUInt8();
                    byte length = ms.ReadUInt8();
                    byte[] v = ms.ReadBytes(length);
                    switch (type)
                    {
                        //Found in CM-SP-MULPIv4.0-I01-190815.pdf, page 118
                        case 1:
                            ModulationType = v[0];
                            break;
                        case 2:
                            if (v[0] == 1)
                                DifferentialEncoding = true;
                            else if (v[0] == 2)
                                DifferentialEncoding = false;
                            else
                                break;
                            break;
                        case 3:
                            (v[1], v[0]) = (v[0], v[1]);
                            PreambleLength = BitConverter.ToUInt16(v, 0);
                            break;
                        case 4:
                            (v[1], v[0]) = (v[0], v[1]);
                            PreambleValueOffset = BitConverter.ToUInt16(v, 0);
                            break;
                        case 5:
                            FecErrorCorrection = v[0];
                            break;
                        case 6:
                            FecCodewordInformationBytes = v[0];
                            break;
                        case 7:
                            (v[1], v[0]) = (v[0], v[1]);
                            ScramblerSeed = BitConverter.ToUInt16(v, 0);
                            ScramblerSeed &= 0x7fff;
                            break;
                        case 8:
                            MaximumBurstSize = v[0];
                            break;
                        case 9:
                            GuardTimeSize = v[0];
                            break;
                        case 10:
                            LastCodewordLength = (CodewordLength)v[0];
                            break;
                        case 11:
                            if (v[0] == 1)
                                Scrambler = true;
                            else if (v[0] == 2)
                                Scrambler = false;
                            else
                                break;
                            break;
                        case 12:
                            RsInterleaverDepth = v[0];
                            break;
                        case 13:
                            (v[1], v[0]) = (v[0], v[1]);
                            RsInterleaverBlockSize = BitConverter.ToUInt16(v, 0);
                            break;
                        case 14:
                            PreambleType = (PreambleTypeEnum)v[0];
                            break;
                        case 19:
                            (v[1], v[0]) = (v[0], v[1]);
                            SubcarriersInitialRanging = BitConverter.ToUInt16(v, 0);
                            break;
                        case 20:
                            (v[1], v[0]) = (v[0], v[1]);
                            SubcarriersFineRanging = BitConverter.ToUInt16(v, 0);
                            break;
                        case 21:
                            OfdmaDataProfiles = new OfdmaDataProfile[length / 2];
                            for (int i = 0; i < length / 2; i++)
                            {
                                OfdmaDataProfiles[i] = new OfdmaDataProfile(v[(i * 2) + 0], v[(i * 2) + 1]);
                            }
                            break;
                        case 22:
                            OfdmaBroadcastIrStartingPowerLevel = (double)v[0] * 1.6;
                            OfdmaBroadcastIrStartingPowerLevelIncrease = (double)v[1] * 0.25;
                            break;
                        default:
                            throw new NotImplementedException(String.Format("{0} {1} {2}", nameof(BurstDescriptor), nameof(type), type));
                    }
                }
            }

            public OfdmaDataProfile[] OfdmaDataProfiles { get; private set; }

            public ushort? SubcarriersFineRanging { get; set; }

            public double? OfdmaBroadcastIrStartingPowerLevelIncrease { get; set; }
            public double? OfdmaBroadcastIrStartingPowerLevel { get; set; }

            public ushort? SubcarriersInitialRanging { get; set; }

            public PreambleTypeEnum? PreambleType { get; private set; }
            public enum PreambleTypeEnum : byte
            {
                QPSK0 = 1,
                QPSK1 = 2
            }

            public ushort? RsInterleaverBlockSize { get; set; }

            public byte? RsInterleaverDepth { get; set; }

            public bool? Scrambler { get; set; }

            public CodewordLength? LastCodewordLength { get; set; }
            public enum CodewordLength : byte
            {
                Fixed = 1,
                Shortened = 2
            }

            public byte? GuardTimeSize { get; set; }

            public byte? MaximumBurstSize { get; set; }

            public int? ScramblerSeed { get; set; }

            public byte? FecCodewordInformationBytes { get; set; }

            public byte? FecErrorCorrection { get; set; }

            public ushort? PreambleValueOffset { get; set; }

            public ushort? PreambleLength { get; set; }

            public bool? DifferentialEncoding { get; set; }

            public byte? ModulationType { get; set; }

            public byte IntervalUsageCode { get; set; }
        }

        public class UcdChangeIndicatorBitmask
        {
            private readonly ushort rawValue;

            public UcdChangeIndicatorBitmask(ushort readUInt16Be)
            {
                this.rawValue = readUInt16Be;
            }

            public bool ChangeIuc14 => (rawValue & 0x0020) != 0;
            public bool ChangeIuc13 => (rawValue & 0x0040) != 0;
            public bool ChangeIuc12 => (rawValue & 0x0080) != 0;
            public bool ChangeIuc11 => (rawValue & 0x0100) != 0;
            public bool ChangeIuc10 => (rawValue & 0x0200) != 0;
            public bool ChangeIuc9 => (rawValue & 0x0400) != 0;
            public bool ChangeIuc6 => (rawValue & 0x0800) != 0;
            public bool ChangeIuc5 => (rawValue & 0x1000) != 0;
            public bool ChangeOtherParameters => (rawValue & 0x2000) != 0;
            public bool ChangeUnusedSubcarrierSpecification => (rawValue & 0x4000) != 0;
            public bool ChangeSubcarrierExclusionBand => (rawValue & 0x8000) != 0;

            public ushort GetRawValue()
            {
                return rawValue;
            }
        }

        public class OfdmaDataProfile
        {
            private readonly byte first;
            private readonly byte second;

            public OfdmaDataProfile(byte first, byte second)
            {
                this.first = first;
                this.second = second;
            }

            public ModulationOrderIndex Modulation => (ModulationOrderIndex)((first & 0xf0) >> 4);

            public int PilotPatternIndex => (first & 0x0f);

            public byte NumberOfMinisolts => second;

            public enum ModulationOrderIndex
            {
                NoBitLoading,
                Reserved,
                QPSK,
                _8QAM,
                _16QAM,
                _32QAM,
                _64QAM,
                _128QAM,
                _256QAM,
                _512QAM,
                _1024QAM,
                _2048QAM,
                _4096QAM
            }
        }
    }
}
