using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Dvb.SystemSoftwareUpdate.Descriptors;
using skyscraper5.Dvb.SystemSoftwareUpdate.Model;
using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Scraper;

namespace skyscraper5.Dvb.SystemSoftwareUpdate
{
    class UntDecoder : IPsiProcessor
    {
        public UpdateNotificationEventHandler EventHandler { get; }
        public ushort ProgramNumber { get; }
        public ProgramMapping ProgramMapping { get; }

        public UntDecoder(UpdateNotificationEventHandler eventHandler, ProgramMapping programMapping)
        {
            EventHandler = eventHandler;
            ProgramNumber = programMapping.ProgramNumber;
            ProgramMapping = programMapping;
        }
        
        
        public void GatherPsi(PsiSection section, int sourcePid)
        {
            byte[] buffer = section.GetDataCopy();
            MemoryStream ms = new MemoryStream(buffer, false);
            byte tableId = ms.ReadUInt8();
            if (tableId != 0x4b)
                return;

            ushort readUInt16Be = ms.ReadUInt16BE();
            bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0;
            if (!sectionSyntaxIndicator)
                return;
            int sectionLength = (readUInt16Be & 0x0fff);

            byte actionType = ms.ReadUInt8();
            byte ouiHash = ms.ReadUInt8();

            byte readUInt8 = ms.ReadUInt8();
            int versionNumber = readUInt8 & 0x3e;
            bool currentNextIndicator = (readUInt8 & 0x01) != 0;

            byte sectionNumber = ms.ReadUInt8();
            byte lastSectionNumber = ms.ReadUInt8();
            byte[] ouiBytes = ms.ReadBytes(3);
            byte processingOrder = ms.ReadUInt8();

            UpdateNotificationGroup common = new UpdateNotificationGroup(actionType, ouiHash, ouiBytes, processingOrder);
            CommonDescriptorLoop(ms, common);
            while (ms.GetAvailableBytes() > 4)
            {
                UpdateNotificationTarget target = new UpdateNotificationTarget();
                CompatibilityDescriptorLoop(ms, target);
                
                ushort platformLoopLength = ms.ReadUInt16BE();
                if (ms.GetAvailableBytes() < platformLoopLength)
                    return;
                byte[] platformLoopData = ms.ReadBytes(platformLoopLength);
                PlatformLoop(platformLoopData, target);
                EventHandler.UpdateNotification(common, target, ProgramNumber);
            }

            uint crc32 = ms.ReadUInt32BE();
        }

        private void PlatformLoop(byte[] buffer, UpdateNotificationTarget output)
        {
            MemoryStream input = new MemoryStream(buffer, false);

            while (input.GetAvailableBytes() > 0)
            {
                Platform platform = new Platform();

                //Target Descriptor Loop
                int targetDescriptorLoopLength = input.ReadUInt16BE() & 0x0fff;
                byte[] targetDescriptorData = input.ReadBytes(targetDescriptorLoopLength);
                TargetDescriptorLoop(targetDescriptorData, platform);

                //Operational Descriptor Loop
                int operationalDescriptorLoopLength = input.ReadUInt16BE() & 0x0fff;
                byte[] operationalDescriptorData = input.ReadBytes(operationalDescriptorLoopLength);
                OperationalDescriptorLoop(operationalDescriptorData, platform);

                output.Platforms.Add(platform);
            }
        }

        private void OperationalDescriptorLoop(byte[] input, Platform output)
        {
            IEnumerable<TsDescriptor> unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "UNT");
            foreach (TsDescriptor descriptor in unpackDescriptors)
            {
                string name = descriptor.GetType().Name;
                switch (name)
                {
                    case nameof(SchedulingDescriptor):
                        SchedulingDescriptor sd = (SchedulingDescriptor)descriptor;
                        if (!sd.StartDateTime.HasValue && !sd.EndDateTime.HasValue)
                            break;
                        throw new NotImplementedException(nameof(SchedulingDescriptor));
                    case nameof(UpdateDescriptor):
                        UpdateDescriptor ud = (UpdateDescriptor)descriptor;
                        output.UpdateFlag = ud.UpdateFlag;
                        output.UpdateMethod = ud.UpdateMethod;
                        output.UpdatePriority = ud.UpdatePriority;
                        break;
                    case nameof(SsuLocationDescriptor):
                        SsuLocationDescriptor ssuld = (SsuLocationDescriptor)descriptor;
                        output.DataBroadcastId = ssuld.DataBroadcastId;
                        output.AssociationTag = ssuld.AssociationTag;
                        output.PrivateData = ssuld.PrivateData;
                        break;
                    case nameof(SsuSubgroupAssociationDescriptor):
                        SsuSubgroupAssociationDescriptor ssusgad = (SsuSubgroupAssociationDescriptor)descriptor;
                        output.SubgroupTag = ssusgad.SubgroupTag;
                        break;
                    default:
                        throw new NotImplementedException(name);
                }
            }
        }

        private void TargetDescriptorLoop(byte[] input, Platform output)
        {
            IEnumerable<TsDescriptor> unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "UNT");
            foreach (TsDescriptor tsDescriptor in unpackDescriptors)
            {
                string name = tsDescriptor.GetType().Name;
                switch (name)
                {
                    case nameof(TargetSmartcardDescriptor):
                        TargetSmartcardDescriptor tsd = (TargetSmartcardDescriptor)tsDescriptor;
                        output.SmartCardNumber = tsd.SmartCardNumber;
                        output.SuperCaSystemId = tsd.SuperCaSystemId;
                        break;
                    case nameof(TargetSerialNumberDescriptor):
                        TargetSerialNumberDescriptor tsnd = (TargetSerialNumberDescriptor)tsDescriptor;
                        output.SerialData = tsnd.SerialData;
                        break;
                    case nameof(TargetMacAddressDescriptor):
                        TargetMacAddressDescriptor tmad = (TargetMacAddressDescriptor)tsDescriptor;
                        output.MacAddressMask = tmad.MacAddressMask;
                        output.MacAddressMatches = tmad.MacAddressMatches;
                        break;
                    default:
                        throw new NotImplementedException(name);
                }
            }
        }

        private void CompatibilityDescriptorLoop(MemoryStream input, UpdateNotificationTarget output)
        {
            ushort compatibilityDescriptorLength = input.ReadUInt16BE();
            ushort descriptorCount = input.ReadUInt16BE();
            for (int i = 0; i < descriptorCount; i++)
            {
                Compatibility child = new Compatibility(input);
                output.Compatibilities.Add(child);
            }
        }

        private void CommonDescriptorLoop(MemoryStream input, UpdateNotificationGroup output)
        {
            int length = input.ReadUInt16BE() & 0x0fff;
            byte[] commonDescriptorLoop = input.ReadBytes(length);
            IEnumerable<TsDescriptor> unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(commonDescriptorLoop, "UNT");
            foreach (TsDescriptor descriptor in unpackDescriptors)
            {
                string name = descriptor.GetType().Name;
                switch (name)
                {
                    case nameof(SsuLocationDescriptor):
                        SsuLocationDescriptor ssuld = (SsuLocationDescriptor)descriptor;
                        output.DataBroadcastId = ssuld.DataBroadcastId;
                        output.AssociationTag = ssuld.AssociationTag;
                        output.PrivateData = ssuld.PrivateData;
                        break;
                    case nameof(UpdateDescriptor):
                        UpdateDescriptor ud = (UpdateDescriptor)descriptor;
                        output.UpdateFlag = ud.UpdateFlag;
                        output.UpdateMethod = ud.UpdateMethod;
                        output.UpdatePriority = ud.UpdatePriority;
                        break;
                    default:
                        throw new NotImplementedException(name);
                }
            }
        }
    }
}
