/// <summary>
        /// Permette di ricevere una richiesta da un vicino.
        /// </summary>
        /// <param name="sourceNodeId">L'identificatore associato al vicino.</param>
        /// <param name="query">La richiesta inviata dal vicino.</param>
        public void Query(string sourceNodeId, QueryData query)
        {
            if (m_KnownNeighbors.Contains(sourceNodeId))
            {
                WriteToLog("The received query from node {0} has been accepted: {1}.", sourceNodeId, query.MsgId);

                m_MessagesHandler.HandleReceivedQuery(sourceNodeId, query);
            }
            else
            {
                WriteToLog("The received query from node {0} has been dropped: {1}.", sourceNodeId, query.MsgId);
            }
        }
        /// <summary>
        /// Avvia una nuova ricerca in base ai dati di ricerca specificati, creando un nuovo messaggio di richiesta
        /// con un valore di TimeToLive impostato secondo la configurazione e inviandolo a tutti gli attuali vicini
        /// di questo nodo della rete. Nell'eventualità remota che l'identificativo generato per il nuovo messaggio
        /// corrisponda ad uno degli identificativi attualmente presenti nella tabella di inoltro, il messaggio non
        /// viene inviato e questo metodo restituisce false; in caso contrario, restituisce true.
        /// </summary>
        /// <param name="data">I dati che rappresentano la ricerca da effettuare.</param>
        /// <returns>true se richiesta relativa alla ricerca viene inviata ai vicini; in caso contrario, false.</returns>
        public bool CreateNewSearch(SearchData data)
        {
            Guid msgId = Guid.NewGuid();

            lock (m_ForwardingTableLocker)
            {
                if (!m_ForwardingTable.Add(msgId, null, data))
                {
                    return false;   // GUID duplicato
                }
            }

            WriteToLog("Sending query {0}: search data = {1}...", msgId, data);

            QueryData query = new QueryData(msgId, m_MessageInitialTtl, 0) { Options = data.GetSearchOptions() };
            SendQuery(query, null);

            return true;
        }
        /// <summary>
        /// Invia la richiesta specificata a tutte le connessioni attive relative ai vicini, escludendo l'eventuale
        /// connessione specificata come destinazione della richiesta.
        /// </summary>
        /// <param name="query">I dati della richiesta da inviare.</param>
        /// <param name="excludedConnectionId">L'identificativo della connessione da escludere.</param>
        /// <exception cref="ArgumentNullException">query è null.</exception>
        /// <remarks>
        /// Per inviare la richiesta specificata a tutti i vicini, è sufficiente specificare null come identificativo
        /// della connessione da escludere: in tal modo non verrà esclusa nessuna connessione tra tutte quelle attive.
        /// </remarks>
        private void SendQuery(QueryData query, string excludedConnectionId)
        {
            m_NeighborhoodManager.SendQuery(query, excludedConnectionId);

            WriteToLog("Query {0} sent to all connections. Excluded connection: {1}.",
                query.MsgId, (excludedConnectionId != null ? excludedConnectionId : "none"));
        }
        /// <summary>
        /// Verifica quali, tra le risorse conosciute da questo nodo, sono compatibili con le opzioni di ricerca
        /// specificate nella query ricevuta, quindi invia l'eventuale risposta sulla connessione di provenienza
        /// della query.
        /// </summary>
        /// <param name="query">I dati della richiesta ricevuta.</param>
        /// <param name="sourceConnectionId">L'identificativo della connessione di provenienza della richiesta.</param>
        /// <remarks>
        /// Se si specifica null per almeno uno dei due parametri oppure se l'identificativo della connessione
        /// di provenienza della richiesta è una stringa vuota oppure formata da soli spazi, questo metodo non
        /// esegue alcuna azione, ignorando i parametri specificati.
        /// </remarks>
        private void ReplyToQuery(QueryData query, string sourceConnectionId)
        {
            Thread.Sleep(100);   // simula ritardo di rete

            if (string.IsNullOrWhiteSpace(sourceConnectionId) || query == null) return;

            WriteToLog("Processing query {0} ...", query.MsgId);

            List<Uri> found = m_ResourceCache.Search(
                delegate(Uri uri, IEnumerable<TaskPerformerInfo> resources)
                {
                    foreach (var resource in resources)
                    {
                        if (resource.Name == query.Options.Name && resource.Version == query.Options.Version)
                        {
                            return true;
                        }
                    }
                    return false;
                }
            ).ToList<Uri>();

            WriteToLog("Processed query {0}: {1} resources found.", query.MsgId, found.Count);

            if (found.Count > 0)
            {
                ReplyData reply = new ReplyData(query.MsgId, query.HopsCount, 0) { FoundServices = found };
                SendReply(reply, sourceConnectionId);
            }
        }
        /// <summary>
        /// Inoltra la richiesta ricevuta da un vicino, verificando se deve essere inoltrata ai restanti vicini
        /// di questo nodo, oppure se deve essere scartata perché scaduta. La procedura prevede l'aggiornamento
        /// preliminare di due proprietà relative alla richiesta: il TimeToLive viene decrementato di 1, mentre
        /// HopsCount viene incrementato di 1. Se il TimeToLive si è azzerato, la richiesta non viene inoltrata
        /// ai restanti vicini, altrimenti verifica che l'identificativo del messaggio non sia già nella tabella
        /// di inoltro, prima di inserirlo in essa ed inoltrare la richiesta agli altri vicini. Invece, qualora
        /// l'identificativo sia già presente nella tabella di inoltro, la richiesta non viene inoltrata.
        /// </summary>
        /// <param name="sourceConnectionId">L'identificativo della connessione di provenienza della richiesta.</param>
        /// <param name="query">I dati della richiesta ricevuta da un vicino.</param>
        /// <remarks>
        /// Se si specifica null per almeno uno dei due parametri, questo metodo non esegue alcuna azione, ignorando
        /// i parametri specificati.
        /// </remarks>
        private void ForwardReceivedQuery(string sourceConnectionId, QueryData query)
        {
            Thread.Sleep(100);   // simula ritardo di rete

            if (string.IsNullOrWhiteSpace(sourceConnectionId) || query == null || query.TimeToLive == 0) return;

            query.TimeToLive--;
            query.HopsCount++;

            if (query.TimeToLive > 0)   // verifica se la query deve essere inoltrata
            {
                WriteToLog("Forwarding query {0}...", query.MsgId);

                lock (m_ForwardingTableLocker)
                {
                    if (!m_ForwardingTable.Add(query.MsgId, sourceConnectionId, null))
                    {
                        return;   // ignora messaggio duplicato
                    }
                }

                SendQuery(query, sourceConnectionId);   // inoltra messaggio ai restanti vicini
            }
            else
            {
                lock (m_ForwardingTableLocker)
                {
                    if (m_ForwardingTable.ContainsEntry(query.MsgId))
                    {
                        return;   // ignora messaggio duplicato
                    }
                }

                WriteToLog("Query {0} from connection {1} has expired.", query.MsgId, sourceConnectionId);
            }
        }
        /// <summary>
        /// Gestisce una richiesta ricevuta da un vicino, verificando se deve essere inoltrata ai restanti vicini ed
        /// elaborandola per fornire un'eventuale risposta destinata alla connessione di provenienza della richiesta.
        /// La procedura prevede l'aggiornamento preliminare di due proprietà relative alla richiesta: il TimeToLive
        /// viene decrementato di 1, mentre HopsCount viene incrementato di 1. Se il TimeToLive è diventato zero, la
        /// richiesta non viene inoltrata ai restanti vicini, altrimenti verifica che l'identificativo del messaggio
        /// non sia già presente nella tabella di inoltro, prima di inserirlo in essa ed inoltrare la richiesta agli
        /// altri vicini, per poi iniziare l'elaborazione in background della richiesta. Invece, se l'identificativo
        /// è già presente nella tabella di inoltro, la richiesta non viene né inoltrata né elaborata.
        /// </summary>
        /// <param name="sourceConnectionId">L'identificativo della connessione di provenienza della richiesta.</param>
        /// <param name="query">I dati della richiesta ricevuta da un vicino.</param>
        /// <remarks>
        /// Se si specifica null per almeno uno dei due parametri, questo metodo non esegue alcuna azione, ignorando
        /// i parametri specificati.
        /// </remarks>
        public void HandleReceivedQuery(string sourceConnectionId, QueryData query)
        {
            if (query.TimeToLive > 0)
            {
                Task.Factory.StartNew(() => ForwardReceivedQuery(sourceConnectionId, query),
                    CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);

                // Inizia l'elaborazione in background della richiesta.
                Task.Factory.StartNew(() => ReplyToQuery(query, sourceConnectionId),
                    CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
            }
        }