using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Dvb.DataBroadcasting.IntDescriptors;
using skyscraper5.Dvb.DataBroadcasting.IntModel;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Net;

namespace skyscraper5.Dvb.DataBroadcasting
{
    internal class IntParser : IPsiProcessor
    {
        public IntEventHandler EventHandler { get; }

        public IntParser(IntEventHandler eventHandler)
        {
            EventHandler = eventHandler;
        }

        public void GatherPsi(PsiSection section, int sourcePid)
        {
            MemoryStream ms = new MemoryStream(section.GetData(), false);
            byte tableId = ms.ReadUInt8();
            if (tableId != 0x4c)
                return;

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

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

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

            byte sectionNumber = ms.ReadUInt8();
            byte lastSectionNumber = ms.ReadUInt8();

            uint platformId = ms.ReadUInt24BE();
            byte processingOrder = ms.ReadUInt8();

            ushort platformDescriptorLoopLength = ms.ReadUInt16BE();
            platformDescriptorLoopLength &= 0x0fff;
            Platform output = new Platform(actionType, platformId, processingOrder);
            ParsePlatformDescriptorLoop(ms.ReadBytes(platformDescriptorLoopLength), output);

            while (ms.GetAvailableBytes() > 4)
            {
                Target outputTarget = new Target();
                ushort targetDescriptorLoopLength = ms.ReadUInt16BE();
                targetDescriptorLoopLength &= 0x0fff;
                ParseTargetDescriptorLoop(ms.ReadBytes(targetDescriptorLoopLength), outputTarget);

                Operational outputOperational = new Operational();
                ushort operationalDescriptorLoopLength = ms.ReadUInt16BE();
                operationalDescriptorLoopLength &= 0x0fff;
                ParseOperationalDescriptorLoop(ms.ReadBytes(operationalDescriptorLoopLength), outputOperational);

                EventHandler.OnIpMacNotification(sourcePid, output, outputTarget, outputOperational);
            }

            uint crc32 = ms.ReadUInt32BE();
        }

        private void ParsePlatformDescriptorLoop(byte[] input, Platform output)
        {
            foreach (TsDescriptor tsDescriptor in TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "INT"))
            {
                string cName = tsDescriptor.GetType().Name;
                switch (cName)
                {
                    case nameof(IpMacPlatformNameDescriptor):
                        if (!string.IsNullOrEmpty(output.Name))
                            throw new NotImplementedException("platform name in multiple languages");
                        
                        IpMacPlatformNameDescriptor impnd = (IpMacPlatformNameDescriptor)tsDescriptor;
                        output.NameLanguageCode = impnd.Iso639LanguageCode;
                        output.Name = impnd.Text;
                        break;
                    case nameof(IpMacPlatformProviderNameDescriptor):
                        if (!string.IsNullOrEmpty(output.ProviderName))
                            throw new NotImplementedException("provider name in multiple languages");

                        IpMacPlatformProviderNameDescriptor ippnd = (IpMacPlatformProviderNameDescriptor)tsDescriptor;
                        output.ProviderNameLanguageCode = ippnd.Iso639LanguageCode;
                        output.ProviderName = ippnd.Text;
                        break;
                    default:
                        throw new NotImplementedException(cName);
                }
            }
        }

        private void ParseTargetDescriptorLoop(byte[] input, Target output)
        {
            int numTargets = 0;
            foreach (TsDescriptor tsDescriptor in TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "INT"))
            {
                string cName = tsDescriptor.GetType().Name;
                switch (cName)
                {
                    case nameof(TargetIpSlashDescriptor):
                        TargetIpSlashDescriptor tisd = (TargetIpSlashDescriptor)tsDescriptor;
                        if (output.TargetIpSlashes == null)
                            output.TargetIpSlashes = new List<CidrSubnet>();
                        output.TargetIpSlashes.AddRange(tisd.Targets);
                        numTargets++;
                        break;
                    default:
                        throw new NotImplementedException(cName);
                }
            }

            if (numTargets == 0)
                output.AllReceivers = true;
        }

        private void ParseOperationalDescriptorLoop(byte[] input, Operational output)
        {
            foreach (TsDescriptor tsDescriptor in TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "INT"))
            {
                string cName = tsDescriptor.GetType().Name;
                switch (cName)
                {
                    case nameof(TimeSliceFecIdentifierDescriptor):
                        TimeSliceFecIdentifierDescriptor tsfid = (TimeSliceFecIdentifierDescriptor)tsDescriptor;
                        output.TimeSliceFrameSize = tsfid.FrameSize;
                        output.TimeSliceMaxAverageRate = tsfid.MaxAverageRate;
                        output.TimeSliceMaxBurstDuration = tsfid.MaxBurstDuration;
                        output.MpeFecAlgorithm = tsfid.MpeFec;
                        output.TimeSliceFecId = tsfid.TimeSliceFecId;
                        output.TimeSlicing = tsfid.TimeSlicing;
                        break;
                    case nameof(IpMacStreamLocationDescriptor):
                        IpMacStreamLocationDescriptor ipmac = (IpMacStreamLocationDescriptor)tsDescriptor;
                        if (output.TransportStreamId.HasValue)
                            throw new NotImplementedException("multiple ip mac stram location descriptors");
                        output.ComponentTag = ipmac.ComponentTag;
                        output.NetworkId = ipmac.NetworkId;
                        output.OriginalNetworkId = ipmac.OriginalNetworkId;
                        output.ServiceId = ipmac.ServiceId;
                        output.TransportStreamId = ipmac.TransportStreamId;
                        break;
                    default:
                        throw new NotImplementedException(cName);
                }
            }
        }
    }
}
