using Npgsql;
using NpgsqlTypes;
using skyscraper5.Docsis.MacManagement;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper5.Data.PostgreSql
{
	partial class PostgresqlDataStore
	{
		private HashSet<Tuple<int, uint>> knownDocsisUpstreams;
		public bool TestForDocsisUpstreamChannel(PhysicalAddress mmmSource, uint mmmFrequency, int currentLocation)
		{
			if (knownDocsisUpstreams == null)
				knownDocsisUpstreams = new HashSet<Tuple<int, uint>>();

			Tuple<int, uint> tuple = new Tuple<int, uint>(currentLocation, mmmFrequency);
			if (knownDocsisUpstreams.Contains(tuple))
				return true;

			using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString()))
			{
				connection.Open();
				NpgsqlCommand command = connection.CreateCommand();
				command.CommandText = "SELECT dateadded FROM docsis_upstream_channel WHERE location = @loc AND frequency=@freq";
				command.Parameters.AddWithValue("@loc", NpgsqlDbType.Integer, currentLocation);
				command.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, (int)mmmFrequency);
				NpgsqlDataReader reader = command.ExecuteReader();
				bool result = reader.Read();
				if (result)
				{
					knownDocsisUpstreams.Add(tuple);
				}
				reader.Close();
				connection.Close();
				return result;
			}
		}

		public void StoreDocsisUpstreamChannel(UpstreamChannelDescriptor mmm, int currentLocation)
		{
			using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString()))
			{
				connection.Open();
				NpgsqlTransaction transaction = connection.BeginTransaction();
				NpgsqlCommand command = connection.CreateCommand();
				command.CommandText = "INSERT INTO docsis_upstream_channel " +
					"VALUES (DEFAULT, DEFAULT, @loc, @freq, @upstreamChannelId, @configChangeCount, @minislotSize, @downstreamChannelId, @randomSeed, @symbolsInOfdmaFrame, " +
					"@centerFrequencyOfSubcarrier, @subcarrierSpacing, @ofdmaRolloffPeriodSize, @ofdmaCyclicPrefixSize, @ofdmaTimesnapSnapshotDivide, @ofdmaTimestampSnapshot, " +
					"@ucdChangeIndicator, @sCdmaMode, @preamblePattern, @modulationRate) RETURNING ss5_serial";
				command.Parameters.AddWithValue("@loc", NpgsqlDbType.Integer, currentLocation);
				command.Parameters.AddWithValue("@freq", NpgsqlDbType.Integer, (int)mmm.Frequency);
				command.Parameters.AddWithValue("@upstreamChannelId", NpgsqlDbType.Integer, mmm.UpstreamChannelID);
				command.Parameters.AddWithValue("@configChangeCount", NpgsqlDbType.Integer, mmm.ConfigurationChangeCount);
				command.Parameters.AddWithValue("@minislotSize", NpgsqlDbType.Integer, mmm.MinislotSize);
				command.Parameters.AddWithValue("@downstreamChannelId", NpgsqlDbType.Integer, mmm.DownstreamChannelId);
				command.Parameters.AddWithValue("@randomSeed", NpgsqlDbType.Integer, (int)mmm.RandomizationSeed);
				command.Parameters.AddWithValue("@symbolsInOfdmaFrame", NpgsqlDbType.Integer, mmm.SymbolsInOfdmaFrame);
				command.Parameters.AddWithValue("@centerFrequencyOfSubcarrier", NpgsqlDbType.Bigint, mmm.CenterFrequencyOfSubcarrier0);
				command.Parameters.AddWithValue("@subcarrierSpacing", NpgsqlDbType.Integer, mmm.SubcarrierSpacing);
				command.Parameters.AddWithValue("@ofdmaRolloffPeriodSize", NpgsqlDbType.Integer, mmm.OfdmaRolloffPeriodSize);
				command.Parameters.AddWithValue("@ofdmaCyclicPrefixSize", NpgsqlDbType.Integer, mmm.OfdmaCyclicPrefixSize);
				command.Parameters.AddWithValue("@ofdmaTimesnapSnapshotDivide", NpgsqlDbType.Integer, mmm.OfdmaTimestampSnapshotDivideBy20);
				command.Parameters.AddWithValue("@ofdmaTimestampSnapshot", NpgsqlDbType.Integer, mmm.OfdmaTimestampSnapshot);
				command.Parameters.AddWithValue("@ucdChangeIndicator", NpgsqlDbType.Integer, mmm.UcdChangeIndicator?.GetRawValue());
				command.Parameters.AddWithValue("@sCdmaMode", NpgsqlDbType.Boolean, mmm.S_CDMAMode);
				command.Parameters.AddWithValue("@preamblePattern", NpgsqlDbType.Bytea, mmm.PreamblePattern);
				command.Parameters.AddWithValue("@modulationRate", NpgsqlDbType.Integer, mmm.ModulationRate);
				SetNulls(command);
				NpgsqlDataReader dataReader = command.ExecuteReader();
				dataReader.Read();
				int ss5serial = dataReader.GetInt32(0);
				dataReader.Close();
				command.Dispose();
				if (mmm.BurstDescriptors != null && mmm.BurstDescriptors.Count > 0)
				{
					InsertDocsisUpstreamChannelBurstDescriptors(connection, ss5serial, mmm.BurstDescriptors);
				}
				if (mmm.SubcarrierExclusionBand != null && mmm.SubcarrierExclusionBand.Length > 0)
				{
					throw new NotImplementedException();
				}
				transaction.Commit();
				connection.Close();
			}
		}

		private void InsertDocsisUpstreamChannelBurstDescriptors(NpgsqlConnection connection, int channelSs5serial, List<UpstreamChannelDescriptor.BurstDescriptor> burstDescriptors)
		{
			NpgsqlCommand cmd = connection.CreateCommand();
			cmd.CommandText = "insert into docsis_upstream_channel_burst_descriptors VALUES (DEFAULT, @related_channel, DEFAULT, @subcarriers_fine_ranging, " +
				"@ofdma_broadcast_ir_starting_power_level_increase, @ofdma_broadcast_ir_starting_power_level, @subcarriers_initial_ranging, @preamble_type, " +
				"@rs_interleaver_block_size, @rs_interleaver_depth, @scrambler, @last_codeword_length, @guard_time_size, @maximum_burst_size, @scrambler_seed, " +
				"@fec_codeword_information_bytes, @fec_error_correction, @preamble_value_offset, @preamble_length, @differential_encoding, @modulation_type, @interval_usage_code) RETURNING ss5_serial";
			cmd.Parameters.AddWithValue("@related_channel", NpgsqlDbType.Integer, channelSs5serial);
			cmd.Parameters.Add("@subcarriers_fine_ranging", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@ofdma_broadcast_ir_starting_power_level_increase", NpgsqlDbType.Double);
			cmd.Parameters.Add("@ofdma_broadcast_ir_starting_power_level", NpgsqlDbType.Double);
			cmd.Parameters.Add("@subcarriers_initial_ranging", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@preamble_type", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@rs_interleaver_block_size", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@rs_interleaver_depth", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@scrambler", NpgsqlDbType.Boolean);
			cmd.Parameters.Add("@last_codeword_length", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@guard_time_size", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@maximum_burst_size", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@scrambler_seed", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@fec_codeword_information_bytes", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@fec_error_correction", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@preamble_value_offset", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@preamble_length", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@differential_encoding", NpgsqlDbType.Boolean);
			cmd.Parameters.Add("@modulation_type", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@interval_usage_code", NpgsqlDbType.Integer);
			foreach (UpstreamChannelDescriptor.BurstDescriptor bd in burstDescriptors)
			{
				cmd.Parameters["@subcarriers_fine_ranging"].Value = bd.SubcarriersFineRanging;
				cmd.Parameters["@ofdma_broadcast_ir_starting_power_level_increase"].Value = bd.OfdmaBroadcastIrStartingPowerLevelIncrease;
				cmd.Parameters["@ofdma_broadcast_ir_starting_power_level"].Value = bd.OfdmaBroadcastIrStartingPowerLevel;
				cmd.Parameters["@subcarriers_initial_ranging"].Value = bd.SubcarriersInitialRanging;
				cmd.Parameters["@preamble_type"].Value = (int)bd.PreambleType;
				cmd.Parameters["@rs_interleaver_block_size"].Value = (int)bd.RsInterleaverBlockSize;
				cmd.Parameters["@rs_interleaver_depth"].Value = bd.RsInterleaverDepth;
				cmd.Parameters["@scrambler"].Value = bd.Scrambler;
				cmd.Parameters["@last_codeword_length"].Value = (int)bd.LastCodewordLength;
				cmd.Parameters["@guard_time_size"].Value = bd.GuardTimeSize;
				cmd.Parameters["@maximum_burst_size"].Value = bd.MaximumBurstSize;
				cmd.Parameters["@scrambler_seed"].Value = bd.ScramblerSeed;
				cmd.Parameters["@fec_codeword_information_bytes"].Value = bd.FecCodewordInformationBytes;
				cmd.Parameters["@fec_error_correction"].Value = bd.FecErrorCorrection;
				cmd.Parameters["@preamble_value_offset"].Value = (int)bd.PreambleValueOffset;
				cmd.Parameters["@preamble_length"].Value = (int)bd.PreambleLength;
				cmd.Parameters["@differential_encoding"].Value = bd.DifferentialEncoding;
				cmd.Parameters["@modulation_type"].Value = (int)bd.ModulationType;
				cmd.Parameters["@interval_usage_code"].Value = bd.IntervalUsageCode;
				SetNulls(cmd);
				NpgsqlDataReader dataReader = cmd.ExecuteReader();
				dataReader.Read();
				int ourSerial = dataReader.GetInt32(0);
				dataReader.Close();
				if (bd.OfdmaDataProfiles != null && bd.OfdmaDataProfiles.Length > 0)
				{
					throw new NotImplementedException();
				}
			}
			cmd.Dispose();
		}

		private HashSet<PhysicalAddress> _knownDocsisParticipants;
		public void StoreDocsisParticipant(PhysicalAddress pa, int currentLocation)
		{
			if (_knownDocsisParticipants == null)
				_knownDocsisParticipants = new HashSet<PhysicalAddress>();

			if (_knownDocsisParticipants.Contains(pa))
				return;

			using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString()))
			{
				connection.Open();
				NpgsqlCommand command = connection.CreateCommand();
				command.CommandText = "SELECT dateadded FROM docsis_participants WHERE physicalAddr=@mac";
				command.Parameters.AddWithValue("@mac", NpgsqlDbType.MacAddr, pa);
				NpgsqlDataReader reader = command.ExecuteReader();
				if (reader.Read())
				{
					_knownDocsisParticipants.Add(pa);
					connection.Close();
					return;
				}
				reader.Close();
				command.Dispose();

				command = connection.CreateCommand();
				command.CommandText = "INSERT INTO docsis_participants (location,physicalAddr) VALUES (@loc,@mac)";
				command.Parameters.AddWithValue("@loc", NpgsqlDbType.Integer, currentLocation);
				command.Parameters.AddWithValue("@mac", NpgsqlDbType.MacAddr, pa);
				command.ExecuteNonQuery();
				_knownDocsisParticipants.Add(pa);
				command.Dispose();
				connection.Close();
			}
		}

		private HashSet<Tuple<int, uint>> _knownDownstreamChannels;
		public bool TestForDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int currentLocation)
		{
			if (_knownDownstreamChannels == null)
				_knownDownstreamChannels = new HashSet<Tuple<int, uint>>();

			Tuple<int, uint> currentTuple = new Tuple<int, uint>(currentLocation, downstreamActiveChannel.Frequency.Value);
			if (_knownDownstreamChannels.Contains(currentTuple))
				return true;

			using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString()))
			{
				connection.Open();
				NpgsqlCommand command = connection.CreateCommand();
				command.CommandText = "SELECT dateadded FROM docsis_downstream_channel WHERE location = @location AND frequency = @frequency";
				command.Parameters.AddWithValue("@location", NpgsqlDbType.Integer, currentLocation);
				command.Parameters.AddWithValue("@frequency", NpgsqlDbType.Bigint, (long)downstreamActiveChannel.Frequency);
				NpgsqlDataReader dataReader = command.ExecuteReader();
				bool result = dataReader.Read();
				if (result)
					_knownDownstreamChannels.Add(currentTuple);
				dataReader.Close();
				connection.Close();
				return result;
			}
		}

		public void StoreDocsisDownstreamChannel(PhysicalAddress physicalAddress, MacDomainDescriptor.DownstreamActiveChannel downstreamActiveChannel, int currentLocation)
		{
			using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString()))
			{
				connection.Open();
			    NpgsqlCommand command = connection.CreateCommand();
				command.CommandText = "insert into docsis_downstream_channel VALUES (DEFAULT, DEFAULT, @physical_address, @location, @frequency, @tukey_raised_cosine_window, " +
					"@cyclic_prefix, @khz_subcarrier_spacing, @qam_fec_lock_recovery, @mdd_recovery, @qam_fec_lock_failure, @mdd_timeout, @primary_capable, @modulation_order, " +
					"@annex, @channel_id)";
				command.Parameters.AddWithValue("@physical_address", NpgsqlDbType.MacAddr, physicalAddress);
				command.Parameters.AddWithValue("@location", NpgsqlDbType.Integer, currentLocation);
				command.Parameters.AddWithValue("@frequency", NpgsqlDbType.Bigint, (long)downstreamActiveChannel.Frequency);
				command.Parameters.AddWithValue("@tukey_raised_cosine_window", NpgsqlDbType.Integer, downstreamActiveChannel.TukeyRaisedCosineWindow);
				command.Parameters.AddWithValue("@cyclic_prefix", NpgsqlDbType.Integer, downstreamActiveChannel.CyclicPrefix);
				command.Parameters.AddWithValue("@khz_subcarrier_spacing", NpgsqlDbType.Boolean, downstreamActiveChannel._50khzSubcarrierSpacing);
				command.Parameters.AddWithValue("@qam_fec_lock_recovery", NpgsqlDbType.Boolean, downstreamActiveChannel.QamFecLockRecovery);
				command.Parameters.AddWithValue("@mdd_recovery", NpgsqlDbType.Boolean, downstreamActiveChannel.MddRecovery);
				command.Parameters.AddWithValue("@qam_fec_lock_failure", NpgsqlDbType.Boolean, downstreamActiveChannel.QamFecLockFailure);
				command.Parameters.AddWithValue("@mdd_timeout", NpgsqlDbType.Boolean, downstreamActiveChannel.MddTimeout);
				command.Parameters.AddWithValue("@primary_capable", NpgsqlDbType.Integer, (int)downstreamActiveChannel.PrimaryCapable);
				
				if (downstreamActiveChannel.ModulationOrder.HasValue)
					command.Parameters.AddWithValue("@modulation_order", NpgsqlDbType.Integer, (int)downstreamActiveChannel.ModulationOrder);
				else
					command.Parameters.AddWithValue("@modulation_order", NpgsqlDbType.Integer, DBNull.Value);
				
				if (downstreamActiveChannel.Annex.HasValue)
					command.Parameters.AddWithValue("@annex", NpgsqlDbType.Integer, (int)downstreamActiveChannel.Annex);
				else
					command.Parameters.AddWithValue("@annex", NpgsqlDbType.Integer, DBNull.Value);

				command.Parameters.AddWithValue("@channel_id", NpgsqlDbType.Integer, downstreamActiveChannel.ChannelId);
				SetNulls(command);
				command.ExecuteNonQuery();
				command.Dispose();
				connection.Close();
			}
		}

		private HashSet<Tuple<PhysicalAddress, IPAddress>> cmts_ips;
		public bool SetCmtsIp(PhysicalAddress arpHeaderSenderHardwareAddress, IPAddress arpHeaderSenderProtocolAddress)
		{
			if (cmts_ips == null)
				cmts_ips = new HashSet<Tuple<PhysicalAddress, IPAddress>>();

			Tuple<PhysicalAddress, IPAddress> currentTuple = new Tuple<PhysicalAddress, IPAddress>(arpHeaderSenderHardwareAddress, arpHeaderSenderProtocolAddress);
			if (cmts_ips.Contains(currentTuple))
				return false;

			using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString()))
			{
				connection.Open();
				NpgsqlCommand command = connection.CreateCommand();
				command.CommandText = "UPDATE docsis_participants SET ip = @ip WHERE physicaladdr = @mac";
				command.Parameters.AddWithValue("@ip", NpgsqlDbType.Inet, arpHeaderSenderProtocolAddress);
				command.Parameters.AddWithValue("@mac", NpgsqlDbType.MacAddr, arpHeaderSenderHardwareAddress);
				int rows = command.ExecuteNonQuery();
				bool result = rows > 0;
				if (result)
					cmts_ips.Add(currentTuple);
				command.Dispose();
				connection.Close();
				return result;
			}
		}
	}
}
