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

namespace skyscraper5.Dvb.Descriptors
{
	[SkyscraperPlugin]
	[TsDescriptor(0x4a,"NIT","BAT","SDT","EIT")]
    [BannedTable("PMT")]
    public class LinkageDescriptor : TsDescriptor
    {
        public LinkageDescriptor()
        {

        }

        public LinkageDescriptor(byte[] buffer)
        {
            if (buffer.Length < 7)
            {
                Valid = false;
                return;
            }

            MemoryStream ms = new MemoryStream(buffer, false);
            TransportStreamId = ms.ReadUInt16BE();
            OriginalNetworkId = ms.ReadUInt16BE();
            ServiceId = ms.ReadUInt16BE();
            LinkageType = (LinkageTypeEnum)ms.ReadUInt8();

            if (ms.GetAvailableBytes() > 0)
            {
                byte linkageTypeRaw = (byte)LinkageType;
                if (LinkageType == LinkageTypeEnum.MobileHandover)
                {
                    byte readUInt8 = ms.ReadUInt8();
                    HandoverType = (HandoverTypeCoding)((readUInt8 & 0xf0) >> 4);
                    HandoverOriginType = (readUInt8 & 0x01) != 0;
                    if (HandoverType == HandoverTypeCoding.NeighbouringCountry ||
                        HandoverType == HandoverTypeCoding.LocalVariationOfSameService ||
                        HandoverType == HandoverTypeCoding.AssociatedService)
                    {
                        HandoverNetworkId = ms.ReadUInt16BE();
                    }

                    if (HandoverOriginType.HasValue)
                        if (!HandoverOriginType.Value)
                            HandoverInitialServiceId = ms.ReadUInt16BE();
                }
                else if (LinkageType == LinkageTypeEnum.EventLinkage)
                {
                    TargetEventId = ms.ReadUInt16BE();

                    byte readUInt8 = ms.ReadUInt8();
                    TargetEventListed = (readUInt8 & 0x80) != 0;
                    TargetEventSimulcasted = (readUInt8 & 0x40) != 0;
                }
                else if ((byte)LinkageType >= 0x0e && (byte)LinkageType <= 0x1f)
                {
                    byte loopLength = ms.ReadUInt8();
                    if (ms.GetAvailableBytes() < loopLength)
                        return;
                    byte[] extendedBytes = ms.ReadBytes(loopLength);
                    ExtendedEventLinkages = UnpackExtendedEventLinkageInfo(extendedBytes).ToArray();
                }
                else if (LinkageType == LinkageTypeEnum.SystemSoftwareUpdate)
                {
                    byte ouiDataLength = ms.ReadUInt8();
                    if (ms.GetAvailableBytes() < ouiDataLength)
                    {
                        Valid = false;
                        return;
                    }
                    byte[] ouiData = ms.ReadBytes(ouiDataLength);
                    bool ouiValid = false;

                    void DecodeOuiData(byte[] ouiData)
                    {
                        SystemSoftwareUpdateLinkStructure = new List<OuiPrivateData>();
                        MemoryStream ms = new MemoryStream(ouiData, false);
                        while (ms.GetAvailableBytes() >= 4)
                        {
                            byte[] oui = ms.ReadBytes(3);
                            byte selectorLength = ms.ReadUInt8();
                            if (ms.GetAvailableBytes() < selectorLength)
                            {
                                return;
                            }
                            byte[] selector = ms.ReadBytes(selectorLength);
                            OuiPrivateData opd = new OuiPrivateData(oui, selector);
                            SystemSoftwareUpdateLinkStructure.Add(opd);
                        }
                        ouiValid = true;
                    }
                    DecodeOuiData(ouiData);
                    if (!ouiValid)
                    {
                        Valid = false;
                        return;
                    }
                }
                else if (LinkageType == LinkageTypeEnum.SystemSoftwareUpdateNit)
                {
                    TableType = (TableTypeCoding)ms.ReadUInt8();
                }
                else if (linkageTypeRaw >= 0x80 && linkageTypeRaw <= 0xfe)
                {
                    PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes());
                }
                else if (LinkageType == LinkageTypeEnum.TsContainingCompleteNetworkSi)
                {
                    PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes());
                }
                else if (LinkageType == LinkageTypeEnum.EpgService)
                {
                    PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes());
                }
                else if (LinkageType == LinkageTypeEnum.IpMacNotificationService)
                {
                    byte platformIdDataLength = ms.ReadUInt8();
                    if (ms.GetAvailableBytes() < platformIdDataLength)
                    {
                        Valid = false;
                        return;
                    }
                    byte[] platformBuffer = ms.ReadBytes(platformIdDataLength);
                    ParseIpMacNotificationLinkage(platformBuffer);
                    PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes());
                }
                else if (LinkageType == LinkageTypeEnum.TsContainingIntBatOrNit)
                {
                    TableType = (TableTypeCoding)ms.ReadUInt8();
                    if (TableType == TableTypeCoding.Bat)
                    {
                        BouquetId = ms.ReadUInt16BE();
                    }
                }
                else if (LinkageType == LinkageTypeEnum.DataBroadcastService)
                {
                    PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes());
                }
                else if (LinkageType == LinkageTypeEnum.InformationService)
                {
                    PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes());
                }
                else if ((byte)LinkageType >= 0x21)
                {
                    //Reserved for future use?
                    PrivateDataBytes = ms.ReadBytes(ms.GetAvailableBytes());
                }
                else
                {
                    throw new NotImplementedException(String.Format("{0:X2}",(byte)LinkageType));
                }
            }

            this.Valid = true;
        }

        private void ParseIpMacNotificationLinkage(byte[] buffer)
        {
            IpMacNotificationLinkages = new List<IpMacNotificationLinkage>();
            MemoryStream ms = new MemoryStream(buffer, false);
            while (ms.GetAvailableBytes() >= 3)
            {
                IpMacNotificationLinkage child = new IpMacNotificationLinkage();
                child.Id = ms.ReadUInt16BE();
                child.Id <<= 8;
                child.Id += ms.ReadUInt8();
                byte platformNameLoopLength = ms.ReadUInt8();
                while (platformNameLoopLength > 0)
                {
                    string iso639LangCode = Encoding.ASCII.GetString(ms.ReadBytes(3));
                    platformNameLoopLength -= 3;
                    byte platformNameLength = ms.ReadUInt8();
                    platformNameLoopLength--;
                    string platformName = En300468AnnexATextDecoder.GetInstance().Decode(ms.ReadBytes(platformNameLength));
                    platformNameLoopLength -= platformNameLength;
                    if (child.Names == null)
                        child.Names = new List<Tuple<string, string>>();
                    child.Names.Add(new Tuple<string, string>(iso639LangCode, platformName));
                }
            }
        }

        public class IpMacNotificationLinkage
        {
            public int Id { get; set; }
            public List<Tuple<string,string>> Names { get; set; }
        }

        public List<IpMacNotificationLinkage> IpMacNotificationLinkages { get; private set; }

        public ushort? BouquetId { get; set; }

        public List<OuiPrivateData> SystemSoftwareUpdateLinkStructure { get; set; }
        public class OuiPrivateData
        {
            public OuiPrivateData(byte[] oui, byte[] selector)
            {
                this.OUI = oui;
                this.Selector = selector;
            }

            public byte[] OUI { get; set; }
            public byte[] Selector { get; set; }
        }
        public byte[] PrivateDataBytes { get; set; }

        public TableTypeCoding? TableType { get; set; }

        public enum TableTypeCoding : byte
        {
            Nit = 1,
            Bat = 2
        }
        public class ExtendedEventLinkageInfo
        {
            public ushort TargetEventId { get; internal set; }
            public bool TargetListed { get; internal set; }
            public bool EventSimulcast { get; internal set; }
            public int LinkType { get; internal set; }
            public TargetIdTypeEncoding TargetIdType { get; internal set; }
            public ushort? UserDefinedId { get; set; }
            public ushort? TargetTransportStreamId { get; set; }
            public ushort? TargetOriginalNetworkId { get; set; }
            public ushort? TargetServiceId { get; set; }
        }

        public enum TargetIdTypeEncoding
        {
            TransportStreamId = 0,
            TargetTransportStreamId = 1,
            Wildcard = 2,
            UserDefinedId = 3,
        }

        private IEnumerable<ExtendedEventLinkageInfo> UnpackExtendedEventLinkageInfo(byte[] buffer)
        {
            MemoryStream ms = new MemoryStream(buffer, false);
            while ((ms.Length - ms.Position) >= 3)
            {
                ExtendedEventLinkageInfo child = new ExtendedEventLinkageInfo();
                child.TargetEventId = ms.ReadUInt16BE();

                byte readUInt8 = ms.ReadUInt8();
                child.TargetListed = (readUInt8 & 0x80) != 0;
                child.EventSimulcast = (readUInt8 & 0x40) != 0;
                child.LinkType = (readUInt8 & 0x30) >> 4;
                child.TargetIdType = (TargetIdTypeEncoding)((readUInt8 & 0x0c) >> 2);
                bool originalNetworkIdFlag = (readUInt8 & 0x02) != 0;
                bool serviceIdFlag = (readUInt8 & 0x01) != 0;
                if (child.TargetIdType == TargetIdTypeEncoding.UserDefinedId)
                {
                    child.UserDefinedId = ms.ReadUInt16BE();
                }
                else
                {
                    if (child.TargetIdType == TargetIdTypeEncoding.TargetTransportStreamId)
                    {
                        child.TargetTransportStreamId = ms.ReadUInt16BE();
                    }

                    if (originalNetworkIdFlag)
                    {
                        child.TargetOriginalNetworkId = ms.ReadUInt16BE();
                    }

                    if (serviceIdFlag)
                    {
                        child.TargetServiceId = ms.ReadUInt16BE();
                    }
                }

                yield return child;
            }
        }

        public ExtendedEventLinkageInfo[] ExtendedEventLinkages { get; private set; }

        public bool? TargetEventSimulcasted { get; private set; }

        public bool? TargetEventListed { get; private set; }

        public ushort? TargetEventId { get; private set; }

        public ushort? HandoverInitialServiceId { get; private set; }


        public ushort? HandoverNetworkId { get; set; }
        public bool? HandoverOriginType { get; private set; }
        public HandoverTypeCoding? HandoverType { get; private set; }

        public LinkageTypeEnum LinkageType { get; private set; }

        public ushort ServiceId { get; private set; }

        public ushort OriginalNetworkId { get; private set; }

        public ushort TransportStreamId { get; private set; }


        public enum LinkageTypeEnum : byte
        {
            Reserved = 0,
            InformationService = 1,
            EpgService = 2,
            CaReplacementService = 3,
            TsContainingCompleteNetworkSi = 4,
            ServiceReplacementService = 5,
            DataBroadcastService = 6,
            RcsMap = 7,
            MobileHandover = 8,
            SystemSoftwareUpdate = 9, /* ETSI TS 102 006 */
            SystemSoftwareUpdateNit = 0x0a, /* ETSI TS 102 006 */
            IpMacNotificationService = 0x0b, /* ETSI EN 301 192 */
            TsContainingIntBatOrNit = 0x0c, /* ETSI EN 301 192 */
            EventLinkage = 0x0d,
            DownloadableFont = 0x20 /* ETSI EN 303 560 */
        }

        public enum HandoverTypeCoding
        {
            Reserved = 0,
            NeighbouringCountry = 1,
            LocalVariationOfSameService = 2,
            AssociatedService = 3
        }

        protected bool Equals(LinkageDescriptor other)
        {
            return LinkageType == other.LinkageType && ServiceId == other.ServiceId && OriginalNetworkId == other.OriginalNetworkId && TransportStreamId == other.TransportStreamId;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((LinkageDescriptor)obj);
        }

        public override int GetHashCode()
        {
            return HashCode.Combine((int)LinkageType, ServiceId, OriginalNetworkId, TransportStreamId);
        }

        public override string ToString()
        {
	        return String.Format("{0} on (0x{1:X4},0x{2:X4},0x{3:X4})", LinkageType, OriginalNetworkId, TransportStreamId, ServiceId);
        }
    }
}
