/// <summary>
 /// Instantiate a new PeerChunkAvailabilityUpdate
 /// </summary>
 /// <param name="sourceNetworkIdentifier">The source network identifier</param>
 /// <param name="itemCheckSum">The related DFS item checksum</param>
 /// <param name="chunkFlags">The chunk availability flags </param>
 public PeerChunkAvailabilityUpdate(string sourceNetworkIdentifier, string itemCheckSum, ChunkFlags chunkFlags)
 {
     this.SourceNetworkIdentifier = sourceNetworkIdentifier;
     this.ItemCheckSum = itemCheckSum;
     this.ChunkFlags = chunkFlags;
 }
 /// <summary>
 /// Updates local chunk flags with those provided.
 /// </summary>
 /// <param name="latestChunkFlags">The new chunk flags</param>
 public void UpdateFlags(ChunkFlags latestChunkFlags)
 {
     flags0 = latestChunkFlags.flags0;
     flags1 = latestChunkFlags.flags1;
     flags2 = latestChunkFlags.flags2;
     flags3 = latestChunkFlags.flags3;
 }
        /// <summary>
        /// Initialise a new PeerInfo
        /// </summary>
        /// <param name="peerConnectionInfo">All ConnectionInfos corresponding with this peer</param>
        /// <param name="peerChunkFlags">The initial ChunkFlags for this peer</param>
        /// <param name="superPeer">True if this is a SuperPeer</param>
        public PeerInfo(List<ConnectionInfo> peerConnectionInfo, ChunkFlags peerChunkFlags, bool superPeer)
        {
            if (peerConnectionInfo.Count == 0)
                throw new ArgumentException("Provided peerConnectionInfo list must contain at least one element.");

            this.PeerNetworkIdentifier = peerConnectionInfo[0].NetworkIdentifier;

            if (this.PeerNetworkIdentifier == null || this.PeerNetworkIdentifier == ShortGuid.Empty)
                throw new Exception("PeerInfo PeerNetworkIdentifier should not be empty.");

            foreach (ConnectionInfo info in peerConnectionInfo)
            {
                if (info.NetworkIdentifier != this.PeerNetworkIdentifier)
                    throw new Exception("The provided peerConnectionInfo contains more than one unique NetworkIdentifier.");

                //Add the necessary entries into status dictionaries
                IPEndPointBusyAnnounceTimeDict[info] = DateTime.Now;
                IPEndPointOnlineDict[info] = false;
                IPEndPointBusyDict[info] = false;
                IPEndPointTimeoutCountDict[info] = 0;
            }

            this.PeerConnectionInfo = peerConnectionInfo;
            this.PeerChunkFlags = peerChunkFlags;
            this.SuperPeer = superPeer;
        }
        /// <summary>
        /// Adds or updates a peer to the local availability list. Useful for when a peer informs us of an updated availability.
        /// </summary>
        /// <param name="connectionInfo">The connectionInfo of the remote peer</param>
        /// <param name="latestChunkFlags">The new chunk flags</param>
        /// <param name="superPeer">True if this peer is a superPeer</param>
        /// <param name="setIPEndPointOnline">Set the relevant IPEndPoint online as a result of updating chunk flags</param>
        public void AddOrUpdateCachedPeerChunkFlags(ConnectionInfo connectionInfo, ChunkFlags latestChunkFlags, bool superPeer = false, bool setIPEndPointOnline = true)
        {
            if (connectionInfo.NetworkIdentifier == ShortGuid.Empty) throw new Exception("networkIdentifier should not be empty.");

            lock (peerLocker)
            {
                if (connectionInfo.ConnectionType != ConnectionType.TCP) throw new Exception("Only the TCP side of a DFS peer should be tracked.");

                //Extract the correct endpoint from the provided connectionInfo
                //If this is taken from a connection we are after the remoteEndPoint
                IPEndPoint endPointToUse = null;
                if (((IPEndPoint)connectionInfo.RemoteEndPoint).Address == IPAddress.Any ||
                    ((IPEndPoint)connectionInfo.RemoteEndPoint).Address == IPAddress.IPv6Any)
                    endPointToUse = (IPEndPoint)connectionInfo.LocalEndPoint;
                else
                    endPointToUse = (IPEndPoint)connectionInfo.RemoteEndPoint;

                string endPointToUseString = endPointToUse.ToString();
                //We can only add a peer if it is listening correctly
                if (endPointToUse.Port <= DFS.MaxTargetLocalPort && endPointToUse.Port >= DFS.MinTargetLocalPort)
                {
                    //Ensure the endpoint is correctly recorded
                    RemoveOldPeerAtEndPoint(connectionInfo.NetworkIdentifier, endPointToUse);

                    //If we have an existing record of this peer
                    if (peerAvailabilityByNetworkIdentifierDict.ContainsKey(connectionInfo.NetworkIdentifier))
                    {
                        //If the existing peerInfo is not aware of this endPoint
                        if (!peerAvailabilityByNetworkIdentifierDict[connectionInfo.NetworkIdentifier].PeerContainsIPEndPoint(connectionInfo.NetworkIdentifier, endPointToUse))
                        {
                            //Add the information to the peerInfo and local index
                            peerAvailabilityByNetworkIdentifierDict[connectionInfo.NetworkIdentifier].AddPeerIPEndPoint(connectionInfo.NetworkIdentifier, endPointToUse);
                            peerEndPointToNetworkIdentifier[endPointToUseString] = connectionInfo.NetworkIdentifier;
                        }

                        //Finally update the chunk flags
                        peerAvailabilityByNetworkIdentifierDict[connectionInfo.NetworkIdentifier].PeerChunkFlags.UpdateFlags(latestChunkFlags);

                        if (DFS.loggingEnabled) DFS.Logger.Trace("Updated existing chunk flags for " + connectionInfo + " [" + latestChunkFlags.NumCompletedChunks() + "].");
                    }
                    else
                    {
                        //If we don't know anything about this peer we add it to our local swarm availability
                        //We used comms to get any existing connections to the peer
                        //We have to create new ConnectionInfo in the select as we need to correctly set the "LOCAL IPEndPoint" when passing to new PeerInfo()
                        List<ConnectionInfo> peerConnectionInfos = (from current in NetworkComms.GetExistingConnection(connectionInfo.NetworkIdentifier, ConnectionType.TCP) select new ConnectionInfo(ConnectionType.TCP, current.ConnectionInfo.NetworkIdentifier, current.ConnectionInfo.RemoteEndPoint, true)).ToList();
                        
                        //Don't forget to add the originating info if it was not pulled out from above
                        ConnectionInfo originatingConnectionInfo = new ConnectionInfo(ConnectionType.TCP, connectionInfo.NetworkIdentifier, endPointToUse, true);
                        if (!peerConnectionInfos.Contains(originatingConnectionInfo)) peerConnectionInfos.Add(originatingConnectionInfo);
                        
                        peerAvailabilityByNetworkIdentifierDict.Add(connectionInfo.NetworkIdentifier, new PeerInfo(peerConnectionInfos, latestChunkFlags, superPeer));

                        //We finish by adding the endPoint references
                        foreach (ConnectionInfo connInfo in peerConnectionInfos)
                            peerEndPointToNetworkIdentifier[connInfo.LocalEndPoint.ToString()] = connectionInfo.NetworkIdentifier;

                        if (DFS.loggingEnabled) DFS.Logger.Trace("Added new chunk flags for " + connectionInfo);
                    }

                    if (setIPEndPointOnline)
                        //By updating cached peer chunk flags we set the peer as being online
                        peerAvailabilityByNetworkIdentifierDict[connectionInfo.NetworkIdentifier].SetPeerIPEndPointOnlineStatus(connectionInfo.NetworkIdentifier, endPointToUse, true);

                    //We will trigger the alive peers event when we have at least a third of the existing peers
                    if (!alivePeersReceivedEvent.WaitOne(0))
                    {
                        int numOnlinePeers = (from current in peerAvailabilityByNetworkIdentifierDict.Values where current.HasAtleastOneOnlineIPEndPoint() select current).Count();
                        if (numOnlinePeers >= DFS.MaxTotalItemRequests || numOnlinePeers > peerAvailabilityByNetworkIdentifierDict.Count / 3.0)
                            alivePeersReceivedEvent.Set();
                    }
                }
                else
                    throw new Exception("Attempted to AddOrUpdateCachedPeerChunkFlags for client which was not listening or was using port outside the valid DFS range. Provided connectionInfo: " + connectionInfo + ". EndPointToUse:" + endPointToUse);
                    //LogTools.LogException("Attempted to AddOrUpdateCachedPeerChunkFlags for client which was not listening or was using port outside the valid DFS range.", "PeerChunkFlagsUpdateError", "IP:" + endPointToUse.Address.ToString() + ", Port:" + endPointToUse.Port);
            }
        }