using log4net;
using log4net.Repository.Hierarchy;
using skyscraper5.Skyscraper.IO.TunerInterface;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper8.Ietf.Rfc4566_SDP
{
	public class SdpAttributeCollection : IEnumerable<SdpAttribute>
	{
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
		private Dictionary<string, SdpAttribute> _attributes;

		internal void SetHint(string args)
		{
			if (_attributes == null)
				_attributes = new Dictionary<string, SdpAttribute>();

			int indexOf = args.IndexOf(':');
			string key = args.Substring(0, indexOf);
			string value = args.Substring(indexOf + 1);
			switch (key)
			{
				case "mbms-mode":
					_attributes.Add(key, new MbmsMode(value));
					break;
				case "source-filter":
					if (!_attributes.ContainsKey(key))
						_attributes.Add(key, new SourceFilter());
					SourceFilter sourceFilter = _attributes[key] as SourceFilter;
					sourceFilter.SetHint(value);
					break;
				case "flute-tsi":
					_attributes.Add(key, new FluteTsi(value));
					break;
				default:
					logger.ErrorFormat("Unknown SDP Attribute: \"{0}\" - please consider sharing a sample of this stream for further analysis.", key);
					break;
			}
		}

		public IEnumerator<SdpAttribute> GetEnumerator()
		{
			return _attributes.Values.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return _attributes.Values.GetEnumerator();
		}
	}

	internal class FluteTsi : SdpAttribute
	{
		public FluteTsi(string value) : base("flute-tsi")
		{
			TSI = long.Parse(value);
		}

		public long TSI { get; private set; }

		public override string ToString()
		{
			return TSI.ToString();
		}
	}

	internal class SourceFilter : SdpAttribute
	{
		public SourceFilter() : base("source-filter")
		{
		}

		public void SetHint(string value)
		{
			if (_filterChain == null)
				_filterChain = new List<SourceFilterChainLink>();
			_filterChain.Add(new SourceFilterChainLink(value.Split(' ',StringSplitOptions.RemoveEmptyEntries)));
		}

		private List<SourceFilterChainLink> _filterChain;

		class SourceFilterChainLink
		{
			private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

			public SourceFilterChainLink(string[] args)
			{
				if (args[0].Equals("incl"))
				{
					Include = true;
				}
				else if (args[0].Equals("excl"))
				{
					Include = false;
				}
				else
				{
					logger.ErrorFormat("Unknown Filter Mode \"{0}\", assuming exclusion just to be sure.", args[0]);
				}

				if (!SdpNetworkType.TryParse(args[1], true, out _networkType))
				{
					logger.ErrorFormat("Unknown Network Type \"{0}\", assuming \"IN\" - please consider sharing a sample of this stream so this can be implemented.", args[1]);
					_networkType = SdpNetworkType.IN;
				}

				if (!SdpAddressType.TryParse(args[2], true, out _addressType))
				{
					logger.ErrorFormat("Unknown Address Type \"{0}\", assuming \"IP4\" - please consider sharing a sample of this stream so this can be implemented.", args[2]);
					_addressType = SdpAddressType.IP4;
				}

				DestinationAddress = args[3];

				int numSources = args.Length - 4;
				Sources = new string[numSources];
				Array.Copy(args, 4, Sources, 0, numSources);
			}

			/// <summary>
			/// If this is true, an incoming packet is accepted, if not it gets discarded.
			/// </summary>
			public bool Include { get; private set; }

			public string[] Sources { get; private set; }
			public string DestinationAddress { get; private set; }
			private SdpAddressType _addressType;
			public SdpAddressType AddressType
			{
				get => _addressType;
			}

			private SdpNetworkType _networkType;
			public SdpNetworkType NetworkType
			{
				get => _networkType;
			}
		}
	}

	internal class MbmsMode : SdpAttribute
	{
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

		public MbmsMode(string value)
			: base("mbms-mode")
		{
			string[] args = value.Split(' ');
			if (args[0].Equals("broadcast-mbsfn"))
			{
				BigInteger tmgi = BigInteger.Parse(args[1]);
				this.Tmgi = tmgi.ToByteArray();
			}
			else if (args[0].Equals("broadcast"))
			{
				BigInteger tmgi = BigInteger.Parse(args[1]);
				this.Tmgi = tmgi.ToByteArray();
				this.MbmsCountingIndication = int.Parse(args[2]);
			}
			else
			{
				logger.ErrorFormat("Unknown MBMS Mode: {0} - please consider a sharing a sample of this stream if you'd like the developer to look further into this.", args[0]);
			}
		}

		public int? MbmsCountingIndication { get; private set; }

		public byte[] Tmgi { get; private set; }

		internal enum MbmsBearerMode
		{
			Unknown,
			Broadcast,
			BroadcastMbsfn
		}

		public MbmsBearerMode BearerMode
		{
			get
			{
				if (Tmgi == null)
					return MbmsBearerMode.Unknown;
				if (MbmsCountingIndication == null)
					return MbmsBearerMode.BroadcastMbsfn;
				else
					return MbmsBearerMode.Broadcast;
			}
		}
	}

	public abstract class SdpAttribute
	{
		protected SdpAttribute(string key)
		{
			this.SdpAttributeName = key;
		}

		public string SdpAttributeName { get; private set; }
	}
}
