using DVBServices;
using skyscraper5.Data.PostgreSql;
using skyscraper5.Skyscraper.Scraper.Storage.InMemory;
using skyscraper8.EPGCollectorPort.SkyscraperSide.Freesat;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Npgsql;
using NpgsqlTypes;

namespace skyscraper8.EPGCollectorPort.SkyscraperSide.MediaHighway2
{
	internal interface IMhw2Storage
	{
		void InsertCategories(int networkId, int transportStreamId, MediaHighway2CategorySection categorySection);
		void InsertChannels(int networkId, int transportStreamId, MediaHighway2ChannelSection channelSection);
		bool TestForEvent(int networkId, int transportStreamId, Mhw2EventBinder binder);
		void InsertEvent(int networkId, int transportStreamId, Mhw2EventBinder binder);
	}
	internal class Mhw2StorageImpl : IMhw2Storage
	{
		private readonly IMhw2Storage _storageEngine;

		public Mhw2StorageImpl(object[] getPluginConnector)
		{
			object o = getPluginConnector[0];
			switch (o)
			{
				case InMemoryPluginToken t1:
					_storageEngine = new Mhw2StorageInMemory();
					break;
				case PostgresqlToken t2:
					_storageEngine = new Mhw2StoragePostgresql(t2);
					break;
				default:
					throw new NotImplementedException(o.GetType().FullName);
			}
		}

		public void InsertCategories(int networkId, int transportStreamId, MediaHighway2CategorySection categorySection)
		{
			_storageEngine.InsertCategories(networkId, transportStreamId, categorySection);
		}

		public void InsertChannels(int networkId, int transportStreamId, MediaHighway2ChannelSection channelSection)
		{
			_storageEngine.InsertChannels(networkId, transportStreamId, channelSection);
		}

		public bool TestForEvent(int networkId, int transportStreamId, Mhw2EventBinder binder)
		{
			return _storageEngine.TestForEvent(networkId, transportStreamId, binder);
		}

		public void InsertEvent(int networkId, int transportStreamId, Mhw2EventBinder binder)
		{
			_storageEngine.InsertEvent(networkId, transportStreamId, binder);
		}
	}

	internal class Mhw2StorageInMemory : IMhw2Storage
	{
		private Dictionary<Tuple<int, int>, MediaHighway2CategorySection> _categories;
		public void InsertCategories(int networkId, int transportStreamId, MediaHighway2CategorySection categorySection)
		{
			if (_categories == null)
				_categories = new Dictionary<Tuple<int, int>, MediaHighway2CategorySection>();

			Tuple<int, int> x = new Tuple<int, int>(networkId, transportStreamId);
			if (!_categories.ContainsKey(x))
			{
				_categories.Add(x, categorySection);
			}
		}

		private Dictionary<Tuple<int, int>, MediaHighway2ChannelSection> _channels;
		public void InsertChannels(int networkId, int transportStreamId, MediaHighway2ChannelSection channelSection)
		{
			if (_channels == null)
				_channels = new Dictionary<Tuple<int, int>, MediaHighway2ChannelSection>();

			Tuple<int, int> x = new Tuple<int, int>(networkId, transportStreamId);
			if (!_categories.ContainsKey(x))
			{
				_channels.Add(x, channelSection);
			}
		}


		private Dictionary<Tuple<int, int, int, DateTime>, Mhw2EventBinder> _events;
		public bool TestForEvent(int networkId, int transportStreamId, Mhw2EventBinder binder)
		{
			if (_events == null)
				return false;

			Tuple<int, int, int, DateTime> coordinates = new Tuple<int, int, int, DateTime>(networkId, transportStreamId, binder.EventId, binder.StartTime);
			return _events.ContainsKey(coordinates);
		}

		public void InsertEvent(int networkId, int transportStreamId, Mhw2EventBinder binder)
		{
			if (_events == null)
				_events = new Dictionary<Tuple<int, int, int, DateTime>, Mhw2EventBinder>();


			Tuple<int, int, int, DateTime> coordinates = new Tuple<int, int, int, DateTime>(networkId, transportStreamId, binder.EventId, binder.StartTime);
			if (!_events.ContainsKey(coordinates))
			{
				_events.Add(coordinates, binder);
			}
		}

	}

	internal class Mhw2StoragePostgresql : IMhw2Storage
	{
		private readonly PostgresqlToken _postgresqlToken;

		public Mhw2StoragePostgresql(PostgresqlToken postgresqlToken)
		{
			_postgresqlToken = postgresqlToken;
		}

		private HashSet<Tuple<int, int, int>> _knownCategories;
		public void InsertCategories(int networkId, int transportStreamId, MediaHighway2CategorySection categorySection)
		{
			if (_knownCategories == null)
				_knownCategories = new HashSet<Tuple<int, int, int>>();

			foreach (MediaHighwayCategoryEntry category in categorySection.Categories)
			{
				Tuple<int, int, int> coordinate = new Tuple<int, int, int>(networkId, transportStreamId, category.Number);
				if (!_knownCategories.Contains(coordinate))
				{
					_postgresqlToken.EnqueueTask(x => InsertCategory(x,networkId, transportStreamId, category));
					_knownCategories.Add(coordinate);
				}
			}
		}

		private HashSet<Tuple<int, int, int, int, int>> _knownChannels;
		public void InsertChannels(int networkId, int transportStreamId, MediaHighway2ChannelSection channelSection)
		{
			if (channelSection == null)
				return;

			if (_knownChannels == null)
				_knownChannels = new HashSet<Tuple<int, int, int, int, int>>();

			foreach (MediaHighwayChannelInfoEntry channel in channelSection.Channels)
			{
				Tuple<int, int, int, int, int> coordinate = new Tuple<int, int, int, int, int>(networkId, transportStreamId, channel.OriginalNetworkID, channel.TransportStreamID, channel.ServiceID);
				if (!_knownChannels.Contains(coordinate))
				{
					_postgresqlToken.EnqueueTask(x => InsertChannel(x, networkId, transportStreamId, channel));
					_knownChannels.Add(coordinate);
				}
			}
		}

		private HashSet<Tuple<int, int, int, DateTime>> _knownEvents;

		public bool TestForEvent(int networkId, int transportStreamId, Mhw2EventBinder binder)
		{
			if (_knownEvents == null)
				_knownEvents = new HashSet<Tuple<int, int, int, DateTime>>();

			Tuple<int, int, int, DateTime> coordinates = new Tuple<int, int, int, DateTime>(networkId, transportStreamId, binder.EventId, binder.StartTime);
			if (_knownEvents.Contains(coordinates))
				return true;

			NpgsqlConnection connection = _postgresqlToken.GetConnection();
			connection.Open();
			NpgsqlCommand command = connection.CreateCommand();
			command.CommandText = "SELECT dateadded FROM mediahighway2_events WHERE nid = @nid AND tsid = @tsid AND eid = @eid AND start_time = @stime";
			command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId);
			command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId);
			command.Parameters.AddWithValue("@eid", NpgsqlDbType.Integer, binder.EventId);
			command.Parameters.AddWithValue("@stime", NpgsqlDbType.Timestamp, binder.StartTime);
			NpgsqlDataReader dataReader = command.ExecuteReader();
			bool result = dataReader.Read();
			if (result)
			{
				_knownEvents.Add(coordinates);
			}
			dataReader.Close();
			command.Dispose();
			connection.Close();
			return result;
		}

		public void InsertEvent(int networkId, int transportStreamId, Mhw2EventBinder binder)
		{
			_postgresqlToken.EnqueueTask(x => InsertEventEx(x, networkId, transportStreamId, binder));
			Tuple<int, int, int, DateTime> coordinates = new Tuple<int, int, int, DateTime>(networkId, transportStreamId, binder.EventId, binder.StartTime);
			_knownEvents.Add(coordinates);
		}

		private void InsertEventEx(NpgsqlConnection connection, int networkId, int transportStreamId, Mhw2EventBinder binder)
		{
			NpgsqlCommand command = connection.CreateCommand();
			command.CommandText =
				"INSERT INTO mediahighway2_events VALUES (@nid,@tsid,@eid,@stime,DEFAULT,@ename,@category,@channel,@duration,@mcategory,@scategory,@description)";
			command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId);
			command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId);
			command.Parameters.AddWithValue("@eid", NpgsqlDbType.Integer, binder.EventId);
			command.Parameters.AddWithValue("@stime", NpgsqlDbType.Timestamp, binder.StartTime);
			command.Parameters.AddWithValue("@ename", NpgsqlDbType.Text, SanitizeString(binder.EventName));
			command.Parameters.AddWithValue("@category", NpgsqlDbType.Integer, binder.CategoryID);
			command.Parameters.AddWithValue("@channel", NpgsqlDbType.Integer, binder.ChannelID);
			command.Parameters.AddWithValue("@duration", NpgsqlDbType.Integer, Convert.ToInt32(binder.Duration.TotalSeconds));
			command.Parameters.AddWithValue("@mcategory", NpgsqlDbType.Integer, binder.MainCategory);
			command.Parameters.AddWithValue("@scategory", NpgsqlDbType.Integer, binder.SubCategory);
			command.Parameters.AddWithValue("@description", NpgsqlDbType.Text, binder.ShortDescription);
			command.ExecuteNonQuery();
		}

		private string SanitizeString(string ename)
		{
			if (ename.Contains("\0"))
			{
				int indexOf = ename.IndexOf("\0");
				ename = ename.Substring(0, indexOf);
				return ename;
			}

			return ename;
		}


		private void InsertChannel(NpgsqlConnection connection, int networkId, int transportStreamId, MediaHighwayChannelInfoEntry channel)
		{
			if (TestChannel(connection, networkId, transportStreamId, channel))
			{
				return;
			}

			NpgsqlCommand command = connection.CreateCommand();
			command.CommandText = "INSERT INTO mediahighway2_channels VALUES (@snid,@stsid,@tonid,@ttsid,@tsid,@name,DEFAULT)";
			command.Parameters.AddWithValue("@snid", NpgsqlDbType.Integer, networkId);
			command.Parameters.AddWithValue("@stsid", NpgsqlDbType.Integer, transportStreamId);
			command.Parameters.AddWithValue("@tonid", NpgsqlDbType.Integer, channel.OriginalNetworkID);
			command.Parameters.AddWithValue("@ttsid", NpgsqlDbType.Integer, channel.TransportStreamID);
			command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, channel.ServiceID);
			command.Parameters.AddWithValue("@name", NpgsqlDbType.Text, channel.Name);
			command.ExecuteNonQuery();
		}

		private bool TestChannel(NpgsqlConnection connection, int networkId, int transportStreamId, MediaHighwayChannelInfoEntry channel)
		{
			NpgsqlCommand command = connection.CreateCommand();
			command.CommandText = "SELECT dateadded FROM mediahighway2_channels WHERE src_nid = @snid AND src_tsid = @stsid AND tgt_onid = @tonid AND tgt_tsid = @ttsid AND tgt_sid = @tsid";
			command.Parameters.AddWithValue("@snid", NpgsqlDbType.Integer, networkId);
			command.Parameters.AddWithValue("@stsid", NpgsqlDbType.Integer, transportStreamId);
			command.Parameters.AddWithValue("@tonid", NpgsqlDbType.Integer, channel.OriginalNetworkID);
			command.Parameters.AddWithValue("@ttsid", NpgsqlDbType.Integer, channel.TransportStreamID);
			command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, channel.ServiceID);
			NpgsqlDataReader dataReader = command.ExecuteReader();
			bool result = dataReader.Read();
			dataReader.Close();
			command.Dispose();
			return result;
		}

		private void InsertCategory(NpgsqlConnection connection, int networkId, int transportStreamId, MediaHighwayCategoryEntry category)
		{
			if (TestCategory(connection, networkId, transportStreamId, category))
			{
				return;
			}

			NpgsqlCommand command = connection.CreateCommand();
			command.CommandText = "INSERT INTO mediahighway2_categories VALUES (@nid,@tsid,@number,@description,DEFAULT)";
			command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId);
			command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId);
			command.Parameters.AddWithValue("@number", NpgsqlDbType.Integer, category.Number);
			command.Parameters.AddWithValue("@description", NpgsqlDbType.Text, category.Description);
			command.ExecuteNonQuery();
		}

		private bool TestCategory(NpgsqlConnection connection, int networkId, int transportStreamId, MediaHighwayCategoryEntry category)
		{
			NpgsqlCommand command = connection.CreateCommand();
			command.CommandText = "SELECT dateadded FROM mediahighway2_categories WHERE nid = @nid AND tsid = @tsid AND number = @number";
			command.Parameters.AddWithValue("@nid", NpgsqlDbType.Integer, networkId);
			command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, transportStreamId);
			command.Parameters.AddWithValue("@number", NpgsqlDbType.Integer, category.Number);
			NpgsqlDataReader dataReader = command.ExecuteReader();
			bool result = dataReader.Read();
			dataReader.Close();
			command.Dispose();
			return result;
		}
	}
}
