using System;
using System.Collections.Generic;
using System.IO;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Mhp.Descriptors;
using skyscraper5.Mhp.Si.Model;
using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Mhp.Si
{
    class AitParser : IPsiProcessor
    {
        public IAitEventHandler EventHandler { get; }
        public ushort ProgramNumber { get; }
        public object Tag { get; set; }

        public AitParser(IAitEventHandler eventHandler, ushort resultProgramNumber)
        {
            EventHandler = eventHandler;
            ProgramNumber = resultProgramNumber;
        }
        
        public void GatherPsi(PsiSection section, int sourcePid)
        {
            MemoryStream ms = new MemoryStream(section.GetDataCopy(), false);
            byte table_id = ms.ReadUInt8();
            if (table_id != 0x74)
                return;
            
            ushort readUInt16Be = ms.ReadUInt16BE();
            bool section_syntax_indicator = (readUInt16Be & 0x8000) != 0;
            if (!section_syntax_indicator)
                return;
            int section_length = (readUInt16Be & 0x0fff);

            readUInt16Be = ms.ReadUInt16BE();
            bool test_application_flag = (readUInt16Be & 0x8000) != 0;
            int application_type = (readUInt16Be & 0x7fff); //1 = DVB-J, 2 = DVB-HTML, 16 = HBBTV

            byte readUInt8 = ms.ReadUInt8();
            int versionNumber = (readUInt8 & 0x37) >> 2;
            bool current_next_indicator = (readUInt8 & 0x01) != 0;

            byte section_number = ms.ReadUInt8();
            byte last_section_number = ms.ReadUInt8();

            readUInt16Be = ms.ReadUInt16BE();
            int common_descriptors_length = (readUInt16Be & 0x0fff);
            if (ms.GetAvailableBytes() < common_descriptors_length)
                return;
            byte[] common_descriptors = ms.ReadBytes(common_descriptors_length);

            ExternalApplicationAuthorisationDescriptor.ExternalAuthorization[] externalAuthorizations = null;
            TransportProtocolDescriptor transportProtocolDescriptor = null;
            OcapAbstractServiceDescriptor ocapAbstractServiceDescriptor = null;
            PrivateDataSpecifierDescriptor privateDataSpecifierDescriptor = null;
            IEnumerable<TsDescriptor> descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(common_descriptors, "AIT");
            foreach (TsDescriptor tsDescriptor in descriptors)
            {
                string name = tsDescriptor.GetType().Name;
                switch (name)
                {
                    case nameof(ExternalApplicationAuthorisationDescriptor):
                        externalAuthorizations = ((ExternalApplicationAuthorisationDescriptor)tsDescriptor).Authorisations;
                        break;
                    case nameof(TransportProtocolDescriptor):
                        transportProtocolDescriptor = (TransportProtocolDescriptor)tsDescriptor;
                        break;
                    case nameof(OcapAbstractServiceDescriptor):
                        ocapAbstractServiceDescriptor = (OcapAbstractServiceDescriptor)tsDescriptor;
                        break;
                    case nameof(PrivateDataSpecifierDescriptor):
                        privateDataSpecifierDescriptor = (PrivateDataSpecifierDescriptor)tsDescriptor;
                        break;
                    case nameof(UserDefinedDescriptor):
                        if (privateDataSpecifierDescriptor == null)
                            continue;
                        throw new NotImplementedException(nameof(UserDefinedDescriptor));
                    default:
                        throw new NotImplementedException(tsDescriptor.GetType().Name);
                }
            }

            readUInt16Be = ms.ReadUInt16BE();
            int application_loop_length = (readUInt16Be & 0x0fff);
            byte[] application_loop = ms.ReadBytes(application_loop_length);
            ParseApplicationLoop(application_loop, externalAuthorizations, transportProtocolDescriptor, ocapAbstractServiceDescriptor);

            uint crc32 = ms.ReadUInt32BE();
        }

        private void ParseApplicationLoop(byte[] applicationLoop, ExternalApplicationAuthorisationDescriptor.ExternalAuthorization[] externalAuthorizations, TransportProtocolDescriptor transportProtocolDescriptor, OcapAbstractServiceDescriptor ocapAbstractServiceDescriptor)
        {
            MemoryStream ms = new MemoryStream(applicationLoop, false);

            while (ms.GetAvailableBytes() > 0)
            {
                ApplicationIdentifier applicationIdentifier = new ApplicationIdentifier();
                applicationIdentifier.OrganisationId = ms.ReadUInt32BE();
                applicationIdentifier.ApplicationId = ms.ReadUInt16BE();
                byte applicationControlCode = ms.ReadUInt8();
                AitApplication aitApplication = new AitApplication(applicationIdentifier, applicationControlCode);

                ushort readUInt16Be = ms.ReadUInt16BE();
                int application_descriptors_loop_length = readUInt16Be & 0x0fff;
                byte[] applicationDescriptorsLoop = ms.ReadBytes(application_descriptors_loop_length);
                ParseApplicationDescriptors(applicationDescriptorsLoop, aitApplication);

                if (externalAuthorizations != null)
                    aitApplication.ExternalAuthorizations = externalAuthorizations;

                if (ocapAbstractServiceDescriptor != null)
                {
                    aitApplication.AutoSelect = ocapAbstractServiceDescriptor.AutoSelect;
                    aitApplication.ServiceId = ocapAbstractServiceDescriptor.ServiceId;
                    aitApplication.ServiceName = ocapAbstractServiceDescriptor.ServiceName;
                }

                if (transportProtocolDescriptor != null && !aitApplication.TransportProtocols.Contains(transportProtocolDescriptor))
                {
                    aitApplication.TransportProtocols.Add(transportProtocolDescriptor);
                }
                EventHandler.OnAitApplication(aitApplication, ProgramNumber);
            }
        }

        private void ParseApplicationDescriptors(byte[] input, AitApplication output)
        {
            IEnumerable<TsDescriptor> descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "AIT");
            foreach (TsDescriptor tsDescriptor in descriptors)
            {
                if (!tsDescriptor.Valid)
                    continue;
                //See Page 248 on ETSI TS 102 812   
                string name = tsDescriptor.GetType().Name;
                switch (name)
                {
                    case nameof(TransportProtocolDescriptor):
                        TransportProtocolDescriptor transportProtocolDescriptor = (TransportProtocolDescriptor)tsDescriptor;
                        output.TransportProtocols.Add(transportProtocolDescriptor);
                        break;
                    case nameof(ApplicationDescriptor):
                        ApplicationDescriptor applicationDescriptor = (ApplicationDescriptor)tsDescriptor;
                        output.ApplicationProfiles = applicationDescriptor.ApplicationProfiles;
                        output.ApplicationPriority = applicationDescriptor.ApplicationPriority;
                        output.ServiceBoundFlag = applicationDescriptor.ServiceBoundFlag;
                        output.TransportProtocolLabel = applicationDescriptor.TransportProtocolLabel;
                        output.Visibility = applicationDescriptor.Visibility;
                        break;
                    case nameof(ApplicationNameDescriptor):
                        output.ApplicationName = ((ApplicationNameDescriptor)tsDescriptor).ApplicationNames;
                        break;
                    case nameof(SimpleApplicationLocationDescriptor):
                        output.InitialPath = ((SimpleApplicationLocationDescriptor)tsDescriptor).InitialPath;
                        break;
                    case nameof(SimpleApplicationBoundaryDescriptor):
                        output.BoundaryExtensions = ((SimpleApplicationBoundaryDescriptor)tsDescriptor).BoundaryExtensions;
                        break;
                    case nameof(ApplicationUsageDescriptor):
                        output.ApplicationUsageCode = ((ApplicationUsageDescriptor)tsDescriptor).UsageType;
                        break;
                    case nameof(DvbJApplicationDescriptor):
                        output.JvmArguments = ((DvbJApplicationDescriptor)tsDescriptor).Args;
                        break;
                    case nameof(DvbJApplicationLocationDescriptor):
                        DvbJApplicationLocationDescriptor dvbJald = (DvbJApplicationLocationDescriptor)tsDescriptor;
                        output.JvmBaseDirectory = dvbJald.BaseDirectory;
                        output.JvmClassPathExtension = dvbJald.ClassPathExtension;
                        output.JvmInitialClass = dvbJald.InitialClass;
                        break;
                    case nameof(ApplicationStorageDescriptor):
                        ApplicationStorageDescriptor asd = (ApplicationStorageDescriptor)tsDescriptor;
                        output.Version = asd.Version;
                        output.IsLaunchableFromOlderVersion = asd.IsLaunchableFromOlderVersion;
                        output.LaunchableCompletelyFromCache = asd.LaunchableCompletelyFromCache;
                        output.NotLaunchableFromBroadcast = asd.NotLaunchableFromBroadcast;
                        output.ApplicationStoragePriority = asd.Priority;
                        output.StorageProperty = asd.StorageProperty;
                        break;
                    case nameof(OcapUnboundApplicationDescriptor):
                        OcapUnboundApplicationDescriptor uad = (OcapUnboundApplicationDescriptor)tsDescriptor;
                        output.ServiceId = uad.ServiceId;
                        output.VersionNumber = uad.VersionNumber;
                        break;
                    case nameof(OcapApplicationStorageDescriptor):
                        OcapApplicationStorageDescriptor oasd = (OcapApplicationStorageDescriptor)tsDescriptor;
                        output.LaunchOrder = oasd.LaunchOrder;
                        output.StoragePriority = oasd.StoragePriority;
                        break;
                    case nameof(ConnectionRequirementDescriptor):
                        ConnectionRequirementDescriptor crd = (ConnectionRequirementDescriptor)tsDescriptor;
                        output.IpConnectionRequired = crd.IpConnectionRequired;
                        break;
                    default:
                        throw new NotImplementedException();
                }
            }
        }
    }


    public sealed class ApplicationIdentifierComparer : IComparer<ApplicationIdentifier>
    {
        public int Compare(ApplicationIdentifier x, ApplicationIdentifier y)
        {
            var organisationIdComparison = x.OrganisationId.CompareTo(y.OrganisationId);
            if (organisationIdComparison != 0) return organisationIdComparison;
            return x.ApplicationId.CompareTo(y.ApplicationId);
        }
    }

    public struct ApplicationIdentifier
    {
        public uint OrganisationId { get; set; }
        public ushort ApplicationId { get; set; }

        public bool Equals(ApplicationIdentifier other)
        {
            return OrganisationId == other.OrganisationId && ApplicationId == other.ApplicationId;
        }

        public override bool Equals(object obj)
        {
            return obj is ApplicationIdentifier other && Equals(other);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return ((int)OrganisationId * 397) ^ ApplicationId.GetHashCode();
            }
        }
    }
}
