using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using skyscraper5.Dvb;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Plugins;
using ExtensionDescriptor = skyscraper5.Dvb.ExtensionDescriptor;

namespace skyscraper5.Mpeg2
{
    public class TsDescriptorUnpacker
    {
        private const bool CRASH_ON_UNIMPLEMENTED_DESCRIPTOR = true;
        private static TsDescriptorUnpacker _instance;
        private IReadOnlyDictionary<string, ConstructorInfo[]> descriptorMap;
        private Dictionary<Type, IDescriptorConverter> descriptorConverters;
        private IReadOnlyDictionary<string, bool[]> bannedTables;
        private TsDescriptorUnpacker()
        {
	        PluginManager pluginManager = PluginManager.GetInstance();
	        descriptorConverters = new Dictionary<Type, IDescriptorConverter>();
	        bannedTables = pluginManager.GetDescriptorTableBans();
	        descriptorMap = pluginManager.GetDescriptorMappings();

	        if (descriptorMap.Count == 0)
            {
                throw new AccessViolationException("Wait, this isn't supposed to happen! There are no descriptor types in my assembly!");
            }

            userPrivates = new bool[byte.MaxValue + 1];
        }

        

        private bool[] userPrivates;

        public void SetUserDefined(byte descriptorId, bool userDefined)
        {
            userPrivates[descriptorId] = userDefined;
        }

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

            return _instance;
        }

        
        public IEnumerable<TsDescriptor> UnpackDescriptors(byte[] buffer, string callingTable)
        {
            MemoryStream ms = new MemoryStream(buffer, false);
            while (ms.GetAvailableBytes() > 2)
            {
                byte descriptorTag = ms.ReadUInt8();
                byte descriptorLength = ms.ReadUInt8();
                if (ms.GetAvailableBytes() < descriptorLength)
                    break;

                if (bannedTables.ContainsKey(callingTable))
                {
	                bool[] upsertBannedTable = bannedTables[callingTable];
	                if (upsertBannedTable[descriptorTag])
		                continue;
                }

                byte[] descriptorContent = ms.ReadBytes(descriptorLength);
                ConstructorInfo[] descriptorAttributes = descriptorMap[callingTable];
                if (descriptorAttributes[descriptorTag] == null)
                {
					if (userPrivates[descriptorTag])
					{
						yield return new UserDefinedDescriptor(descriptorTag, descriptorContent);
						continue;
					}
					string format = String.Format("Descriptor 0x{0:X2} ({0}) not implemented for table \"{1}\".", descriptorTag,callingTable);
                    if (CRASH_ON_UNIMPLEMENTED_DESCRIPTOR)
                       throw new NotImplementedException(format);
                    else
                        Console.WriteLine(format);
                    continue;
                }
                //if (!descriptorAttributes[descriptorTag].Matches(callingTable))
                //    continue;

                object result = descriptorAttributes[descriptorTag].Invoke(new object[] { descriptorContent });

                Attribute attribute = result.GetType().GetCustomAttributes(typeof(DescriptorConversionNeededAttribute)).FirstOrDefault();
                if (attribute != null)
                {
                    DescriptorConversionNeededAttribute conversionNeededAttribute = (DescriptorConversionNeededAttribute)attribute;
                    Type converterType = conversionNeededAttribute.ConverterType;
                    IDescriptorConverter converter = null;
                    if (descriptorConverters.ContainsKey(converterType))
                    {
                        converter = descriptorConverters[converterType];
                    }
                    else
                    {
                        converter = (IDescriptorConverter)Activator.CreateInstance(converterType);
                        descriptorConverters.Add(converterType, converter);
                    }
                    result = converter.Convert((TsDescriptor)result, callingTable);
                    if (result == null)
                        continue;
                }

                yield return (TsDescriptor)result;
            }
            yield break;
        }

        
    }
}
