using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Skyscraper.Plugins;

namespace skyscraper5.Dvb
{
	//TODO: can we get these? https://www.streamguru.de/mpeg-analyzer/supported-descriptor-list/supported-descriptor-list-user-private-ranges/

	class UserDefinedDescriptorUnpacker
    {
        private const bool WARN_ON_MISSING = true;
        private static UserDefinedDescriptorUnpacker _instance;

        public static UserDefinedDescriptorUnpacker GetInstance()
        {
            if (_instance == null)
            {
                _instance = new UserDefinedDescriptorUnpacker();
            }
            return _instance;
        }

        private UserDefinedDescriptorUnpacker()
        {
        }

        private bool IsBannedTable(ConstructorInfo constructorInfo, string askingTable)
        {
            Type sourceType = constructorInfo.DeclaringType;
            Attribute attribute = sourceType.GetCustomAttribute(typeof(BannedTableAttribute));
            if (attribute == null)
                return false;
            BannedTableAttribute bannedTableAttribute = (BannedTableAttribute)attribute;
            foreach (string table in bannedTableAttribute.Tables)
            {
                if (table.Equals(askingTable))
                    return true;
            }
            return false;
        }

        private IReadOnlyDictionary<UserDefinedDescriptorAttribute, ConstructorInfo> constructors;

        public TsDescriptor UnpackUserDefinedDescriptor(UserDefinedDescriptor userDefinedDescriptor, uint privateDataSpecifier, string lookupTable, bool crashOnMissingDescriptor = false)
        {
	        if (constructors == null)
	            constructors = PluginManager.GetInstance().GetUserDefinedDescriptors();
            

            foreach (KeyValuePair<UserDefinedDescriptorAttribute,ConstructorInfo> keyValuePair in constructors)
            {
                UserDefinedDescriptorAttribute key = keyValuePair.Key;
                if (key.DescriptorId != userDefinedDescriptor.DescriptorTag)
                    continue;
                if (IsBannedTable(keyValuePair.Value, lookupTable))
                    return null;
                if (!key.Matches(privateDataSpecifier,lookupTable))
                    continue;

                ConstructorInfo constructorInfo = keyValuePair.Value;
                return (TsDescriptor)constructorInfo.Invoke(new object[] { userDefinedDescriptor.Data });
            }
            WarnMissingDescriptor(userDefinedDescriptor.DescriptorTag, privateDataSpecifier);
            if (crashOnMissingDescriptor)
            {
                throw new NotImplementedException(String.Format("Missing Descriptor 0x{0:X2} from private data specifier 0x{1:X8} in {2}", userDefinedDescriptor.DescriptorTag, privateDataSpecifier, lookupTable));
            }
            return null;
        }

        private HashSet<KeyValuePair<byte, uint>> warned;

        private void WarnMissingDescriptor(byte descriptorTag, uint privateDataSpecifier, bool silent = false)
        {
            if (!WARN_ON_MISSING)
                return;

            if (warned == null)
                warned = new HashSet<KeyValuePair<byte, uint>>();

            if (warned.Add(new KeyValuePair<byte, uint>(descriptorTag, privateDataSpecifier)))
            {
                if (!silent)
                {
                    Debug.WriteLine("Missing descriptor 0x{0:X2} for private data specifier 0x{0:X8}", descriptorTag,
                        privateDataSpecifier);
                }
            }
        }
    }
}
