using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Numerics;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Tasks;
using ImGuiNET;
using SDL2Demo.Net;
using skyscraper5.Docsis;
using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Dvb.Psi.Model;
using skyscraper5.Mhp;
using skyscraper5.Mhp.Descriptors;
using skyscraper5.Mhp.Si;
using skyscraper5.Mhp.Si.Model;
using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Scte35;
using skyscraper5.Skyscraper.Net;
using skyscraper5.Skyscraper.Scraper;
using skyscraper5.src.Skyscraper.FrequencyListGenerator;
using skyscraper5.Teletext.Wss;
using testdrid.SdlWrapper;

namespace SDL2Demo.Forms
{
    internal class JobDisplay : IRenderable, ISkyscraperUiJunction
    {

        public void Render()
        {
            if (!memorySaverMode)
            {
                RenderTableIndex();
                RenderEit();
                RenderSdt();
                RenderPat();
                RenderPmt();
                RenderNit();
                RenderMpe();
                RenderAit();
                RenderDataCarousels();
                RenderWss();
                RenderBat();
                RenderObjectCarousels();
                RenderTot();
                RenderTdt();
                RenderCat();
                RenderScte35();
                RenderDocsis();
                RenderFramegrabs();
            }
        }
        
#region Table-Index

    private int TsType;
    private bool HasPat;
    private bool HasPmt;
    private bool HasSdt;
    private bool HasNit;
    private bool HasEit;
    private bool HasMpe;
    private bool HasAit;
    private bool HasDsmCc;
    private bool HasWss;
    private bool HasBat;
    private bool HasTot;
    private bool HasTdt;
    private bool HasCat;
    private bool HasScte35;
    private bool ipTrafficContainsDab;
    private Process myProcess;

    private void RenderTableIndex()
    {
        if (ImGui.Begin("Table Overview"))
        {
            if (myProcess == null)
                myProcess = Process.GetCurrentProcess();

            myProcess.Refresh();
            SafeText(String.Format("Used memory: {0} MB",myProcess.PrivateMemorySize64 / 1000 / 1000));

            ImGui.BeginDisabled(true);
            ImGui.RadioButton("TS", ref TsType, 1);
            ImGui.SameLine();
            ImGui.RadioButton("GS", ref TsType, 2);
            ImGui.SameLine();
            ImGui.RadioButton("DOCSIS", ref TsType, 3);
            ImGui.SameLine();
            ImGui.RadioButton("Blockstream", ref TsType, 4);

			ImGuiStylePtr style = ImGui.GetStyle();
            float window_visible_x2 = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
            float el_width = 100;
            if (TsType == 1)
            {
                ImGui.SetNextItemWidth(el_width);
                ImGui.Checkbox("PAT    ", ref HasPat);
                float lastButtonX2 = ImGui.GetItemRectMax().X;
                float nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                if (nextButtonX2 < window_visible_x2)
                    ImGui.SameLine();

                ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("PMT    ", ref HasPmt);
                lastButtonX2 = ImGui.GetItemRectMax().X;
                nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                if (nextButtonX2 < window_visible_x2)
                    ImGui.SameLine();

                ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("SDT    ", ref HasSdt);
                lastButtonX2 = ImGui.GetItemRectMax().X;
                nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                if (nextButtonX2 < window_visible_x2)
                    ImGui.SameLine();

                ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("NIT    ", ref HasNit);
                lastButtonX2 = ImGui.GetItemRectMax().X;
                nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                if (nextButtonX2 < window_visible_x2)
                    ImGui.SameLine();

                ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("EIT    ", ref HasEit);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();

                    ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("MPE    ", ref HasMpe);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();

                    ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("AIT    ", ref HasAit);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();

                    ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("DSM-CC ", ref HasDsmCc);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();

                    ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("WSS    ", ref HasWss);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();

                    ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("BAT    ", ref HasBat);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();

                    ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("TOT    ", ref HasTot);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();

                    ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("TDT    ", ref HasTdt);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();

                    ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("CAT    ", ref HasCat);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();

                    ImGui.SetNextItemWidth(el_width);
                    ImGui.Checkbox("SCTE-35", ref HasScte35);
                    lastButtonX2 = ImGui.GetItemRectMax().X;
                    nextButtonX2 = lastButtonX2 + style.ItemSpacing.X + el_width;
                    if (nextButtonX2 < window_visible_x2)
                        ImGui.SameLine();
                }

            if (TsType == 2)
            {
                ImGui.Checkbox("IP", ref HasMpe);
                ImGui.Checkbox("SDAB", ref ipTrafficContainsDab);
            }

            if (TsType == 3)
            {
                ImGui.Checkbox("Packets", ref _docsisEnvironment.DocsisEnvironmentStatistics.PacketPduPresent);
                ImGui.Checkbox("Special Use", ref _docsisEnvironment.DocsisEnvironmentStatistics.SpecialUseMacPresent);
                ImGui.Checkbox("Isolation Packets", ref _docsisEnvironment.DocsisEnvironmentStatistics.IsolationPacketPduPresent);
                ImGui.Checkbox("MAC Specific", ref _docsisEnvironment.DocsisEnvironmentStatistics.MacSpecificHeadersPresent);
            }
            ImGui.EndDisabled();
            ImGui.End();
        }
    }

    private void SafeText(string s)
    {
        if (string.IsNullOrEmpty(s))
        {
            ImGui.Text("<null>");
            return;
        }

        if (string.IsNullOrWhiteSpace(s))
        {
            ImGui.Text("[null]");
            return;
        }

        if (s.Contains("% "))
            s = s.Replace("% ", "%% ");

        ImGui.Text(s);
    }
#endregion Table-Index

#region EIT
        private struct EitEventCoordinate
        {
            public DateTime StartTime;
            public ushort NetworkId;
            public ushort ServiceId;
        }

        private struct EitDay
        {
            public DateTime Date;
            public bool Visible;
            public Guid tableGuid;
        }

        private class EitDayComparer : IComparer<EitDay>
        {
            public int Compare(EitDay x, EitDay y)
            {
                return x.Date.CompareTo(y.Date);
            }
        }

        private class EitEventCoordinateComparer : IComparer<EitEventCoordinate>
        {
            public int Compare(EitEventCoordinate x, EitEventCoordinate y)
            {
                return x.StartTime.Ticks.CompareTo(y.StartTime.Ticks);
            }
        }

        private SortedList<EitDay, SortedList<EitEventCoordinate, string>> eitDisplay;
        private void RenderEit()
        {
            if (eitDisplay == null)
                return;

            if (ImGui.Begin("Event Information Table"))
            {
                lock (eitDisplay)
                {
                    foreach (KeyValuePair<EitDay, SortedList<EitEventCoordinate, string>> keyValuePair in eitDisplay)
                    {
                        EitDay day = keyValuePair.Key;
                        if (ImGui.CollapsingHeader(keyValuePair.Key.Date.ToLongDateString(), ref day.Visible))
                        {
                            if (day.tableGuid == Guid.Empty)
                                day.tableGuid = Guid.NewGuid();
                            ImGui.BeginTable(day.ToString(), 3,
                                ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings);
                            foreach (KeyValuePair<EitEventCoordinate, string> valuePair in keyValuePair.Value)
                            {
                                ImGui.TableNextRow();
                                ImGui.TableSetColumnIndex(0);
                                SafeText(valuePair.Key.StartTime.ToShortTimeString());
                                ImGui.TableSetColumnIndex(1);
                                string sName = ResolveServiceDisplayName(valuePair.Key.ServiceId);
                                SafeText(sName);
                                ImGui.TableSetColumnIndex(2);
                                SafeText(valuePair.Value);
                            }

                            ImGui.EndTable();
                        }
                    }
                }

                ImGui.End();
            }
        }

        public void NotifyEvent(EitEvent eitEvent)
        {
            if (eitDisplay == null)
                eitDisplay = new SortedList<EitDay, SortedList<EitEventCoordinate, string>>(new EitDayComparer());

            TsType = 1;
            HasEit = true;

            EitDay date = new EitDay();
            date.Date = eitEvent.StartTime.Date;
            date.Visible = true;
            lock (eitDisplay)
            {
                if (!eitDisplay.ContainsKey(date))
                {
                    eitDisplay[date] = new SortedList<EitEventCoordinate, string>(new EitEventCoordinateComparer());
                }

                SortedList<EitEventCoordinate, string> sortedList = eitDisplay[date];

                EitEventCoordinate coordinate = new EitEventCoordinate();
                coordinate.StartTime = eitEvent.StartTime;
                coordinate.NetworkId = eitEvent.OriginalNetworkId;
                coordinate.ServiceId = eitEvent.ServiceId;
                if (!sortedList.ContainsKey(coordinate))
                {
                    sortedList.Add(coordinate, eitEvent.EventName);
                }
            }
        }
        
        #endregion EIT

        #region SDT

        private string ResolveServiceDisplayName(ushort serviceId)
        {
            if (sdtDisplay == null)
                return String.Format("0x{0:X4}", serviceId);

            lock (sdtDisplay)
            {
                if (sdtDisplay.ContainsKey(serviceId))
                {
                    string v = sdtDisplay[serviceId].ServiceName;
                    if (string.IsNullOrEmpty(v))
                        return "???";
                    else
                        return v;
                }
            }

            return String.Format("0x{0:X4}", serviceId);
        }

        private SdtCoordinate ResolveService(ushort serviceId)
        {
	        if (sdtDisplay == null)
		        return new SdtCoordinate();

	        lock (sdtDisplay)
	        {
		        if (sdtDisplay.ContainsKey(serviceId))
		        {
			        return sdtDisplay[serviceId];
				}
	        }

			return new SdtCoordinate();
		}

		private struct SdtCoordinate
        {
            public string ProviderName;
            public string ServiceName;
            public bool FreeCaMode;
            public ushort[] CaIdentifiers;
            public ServiceDescriptor.ServiceTypeCoding? ServiceType;
        }

        private SortedList<ushort, SdtCoordinate> sdtDisplay;
        private string sdtTableUuid;
        private void RenderSdt()
        {
            if (sdtDisplay == null)
                return;

            if (string.IsNullOrEmpty(sdtTableUuid))
                sdtTableUuid = Guid.NewGuid().ToString();


            ImGui.Begin("Service Description Table");
            if (ImGui.BeginTable(sdtTableUuid, 4, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit))
            {
                ImGui.TableSetupColumn("Service ID");
                ImGui.TableSetupColumn("Name");
                ImGui.TableSetupColumn("Provider");
                ImGui.TableSetupColumn("CA");
                ImGui.TableHeadersRow();

                lock (sdtDisplay)
                {
                    foreach (KeyValuePair<ushort, SdtCoordinate> keyValuePair in sdtDisplay)
                    {
                        ImGui.TableNextRow();
                        ImGui.TableSetColumnIndex(0);
                        SafeText(String.Format("{0:X4}", keyValuePair.Key));

                        ImGui.TableSetColumnIndex(1);
                        SafeText(keyValuePair.Value.ServiceName);

                        ImGui.TableSetColumnIndex(2);
                        SafeText(keyValuePair.Value.ProviderName);

                        ImGui.TableSetColumnIndex(3);
                        bool ca = keyValuePair.Value.FreeCaMode;
                        ImGui.BeginDisabled(true);
                        ImGui.Checkbox("", ref ca);
                        ImGui.EndDisabled();
                    }
                }

                ImGui.EndTable();
                ImGui.End();
            }
        }

        public void NotifySdtService(SdtService sdtService)
        {
            TsType = 1;
            HasSdt = true;
            if (sdtDisplay == null)
                sdtDisplay = new SortedList<ushort, SdtCoordinate>();

            if (sdtDisplay.ContainsKey(sdtService.ServiceId))
                return;

            SdtCoordinate child = new SdtCoordinate();
            child.ProviderName = sdtService.ServiceProviderName;
            child.ServiceName = sdtService.ServiceName;
            child.FreeCaMode = sdtService.FreeCaMode;
            child.CaIdentifiers = sdtService.CaIdentifiers;
            child.ServiceType = sdtService.ServiceType;
            
            lock (sdtDisplay)
            {
                sdtDisplay.Add(sdtService.ServiceId, child);
            }
        }

        #endregion

        #region PAT
        private class PatEntry
        {
            public ushort programId;
            public ProgramMapping pmt;
            public string PmtViewUuid;
        }

        private SortedList<int, PatEntry> patDisplay;
        public void NotifyPatProgram(int pmtPid, ushort programId)
        {
            HasPat = true;
            TsType = 1;

            if (patDisplay == null)
                patDisplay = new SortedList<int, PatEntry>();

            if (patDisplay.ContainsKey(pmtPid))
                return;

            PatEntry patEntry = new PatEntry();
            patEntry.programId = programId;
            lock (patDisplay)
            {
                patDisplay.Add(pmtPid, patEntry);
            }
        }
        
        private string patTableUuid;
        public void RenderPat()
        {
            if (patDisplay == null)
                return;

            if (string.IsNullOrEmpty(patTableUuid))
                patTableUuid = Guid.NewGuid().ToString();
            if (ImGui.Begin("Program Allocation Table"))
            {
                if (ImGui.BeginTable(patTableUuid, 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit))
                {
                    ImGui.TableSetupColumn("PMT PID");
                    ImGui.TableSetupColumn("Service");
                    ImGui.TableHeadersRow();

                    lock (patDisplay)
                    {
                        foreach (KeyValuePair<int, PatEntry> keyValuePair in patDisplay)
                        {
                            string k = String.Format("0x{0:X4}", keyValuePair.Key);
                            string v = ResolveServiceDisplayName(keyValuePair.Value.programId);
                            if (v == null)
                                v = "???";

                            ImGui.TableNextRow();
                            ImGui.TableSetColumnIndex(0);
                            SafeText(k);

                            ImGui.TableSetColumnIndex(1);
                            SafeText(v);
                        }
                    }
                    ImGui.EndTable();
                }
                ImGui.End();
            }
        }
        #endregion

        #region PMT

        public void NotifyPmtProgram(ProgramMapping result, int pmtPid)
        {
            HasPmt = true;
            TsType = 1;

            if (!patDisplay.ContainsKey(pmtPid))
                return;
            
            PatEntry patEntry = patDisplay[pmtPid];
            if (patEntry.pmt != null)
                return;

            patEntry.pmt = result;
        }
        
        private string pmtTabBarUuid;
        public void RenderPmt()
        {
            if (!HasPmt)
                return;

            if (string.IsNullOrEmpty(pmtTabBarUuid))
                pmtTabBarUuid = Guid.NewGuid().ToString();

            if (ImGui.Begin("Program Map Table"))
            {
                if (ImGui.BeginTabBar(pmtTabBarUuid, ImGuiTabBarFlags.FittingPolicyScroll | ImGuiTabBarFlags.TabListPopupButton))
                {
                    lock (patDisplay)
                    {
                        foreach (KeyValuePair<int, PatEntry> keyValuePair in patDisplay)
                        {
                            if (ImGui.BeginTabItem(ResolveServiceDisplayName(keyValuePair.Value.programId)))
                            {
                                if (string.IsNullOrEmpty(keyValuePair.Value.PmtViewUuid))
                                    keyValuePair.Value.PmtViewUuid = Guid.NewGuid().ToString();

                                if (keyValuePair.Value.pmt == null)
                                    continue;

                                ProgramMapping pmt = keyValuePair.Value.pmt;

                                SafeText(String.Format("Service ID: 0x{0:X4}", pmt.ProgramNumber));

                                ImGui.BeginTable(keyValuePair.Value.PmtViewUuid, 2, ImGuiTableFlags.NoSavedSettings);
                                ImGui.TableSetupColumn("PID");
                                ImGui.TableSetupColumn("Type");
                                ImGui.TableHeadersRow();

                                ImGui.TableNextRow();
                                ImGui.TableSetColumnIndex(0);
                                SafeText(String.Format("0x{0:X4}", keyValuePair.Key));

                                ImGui.TableSetColumnIndex(1);
                                SafeText("PMT");

                                ImGui.TableNextRow();
                                ImGui.TableSetColumnIndex(0);
                                SafeText(String.Format("0x{0:X4}", keyValuePair.Value.pmt.PcrPid));

                                ImGui.TableSetColumnIndex(1);
                                SafeText("PCR");

                                foreach (ProgramMappingStream programMappingStream in pmt.Streams)
                                {
                                    ImGui.TableNextRow();
                                    ImGui.TableSetColumnIndex(0);
                                    SafeText(String.Format("0x{0:X4}", programMappingStream.ElementaryPid));

                                    ImGui.TableSetColumnIndex(1);
                                    SafeText(StreamTypeAsString(programMappingStream,pmt.RegistrationFormatIdentifier));
                                }

                                ImGui.EndTable();
                                ImGui.EndTabItem();
                            }
                        }
                    }
                    ImGui.EndTabBar();
                }
                ImGui.End();
            }
        }

        private string StreamTypeAsString(ProgramMappingStream stream, uint? parentRegistrationFormatIdentifier)
        {
            if (streamTypes != null)
            {
                if (streamTypes.ContainsKey(stream.ElementaryPid))
                {
                    return streamTypes[stream.ElementaryPid];
                }
            }
            if (stream.StreamType == PmtStreamType.H262)
                return "MPEG-2 Video";
            if (stream.AvcStillPresent.HasValue)
                return "MPEG-4 Video (still frame)";
            if (stream.AudioType.HasValue)
            {
                switch (stream.AudioType)
                {
                    case AudioType.CleanEffects: return "Audio (no effects)";
                    case AudioType.HearingImpaired: return "Audio (for hearing impaired)";
                    case AudioType.VisualImpairedCommentary: return "Audio (for visually impaired)";
                }
            }

            if (stream.StreamType == PmtStreamType.AvcVideoStream)
            {
                return "MPEG-4 Video";
            }

            if (stream.StreamType == PmtStreamType.Iso11172Audio)
            {
                return "Audio";
            }

            if (stream.StreamType == PmtStreamType.Iso13818_3Audio)
                return "MPEG-2 Audio";
            if (stream.StreamType == PmtStreamType.Iso13818_7AudioADTS)
                return "AAC Audio";
            if (stream.StreamType == PmtStreamType.HevcVideoStream)
                return "H.265 Video";
            if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.Ac4ChannelMode.HasValue)
                return "Dolby AC-4 Audio";
            if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.BSID.HasValue)
                return "Dolby AC-3 Audio";
            if (stream.StreamType == PmtStreamType.Iso14496_3Audio && stream.AacProfileAndLevel.HasValue)
                return "AAC Audio";
            if ((int)stream.StreamType == 0x81 && stream.ComponentType.HasValue)
                return "Dolby AC-3 Audio";
            if (stream.Teletexts != null)
                return "Teletext";

            if (stream.Applications != null)
                return "Red-button Application";

            if (stream.DataBroadcastId == 0x0123)
                return "Red-button Application";

            if (stream.DataBroadcastId == 0x000a)
                return "System Software Update";

            if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.Subtitlings != null && stream.Subtitlings.Length > 0)
                return "Subtitle";

            if (stream.DataBroadcastId == 0xf0)
                return "MHP Application";


            if (stream.FormatIdentifier.HasValue && stream.FormatIdentifier == 0x43554549 && (int)stream.StreamType == 0x86)
                return "Advertising Metadata";

            if ((int)stream.StreamType == 0x86 && parentRegistrationFormatIdentifier.HasValue && parentRegistrationFormatIdentifier.Value == 0x43554549)
                return "Advertising Metadata";

            if (stream.DataBroadcastId == 0x0007)
                return "Object Carousel";

            if (stream.StreamType == PmtStreamType.Iso13818_1PesPackets && stream.VbiData != null)
                return "Teletext";

            if ((byte)stream.StreamType == 0x89 && stream.AncillaryDataDescriptor != null && stream.AncillaryDataDescriptor.RdsOnly)
                return "Radio Data Service";

            if (stream.DataBroadcastId.HasValue && stream.DataBroadcastId.Value == 0x0005 /*&& stream.StreamType == PmtStreamType.Iso13818_6TypeD*/)
                return "Multiprotocol Encapsulation";

            if (stream.DataBroadcastId == 0x0106)
                return "MHEG-5";

            if (stream.DataBroadcastId == 0x000b)
                return "IP/MAC Notification";

            if (stream.RelatedContentDescriptorPresent.HasValue)
                if (stream.RelatedContentDescriptorPresent.Value && stream.StreamType == PmtStreamType.Iso13818_1PrivateSections)
                return "Related Content Table Information";

            if (stream.NumT2MiStreams.HasValue && stream.StreamType == PmtStreamType.Iso13818_1PesPackets)
                return "T2-MI";
            
            return String.Format("??? (Type 0x{0:X2})", (int)stream.StreamType);
        }
        #endregion

        #region NIT

        private class NitNetworkMeta
        {
            public ushort ONID;
            public string DisplayUUID;

            public override string ToString()
            {
                return String.Format("Network 0x{0:X4}", ONID);
            }

            public bool Equals(NitNetworkMeta other)
            {
                return ONID == other.ONID;
            }

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

            public override int GetHashCode()
            {
                return ONID.GetHashCode();
            }
        }

        private class NitCoordinateComparer : IComparer<NitNetworkMeta>
        {
            public int Compare(NitNetworkMeta x, NitNetworkMeta y)
            {
                return x.ONID.CompareTo(y.ONID);
            }
        }

        private SortedList<NitNetworkMeta,SortedList<ushort, NitTransportStream>> nitDisplay;
        private string nitTableUuid;

        public void NotifyNit(NitTransportStream transportStream)
        {
            TsType = 1;
            HasNit = true;

            if (nitDisplay == null)
                nitDisplay = new SortedList<NitNetworkMeta, SortedList<ushort, NitTransportStream>>(new NitCoordinateComparer());

            NitNetworkMeta nnm = new NitNetworkMeta();
            nnm.ONID = transportStream.OriginalNetworkId;
            if (!nitDisplay.ContainsKey(nnm))
            {
                lock (nitDisplay)
                {
                    nitDisplay.Add(nnm, new SortedList<ushort, NitTransportStream>());
                }
            }

            SortedList<ushort, NitTransportStream> nitTransportStreams = nitDisplay[nnm];
            if (nitTransportStreams.ContainsKey(transportStream.TransportStreamId))
                return;

            lock (nitTransportStreams)
            {
                nitTransportStreams.Add(transportStream.TransportStreamId, transportStream);
            }
        }
        
        private string GetFrequencyString(NitTransportStream nts)
        {
            if (nts.Frequency.HasValue && nts.Polarization.HasValue)
                return String.Format("{0} Mhz, {1}", nts.Frequency.Value / 100, nts.Polarization.ToString().Substring(0, 1));

            return "???";
        }
        
        private void RenderNit()
        {
            if (nitDisplay == null)
                return;

            if (string.IsNullOrWhiteSpace(nitTableUuid))
                nitTableUuid = Guid.NewGuid().ToString();

            if (ImGui.Begin("Network Information Table"))
            {
                lock (nitDisplay)
                {
                    foreach (KeyValuePair<NitNetworkMeta, SortedList<ushort, NitTransportStream>> keyValuePair in nitDisplay)
                    {
                        if (ImGui.CollapsingHeader(keyValuePair.Key.ToString()))
                        {
                            if (string.IsNullOrEmpty(keyValuePair.Key.DisplayUUID))
                                keyValuePair.Key.DisplayUUID = Guid.NewGuid().ToString();
                            if (ImGui.BeginTable(keyValuePair.Key.DisplayUUID, 4, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit))
                            {
                                lock (keyValuePair.Value)
                                {
                                    foreach (KeyValuePair<ushort, NitTransportStream> nitTransportStream in keyValuePair
                                                 .Value)
                                    {
                                        ImGui.TableNextRow();
                                        ImGui.TableSetColumnIndex(0);
                                        SafeText(nitTransportStream.Value.DeliveryMethod.ToString().Replace('_', '_'));

                                        ImGui.TableSetColumnIndex(1);
                                        if (nitTransportStream.Value.OrbitalPosition.HasValue &&
                                            nitTransportStream.Value.East.HasValue)
                                        {
                                            SafeText(String.Format("{0:F1} °{1}",
                                                nitTransportStream.Value.OrbitalPosition.Value,
                                                nitTransportStream.Value.East.Value ? "E" : "W"));
                                        }

                                        ImGui.TableSetColumnIndex(2);
                                        SafeText(GetFrequencyString(nitTransportStream.Value));

                                        ImGui.TableSetColumnIndex(3);
                                        if (nitTransportStream.Value.SymbolRate.HasValue)
                                            SafeText(String.Format("{0} ksym/s",
                                                nitTransportStream.Value.SymbolRate / 10));
                                    }
                                }
                            }
                            ImGui.EndTable();
                        }
                    }
                }
                ImGui.End();
            }
        }

        #endregion

        #region MPE

        private IpTrafficInfoComparer trafficInfoComparer;
        private string mpeTableUuid;
        private List<KeyValuePair<IpTrafficInfo, IpPerformanceInfo>> mpeDisplays;
        public void NotifyMpeTraffic(IpTrafficInfo iti, int ipv4PacketLength)
        {
            if (trafficInfoComparer == null)
                trafficInfoComparer = new IpTrafficInfoComparer();
            HasMpe = true;
            if (mpeDisplays == null)
                mpeDisplays = new List<KeyValuePair<IpTrafficInfo, IpPerformanceInfo>>();

            lock (mpeDisplays)
            {
                foreach (KeyValuePair<IpTrafficInfo, IpPerformanceInfo> line in mpeDisplays)
                {
                    if (line.Key.Equals(iti))
                    {
                        line.Value.CountPacket(ipv4PacketLength);
                        return;
                    }
                }

                KeyValuePair<IpTrafficInfo, IpPerformanceInfo> newChild = new KeyValuePair<IpTrafficInfo, IpPerformanceInfo>(iti, new IpPerformanceInfo());
                newChild.Value.CountPacket(ipv4PacketLength);
                mpeDisplays.Add(newChild);
            }
        }

        private DateTime prev, next;
        private void RenderMpe()
        {
            if (mpeDisplays == null)
                return;

            prev = next;
            next = DateTime.Now;
            if (prev.Second != next.Second)
            {
                lock (mpeDisplays)
                {
                    foreach (KeyValuePair<IpTrafficInfo, IpPerformanceInfo> line in mpeDisplays)
                    {
                        line.Value.UpdatePerSeconds();
                    }
                }
            }

            string windowName = String.Format("{0} Encapsulation", TsType == 1 ? "Multiprotocol" : "Generic Stream");
            if (ImGui.Begin(windowName))
            {
                if (mpeDisplays != null)
                {
                    if (mpeDisplays.Count > 0)
                    {
                        SafeText(String.Format("Communication parties: {0}", mpeDisplays.Count));
                    }
                }
                if (string.IsNullOrEmpty(mpeTableUuid))
                    mpeTableUuid = Guid.NewGuid().ToString();
                if (ImGui.BeginTable(mpeTableUuid, 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings))
                {
                    ImGui.TableSetupColumn("Protocol");
                    ImGui.TableSetupColumn("Source IP");
                    ImGui.TableSetupColumn("Destination IP");
                    ImGui.TableSetupColumn("Packets");
                    ImGui.TableSetupColumn("Traffic");
                    ImGui.TableHeadersRow();

                    lock (mpeDisplays)
                    {
                        mpeDisplays.Sort(trafficInfoComparer);
                        foreach (KeyValuePair<IpTrafficInfo, IpPerformanceInfo> ipPerformanceInfo in mpeDisplays)
                        {

                            ImGui.TableNextRow();
                            ImGui.TableSetColumnIndex(0);
                            SafeText(IpTrafficInfo.GetProtocolName(ipPerformanceInfo.Key.Protocol));

                            ImGui.TableSetColumnIndex(1);
                            SafeText(RenderIp(ipPerformanceInfo, false));

                            ImGui.TableSetColumnIndex(2);
                            SafeText(RenderIp(ipPerformanceInfo, true));

                            ImGui.TableSetColumnIndex(3);
                            SafeText(ipPerformanceInfo.Value.TotalPackets.ToString());

                            ImGui.TableSetColumnIndex(4);
                            SafeText(String.Format("{0:F1} kb/s",
                                (double)ipPerformanceInfo.Value.BytesPerSecond / 1000.0));
                        }
                    }

                    ImGui.EndTable();
                }
                ImGui.End();
            }
        }

        private string RenderIp(KeyValuePair<IpTrafficInfo, IpPerformanceInfo> ipPerformanceInfo, bool destination)
        {
            if (!destination)
            {
                if (string.IsNullOrEmpty(ipPerformanceInfo.Key.SourceName))
                    return ipPerformanceInfo.Key.Source.ToString();
                else
                    return ipPerformanceInfo.Key.SourceName;
            }
            else
            {
                if (string.IsNullOrEmpty(ipPerformanceInfo.Key.TargetName))
                    return ipPerformanceInfo.Key.Target.ToString();
                else
                    return ipPerformanceInfo.Key.TargetName;
            }
            /*if (destination)
            {
                if (!string.IsNullOrEmpty(ipPerformanceInfo.Value.DestinationResolution))
                {
                    return ipPerformanceInfo.Value.DestinationResolution;
                }
                else
                {
                    return ipPerformanceInfo.Key.Target.ToString();
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(ipPerformanceInfo.Value.SourceResolution))
                {
                    return ipPerformanceInfo.Value.SourceResolution;
                }
                else
                {
                    return ipPerformanceInfo.Key.Source.ToString();
                }
            }*/
        }

        public int GseCommunicationParties
        {
            get
            {
                if (TsType != 2)
                    return 0;

                if (mpeDisplays == null)
                    return 0;

                return mpeDisplays.Count;
            }
        }
        #endregion

        #region AIT

        private SortedList<ApplicationIdentifier, AitApplication> aitDisplay;
        private string aitTableUuid;
        public void NotifyAit(AitApplication aitApplication)
        {
            TsType = 1;
            HasAit = true;

            if (aitDisplay == null)
                aitDisplay = new SortedList<ApplicationIdentifier, AitApplication>(new ApplicationIdentifierComparer());

            lock (aitDisplay)
            {
                if (!aitDisplay.ContainsKey(aitApplication.ApplicationIdentifier))
                {
                    aitDisplay.Add(aitApplication.ApplicationIdentifier, aitApplication);
                }
            }
        }
        
        private void RenderAit()
        {
            if (aitDisplay == null)
                return;

            if (ImGui.Begin("Application Identification Table"))
            {
                if (string.IsNullOrEmpty(aitTableUuid))
                    aitTableUuid = Guid.NewGuid().ToString();

                if (ImGui.BeginTable(aitTableUuid, 2,ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit))
                {
                    ImGui.TableSetupColumn("Application Name");
                    ImGui.TableSetupColumn("Transport");
                    ImGui.TableHeadersRow();

                    lock (aitDisplay)
                    {
                        foreach (KeyValuePair<ApplicationIdentifier, AitApplication> keyValuePair in aitDisplay)
                        {
                            ImGui.TableNextRow();
                            ImGui.TableSetColumnIndex(0);
                            SafeText(keyValuePair.Value.TryGetName());

                            ImGui.TableSetColumnIndex(1);
                            foreach (TransportProtocolDescriptor transportProtocolDescriptor in keyValuePair.Value.TransportProtocols)
                            {
                                SafeText(transportProtocolDescriptor.Selector.ToString());
                            }
                        }
                    }

                    ImGui.EndTable();
                }
                ImGui.End();
            }
        }
        #endregion

        #region Data Carousel

        private struct DsmCcModuleIdentifier
        {
            public int pid;
            public ushort moduleId;
            public byte moduleVersion;

        }

        private sealed class DsmCcModuleIdentifierComparer : IComparer<DsmCcModuleIdentifier>
        {
            public int Compare(DsmCcModuleIdentifier x, DsmCcModuleIdentifier y)
            {
                var pidComparison = x.pid.CompareTo(y.pid);
                if (pidComparison != 0) return pidComparison;
                var moduleIdComparison = x.moduleId.CompareTo(y.moduleId);
                if (moduleIdComparison != 0) return moduleIdComparison;
                return x.moduleVersion.CompareTo(y.moduleVersion);
            }
        }


        private SortedList<DsmCcModuleIdentifier, double> dsmCcDisplay;
        private string objectCarouselTableUuid;

        public void DsmCcModuleAdd(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion)
        {
            TsType = 1;
            HasDsmCc = true;

            if (dsmCcDisplay == null)
                dsmCcDisplay = new SortedList<DsmCcModuleIdentifier, double>(new DsmCcModuleIdentifierComparer());

            DsmCcModuleIdentifier id = new DsmCcModuleIdentifier();
            id.pid = elementaryPid;
            id.moduleId = moduleInfoModuleId;
            id.moduleVersion = moduleInfoModuleVersion;
            lock (dsmCcDisplay)
            {
                dsmCcDisplay.Add(id, 0);
            }
        }

        public void DsmCcModuleProgress(int elementaryPid, ushort moduleInfoModuleId, byte moduleInfoModuleVersion, double progress)
        {
            TsType = 1;
            HasDsmCc = true;

            if (dsmCcDisplay == null)
                return;

            DsmCcModuleIdentifier id = new DsmCcModuleIdentifier();
            id.pid = elementaryPid;
            id.moduleId = moduleInfoModuleId;
            id.moduleVersion = moduleInfoModuleVersion;
            if (!dsmCcDisplay.ContainsKey(id))
                return;
            lock (dsmCcDisplay)
            {
                dsmCcDisplay[id] = progress;
            }
        }

        public void DsmCcModuleComplete(int elementaryPid, ushort moduleModuleId, byte moduleModuleVersion)
        {
            TsType = 1;
            HasDsmCc = true;

            if (dsmCcDisplay == null)
                return;

            DsmCcModuleIdentifier id = new DsmCcModuleIdentifier();
            id.pid = elementaryPid;
            id.moduleId = moduleModuleId;
            id.moduleVersion = moduleModuleVersion;
            lock (dsmCcDisplay)
            {
                dsmCcDisplay.Remove(id);
            }
        }
        
        private void RenderDataCarousels()
        {
            if (dsmCcDisplay == null)
                return;
            if (dsmCcDisplay.Count == 0)
                return;

            if (ImGui.Begin("Digital Storage Media Command & Control, Data Carousels"))
            {
                if (string.IsNullOrEmpty(objectCarouselTableUuid))
                {
                    objectCarouselTableUuid = Guid.NewGuid().ToString();
                }

                if (ImGui.BeginTable(objectCarouselTableUuid, 4, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit))
                {
                    ImGui.TableSetupColumn("PID");
                    ImGui.TableSetupColumn("Module ID");
                    ImGui.TableSetupColumn("Version");
                    ImGui.TableSetupColumn("Progress");
                    ImGui.TableHeadersRow();

                    lock (dsmCcDisplay)
                    {
                        foreach (KeyValuePair<DsmCcModuleIdentifier, double> keyValuePair in dsmCcDisplay)
                        {
                            ImGui.TableNextRow();
                            ImGui.TableSetColumnIndex(0);
                            SafeText(String.Format("{0}", keyValuePair.Key.pid));

                            ImGui.TableSetColumnIndex(1);
                            SafeText(String.Format("{0}", keyValuePair.Key.moduleId));

                            ImGui.TableSetColumnIndex(2);
                            SafeText(String.Format("{0}", keyValuePair.Key.moduleVersion));

                            ImGui.TableSetColumnIndex(3);
                            ImGui.SetNextItemWidth(100);
                            ImGui.ProgressBar(Convert.ToSingle(keyValuePair.Value) / 100.0f, Vector2.Zero, String.Format("{0}%", keyValuePair.Value));
                        }
                    }
                    ImGui.EndTable();
                }
                ImGui.End();
            }
            
        }

        #endregion

        #region WSS

        private string wssTableUuid;
        private SortedList<ushort, WssDataBlock> wssDisplay;
        public void NotifyWss(ushort programNumber, WssDataBlock wssDataBlock)
        {
            HasWss = true;
            TsType = 1;

            if (wssDisplay == null)
                wssDisplay = new SortedList<ushort, WssDataBlock>();

            if (!wssDisplay.ContainsKey(programNumber))
            {
                lock (wssDisplay)
                {
                    wssDisplay.Add(programNumber, wssDataBlock);
                }
            }
        }
        
        private void RenderWss()
        {
            if (wssDisplay == null)
                return;

            if (ImGui.Begin("Wide Screen Signalling"))
            {
                if (String.IsNullOrEmpty(wssTableUuid))
                    wssTableUuid = Guid.NewGuid().ToString();

                if (ImGui.BeginTable(wssTableUuid, 7, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings))
                {
                    ImGui.TableSetupColumn("Service");
                    ImGui.TableSetupColumn("Aspect Ratio");
                    ImGui.TableSetupColumn("Film Mode");
                    ImGui.TableSetupColumn("Motion Adaptive Color");
                    ImGui.TableSetupColumn("Subtitle");
                    ImGui.TableSetupColumn("Surround Sound");
                    ImGui.TableSetupColumn("Copy restricted");
                    ImGui.TableHeadersRow();

                    lock (wssDisplay)
                    {
                        foreach (KeyValuePair<ushort, WssDataBlock> wssDataBlock in wssDisplay)
                        {
                            ImGui.TableNextRow();
                            ImGui.TableSetColumnIndex(0);
                            SafeText(ResolveServiceDisplayName(wssDataBlock.Key));

                            ImGui.TableSetColumnIndex(1);
                            SafeText(wssDataBlock.Value.AspectRatioToString());

                            ImGui.TableSetColumnIndex(2);
                            ImGui.BeginDisabled(true);
                            bool b = wssDataBlock.Value.FilmMode;
                            ImGui.Checkbox("", ref b);

                            ImGui.TableSetColumnIndex(3);
                            b = wssDataBlock.Value.MotionAdaptiveColorPlus;
                            ImGui.Checkbox("", ref b);

                            ImGui.TableSetColumnIndex(4);
                            b = wssDataBlock.Value.SubtitlesWithinTeletext;
                            ImGui.Checkbox("", ref b);

                            ImGui.TableSetColumnIndex(5);
                            b = wssDataBlock.Value.SurroundSound;
                            ImGui.Checkbox("", ref b);

                            ImGui.TableSetColumnIndex(6);
                            b = wssDataBlock.Value.CopyingRestricted;
                            ImGui.Checkbox("", ref b);
                            ImGui.EndDisabled();
                        }
                    }

                    ImGui.EndTable();
                }
                ImGui.End();
            }
        }
        #endregion

        #region Stream Type Autodetection

        private Dictionary<int, string> streamTypes;

        public void NotifyStreamTypeDetection(string contestantTag, int pid)
        {
            if (streamTypes == null)
                streamTypes = new Dictionary<int, string>();
            streamTypes[pid] = contestantTag;
        }



        #endregion

        #region BAT

        private struct DisplayableBat
        {
            public string Name;
            public List<LinkageDescriptor> Linkages;
            public string TableUuid;
            public HashSet<BatTransportStream> TransportStreams;
        }

        private SortedList<ushort, DisplayableBat> batDisplay;

        public void NotifyBat(BatBouquet batBouquet)
        {
            TsType = 1;
            HasBat = true;

            if (batDisplay == null)
                batDisplay = new SortedList<ushort, DisplayableBat>();

            if (batDisplay.ContainsKey(batBouquet.BouquetId))
                return;

            DisplayableBat child = new DisplayableBat();
            child.Name = batBouquet.TryGetName();
            child.Linkages = batBouquet.Linkages;
            child.TableUuid = Guid.NewGuid().ToString();
            child.TransportStreams = new HashSet<BatTransportStream>();

            lock (batDisplay)
            {
                batDisplay[batBouquet.BouquetId] = child;
            }
        }

        public void NotifyBatTs(ushort batBouquetBouquetId, BatTransportStream child)
        {
            TsType = 1;
            HasBat = true;

            if (batDisplay == null)
                return;
            if (!batDisplay.ContainsKey(batBouquetBouquetId))
                return;

            batDisplay[batBouquetBouquetId].TransportStreams.Add(child);
        }
        
        private void RenderBat()
        {
            if (batDisplay == null)
                return;

            if (batDisplay.Count == 0)
                return;

            if (ImGui.Begin("Bouquet Association Table"))
            {
                lock (batDisplay)
                {
                    foreach (KeyValuePair<ushort, DisplayableBat> keyValuePair in batDisplay)
                    {
                        if (ImGui.CollapsingHeader(String.Format("Bouquet #{0} - {1}", keyValuePair.Key, keyValuePair.Value.Name)))
                        {
                            if (ImGui.BeginTable(keyValuePair.Value.TableUuid, 5, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit))
                            {
                                ImGui.TableSetupColumn("L/TS");
                                ImGui.TableSetupColumn("ONID");
                                ImGui.TableSetupColumn("TSID");
                                ImGui.TableSetupColumn("Service");
                                ImGui.TableSetupColumn("");
                                ImGui.TableHeadersRow();

                                lock (keyValuePair.Value.Linkages)
                                {
                                    foreach (LinkageDescriptor linkageDescriptor in keyValuePair.Value.Linkages)
                                    {
                                        ImGui.TableNextRow();
                                        ImGui.TableSetColumnIndex(0);
                                        SafeText("Link");

                                        ImGui.TableSetColumnIndex(1);
                                        SafeText(String.Format("{0:X4}",linkageDescriptor.OriginalNetworkId));

                                        ImGui.TableSetColumnIndex(2);
                                        SafeText(String.Format("{0:X4}", linkageDescriptor.TransportStreamId));

                                        ImGui.TableSetColumnIndex(3);
                                        SafeText(ResolveServiceDisplayName(linkageDescriptor.ServiceId));

                                        ImGui.TableSetColumnIndex(4);
                                        SafeText(linkageDescriptor.LinkageType.ToString());
                                    }
                                }

                                lock (keyValuePair.Value.TransportStreams)
                                {
                                    foreach (BatTransportStream valueTransportStream in keyValuePair.Value.TransportStreams)
                                    {
                                        ImGui.TableNextRow();
                                        ImGui.TableSetColumnIndex(0);
                                        SafeText("TS");

                                        ImGui.TableSetColumnIndex(1);
                                        SafeText(String.Format("{0:X4}", valueTransportStream.OriginalNetworkId));

                                        ImGui.TableSetColumnIndex(2);
                                        SafeText(String.Format("{0:X4}", valueTransportStream.TransportStreamId));

                                        if (valueTransportStream.ServiceList != null)
                                        {
                                            foreach (ServiceListDescriptor.Service service in valueTransportStream.ServiceList)
                                            {
                                                ImGui.TableNextRow();
                                                ImGui.TableSetColumnIndex(0);
                                                SafeText("Service");

                                                ImGui.TableSetColumnIndex(3);
                                                SafeText(ResolveServiceDisplayName(service.ServiceId));

                                                ImGui.TableSetColumnIndex(4);
                                                SafeText(service.ServiceType.ToString());
                                            }
                                        }
                                    }
                                }
                                ImGui.EndTable();
                            }
                        }
                    }
                }
                ImGui.End();
            }
        }

        #endregion

        #region Object Carousel

        private SortedList<int, VfsDirectory> displayObjectCarouselRoots;

        private string GetServiceNameFromPid(int pid)
        {
            lock (patDisplay)
            {
                foreach (KeyValuePair<int, PatEntry> keyValuePair in patDisplay)
                {
                    PatEntry patEntry = keyValuePair.Value;
                    if (patEntry.pmt == null)
                        continue;

                    foreach (ProgramMappingStream programMappingStream in patEntry.pmt.Streams)
                    {
                        if (programMappingStream.ElementaryPid == pid)
                        {
                            return ResolveServiceDisplayName(patEntry.programId);
                        }
                    }
                }
            }

            return "???";
        }

        public void DsmCcVfs(VfsFile vfsFile)
        {
            TsType = 1;
            HasDsmCc = true;
            if (displayObjectCarouselRoots == null)
                displayObjectCarouselRoots = new SortedList<int, VfsDirectory>();
            if (displayObjectCarouselRoots.ContainsKey(vfsFile.SourcePid))
                return;

            VfsDirectory masterDirectory = vfsFile.ParentDirectory;
            while (masterDirectory.ParentDirectory != null)
                masterDirectory = masterDirectory.ParentDirectory;

            lock (displayObjectCarouselRoots)
            {
                displayObjectCarouselRoots.Add(vfsFile.SourcePid, masterDirectory);
            }
        }
        
        private void RenderObjectCarousels()
        {
            if (displayObjectCarouselRoots == null)
                return;

            if (ImGui.Begin("Digital Storage Media - Command & Control, Object Carousels"))
            {
                lock (displayObjectCarouselRoots)
                {
                    foreach (KeyValuePair<int, VfsDirectory> displayObjectCarouselRoot in displayObjectCarouselRoots)
                    {
                        string name = String.Format("Object Carousel in PID 0x{1:X4} (Service: {0})", GetServiceNameFromPid(displayObjectCarouselRoot.Key), displayObjectCarouselRoot.Key);
                        if (ImGui.CollapsingHeader(name))
                        {
                            RenderDirectoryNode(displayObjectCarouselRoot.Value);
                        }
                    }
                }
            }
        }

        private void RenderDirectoryNode(VfsDirectory directory)
        {
            if (!directory.SkyscrpaerUiData.ContainsKey("UUID"))
                directory.SkyscrpaerUiData.Add("UUID", Guid.NewGuid().ToString());

            string dname = directory.Name;
            if (string.IsNullOrEmpty(dname) && directory.ParentDirectory == null)
                dname = "<root directory>";

            if (ImGui.TreeNode(directory.SkyscrpaerUiData["UUID"].ToString(), dname))
            {
                foreach (VfsDirectory subdirectory in directory.subdirectories)
                {
                    RenderDirectoryNode(subdirectory);
                }

                foreach (VfsFile directoryFile in directory.files)
                {
                    SafeText(directoryFile.Name);
                }

                foreach (VfsEvent directoryEvent in directory.events)
                {
                    SafeText(directoryEvent.Name);
                }
                ImGui.TreePop();
            }
        }

        #endregion

        #region TOT

        private DateTime? totTime;
        private LocalTimeOffsetDescriptor totDescriptor;
        private string totDisplayUuid;

        public void NotifyTot(DateTime utcTime, LocalTimeOffsetDescriptor ltod)
        {
            TsType = 1;
            HasTot = true;

            totTime = utcTime;
            totDescriptor = ltod;
        }

        private void RenderTot()
        {
            if (!totTime.HasValue)
                return;

            if (ImGui.Begin("Time Offset Table"))
            {
                SafeText(totTime.ToString());

                if (totDescriptor != null)
                {
                    if (totDescriptor.Valid)
                    {
                        if (string.IsNullOrEmpty(totDisplayUuid))
                            totDisplayUuid = Guid.NewGuid().ToString();

                        if (ImGui.BeginTable(totDisplayUuid, 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoSavedSettings))
                        {
                            ImGui.TableSetupColumn("Region");
                            ImGui.TableSetupColumn("Polarity");
                            ImGui.TableSetupColumn("Offset");
                            ImGui.TableHeadersRow();

                            foreach (LocalTimeOffsetDescriptor.LocalTime totDescriptorLocalTimeOffset in totDescriptor.LocalTimeOffsets)
                            {
                                ImGui.TableNextRow();
                                ImGui.TableSetColumnIndex(0);
                                SafeText(totDescriptorLocalTimeOffset.CountryCode);

                                ImGui.TableSetColumnIndex(1);
                                bool b = totDescriptorLocalTimeOffset.LocalTimeOffsetPolarity;
                                ImGui.BeginDisabled(true);
                                ImGui.Checkbox("", ref b);
                                ImGui.EndDisabled();

                                ImGui.TableSetColumnIndex(2);
                                SafeText(totDescriptorLocalTimeOffset.LocalTimeOffset.ToString());
                            }
                            ImGui.EndTable();
                        }
                    }
                }
                ImGui.End();
            }
        }

        #endregion

        #region TDT

        private DateTime? tdtDisplay;
        public void NotifyTdt(DateTime utcTime)
        {
            TsType = 1;
            HasTdt = true;
            tdtDisplay = utcTime;
        }



        private void RenderTdt()
        {
            if (tdtDisplay.HasValue)
            {
                if (ImGui.Begin("Time and Date Table"))
                {
                    SafeText(tdtDisplay.Value.ToString());
                    ImGui.End();
                }
            }
        }

        #endregion

        #region CAT
        private string caTableUuid;
        private HashSet<CaDescriptor> caDisplay;
        public void NotifyCat(CaDescriptor caDescriptor)
        {
            TsType = 1;
            HasCat = true;

            if (caDisplay == null)
                caDisplay = new HashSet<CaDescriptor>();

            lock (caDisplay)
            {
                caDisplay.Add(caDescriptor);
            }
        }
        private void RenderCat()
        {
            if (caDisplay == null)
                return;

            if (ImGui.Begin("Conditional Access Table"))
            {
                if (string.IsNullOrEmpty(caTableUuid))
                    caTableUuid = Guid.NewGuid().ToString();

                if (ImGui.BeginTable(caTableUuid, 3, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit))
                {
                    ImGui.TableSetupColumn("CA System");
                    ImGui.TableSetupColumn("ECM/EMM PID");
                    ImGui.TableSetupColumn("Private Data");
                    ImGui.TableHeadersRow();
                    lock (caDisplay)
                    {
                        foreach (CaDescriptor caDescriptor in caDisplay)
                        {
                            ImGui.TableNextRow();
                            ImGui.TableSetColumnIndex(0);
                            SafeText(CaSystemNames.GetHumanReadableName(caDescriptor.CaSystemId));

                            ImGui.TableSetColumnIndex(1);
                            SafeText(String.Format("{0:X4}", caDescriptor.CaPid));

                            ImGui.TableSetColumnIndex(2);
                            if (caDescriptor.PrivateData != null)
                            {
                                if (caDescriptor.PrivateData.Length != 0)
                                    SafeText(BitConverter.ToString(caDescriptor.PrivateData));
                            }
                        }
                    }
                    ImGui.EndTable();
                }
                ImGui.End();
            }
        }
        #endregion CAT

        #region SCTE-35
        class Scte35DisplayData
        {
            private const string NONE = "<none>";

            public SpliceInsert LastSplice { get; set; }
            public TimeSignal TimeSignal { get; set; }

            public string LastSpliceToString()
            {
                if (LastSplice == null)
                    return NONE;

                if (!LastSplice.SpliceTime.HasValue)
                    return NONE;

                return String.Format("0x{0:X12}", LastSplice.SpliceTime.Value);
            }

            public string TimeSignalToString()
            {
                if (TimeSignal == null)
                    return NONE;

                if (!TimeSignal.Value.HasValue)
                    return NONE;

                return String.Format("0x{0:X12}", TimeSignal.Value.Value);
            }
        }

        private SortedList<ushort, Scte35DisplayData> _scte35DisplayDatas;
        private string scte35DisplayTableId;

        private Scte35DisplayData GetScte35DisplayData(ushort program)
        {
            if (_scte35DisplayDatas == null)
                _scte35DisplayDatas = new SortedList<ushort, Scte35DisplayData>();

            scte35DisplayTableId = Guid.NewGuid().ToString();

            if (_scte35DisplayDatas.ContainsKey(program))
            {
                return _scte35DisplayDatas[program];
            }
            else
            {
                Scte35DisplayData result = new Scte35DisplayData();
                lock (_scte35DisplayDatas)
                {
                    _scte35DisplayDatas.Add(program, result);
                }
                return result;
            }
        }

        public void NotifyScte35(ushort programNumber, SpliceInsert spliceInsert)
        {
            TsType = 1;
            HasScte35 = true;
            Scte35DisplayData mod = GetScte35DisplayData(programNumber);
            mod.LastSplice = spliceInsert;
        }

        public void NotifyScte35(ushort programNumber, TimeSignal spliceInsert)
        {
            TsType = 1;
            HasScte35 = true;
            Scte35DisplayData mod = GetScte35DisplayData(programNumber);
            mod.TimeSignal = spliceInsert;
        }

        private bool memorySaverMode;
        private readonly JobContext jobContext;

        public JobDisplay(JobContext jobContext)
        {
            this.jobContext = jobContext;
        }

        public void SetMemorySaverMode(bool saveMemory)
        {
            this.memorySaverMode = saveMemory;
            this.jobContext.MemorySaverMode = saveMemory;
        }
        

        public void RenderScte35()
        {
            if (_scte35DisplayDatas == null)
                return;

            if (ImGui.Begin("SCTE-35 Cue Messages"))
            {
                if (ImGui.BeginTable(scte35DisplayTableId, 3,
                        ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit))
                {
                    ImGui.TableSetupColumn("Service");
                    ImGui.TableSetupColumn("Time Signal");
                    ImGui.TableSetupColumn("Last Splice");
                    ImGui.TableHeadersRow();

                    lock (_scte35DisplayDatas)
                    {
                        foreach (KeyValuePair<ushort, Scte35DisplayData> scte35DisplayData in _scte35DisplayDatas)
                        {
                            ImGui.TableNextRow();
                            ImGui.TableSetColumnIndex(0);
                            SafeText(ResolveServiceDisplayName(scte35DisplayData.Key));

                            ImGui.TableSetColumnIndex(1);
                            SafeText(scte35DisplayData.Value.TimeSignalToString());

                            ImGui.TableSetColumnIndex(2);
                            SafeText(scte35DisplayData.Value.LastSpliceToString());
                        }
                    }
                    ImGui.EndTable();
                }
                ImGui.End();
            }
        }
        #endregion

        #region DOCSIS
        private DocsisEnvironment _docsisEnvironment;
        public void NotifyDocsisCarrier(DocsisEnvironment docsisEnvironment)
        {
            _docsisEnvironment = docsisEnvironment;
            TsType = 3;
        }

        private void RenderDocsis()
        {
            if (_docsisEnvironment == null)
                return;

            RenderDocsisParticipants();
            RenderDocsisFrequencies();
        }

        private DateTime prevStamp, currStamp;
        private void RenderDocsisParticipants()
        {
            prevStamp = currStamp;
            currStamp = DateTime.Now;
            if (currStamp.Second != prevStamp.Second)
            {
                _docsisEnvironment.DocsisEnvironmentStatistics.ParticipantStatistics.Sort((x, y) => y.Throughput.CompareTo(x.Throughput));
                _docsisEnvironment.DocsisEnvironmentStatistics.CaptureSecond();
            }

            ImGui.Begin("DOCSIS Participant infos");
            SafeText(String.Format("Total scrambled packets: {0} ({1} p/s)", _docsisEnvironment.DocsisEnvironmentStatistics.ScrambledPacketsTotal,_docsisEnvironment.DocsisEnvironmentStatistics.ScrambledPacketsPerSecond));
            SafeText(String.Format("Total clear packets: {0} ({1} p/s)", _docsisEnvironment.DocsisEnvironmentStatistics.UnscrambledPacketsTotal,_docsisEnvironment.DocsisEnvironmentStatistics.UnscrambledPacketsPerSecond));
            SafeText(String.Format("Current bandwidth: {0:F1} mb/s", (float)_docsisEnvironment.DocsisEnvironmentStatistics.ThroughputPerSecond / 1000000.0f));
            
            ImGui.BeginTable("DOCSIS Participant Index", 6, ImGuiTableFlags.Resizable);

            ImGui.TableSetupColumn("MAC");
            ImGui.TableSetupColumn("IP");
            ImGui.TableSetupColumn("Manufacturer");
            ImGui.TableSetupColumn("Packet/s");
            ImGui.TableSetupColumn("Bandwidth used");
            ImGui.TableSetupColumn("Cumulative usage");
            ImGui.TableHeadersRow();

            lock (_docsisEnvironment.DocsisEnvironmentStatistics.ParticipantStatistics)
            {
                foreach (DocsisEnvironment.Statistics.ParticipantStatistic participantStatistic in _docsisEnvironment.DocsisEnvironmentStatistics.ParticipantStatistics)
                {
                    ImGui.TableNextRow();
                    ImGui.TableSetColumnIndex(0);
                    SafeText(participantStatistic.Address.ToString());

                    ImGui.TableSetColumnIndex(1);
                    SafeText(participantStatistic.Ip?.ToString());
                    
                    ImGui.TableSetColumnIndex(2);
                    SafeText(GetParticipantManufacturer(participantStatistic));

                    ImGui.TableSetColumnIndex(3);
                    SafeText(participantStatistic.PacketsPerSecond.ToString());

                    ImGui.TableSetColumnIndex(4);
                    SafeText(String.Format("{0:F1} kb/s", (double)participantStatistic.ThroughputPerSecond / 1000.0));

                    ImGui.TableSetColumnIndex(5);
                    double percent = ((double)participantStatistic.ThroughputTotal * 100.0) /
                                     (double)_docsisEnvironment.DocsisEnvironmentStatistics.ThroughputTotal;
                    SafeText(String.Format("{0:F1} %%", percent));
                }
            }
            ImGui.EndTable();
            ImGui.End();
        }

        private bool dontBotherWithOuis;
        private Dictionary<string, string> ouiLookupTable;
        private string GetParticipantManufacturer(DocsisEnvironment.Statistics.ParticipantStatistic stat)
        {
            if (dontBotherWithOuis)
                return "???";

            if (ouiLookupTable == null)
            {
                ouiLookupTable = new Dictionary<string, string>();
                FileInfo dbFile = new FileInfo("standards-oui.ieee.org.txt");
                if (!dbFile.Exists)
                {
                    dontBotherWithOuis = true;
                    return "???";
                }

                StreamReader streamReader = dbFile.OpenText();
                while (!streamReader.EndOfStream)
                {
                    string? readLine = streamReader.ReadLine();
                    if (readLine.Contains(" (base 16)"))
                    {
                        string[] strings = readLine.Split("\t");
                        string k = strings[0].Split("  ")[0];
                        if (ouiLookupTable.ContainsKey(k))
                            continue;
                        string v = strings[strings.Length - 1];
                        ouiLookupTable.Add(k, v);
                    }
                }
                streamReader.Dispose();
            }

            string victim = stat.Address.ToString().Substring(0, 6);
            if (ouiLookupTable.ContainsKey(victim))
                return ouiLookupTable[victim];
            return "???";
        }

        private SortedList<uint, Tuple<bool, object>> docsisFreqs;
        public void NotifyDocsisFrequency(uint? frequency, bool isUpstream, object mmm)
        {
            if (!frequency.HasValue)
                return;

            if (docsisFreqs == null)
                docsisFreqs = new SortedList<uint, Tuple<bool, object>>();

            if (docsisFreqs.ContainsKey(frequency.Value))
                return;

            lock (docsisFreqs)
            {
                docsisFreqs.Add(frequency.Value, new Tuple<bool, object>(isUpstream, mmm));
            }
        }

        public void SetGseMode()
        {
            TsType = 2;
        }


        private void RenderDocsisFrequencies()
        {
            if (docsisFreqs == null)
                return;

            ImGui.Begin("DOCSIS Frequency Mapping");
            
            ImGui.BeginTable("DOCSIS Participant Index", 3);

            ImGui.TableSetupColumn("Frequency");
            ImGui.TableSetupColumn("Usage");
            ImGui.TableHeadersRow();

            lock (docsisFreqs)
            {
                foreach (KeyValuePair<uint, Tuple<bool, object>> docsisFreq in docsisFreqs)
                {
                    ImGui.TableNextRow();
                    ImGui.TableSetColumnIndex(0);
                    SafeText(String.Format("{0:F1} Mhz", (float)docsisFreq.Key / 1000000.0f));

                    ImGui.TableSetColumnIndex(1);
                    SafeText(docsisFreq.Value.Item1 ? "Upstream" : "Downstream");
                }
            }

            ImGui.EndTable();

            ImGui.End();
        }
        #endregion

        #region Framegrabs
        private List<PictureWindow> _framegrabWindows;
        private Queue<Tuple<Renderer, string, byte[]>> _framegrabQueue;
        public void ShowFramegrab(int currentNetworkId, int transportStreamId, ushort mappingProgramNumber, int mappingStreamElementaryPid, byte[] imageData)
        {
            string resolveServiceDisplayName = ResolveServiceDisplayName(mappingProgramNumber);
            if (string.IsNullOrEmpty(resolveServiceDisplayName))
                resolveServiceDisplayName = String.Format("Framegrab of Service #{0:X4}");
            else
                resolveServiceDisplayName = String.Format("Framegrab of \"{0}\"", resolveServiceDisplayName);

            if (PictureWindow.Renderer == null)
                return;

            if (_framegrabQueue == null)
                _framegrabQueue = new Queue<Tuple<Renderer, string, byte[]>>();

            lock (_framegrabQueue)
            {
                _framegrabQueue.Enqueue(new Tuple<Renderer, string, byte[]>(PictureWindow.Renderer, resolveServiceDisplayName, imageData));
            }
        }
        
        private void RenderFramegrabs()
        {
            if (_framegrabQueue != null)
            {
                if (_framegrabQueue.Count > 0)
                {
                    Tuple<Renderer, string, byte[]> candidate = null;
                    lock (_framegrabQueue)
                    {
                        candidate = _framegrabQueue.Dequeue();
                    }

                    PictureWindow child = new PictureWindow(candidate.Item1, candidate.Item2, candidate.Item3);
                    if (_framegrabWindows == null)
                        _framegrabWindows = new List<PictureWindow>();
                    lock (_framegrabWindows)
                    {
                        int pos = _framegrabWindows.Count + 1;
                        pos *= 20;
                        pos += 100;
                        child.SetPosition(pos, pos);
                        _framegrabWindows.Add(child);
                    }
                }
            }


            if (_framegrabWindows != null)
            {
                lock (_framegrabWindows)
                {
                    _framegrabWindows.ForEach(x => x.Render());
                }
            }
        }
		#endregion

		#region Blockstream

		public void NotifyBlockstreamCarrier()
		{
			TsType = 4;
		}

		#endregion

		#region DB-Schnittstelle

		public IEnumerable<HumanReadableService> GetServices()
		{
			if (HasPmt)
			{
				foreach (var (pmtPid, patValue) in patDisplay)
				{
					ushort serviceId = patValue.programId;
					SdtCoordinate sdt = ResolveService(serviceId);
					string providerName = sdt.ProviderName;
					string serviceName = sdt.ServiceName;
					ushort? caId = ResolveCaId(patValue.pmt, sdt);
					ServiceDescriptor.ServiceTypeCoding? serviceType = sdt.ServiceType;
					yield return new HumanReadableService(serviceId, providerName, serviceName, caId, serviceType.GetValueOrDefault());
				}
			}
		}

		private ushort? ResolveCaId(ProgramMapping patValuePmt, SdtCoordinate sdt)
		{
			if (patValuePmt != null)
			{
				if (patValuePmt.CaSystemId.HasValue)
					return patValuePmt.CaSystemId.Value;

				foreach (ProgramMappingStream stream in patValuePmt.Streams)
				{
					if (stream.CaSystemId.HasValue)
						return stream.CaSystemId;
				}
			}

			if (sdt.CaIdentifiers != null)
			{
				if (sdt.CaIdentifiers.Length > 0)
					return sdt.CaIdentifiers[0];
			}

			return null;
		}

		#endregion

	}
}
