using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Dvb.Psi.Model;
using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Plugins;
using skyscraper8.Dvb.Descriptors.Extension;

namespace skyscraper5.Dvb.Psi
{
    public class EitParser : IPsiProcessor
    {
        public EitParser(IEitEventHandler eventHandler)
        {
            this.EventHandler = eventHandler;
        }

        public IEitEventHandler EventHandler { get; private set; }

        public void GatherPsi(PsiSection section, int sourcePid)
        {
            byte[] buffer = section.GetDataCopy();
            MemoryStream ms = new MemoryStream(buffer, false);

            byte tableId = ms.ReadUInt8();
            ushort readUInt16Be = ms.ReadUInt16BE();
            bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0;
            if (!sectionSyntaxIndicator)
            {
                //According to en_300468v011601p.pdf, page 34 this shall be set to true
                return;
            }
            int sectionLength = (readUInt16Be & 0x0fff);

            bool isActualTs = false;
            if (tableId == 0x4e)
                isActualTs = true;
            else if (tableId == 0x4f)
                isActualTs = false;
            else if (tableId >= 0x50 && tableId <= 0x5f)
                isActualTs = true;
            else if (tableId >= 0x60 && tableId <= 0x6f)
                isActualTs = false;
            else if (tableId < 0x4e)
                return;
            else if (tableId >= 0x70)
                return;
            else
                throw new NotImplementedException("wait, what?");

            if (ms.Length <= 14)
                return;
            
            ushort serviceId = ms.ReadUInt16BE();

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

            byte sectionNumber = ms.ReadUInt8();

            byte lastSectionNumber = ms.ReadUInt8();

            ushort transportStreamId = ms.ReadUInt16BE();
            ushort originalNetworkId = ms.ReadUInt16BE();
            if (isActualTs)
            {
                EventHandler.SetTransportStreamId(transportStreamId);
                EventHandler.SetNetworkId(originalNetworkId);
            }

            byte segmentLastSectionNumber = ms.ReadUInt8();
            byte lastTableId = ms.ReadUInt8();

            while ((ms.Length - ms.Position) > 7)
            {
                EitEvent eitEvent = new EitEvent(serviceId, transportStreamId, originalNetworkId);
                eitEvent.EventId = ms.ReadUInt16BE();

                DateTime? tmp = ms.ReadEtsiEn300468AnnexCDateTime();
                if (tmp == null)
                {
                    return;
                }

                eitEvent.StartTime = tmp.Value;
                
                eitEvent.Duration = ReadEitDuration(ms);

                readUInt16Be = ms.ReadUInt16BE();
                eitEvent.RunningStatus = (RunningStatus)((readUInt16Be & 0xe000) >> 13);
                eitEvent.FreeCa = (readUInt16Be & 0x1000) != 0;

                int descriptorsLoopLength = readUInt16Be & 0x0fff;
                if (ms.GetAvailableBytes() < descriptorsLoopLength)
                    break;
                byte[] readBytes = ms.ReadBytes(descriptorsLoopLength);
                UnpackDescriptors(readBytes, eitEvent);
                if (!string.IsNullOrEmpty(eitEvent.EventName))
                    EventHandler.OnEitEvent(eitEvent);
                else
                    return;
            }

            uint crc32 = ms.ReadUInt32BE();
        }

        private static TimeSpan ReadEitDuration(MemoryStream ms)
        {
            long hours = ms.ReadUInt8().UnpackBcd();
            long minutes = ms.ReadUInt8().UnpackBcd();
            long seconds = ms.ReadUInt8().UnpackBcd();
            return new TimeSpan((int)hours, (int)minutes, (int)seconds);
        }

        private void UnpackDescriptors(byte[] input, EitEvent output)
        {
            IEnumerable<TsDescriptor> dvbDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(input, "EIT");
            foreach (TsDescriptor dvbDescriptor in dvbDescriptors)
            {
                string name = dvbDescriptor.GetType().Name;
                switch (name)
                {
                    case nameof(ShortEventDescriptor):
                        ShortEventDescriptor shortEventDescriptor = (ShortEventDescriptor)dvbDescriptor;
                        if (string.IsNullOrEmpty(shortEventDescriptor.Text) && string.IsNullOrEmpty(shortEventDescriptor.EventName))
                            return;
                        output.Iso639LanguageCode = shortEventDescriptor.Iso639LanguageCode;
                        output.EventName = shortEventDescriptor.EventName;
                        output.Text = shortEventDescriptor.Text;
                        break;
                    case nameof(ExtendedEventDescriptor):
                        ExtendedEventDescriptor extendedEventDescriptor = (ExtendedEventDescriptor)dvbDescriptor;
                        if (extendedEventDescriptor.Text == null)
                            break;
                        if (!string.IsNullOrEmpty(output.Iso639LanguageCode))
                        {
                            if (!output.Iso639LanguageCode.Equals(extendedEventDescriptor.Iso639LanguageCode))
                                break;
                        }
                        else
                        {
                            output.Iso639LanguageCode = extendedEventDescriptor.Iso639LanguageCode;
                        }
                        extendedEventDescriptor.Items?.ForEach(output.AddItem);
                        if (output.ExtendedText == null)
                            output.ExtendedText = new string[extendedEventDescriptor.LastDescriptorNumber + 1];
                        if (extendedEventDescriptor.DescriptorNumber == output.ExtendedText.Length)
                        {
                            string[] resized = new string[output.ExtendedText.Length + 1];
                            Array.Copy(output.ExtendedText, 0, resized, 0, output.ExtendedText.Length);
                            output.ExtendedText = resized;
                        }
                        output.ExtendedText[extendedEventDescriptor.DescriptorNumber] = extendedEventDescriptor.Text;
                        break;
                    case nameof(PdcDescriptor):
                        PdcDescriptor pdcDescriptor = (PdcDescriptor)dvbDescriptor;
                        if (pdcDescriptor.Valid)
                        {
                            output.Pdc = new DateTime(output.StartTime.Year, pdcDescriptor.Month, pdcDescriptor.Day,
                                pdcDescriptor.Hour, pdcDescriptor.Minute, 0);
                        }
                        break;
                    case nameof(ComponentDescriptor):
                        ComponentDescriptor componentDescriptor = (ComponentDescriptor)dvbDescriptor;
                        if (!componentDescriptor.Valid)
                            break;
                        if (output.Components == null)
                            output.Components = new HashSet<ComponentDescriptor>();
                        output.Components.Add(componentDescriptor);
                        break;
                    case nameof(ContentDescriptor):
                        ContentDescriptor cd = (ContentDescriptor)dvbDescriptor;
                        output.Content = cd.Content;
                        break;
                    case nameof(PrivateDataSpecifierDescriptor):
                        output.PrivateDataSpecifier = ((PrivateDataSpecifierDescriptor)dvbDescriptor).PrivateDataSpecifier;
                        break;
                    case nameof(UserDefinedDescriptor):
                        if (!output.PrivateDataSpecifier.HasValue)
                            break;
                        TsDescriptor unpacked = UserDefinedDescriptorUnpacker.GetInstance().UnpackUserDefinedDescriptor((UserDefinedDescriptor)dvbDescriptor, output.PrivateDataSpecifier.Value, "EIT");
                        if (unpacked == null)
	                        break;
                        HandleUserDefinedDescriptor(unpacked, output);
                        break;
                    case nameof(ParentalRatingDescriptor):
                        ParentalRatingDescriptor ratingDescriptor = (ParentalRatingDescriptor)dvbDescriptor;
                        output.ParentalRatings = ratingDescriptor.ParentalRatings;
                        break;
                    case nameof(LinkageDescriptor):
                        LinkageDescriptor ld = (LinkageDescriptor)dvbDescriptor;
                        output.Linkages.Add(ld);
                        break;
                    case nameof(CaIdentifierDescriptor):
                        CaIdentifierDescriptor cid = (CaIdentifierDescriptor)dvbDescriptor;
                        output.CaSystems = cid.CaSystemIds;
                        break;
                    case nameof(TimeShiftedEventDescriptor):
                        TimeShiftedEventDescriptor tsed = (TimeShiftedEventDescriptor)dvbDescriptor;
                        output.ReferenceServiceId = tsed.ReferenceServiceId;
                        output.ReferenceEventId = tsed.ReferenceEventId;
                        break;
                    case nameof(ContentIdentifierDescriptor):
                        ContentIdentifierDescriptor coid = (ContentIdentifierDescriptor)dvbDescriptor;
                        output.Crids = coid.Crids;
                        break;
                    case nameof(FtaContentManagementDescriptor):
                        FtaContentManagementDescriptor fsnd = (FtaContentManagementDescriptor)dvbDescriptor;
                        output.ControlRemoteAccessOverInternet = fsnd.ControlRemoteAccessOverInternet;
                        output.DoNotApplyRevocation = fsnd.DoNotApplyRevocation;
                        output.DoNotScramble = fsnd.DoNotScramble;
                        break;
                    case nameof(_0x00_ImageIconDescriptor):
	                    _0x00_ImageIconDescriptor iid = (_0x00_ImageIconDescriptor)dvbDescriptor;
	                    if (iid.LastDescriptorNumber != 0)
	                    {
		                    if (output.IconComponents == null)
		                    {
			                    output.IconComponents = new _0x00_ImageIconDescriptor[iid.LastDescriptorNumber];
		                    }
		                    output.IconComponents[iid.DescriptiorNumber] = iid;
	                    }
	                    else
	                    {
		                    output.IconUrl = iid.Url;
		                    output.IconData = iid.IconData;
		                    output.IconType = iid.IconType;
		                    output.IconVerticalOrigin = iid.IconVerticalOrigin;
							output.IconHorizontalOrigin = iid.IconHorizontalOrigin;
							output.IconCoordinateSystem = iid.CoordinateSystem;
							output.IconTransportMode = iid.IconTransportMode;
	                    }
	                    break;
	                default:
                        throw new NotImplementedException(name);
                }
            }
        }

        private void HandleUserDefinedDescriptor(TsDescriptor unpacked, EitEvent output)
        {
	        Attribute customAttribute = unpacked.GetType().GetCustomAttribute(typeof(DescriptorPluginEitHandlerAttribute));
	        if (customAttribute == null)
	        {
		        throw new NotImplementedException(String.Format("{0} has no {1} defined.", unpacked.GetType().Name, nameof(DescriptorPluginEitHandlerAttribute)));
	        }

            DescriptorPluginEitHandlerAttribute handlerAttribute = (DescriptorPluginEitHandlerAttribute)customAttribute;
            handlerAttribute.Handler.HandleEit(output, unpacked);
        }
    }
}
