using log4net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.Plugins;

namespace skyscraper8.Skyscraper.Security.AccessControl
{
	internal class SoftcamKeyset
	{
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
		private static SoftcamKeyset _singleton;
		public static SoftcamKeyset GetInstance()
		{
			if (_singleton == null)
			{
				_singleton = new SoftcamKeyset();
			}
			return _singleton;
		}

		public bool AutoInitalize()
		{
			PluginManager pluginManager = PluginManager.GetInstance();
			Ini ini = pluginManager.Ini;
			bool softcamEnabled = ini.ReadValue("softcam", "enable", false);
			if (!softcamEnabled)
				return false;

			string softcamKeyPath = ini.ReadValue("softcam", "path", FILE_NAME);
			FileInfo softcamKeyFileInfo = new FileInfo(softcamKeyPath);
			if (!softcamKeyFileInfo.Exists)
				return false;

			int result = LoadSoftcamKey(softcamKeyFileInfo);
			return result > 0;
		}
		
		private const string FILE_NAME = "SoftCam.Key";
		private int LoadSoftcamKey(FileInfo fi)
		{
			int result = 0;
			StreamReader streamReader = fi.OpenText();
			PowerVuKeyGroup currentPowerVuKeyGroup = null;
			while (!streamReader.EndOfStream)
			{
				string line = streamReader.ReadLine();
				line = line.Trim();
				if (line.Contains(";"))
				{
					int indexOf = line.IndexOf(";");
					line = line.Substring(0, indexOf);
				}
				if (string.IsNullOrEmpty(line))
					continue;

				char keyType = line[0];
				string[] split = line.Split(' ');
				byte[] keyData = null;
				switch (keyType)
				{
					case '#':
					case ';':	//comment
						break;
					case '-':	//unknown, perhaps seperator?
						break;
					case 'V':	//Viaccess
						uint viaccessGroup = split[1].HexToUint32BE();
						string viaccessKeyType = split[2];
						keyData = split[3].HexToBytes();

						if (_viaccessKeys == null)
							_viaccessKeys = new List<ViaccessKey>();
						ViaccessKey viaccessKey = _viaccessKeys.FirstOrDefault(x => x.Group == viaccessGroup);
						if (viaccessKey == null)
						{
							viaccessKey = new ViaccessKey(viaccessGroup);
							_viaccessKeys.Add(viaccessKey);
						}

						if (viaccessKey.SetHint(viaccessKeyType, keyData))
							result++;
						break;
					case 'N':
						uint nagravisionGroupid = split[1].HexToUint32BE();
						string nagravisionKeyType = split[2];
						keyData = split[3].HexToBytes();

						if (_nagravisionKeys == null)
							_nagravisionKeys = new List<NagravisionKey>();
						NagravisionKey nagravisionKey = _nagravisionKeys.FirstOrDefault(x => x.Group == nagravisionGroupid);
						if (nagravisionKey == null)
						{
							nagravisionKey = new NagravisionKey(nagravisionGroupid);
							_nagravisionKeys.Add(nagravisionKey);
						}

						if (nagravisionKey.SetHint(nagravisionKeyType, keyData))
							result++;
						break;
					case 'T':
						ushort tandbergChannelId = split[1].HexToUInt16BE();
						byte tandbergStreamId = split[2].HexToUInt8();
						keyData = split[3].HexToBytes();
						
						TandbergKey tandbergKey = new TandbergKey(tandbergChannelId, tandbergStreamId, keyData);
						if (tandbergKey.Valid)
						{
							if (_tandbergKeys == null)
								_tandbergKeys = new HashSet<TandbergKey>();
							_tandbergKeys.Add(tandbergKey);
							result++;
						}
						break;
					case 'P':
					case 'p':
						uint powerVuKeyCoordinate = split[1].HexToUint32BE();
						string powerVuKeyType = split[2];
						if (powerVuKeyType.Equals("GROUP"))
						{
							ushort groupId = split[3].HexToUInt16BE();
							PowerVuKeyGroup powerVuGroup = new PowerVuKeyGroup(powerVuKeyCoordinate, groupId);
							currentPowerVuKeyGroup = powerVuGroup;
							if (_powerVuGroups == null)
								_powerVuGroups = new List<PowerVuKeyGroup>();
							_powerVuGroups.Add(currentPowerVuKeyGroup);
						}
						else
						{
							keyData = split[3].HexToBytes();
							currentPowerVuKeyGroup.SetHint(powerVuKeyCoordinate, powerVuKeyType.HexToUint32LE(), keyData);
							result++;
						}
						break;
					case 'F':
						uint bissCoordinate = split[1].HexToUint32BE();
						uint bissFixed = split[2].HexToUint32BE();
						keyData = split[3].HexToBytes();
						if (bissFixed != 0)
						{
							logger.ErrorFormat("I don't understand this key: {0}", line);
							break;
						}

						if (_bissKeys == null)
							_bissKeys = new HashSet<BissKey>();
						if (_bissKeys.Add(new BissKey(bissCoordinate, keyData)))
							result++;
						else
							logger.ErrorFormat("Duplicate BISS Key for {0}", split[1]);
						break;
					default:
						logger.ErrorFormat("Unknown Key Type \"{0}\". Abort loading key set.",keyType);
						return result;
				}
			}

			return result;
		}

		private List<ViaccessKey> _viaccessKeys;
		public IReadOnlyList<ViaccessKey> ViaccessKeys => _viaccessKeys;

		private List<NagravisionKey> _nagravisionKeys;
		public IReadOnlyList<NagravisionKey> NagravisionKeys => _nagravisionKeys;

		private HashSet<TandbergKey> _tandbergKeys;
		public IReadOnlySet<TandbergKey> TandbergKeys => _tandbergKeys;

		private List<PowerVuKeyGroup> _powerVuGroups;
		public IReadOnlyList<PowerVuKeyGroup> PowerVuGroups => _powerVuGroups;

		private HashSet<BissKey> _bissKeys;
		public IReadOnlySet<BissKey> BissKeys => _bissKeys;

		public byte[] FindBissKey(ushort transportStreamId, ushort networkId)
		{
			if (_bissKeys == null)
				return null;

			byte[] transportStreamIdBytes = BitConverter.GetBytes(transportStreamId);
			byte[] networkIdBytes = BitConverter.GetBytes(networkId);
			byte[] keyBytes = new byte[] { networkIdBytes[0], networkIdBytes[1], transportStreamIdBytes[0], transportStreamIdBytes[1] };
			uint coordinate = BitConverter.ToUInt32(keyBytes);

			BissKey bissKey = _bissKeys.FirstOrDefault(x => x.Coordinate == coordinate);
			if (bissKey != null)
				return bissKey.Key;
			else
				return null;
		}
	}
}
