using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Npgsql;
using Npgsql.Internal.Postgres;
using NpgsqlTypes;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Dvb.Descriptors.Extension;
using skyscraper5.Dvb.Psi.Model;
using skyscraper5.Skyscraper.Scraper.Storage.Utilities;
using static skyscraper5.Dvb.Descriptors.NvodReferenceDescriptor;

namespace skyscraper5.Data.PostgreSql
{
    public partial class PostgresqlDataStore
    {
        public bool TestForSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService)
        {
            if (knownSdtServices == null)
                knownSdtServices = new HashSet<DatabaseKeySdtService>();
            DatabaseKeySdtService key = new DatabaseKeySdtService(transportStreamId, originalNetworkId, sdtService.ServiceId);
            if (knownSdtServices.Contains(key))
                return true;

            using (NpgsqlConnection connection = new NpgsqlConnection(connectionStringBuilder.ToString()))
            {
                connection.Open();
                NpgsqlCommand command = connection.CreateCommand();
                command.CommandText =
                    "SELECT dateadded FROM dvb_sdt WHERE tsid = @tsid AND onid = @onid AND sid = @sid";
                command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId);
                command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId);
                command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId);
                NpgsqlDataReader dataReader = command.ExecuteReader();
                bool result = false;
                if (dataReader.Read())
                {
                    result = true;
                    knownSdtServices.Add(key);
                }
                dataReader.Close();
                connection.Close();
                return result;
            }
        }

        private HashSet<DatabaseKeySdtService> knownSdtServices;

        public void StoreSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService)
        {
	        if (updatedSdtServices == null)
		        updatedSdtServices = new HashSet<DatabaseKeySdtService>();

			DatabaseKeySdtService key = new DatabaseKeySdtService(transportStreamId, originalNetworkId, sdtService.ServiceId);
            EnqueueTask(x => WriteSdtService(x, transportStreamId, originalNetworkId, sdtService));
            knownSdtServices.Add(key);
            updatedSdtServices.Add(key);
        }

        private void WriteSdtService(NpgsqlConnection connection, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService)
        {
            NpgsqlCommand command = connection.CreateCommand();
            command.CommandText = "SELECT dateadded FROM dvb_sdt WHERE tsid = @tsid AND onid = @onid AND sid = @sid";
            command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId);
            command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId);
            command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId);
            NpgsqlDataReader reader = command.ExecuteReader();
            bool alreadyKnown = reader.Read();
            reader.Close();
            command.Dispose();
            if (alreadyKnown)
                return;

            command = connection.CreateCommand();
            command.CommandText = "INSERT INTO dvb_sdt " +
                                  "( tsid, onid, sid, eit_schedule_flag, eit_present_following_flag, running_status, free_ca_mode, service_name, service_provider_name, service_type_coding, private_data_specifier, data_broadcast_id, selector, default_authority, control_remote_access_over_internet, reference_service_id, do_not_apply_revocation, do_not_scramble, old_onid, old_sid, old_tsid, component_tag, iso_639_language_code, text) " +
                                  "VALUES " +
                                  "( @tsid, @onid, @sid, @eit_schedule_flag, @eit_present_following_flag, @running_status, @free_ca_mode, @service_name, @service_provider_name, @service_type_coding, " +
                                  "  @private_data_specifier, @data_broadcast_id, @selector, @default_authority, @control_remote_access_over_internet, @reference_service_id, @do_not_apply_revocation, " +
                                  "  @do_not_scramble, @old_onid, @old_sid, @old_tsid, @component_tag, @iso_639_language_code, @text)";
            command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId);
            command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId);
            command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId);
            command.Parameters.AddWithValue("@eit_schedule_flag", NpgsqlDbType.Boolean, sdtService.EitScheduleFlag);
            command.Parameters.AddWithValue("@eit_present_following_flag", NpgsqlDbType.Boolean, sdtService.EitPresentFollowingFlag);
            command.Parameters.AddWithValue("@running_status", NpgsqlDbType.Integer, (int)sdtService.RunningStatus);
            command.Parameters.AddWithValue("@free_ca_mode", NpgsqlDbType.Boolean, sdtService.FreeCaMode);
            command.Parameters.AddWithValue("@service_name", NpgsqlDbType.Text, sdtService.ServiceName);
            command.Parameters.AddWithValue("@service_provider_name", NpgsqlDbType.Text, sdtService.ServiceProviderName);

            if (sdtService.ServiceType.HasValue)
                command.Parameters.AddWithValue("@service_type_coding", NpgsqlDbType.Integer, (int)sdtService.ServiceType);
            else
                command.Parameters.AddWithValue("@service_type_coding", NpgsqlDbType.Integer, DBNull.Value);

            if (sdtService.PrivateDataSpecifier.HasValue)
                command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, (long)sdtService.PrivateDataSpecifier);
            else
                command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, DBNull.Value);

            if (sdtService.DataBroadcastId.HasValue)
                command.Parameters.AddWithValue("@data_broadcast_id", NpgsqlDbType.Integer, (int)sdtService.DataBroadcastId);
            else
                command.Parameters.AddWithValue("@data_broadcast_id", NpgsqlDbType.Integer, DBNull.Value);

            if (sdtService.Selector != null)
                command.Parameters.AddWithValue("@selector", NpgsqlDbType.Bytea, sdtService.Selector);
            else
                command.Parameters.AddWithValue("@selector", NpgsqlDbType.Bytea, DBNull.Value);

            command.Parameters.AddWithValue("@default_authority", NpgsqlDbType.Varchar, sdtService.DefaultAuthority);

            if (sdtService.ControlRemoteAccessOverInternet.HasValue)
                command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, sdtService.ControlRemoteAccessOverInternet);
            else
                command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, DBNull.Value);

            //@do_not_scramble, @old_onid, @old_sid, @old_tsid, @component_tag, @iso_639_language_code, @text
            if (sdtService.ReferenceServiceId.HasValue)
                command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, (int)sdtService.ReferenceServiceId);
            else
                command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, DBNull.Value);

            if (sdtService.DoNotApplyRevocation.HasValue)
                command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, sdtService.DoNotApplyRevocation);
            else
                command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, DBNull.Value);

            if (sdtService.DoNotScramble.HasValue)
                command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, sdtService.DoNotScramble);
            else
                command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, DBNull.Value);

            if (sdtService.OldOriginalNetworkId.HasValue)
                command.Parameters.AddWithValue("@old_onid", NpgsqlDbType.Integer, (int)sdtService.OldOriginalNetworkId);
            else
                command.Parameters.AddWithValue("@old_onid", NpgsqlDbType.Integer, DBNull.Value);

            if (sdtService.OldServiceId.HasValue)
                command.Parameters.AddWithValue("@old_sid", NpgsqlDbType.Integer, (int)sdtService.OldServiceId);
            else
                command.Parameters.AddWithValue("@old_sid", NpgsqlDbType.Integer, DBNull.Value);

            if (sdtService.OldTransportStreamId.HasValue)
                command.Parameters.AddWithValue("@old_tsid", NpgsqlDbType.Integer, (int)sdtService.OldTransportStreamId);
            else
                command.Parameters.AddWithValue("@old_tsid", NpgsqlDbType.Integer, DBNull.Value);

            if (sdtService.ComponentTag.HasValue)
                command.Parameters.AddWithValue("@component_tag", NpgsqlDbType.Integer, sdtService.ComponentTag);
            else
                command.Parameters.AddWithValue("@component_tag", NpgsqlDbType.Integer, DBNull.Value);

            command.Parameters.AddWithValue("@iso_639_language_code", NpgsqlDbType.Varchar, sdtService.Iso639LanguageCode);
            command.Parameters.AddWithValue("@text", NpgsqlDbType.Text, sdtService.Text);
            command.Parameters.CheckNulls();
            command.ExecuteNonQuery();

            WriteSdtCaIdentifiers(connection, sdtService, transportStreamId, originalNetworkId);
            WriteSdtComponents(connection, sdtService, transportStreamId, originalNetworkId);
            WriteSdtCountryAvailability(connection, sdtService, transportStreamId, originalNetworkId);
            WriteSdtLinkages(connection, sdtService, transportStreamId, originalNetworkId);
            WriteSdtMultilingualServiceName(connection, sdtService, transportStreamId, originalNetworkId);
            WriteSdtNvodReferences(connection, sdtService, transportStreamId, originalNetworkId);
            WriteSdtMessages(connection, sdtService, transportStreamId, originalNetworkId);
        }

		private void WriteSdtMessages(NpgsqlConnection connection, SdtService sdtService, ushort transportStreamId, ushort originalNetworkId)
		{
            if (sdtService.Messages == null)
                return;

			NpgsqlCommand cmd = connection.CreateCommand();
			cmd.CommandText = "insert into dvb_sdt_messages (tsid, onid, sid, msgid, language_code, message, dateadded)\r\nvalues (@tsid,@onid,@sid,@msgid,@language,@message,DEFAULT)";
			cmd.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId);
			cmd.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId);
			cmd.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId);
            cmd.Parameters.Add("@msgid", NpgsqlDbType.Integer);
			cmd.Parameters.Add("@language", NpgsqlDbType.Varchar);
			cmd.Parameters.Add("@message", NpgsqlDbType.Text);

			foreach (MessageDescriptor message in sdtService.Messages)
            {
                cmd.Parameters["@msgid"].Value = (int)message.MessageId;
                cmd.Parameters["@language"].Value = message.Iso639LanguageCode;
                cmd.Parameters["@message"].Value = message.Message;
                cmd.ExecuteNonQuery();
            }
		}

		private void WriteSdtNvodReferences(NpgsqlConnection connection, SdtService sdtService, int tsid, int onid)
        {
            if (sdtService.NvodReferences == null)
                return;

            NpgsqlCommand cmd = connection.CreateCommand();
            cmd.CommandText = "insert into dvb_sdt_nvod_references (tsid, onid, sid, ordinal, nvod_tsid, nvod_onid, nvod_sid)\r\n" +
                "values (@tsid, @onid, @sid, @ordinal, @nvod_tsid, @nvod_onid, @nvod_sid)";
            cmd.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, tsid);
            cmd.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, onid);
            cmd.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId);
            cmd.Parameters.Add("@ordinal", NpgsqlDbType.Integer);
            cmd.Parameters.Add("@nvod_tsid", NpgsqlDbType.Integer);
            cmd.Parameters.Add("@nvod_onid", NpgsqlDbType.Integer);
            cmd.Parameters.Add("@nvod_sid", NpgsqlDbType.Integer);

            for (int i = 0; i < sdtService.NvodReferences.Length; i++)
            {
                NvodReference nvod = sdtService.NvodReferences[i];
                if (nvod == null)
                    continue;

                cmd.Parameters["@ordinal"].Value = i;
                cmd.Parameters["@nvod_tsid"].Value = (int)nvod.TransportStreamId;
                cmd.Parameters["@nvod_onid"].Value = (int)nvod.OriginalNetworkId;
                cmd.Parameters["@nvod_sid"].Value = (int)nvod.ServiceId;
                cmd.ExecuteNonQuery();
            }
        }

        private void WriteSdtMultilingualServiceName(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid)
        {
            if (sdtService.MultilingualServiceName == null)
                return;

            NpgsqlCommand cmd = connection.CreateCommand();
            cmd.CommandText = "insert into dvb_sdt_multilingual_service_name (tsid, onid, sid, ordinal, iso_639_language_code, service_provider_name, service_name) " +
                "values " +
                "(@tsid, @onid, @sid, @ordinal, @iso_639_language_code, @service_provider_name, @service_name)";
            cmd.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)tsid);
            cmd.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)onid);
            cmd.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId);
            cmd.Parameters.Add("@ordinal", NpgsqlDbType.Integer);
            cmd.Parameters.Add("@iso_639_language_code", NpgsqlDbType.Varchar);
            cmd.Parameters.Add("@service_provider_name", NpgsqlDbType.Text);
            cmd.Parameters.Add("@service_name", NpgsqlDbType.Text);
            for (int i = 0; i < sdtService.MultilingualServiceName.Count; i++)
            {
                MultilingualServiceNameDescriptor.MultilingualServiceName multilingualServiceName = sdtService.MultilingualServiceName[i];
                cmd.Parameters["@ordinal"].Value = i;
                cmd.Parameters["@iso_639_language_code"].Value = multilingualServiceName.Iso639LanguageCode;
                cmd.Parameters["@service_provider_name"].Value = multilingualServiceName.ServiceProviderName;
                cmd.Parameters["@service_name"].Value = multilingualServiceName.ServiceName;
                cmd.Parameters.CheckNulls();
                cmd.ExecuteNonQuery();
            }
        }

        private void WriteSdtLinkages(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid)
        {
			if (sdtService.Linkages == null)
				return;

			NpgsqlCommand command = connection.CreateCommand();
			command.CommandText =
				"insert into dvb_sdt_linkages (tsid, onid, sid, ordinal, l_tsid, l_onid, l_sid, linkage_type, handover_type, handover_origin_type, handover_network_id, handover_initial_service_id, target_event_id, target_event_listed, target_event_simulcasted, table_type, private_data_bytes, bouquet_id) " +
				"values " +
				"(@tsid,@onid,@sid,@ordinal,@l_tsid,@l_onid,@l_sid,@linkage_type,@handover_type,@handover_origin_type,@handover_network_id,@handover_initial_service_id,@target_event_id,@target_event_listed,@target_event_simulcasted,@table_type,@private_data_bytes,@bouquet_id)";
			command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, tsid);
			command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, onid);
			command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, sdtService.ServiceId);
			command.Parameters.Add("@ordinal", NpgsqlDbType.Integer);
			command.Parameters.Add("@l_tsid", NpgsqlDbType.Integer);
			command.Parameters.Add("@l_onid", NpgsqlDbType.Integer);
			command.Parameters.Add("@l_sid", NpgsqlDbType.Integer);
			command.Parameters.Add("@linkage_type", NpgsqlDbType.Integer);
			command.Parameters.Add("@handover_type", NpgsqlDbType.Integer);
			command.Parameters.Add("@handover_origin_type", NpgsqlDbType.Boolean);
			command.Parameters.Add("@handover_network_id", NpgsqlDbType.Integer);
			command.Parameters.Add("@handover_initial_service_id", NpgsqlDbType.Integer);
			command.Parameters.Add("@target_event_id", NpgsqlDbType.Integer);
			command.Parameters.Add("@target_event_listed", NpgsqlDbType.Boolean);
			command.Parameters.Add("@target_event_simulcasted", NpgsqlDbType.Boolean);
			//@table_type,@private_data_bytes,@bouquet_id)";
			command.Parameters.Add("@table_type", NpgsqlDbType.Integer);
			command.Parameters.Add("@private_data_bytes", NpgsqlDbType.Bytea);
			command.Parameters.Add("@bouquet_id", NpgsqlDbType.Integer);

			int i = 0;
            foreach (LinkageDescriptor linkage in sdtService.Linkages) 
            {
				command.Parameters["@ordinal"].Value = i++;
				command.Parameters["@l_tsid"].Value = (int)linkage.TransportStreamId;
				command.Parameters["@l_onid"].Value = (int)linkage.OriginalNetworkId;
				command.Parameters["@l_sid"].Value = (int)linkage.ServiceId;
				command.Parameters["@linkage_type"].Value = (int)linkage.LinkageType;
				command.Parameters["@handover_type"].Value = linkage.HandoverType.HasValue ? (int)linkage.HandoverType.Value : DBNull.Value;
				command.Parameters["@handover_origin_type"].Value = linkage.HandoverOriginType.HasValue ? linkage.HandoverOriginType.Value : DBNull.Value;
				command.Parameters["@handover_network_id"].Value = linkage.HandoverNetworkId.HasValue ? (int)linkage.HandoverNetworkId.Value : DBNull.Value;
				command.Parameters["@handover_initial_service_id"].Value = linkage.HandoverInitialServiceId.HasValue ? linkage.HandoverInitialServiceId.Value : DBNull.Value;
				command.Parameters["@target_event_id"].Value = linkage.TargetEventId.HasValue ? linkage.TargetEventId.Value : DBNull.Value;
				command.Parameters["@target_event_listed"].Value = linkage.TargetEventListed.HasValue ? linkage.TargetEventListed.Value : DBNull.Value;
				command.Parameters["@target_event_simulcasted"].Value = linkage.TargetEventSimulcasted.HasValue ? linkage.TargetEventSimulcasted.Value : DBNull.Value;
				command.Parameters["@table_type"].Value = linkage.TableType.HasValue ? (int)linkage.TableType.Value : DBNull.Value;
				command.Parameters["@private_data_bytes"].Value = linkage.PrivateDataBytes != null ? linkage.PrivateDataBytes : DBNull.Value;
				command.Parameters["@bouquet_id"].Value = linkage.BouquetId.HasValue ? linkage.BouquetId : DBNull.Value;
				command.ExecuteNonQuery();

				WriteSdtLinkageExtendedEventLinkages(connection, sdtService, tsid, onid, linkage, i);
				WriteSdtLinkageIpmacLinkages(connection, sdtService, tsid, onid, linkage, i);
				WriteSdtLinkageSsuLinkStructure(connection, sdtService, tsid, onid, linkage, i);
			}
		}

        private void WriteSdtLinkageSsuLinkStructure(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid, LinkageDescriptor linkage, int i)
        {
	        if (linkage.SystemSoftwareUpdateLinkStructure == null)
		        return;

	        foreach (LinkageDescriptor.OuiPrivateData ouiPrivateData in linkage.SystemSoftwareUpdateLinkStructure)
	        {
		        throw new NotImplementedException();
	        }
        }

        private void WriteSdtLinkageIpmacLinkages(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid, LinkageDescriptor linkage, int i)
        {
	        if (linkage.IpMacNotificationLinkages == null)
		        return;

	        foreach (LinkageDescriptor.IpMacNotificationLinkage notificationLinkage in
	                 linkage.IpMacNotificationLinkages)
	        {
		        throw new NotImplementedException();
	        }
        }

        private void WriteSdtLinkageExtendedEventLinkages(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid, LinkageDescriptor linkage, int i)
        {
	        if (linkage.ExtendedEventLinkages == null)
		        return;

	        foreach (LinkageDescriptor.ExtendedEventLinkageInfo eventLinkage in linkage.ExtendedEventLinkages)
	        {
		        throw new NotImplementedException();
	        }
        }

        private void WriteSdtCountryAvailability(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid)
        {
            if (sdtService.CountryAvailability == null)
                return;

            NpgsqlCommand command = connection.CreateCommand();
            command.CommandText = "INSERT INTO dvb_sdt_country_availability (tsid, onid, sid, ordinal, k, v) " +
                                  "VALUES (@tsid, @onid, @sid, @ordinal, @k, @v)";
            command.Parameters.AddParameter("@tsid",NpgsqlDbType.Integer,tsid);
            command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, onid);
            command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, sdtService.ServiceId);
            command.Parameters.Add("@ordinal", NpgsqlDbType.Integer);
            command.Parameters.Add("@k", NpgsqlDbType.Varchar);
            command.Parameters.Add("@v", NpgsqlDbType.Boolean);
			int ordinal = 0;
            foreach (var (key, value) in sdtService.CountryAvailability)
            {
	            command.Parameters["@ordinal"].Value = ordinal++;
	            command.Parameters["@k"].Value = key;
	            command.Parameters["@v"].Value = value;
	            command.ExecuteNonQuery();
            }
        }

        private void WriteSdtComponents(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid)
        {
            if (sdtService.Components == null)
                return;

            NpgsqlCommand command = connection.CreateCommand();
            command.CommandText =
	            "insert into dvb_sdt_components (tsid, onid, sid, ordinal, stream_content_ext, stream_content, component_type,component_tag, iso_639_language_code, text)" +
	            "values ( @tsid, @onid, @sid, @ordinal, @stream_content_ext, @stream_content, @component_type,@component_tag, @iso_639_language_code, @text)";
            command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, tsid);
            command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, onid);
            command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, sdtService.ServiceId);
            command.Parameters.Add("@ordinal", NpgsqlDbType.Integer);
            command.Parameters.Add("@stream_content_ext", NpgsqlDbType.Integer);
            command.Parameters.Add("@stream_content", NpgsqlDbType.Integer);
            command.Parameters.Add("@component_type", NpgsqlDbType.Integer);
            command.Parameters.Add("@component_tag", NpgsqlDbType.Integer);
            command.Parameters.Add("@iso_639_language_code", NpgsqlDbType.Varchar);
            command.Parameters.Add("@text", NpgsqlDbType.Text);
			for (int i = 0; i < sdtService.Components.Count; i++)
			{
				ComponentDescriptor component = sdtService.Components[i];
				command.Parameters["@ordinal"].Value = i;
				command.Parameters["@stream_content_ext"].Value = component.StreamContentExt;
				command.Parameters["@stream_content"].Value = component.StreamContent;
				command.Parameters["@component_type"].Value = component.ComponentType;
				command.Parameters["@component_tag"].Value = component.ComponentTag;
				command.Parameters["@iso_639_language_code"].Value = component.Iso639LanguageCode;
				command.Parameters["@text"].Value = component.Text;
                command.Parameters.CheckNulls();
                command.ExecuteNonQuery();
			}
        }

        private void WriteSdtCaIdentifiers(NpgsqlConnection connection, SdtService sdtService, ushort tsid, ushort onid)
        {
            if (sdtService.CaIdentifiers == null)
                return;

            NpgsqlCommand command = connection.CreateCommand();
            command.CommandText = "INSERT INTO dvb_sdt_ca_identifiers (tsid, onid, sid, ordinal, caid) " +
                                  "VALUES (@tsid, @onid, @sid, @ordinal, @caid)";
            command.Parameters.AddParameter("@tsid", NpgsqlDbType.Integer, tsid);
            command.Parameters.AddParameter("@onid", NpgsqlDbType.Integer, onid);
            command.Parameters.AddParameter("@sid", NpgsqlDbType.Integer, sdtService.ServiceId);
            command.Parameters.Add("@ordinal", NpgsqlDbType.Integer);
            command.Parameters.Add("@caid", NpgsqlDbType.Integer);
            for (int i = 0; i < sdtService.CaIdentifiers.Length; i++)
            {
	            command.Parameters["@ordinal"].Value = i;
                command.Parameters["@caid"].Value = (int)sdtService.CaIdentifiers[i];
                command.ExecuteNonQuery();
            }
        }

        private HashSet<DatabaseKeySdtService> updatedSdtServices;
        public bool UpdateSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService newerSdtService)
        {
	        if (updatedSdtServices == null)
		        updatedSdtServices = new HashSet<DatabaseKeySdtService>();

	        DatabaseKeySdtService key = new DatabaseKeySdtService(transportStreamId, originalNetworkId, newerSdtService.ServiceId);
	        if (updatedSdtServices.Contains(key))
		        return false;

	        SdtService olderSdtService = ReadSdtService(transportStreamId, originalNetworkId, newerSdtService.ServiceId, false);
	        bool result = olderSdtService.NeedsUpdate(newerSdtService);
            if (result)
	        {
		        EnqueueTask(x => WriteSdtServiceUpdate(x, transportStreamId, originalNetworkId, newerSdtService));
	        }
            updatedSdtServices.Add(key);
	        return result;
        }

        private void WriteSdtServiceUpdate(NpgsqlConnection connection, ushort transportStreamId, ushort originalNetworkId, SdtService sdtService)
        {
	        NpgsqlCommand command = connection.CreateCommand();
	        command.CommandText =
		        "UPDATE dvb_sdt\r\nSET eit_schedule_flag = @eit_schedule_flag,\r\n    eit_present_following_flag = @eit_present_following_flag,\r\n    running_status = @running_status,\r\n    free_ca_mode = @free_ca_mode,\r\n    service_name = @service_name,\r\n    service_provider_name = @service_provider_name,\r\n    service_type_coding = @service_type_coding,\r\n    private_data_specifier = @private_data_specifier,\r\n    data_broadcast_id = @data_broadcast_id,\r\n    selector = @selector,\r\n    default_authority = @default_authority,\r\n    control_remote_access_over_internet = @control_remote_access_over_internet,\r\n    reference_service_id = @reference_service_id,\r\n    do_not_apply_revocation = @do_not_apply_revocation,\r\n    do_not_scramble = @do_not_scramble,\r\n    old_onid = @old_onid,\r\n    old_sid = @old_sid,\r\n    old_tsid = @old_tsid,\r\n    component_tag = @component_tag,\r\n    iso_639_language_code = @iso_639_language_code,\r\n    text = @text,\r\n    updated_counter = updated_counter + 1,\r\n    updated_timestamp = CURRENT_TIMESTAMP\r\nWHERE tsid = @tsid\r\nAND onid = @onid\r\nAND sid = @sid";

			command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId);
			command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId);
			command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)sdtService.ServiceId);
			command.Parameters.AddWithValue("@eit_schedule_flag", NpgsqlDbType.Boolean, sdtService.EitScheduleFlag);
			command.Parameters.AddWithValue("@eit_present_following_flag", NpgsqlDbType.Boolean, sdtService.EitPresentFollowingFlag);
			command.Parameters.AddWithValue("@running_status", NpgsqlDbType.Integer, (int)sdtService.RunningStatus);
			command.Parameters.AddWithValue("@free_ca_mode", NpgsqlDbType.Boolean, sdtService.FreeCaMode);
			command.Parameters.AddWithValue("@service_name", NpgsqlDbType.Text, sdtService.ServiceName);
			command.Parameters.AddWithValue("@service_provider_name", NpgsqlDbType.Text, sdtService.ServiceProviderName);

			if (sdtService.ServiceType.HasValue)
				command.Parameters.AddWithValue("@service_type_coding", NpgsqlDbType.Integer, (int)sdtService.ServiceType);
			else
				command.Parameters.AddWithValue("@service_type_coding", NpgsqlDbType.Integer, DBNull.Value);

			if (sdtService.PrivateDataSpecifier.HasValue)
				command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, (long)sdtService.PrivateDataSpecifier);
			else
				command.Parameters.AddWithValue("@private_data_specifier", NpgsqlDbType.Bigint, DBNull.Value);

			if (sdtService.DataBroadcastId.HasValue)
				command.Parameters.AddWithValue("@data_broadcast_id", NpgsqlDbType.Integer, (int)sdtService.DataBroadcastId);
			else
				command.Parameters.AddWithValue("@data_broadcast_id", NpgsqlDbType.Integer, DBNull.Value);

			if (sdtService.Selector != null)
				command.Parameters.AddWithValue("@selector", NpgsqlDbType.Bytea, sdtService.Selector);
			else
				command.Parameters.AddWithValue("@selector", NpgsqlDbType.Bytea, DBNull.Value);

			command.Parameters.AddWithValue("@default_authority", NpgsqlDbType.Varchar, sdtService.DefaultAuthority);

			if (sdtService.ControlRemoteAccessOverInternet.HasValue)
				command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, sdtService.ControlRemoteAccessOverInternet);
			else
				command.Parameters.AddWithValue("@control_remote_access_over_internet", NpgsqlDbType.Integer, DBNull.Value);

			//@do_not_scramble, @old_onid, @old_sid, @old_tsid, @component_tag, @iso_639_language_code, @text
			if (sdtService.ReferenceServiceId.HasValue)
				command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, (int)sdtService.ReferenceServiceId);
			else
				command.Parameters.AddWithValue("@reference_service_id", NpgsqlDbType.Integer, DBNull.Value);

			if (sdtService.DoNotApplyRevocation.HasValue)
				command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, sdtService.DoNotApplyRevocation);
			else
				command.Parameters.AddWithValue("@do_not_apply_revocation", NpgsqlDbType.Boolean, DBNull.Value);

			if (sdtService.DoNotScramble.HasValue)
				command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, sdtService.DoNotScramble);
			else
				command.Parameters.AddWithValue("@do_not_scramble", NpgsqlDbType.Boolean, DBNull.Value);

			if (sdtService.OldOriginalNetworkId.HasValue)
				command.Parameters.AddWithValue("@old_onid", NpgsqlDbType.Integer, sdtService.OldOriginalNetworkId);
			else
				command.Parameters.AddWithValue("@old_onid", NpgsqlDbType.Integer, DBNull.Value);

			if (sdtService.OldServiceId.HasValue)
				command.Parameters.AddWithValue("@old_sid", NpgsqlDbType.Integer, sdtService.OldServiceId);
			else
				command.Parameters.AddWithValue("@old_sid", NpgsqlDbType.Integer, DBNull.Value);

			if (sdtService.OldTransportStreamId.HasValue)
				command.Parameters.AddWithValue("@old_tsid", NpgsqlDbType.Integer, sdtService.OldTransportStreamId);
			else
				command.Parameters.AddWithValue("@old_tsid", NpgsqlDbType.Integer, DBNull.Value);

			if (sdtService.ComponentTag.HasValue)
				command.Parameters.AddWithValue("@component_tag", NpgsqlDbType.Integer, sdtService.ComponentTag);
			else
				command.Parameters.AddWithValue("@component_tag", NpgsqlDbType.Integer, DBNull.Value);

			command.Parameters.AddWithValue("@iso_639_language_code", NpgsqlDbType.Varchar, sdtService.Iso639LanguageCode);
			command.Parameters.AddWithValue("@text", NpgsqlDbType.Text, sdtService.Text);
			command.Parameters.CheckNulls();
			command.ExecuteNonQuery();
            command.Dispose();
		}

        private SdtService ReadSdtService(ushort transportStreamId, ushort originalNetworkId, ushort serviceId, bool recursive)
        {
	        using (NpgsqlConnection conn = new NpgsqlConnection(connectionStringBuilder.ToString()))
	        {
                conn.Open();
                NpgsqlCommand command = conn.CreateCommand();
                command.CommandText = "SELECT * FROM dvb_sdt WHERE tsid = @tsid AND onid = @onid AND sid = @sid";
                command.Parameters.AddWithValue("@tsid", NpgsqlDbType.Integer, (int)transportStreamId);
                command.Parameters.AddWithValue("@onid", NpgsqlDbType.Integer, (int)originalNetworkId);
                command.Parameters.AddWithValue("@sid", NpgsqlDbType.Integer, (int)serviceId);
                NpgsqlDataReader dataReader = command.ExecuteReader();
                SdtService result = null;
                if (dataReader.Read())
                {
	                transportStreamId = (ushort)dataReader.GetInt32(0);
	                originalNetworkId = (ushort)dataReader.GetInt32(1);
	                serviceId = (ushort)dataReader.GetInt32(2);
	                DateTime dateAdded = dataReader.GetDateTime(3);
	                bool eitScheduleFlag = dataReader.GetBoolean(4);
	                bool eitPresentFollowingFlag = dataReader.GetBoolean(5);
	                RunningStatus runningStatus = (RunningStatus)dataReader.GetInt32(6);
	                bool freeCaMode = dataReader.GetBoolean(7);
	                result = new SdtService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode);
                    result.ServiceName = dataReader.IsDBNull(8) ? null : dataReader.GetString(8);
                    result.ServiceProviderName = dataReader.IsDBNull(9) ? null : dataReader.GetString(9);
                    result.ServiceType = dataReader.IsDBNull(10) ? null : (ServiceDescriptor.ServiceTypeCoding)dataReader.GetInt32(10);
                    result.PrivateDataSpecifier = dataReader.IsDBNull(11) ? null : (uint)dataReader.GetInt64(11);
                    result.DataBroadcastId = dataReader.IsDBNull(12) ? null : (ushort)dataReader.GetInt32(12);
                    result.Selector = dataReader.IsDBNull(13) ? null : dataReader.GetByteArray(13);
                    result.DefaultAuthority = dataReader.IsDBNull(14) ? null : dataReader.GetString(14);
                    result.ControlRemoteAccessOverInternet = dataReader.IsDBNull(15) ? null : (int)dataReader.GetInt32(15);
                    result.ReferenceServiceId = dataReader.IsDBNull(16) ? null : (ushort)dataReader.GetInt32(16);
                    result.DoNotApplyRevocation = dataReader.IsDBNull(17) ? null : (bool)dataReader.GetBoolean(17);
                    result.DoNotScramble = dataReader.IsDBNull(18) ? null : (bool)dataReader.GetBoolean(18);
                    result.OldOriginalNetworkId = dataReader.IsDBNull(19) ? null : (ushort)dataReader.GetInt32(19);
                    result.OldServiceId = dataReader.IsDBNull(20) ? null : (ushort)dataReader.GetInt32(20);
                    result.OldTransportStreamId = dataReader.IsDBNull(21) ? null : (ushort)dataReader.GetInt32(21);
                    result.ComponentTag = dataReader.IsDBNull(22) ? null : (byte)dataReader.GetInt32(22);
                    result.Iso639LanguageCode = dataReader.IsDBNull(23) ? null : dataReader.GetString(23);
                    result.Text = dataReader.IsDBNull(24) ? null : dataReader.GetString(24);
                }
                dataReader.Close();
                command.Dispose();
                conn.Close();
                return result;
	        }
        }
	}
}
