using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Reflection;

using System.Runtime;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

using System.Threading;
using System.Net;


namespace DarkStrideToolbox
{
	[Serializable]
	public class DSSelfMappingNetwork
	{
		#region Properties
		private const long m_cMAXSIMULCONNECTS	= 5;
		private const string m_cUNKNOWN			= "<Unknown>";
		private const long m_cDEFAULTPORT		= 64134;

		private const string m_cINI_HOSTS		= "Host";
		
		private const string m_cINI_HOSTNAME	= "Name";
		private const string m_cINI_HOSTIP		= "IP";
		private const string m_cINI_HOSTPORT	= "Port";

		private const string m_cINI_MYGUID		= "GUID";
		private const string m_cINI_MYNAME		= "Name";
		private const string m_cINI_MYPORT		= "Port";
		private const string m_cINI_MYIP		= "IP";

		private int m_nTiming_MidCycleDelay				= 1000;
		private int m_nTiming_CompletedCycleDelay		= 10000;//60000;
		private int m_nTiming_FailedCycleDelay			= 1000;//10000;
		private int m_nTiming_DelayBetweenHookinTries	= 10000;//60000;
		private int m_nMaxSimultaniousConns				= 5;

		private DSSortedList m_oHookinPoints = new DSSortedList();
		private long m_nNextGUID = 0;

		private Thread m_oUpdateNetworkTieIn = null;
		private DSINIFile m_oINIFile = new DSINIFile();
		private IdentificationBuoy m_oBuoy = new IdentificationBuoy();

		//This is the collection of threads updating our users
		private DSSortedList m_oUsersBeingTiedTo = new DSSortedList();
		private DSSortedList m_oHookinsBeingTiedTo = new DSSortedList();

		private bool m_bSavingINI = false;
		private bool m_bDebugMode = false;
		private string m_sDebugFileName = "";
		#endregion

		#region Events
		public class UpdateUserEventArgs: EventArgs 
		{
			public UpdateUserEventArgs( User oUserToUpdate,bool bDeleteUser,bool bIsAHookin ) 
			{
				m_oUserToUpdate = oUserToUpdate;
				m_bDeleteUser = bDeleteUser;
				m_bIsAHookin = bIsAHookin;
			}
			private User m_oUserToUpdate = null;
			private bool m_bDeleteUser = false;
			private bool m_bIsAHookin = false;
			public User UserToUpdate
			{
				get
				{
					return( m_oUserToUpdate );
				}
			}
			public bool DeleteUser
			{
				get
				{
					return( m_bDeleteUser );
				}
			}
			public bool IsAHookin
			{
				get
				{
					return( m_bIsAHookin);
				}
			}
		}

		public event EventHandler UpdateUserEvent;

		protected void RaiseUpdateUserEvent( User oUserToUpdate,bool bDeleteUser,bool bIsAHookin )
		{
			UpdateUserEventArgs oArgs = null;

			if( UpdateUserEvent != null )
			{
				oArgs = new UpdateUserEventArgs( oUserToUpdate,bDeleteUser,bIsAHookin );
				UpdateUserEvent( null, oArgs );
			}
		}

		#endregion       		


		public DSSelfMappingNetwork()
		{
			//Add our own event callback into this object so we know when people update our info.
			m_oBuoy.UpdateUserListEvent += new EventHandler( this.SomeoneUpdatedOurUserList );
		}

		public void Dispose()
		{
			IDBuoyUpdateThread oUpdate = null;


			WriteDebug( 0,"Close Network" );

			if( m_oUpdateNetworkTieIn != null )
			{
				m_oUpdateNetworkTieIn.Abort();
				m_oUpdateNetworkTieIn = null;
			}

			while( m_oUsersBeingTiedTo.Count > 0 )
			{
				oUpdate = (IDBuoyUpdateThread)m_oUsersBeingTiedTo.GetByIndex( 0 );
				oUpdate.Dispose();
				m_oUsersBeingTiedTo.RemoveAt( 0 );
			}
		}


		public User CreateNewUserForUs( string sName,string sIP,long nPort )
		{
			User oUs = null;
			long nTempPort = 0;


			m_oINIFile.AddKey( m_cINI_MYNAME,sName );
			m_oINIFile.AddKey( m_cINI_MYGUID,sIP );
			m_oINIFile.AddKey( m_cINI_MYIP,sIP );

			if( nPort == 0 )
			{
				m_oINIFile.AddKey( m_cINI_MYPORT,m_cDEFAULTPORT.ToString() );
				nTempPort = m_cDEFAULTPORT;
			}
			else
			{
				m_oINIFile.AddKey( m_cINI_MYPORT,nPort.ToString() );
				nTempPort = nPort;
			}

			oUs = new User();
			oUs.Name = sName;
			oUs.IP = sIP;
			oUs.Port = nTempPort;
			oUs.GUID = Guid.NewGuid().ToString();
			oUs.Status = enumStatus.Online;
			oUs.TimeOfLatestInfo = DateTime.Now;
			oUs.TimeILoggedOn = DateTime.Now;
			
			m_oBuoy.AddUser( oUs );
			m_oBuoy.MyUser = oUs;

			WriteDebug( 1,"MyBuoy: Created new user Name=" + sName + " IP=" + sIP + " Port=" + nPort.ToString() );

			SaveSettings();


			return( oUs );
		}

		public void LaunchNetwork()
		{
			User oUser = null;


			WriteDebug( 0,"Launching Network" );

			//Did we find our player?
			if( m_oBuoy.MyUser == null )
			{
				throw new System.Exception( "No user has been specified." );
			}

			LaunchIDBuoy();

			//Update any users or hookins we have at the beginning
			for( int i=0 ; i<m_oBuoy.Users.Count ; i++ )
			{
				if( i >= m_oBuoy.Users.Count ){ break; }
				oUser = (User)m_oBuoy.Users.GetByIndex( i );
				RaiseUpdateUserEvent( oUser,false,false );
			}
			for( int i=0 ; i<m_oHookinPoints.Count ; i++ )
			{
				if( i >= m_oHookinPoints.Count ){ break; }
				oUser = (User)m_oHookinPoints.GetByIndex( i );
				RaiseUpdateUserEvent( oUser,false,false );
			}

			//Start our network updating
			m_oUpdateNetworkTieIn = new Thread(new ThreadStart(this.TieIntoTheNetwork));
			m_oUpdateNetworkTieIn.IsBackground = true;
			m_oUpdateNetworkTieIn.Start();		
		}

		private void LaunchIDBuoy()
		{
			int nPort = 0;			
			TcpChannel oServerChannel = null;
			BinaryServerFormatterSinkProvider oServerProv = null;
			BinaryClientFormatterSinkProvider oClientProv = null;
			IDictionary oProperties = null;


			try
			{
				WriteDebug( 1,"Launching ID Buoy" );

				//Configure customErrors
				//RemotingConfiguration.Configure("DarkStrideToolbox.dll.config");
				//http://dotnetjunkies.com/WebLog/chris.taylor/comments/5566.aspx
				System.Reflection.Assembly remoting = System.Reflection.Assembly.GetAssembly( typeof(System.Runtime.Remoting.RemotingConfiguration) );
				Type remotingConfigHandler = remoting.GetType("System.Runtime.Remoting.RemotingConfigHandler");
				Type customErrorsModes = remoting.GetType("System.Runtime.Remoting.CustomErrorsModes");
				FieldInfo errorMode = remotingConfigHandler.GetField( "_errorMode",BindingFlags.Static | BindingFlags.NonPublic );
				FieldInfo mode = customErrorsModes.GetField( "Off" );
				errorMode.SetValue( null, mode.GetValue( null ) );


				////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				//Step 1:  Create the client portion of the channel
				////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				//We have to specify the binary serialization method.  This is because in v1.0 sp3 and v1.1 versions 
				//of the .NET framework security was tightened.  Without this we will get an error whenever the host
				//sents us an event.  The error occurs because the event is sending back a custom object, its
				//DSClientListUpdate object.  Delegates don't need this formater on the client unless the delegate 
				//includes a non-standard type.
				oClientProv = new BinaryClientFormatterSinkProvider();
				//In order for us to receive events a port has to be explicitly specified.
				//oProperties = new Hashtable();
				//oProperties["port"] = 0;

				////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				//Step 2:  Create the server portion of the channel
				////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				//We'll use the port the user specified in the GUI
				nPort = Convert.ToInt32( m_oINIFile.GetValueForKey( m_cINI_MYPORT,m_cDEFAULTPORT.ToString() ) );
				oProperties = new Hashtable();
				oProperties["port"] = nPort;
				oProperties["name"] = "Primary SMN Buoy Channel";

				WriteDebug( 2,"Port = " + nPort.ToString() );

				//We have to set the type filter to full filter level.  This is because in v1.0 sp3 and v1.1 versions 
				//of the .NET framework security was tightened.  Without full filtering the callbacks will be blocked
				//by security precautions.  http://blogs.msdn.com/manishg/archive/2004/10/27/248841.aspx seems to hint
				//that this can open you up to some security vulnerabilities.
				oServerProv = new BinaryServerFormatterSinkProvider();
				oServerProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
				//Open and register our channel
				oServerChannel = new TcpChannel( oProperties,oClientProv,oServerProv );
				if( DSMisc.IsChannelRegistered( oServerChannel.ChannelName ) == false )
				{
					ChannelServices.RegisterChannel( oServerChannel );
				}

				////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				//Step 3: Register the service that publishes DbConnect for remote access in SingleCall mode
				////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				RemotingConfiguration.RegisterWellKnownServiceType( typeof(IdentificationBuoy),"IdentificationBuoy",WellKnownObjectMode.Singleton );

				////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				//Step 4: Setup our singleton buoy object
				////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				//This passes in our object as "THE" singleton object.  By doing this we are able to access the ClientReg
				//object and see what other users are registered.
				RemotingServices.Marshal( m_oBuoy, "IdentificationBuoy" );
			}
			catch( System.Exception oErr )
			{
				WriteDebug( 2,"Error: " + oErr.Message );
				throw new System.Exception( "Error trying to launch buoy failed (" + oErr.Message + ")" );
			}
		}
		

		public void LoadSettings( string sFileName )
		{
			string cSTRINGIFUSERNOTFOUND = "<UserNotFoundInINIFile>";
			User oUser = null;
			DSSortedList oLoopHost = null;
			DSSortedList oHosts = null;


			m_oINIFile.OpenINIFile( sFileName );

			//Load in our list of previous players then start the task of figuring out their statuses
			oHosts = m_oINIFile.GetClusterForKey( m_cINI_HOSTS );
			if( oHosts != null )
			{
				//Populate our list view
				for( int nLoopHost=0 ; nLoopHost<oHosts.Count ; nLoopHost++ )
				{
					oLoopHost = (DSSortedList)oHosts.GetByIndex( nLoopHost );
				
					oUser = new User();
					oUser.IP		= m_oINIFile.GetValueForKey( oLoopHost,m_cINI_HOSTIP,m_cUNKNOWN );
					oUser.Name		= m_oINIFile.GetValueForKey( oLoopHost,m_cINI_HOSTNAME,m_cUNKNOWN );
					oUser.Port		= Convert.ToInt32( m_oINIFile.GetValueForKey( oLoopHost,m_cINI_HOSTPORT,m_cDEFAULTPORT.ToString() ) );
					oUser.GUID	= m_oINIFile.GetValueForKey( oLoopHost,m_cINI_MYGUID,"" );
				
					if( oUser.GUID.Length > 0 )
					{
						m_oBuoy.AddUser( oUser );
					}
					else
					{
						oUser.GUID = m_nNextGUID.ToString();
						m_nNextGUID++;
						m_oHookinPoints.Add( oUser.GUID,oUser );
					}
				}
			}


			//Add us to our own buoy so we have a status we can update
			oUser = new User();
			if( m_oINIFile.GetValueForKey( m_cINI_MYNAME,cSTRINGIFUSERNOTFOUND ) != cSTRINGIFUSERNOTFOUND )
			{
				oUser.Name				= m_oINIFile.GetValueForKey( m_cINI_MYNAME,m_cUNKNOWN );
				oUser.IP				= m_oINIFile.GetValueForKey( m_cINI_MYIP,m_cUNKNOWN );
				oUser.Port				= Convert.ToInt32( m_oINIFile.GetValueForKey( m_cINI_MYPORT,m_cDEFAULTPORT.ToString() ) );			
				oUser.GUID				= m_oINIFile.GetValueForKey( m_cINI_MYGUID,"" );
				oUser.Status			= enumStatus.Online;
				oUser.TimeOfLatestInfo	= DateTime.Now;
				oUser.TimeILoggedOn		= DateTime.Now;

				m_oBuoy.AddUser( oUser );
				m_oBuoy.MyUser = oUser;
			}
		}

		public void SaveSettings()
		{
			User oUser = null;
			DSSortedList oLoopHost = null;
			DSSortedList oHosts = null;
			long nNextUniqueKey = 0;


			if( m_bSavingINI == false )
			{
				m_bSavingINI = true;


				//Go through and update all our users into the INI file
				m_oINIFile.RemoveKey( m_cINI_HOSTS );

				//Save our settings
				if( m_oBuoy.MyUser != null )
				{
					m_oINIFile.AddKey( m_cINI_MYNAME,m_oBuoy.MyUser.Name );
					m_oINIFile.AddKey( m_cINI_MYIP,m_oBuoy.MyUser.IP );
					m_oINIFile.AddKey( m_cINI_MYPORT,m_oBuoy.MyUser.Port.ToString() );
					m_oINIFile.AddKey( m_cINI_MYGUID,m_oBuoy.MyUser.GUID );
				}

				//Add our hosts
				oHosts = m_oINIFile.AddCluster( m_cINI_HOSTS );
				//Populate our list view
				for( int nLoopHost=0 ; nLoopHost<m_oBuoy.Users.Count ; nLoopHost++ )
				{
					oUser = (User)m_oBuoy.Users.GetByIndex( nLoopHost );
				
					if( oUser != m_oBuoy.MyUser )
					{
						oLoopHost = m_oINIFile.AddCluster( oHosts,oUser.GUID );

						m_oINIFile.AddKey( oLoopHost,m_cINI_HOSTNAME,oUser.Name );
						m_oINIFile.AddKey( oLoopHost,m_cINI_HOSTIP,oUser.IP );
						m_oINIFile.AddKey( oLoopHost,m_cINI_HOSTPORT,oUser.Port.ToString() );
						m_oINIFile.AddKey( oLoopHost,m_cINI_MYGUID,oUser.GUID );
					}
				}


				//Populate our list view
				for( int nLoopHost=0 ; nLoopHost<m_oHookinPoints.Count ; nLoopHost++ )
				{
					oUser = (User)m_oHookinPoints.GetByIndex( nLoopHost );
				
					oLoopHost = m_oINIFile.AddCluster( oHosts,"Hookin:" + nNextUniqueKey.ToString() );
					nNextUniqueKey++;

					m_oINIFile.AddKey( oLoopHost,m_cINI_HOSTIP,oUser.IP );
					m_oINIFile.AddKey( oLoopHost,m_cINI_HOSTPORT,oUser.Port.ToString() );
				}


				m_oINIFile.SaveINIFile();


				m_bSavingINI = false;
			}
		}


		public User AddHookin( string sIP,long nPort )
		{
			User oUser = null;
			IDBuoyUpdateThread oNewGuy = null;


			//Add us to our own buoy so we have a status we can update
			oUser = new User();
			oUser.Name = "<Hookin>";
			oUser.IP = sIP;
			if( nPort == 0 )
			{
				oUser.Port = m_cDEFAULTPORT;
			}
			else
			{
				oUser.Port = nPort;
			}
			oUser.GUID = m_nNextGUID.ToString();
			m_nNextGUID++;
			m_oHookinPoints.Add( oUser.GUID,oUser );

			//Kick off an auto update on this guy no matter what the network is doing to bring him into the fold
			oNewGuy = new IDBuoyUpdateThread( m_oBuoy,oUser );
			m_oHookinsBeingTiedTo.Add( oUser.GUID,oNewGuy );

			SaveSettings();

			return( oUser );
		}


		private void SomeoneUpdatedOurUserList(object sender, System.EventArgs e)
		{
			User oPendingUser = null;
			User oKnownUser = null;
			ArrayList oUsersToRaiseEventOn = new ArrayList();


			lock( m_oBuoy.PendingUsers )
			{
				for( int nLoopPendingUserIndex=0 ; nLoopPendingUserIndex<m_oBuoy.PendingUsers.Count ; nLoopPendingUserIndex++ )
				{
					oPendingUser = (User)m_oBuoy.PendingUsers.GetByIndex( nLoopPendingUserIndex );

					//First find out if we knew about this guy
					oKnownUser = (User)m_oBuoy.Users.GetByKey( oPendingUser.GUID );
					//This means we have a new user!
					if( oKnownUser == null )
					{
						//Add our user
						m_oBuoy.Users.Add( oPendingUser.GUID,oPendingUser );
						//RaiseUpdateUserEvent( oPendingUser,false,false );
						oUsersToRaiseEventOn.Add( oPendingUser );
					}
						//If the update they gave us is newer than ours then we need to update ours
					else if( oKnownUser != null &&
						oKnownUser.TimeOfLatestInfo < oPendingUser.TimeOfLatestInfo )
					{
						oKnownUser.UpdateUserFrom( oPendingUser );
						//RaiseUpdateUserEvent( oKnownUser,false,false );
						oUsersToRaiseEventOn.Add( oKnownUser );
					}

					m_oBuoy.RemovePendingUser( oPendingUser.GUID );
					nLoopPendingUserIndex--;
				}
			}

			//Raise our events
			for( int i=0 ; i<oUsersToRaiseEventOn.Count ; i++ )
			{
				RaiseUpdateUserEvent( (User)oUsersToRaiseEventOn[ i ],false,false );
			}

			if( oUsersToRaiseEventOn.Count > 0 )
			{
				SaveSettings();
			}
		}

		//Go through all our users and see if we are any of them buddies. 
		private void UpdateBuddiesWhoWereToConnectToUs()
		{
			User oUser = null;
			TimeSpan oDiff = TimeSpan.MinValue;


			for( int nUserIndex=0 ; nUserIndex<m_oBuoy.Users.Count ; nUserIndex++ )
			{
				if( nUserIndex < m_oBuoy.Users.Count ){ break; }
				oUser = (User)m_oBuoy.Users.GetByIndex( nUserIndex );

				if( oUser != null )
				{
					//Now are I in this guys list?
					for( int nBuddyIndex=0 ; nBuddyIndex<oUser.Buddies.Length ; nBuddyIndex++ )
					{
						if( nBuddyIndex < oUser.Buddies.Length ){ break; }

						//This means the user has set us as his buddy last time he connected.  Now, have we seen him 
						//since he last connected?
						if( oUser.Buddies[ nBuddyIndex ] == m_oBuoy.MyUser.GUID )
						{
							oDiff = DateTime.Now - oUser.TimeOfLatestInfo; 

							//This means its been to long since our "buddy" contacted us, write him off.
							if( oDiff.TotalSeconds > m_nTiming_CompletedCycleDelay )
							{
								if( DateTime.Now <= oUser.TimeOfLatestInfo )
								{
									oDiff = new TimeSpan( 0,0,0,1,0 );
									oUser.TimeOfLatestInfo = oUser.TimeOfLatestInfo + oDiff;;
								}
								else
								{
									oUser.TimeOfLatestInfo = DateTime.Now;
								}
								oUser.Status = enumStatus.Offline;
								RaiseUpdateUserEvent( oUser,false,false );
							}
						}
					}
				}
			}
		}


		public string GetStatus( User oUser )
		{
			string sRetVal = "";


			if( ( oUser.Status == enumStatus.Offline || oUser.Status == enumStatus.Unknown ) &&
				( m_oUsersBeingTiedTo.Contains( oUser.GUID ) == true || m_oHookinsBeingTiedTo.Contains( oUser.GUID ) == true ) )
			{
				sRetVal = "Connecting...";
			}
			else
			{
				sRetVal = oUser.Status.ToString();
			}


			return( sRetVal );
		}



		public void ActivityRemove( string sActivityGUID )
		{
			if( m_oBuoy.MyUser.UserActivity.ContainsKey( sActivityGUID ) == true )
			{
				m_oBuoy.MyUser.UserActivity.Remove( sActivityGUID );
			}
		}
		public void ActivityUpdate( string sActivityDesc,string sActivityGUID,object oActivityData )
		{
			UserActivity oUserActivity = null;


			//Find out if this activity is already listed
			oUserActivity = (UserActivity)m_oBuoy.MyUser.UserActivity.GetByKey( sActivityGUID );
			if( oUserActivity == null )
			{
				oUserActivity = new UserActivity();				
			}
			else
			{
				m_oBuoy.MyUser.UserActivity.Remove( sActivityGUID );
			}

			oUserActivity.Desc = sActivityDesc;
			oUserActivity.GUID = sActivityGUID;
			oUserActivity.Data = oActivityData;

			m_oBuoy.MyUser.UserActivity.Add( oUserActivity.GUID,oUserActivity );
		}
		public UserActivity ActivityGet( string sActivityGUID )
		{
			return( (UserActivity)m_oBuoy.MyUser.UserActivity.GetByKey( sActivityGUID ) );
		}
		public void UpdateMyStatus( enumStatus nStatus,
									string sActivityDesc,string sActivityGUID,object oActivityData,
									User oClientToUser )
		{
			m_oBuoy.MyUser.Status = nStatus;

			//Find out if this activity is already listed
			ActivityUpdate( sActivityDesc,sActivityGUID,oActivityData );

			if( oClientToUser != null )
			{
				m_oBuoy.MyUser.ClientingToGUID = oClientToUser.GUID;
			}
			else
			{
				m_oBuoy.MyUser.ClientingToGUID = "";
			}

			RaiseUpdateUserEvent( m_oBuoy.MyUser,false,false );
		}


		private void TieIntoTheNetwork()
		{
			bool bFirstRun = true;
			int nNextIndex = 0;
			int nMyLayer = 0;
			int nRndUser = 0;
			string sGUID = "";
			User oUser = null;
			DSSortedList oUnusedUsersThisCycle = new DSSortedList();
			DSSortedList oUnusedHookinsThisCycle = new DSSortedList();
			DSSortedList oGoodConnectsThisCycle = new DSSortedList();
			System.Collections.SortedList[] oaCatagorizedUsers = null;
			IDBuoyUpdateThread oUpdateThread = null;


			//Start us off by connecting to everyone
			oUnusedUsersThisCycle = m_oBuoy.Users.Clone();
            
			while( true )
			{
				//Update us
				m_oBuoy.MyUser.UpdateMyUser();
				//Resolve any pending connection attempts
				ResolvePendingTieInAttempts( oGoodConnectsThisCycle );
				//Launch some new connections
				LaunchNewTieInAttempts( oGoodConnectsThisCycle,oUnusedUsersThisCycle,oUnusedHookinsThisCycle );

				//Check the buddies who were supposed to update us
				UpdateBuddiesWhoWereToConnectToUs();

				//No matter what we have to come in to check on, this batch of call outs (even if its the last) gets 
				//to finish.  Or if we have users or hookins left to try then they get to finish too.
				if( m_oUsersBeingTiedTo.Count > 0 || oUnusedUsersThisCycle.Count > 0 || oUnusedHookinsThisCycle.Count > 0 || m_oHookinsBeingTiedTo.Count > 0 )
				{
					Thread.Sleep( m_nTiming_MidCycleDelay );
				}
				//Time to start over on our network sync up!  We successfully finished so were good.  If we are alone then
				//consider it a finished cycle as well.
				else if( oGoodConnectsThisCycle.Count > 0 || oUnusedUsersThisCycle.Count == 0 )
				{
					//Go to sleep and start the next cycle in a moment
					SaveSettings();
					if( bFirstRun == false )
					{
						Thread.Sleep( m_nTiming_CompletedCycleDelay );
					}
					bFirstRun = false;

					//////////////////////////////////////////////////////////////////////////////////////////////////
					//Start a new cycle!
					//////////////////////////////////////////////////////////////////////////////////////////////////
					oUnusedUsersThisCycle = m_oBuoy.Users.Clone();
					oUnusedHookinsThisCycle = m_oHookinPoints.Clone();
					//Make sure we don't try to connect to ourselves
					oUnusedUsersThisCycle.Remove( m_oBuoy.MyUser.GUID );


					//We need to make sure to connect again to anyone we considered a buddy last time
					for( int nBuddyIndex=0 ; nBuddyIndex<m_oBuoy.MyUser.Buddies.Length ; nBuddyIndex++ )
					{
						sGUID = (string)m_oBuoy.MyUser.Buddies[ nBuddyIndex ];
						if( sGUID != null )
						{
							oUser = (User)m_oBuoy.Users.GetByKey( sGUID );
							if( oUser != null && oUnusedUsersThisCycle.ContainsKey( oUser.GUID ) == true )
							{
								oUnusedUsersThisCycle.Remove( oUser.GUID );
								oUpdateThread = new IDBuoyUpdateThread( m_oBuoy,oUser );
								m_oUsersBeingTiedTo.Add( oUser.GUID,oUpdateThread );
								RaiseUpdateUserEvent( oUser,false,false );
							}
						}
					}
					//The last round was really a successful connection so we need to pick our next round of 2 buddies
					m_oBuoy.MyUser.Buddies = new string[ 2 ];
					nNextIndex = 0;

					//We can only do this if we have connections left
					if( oGoodConnectsThisCycle.Count > 0 )
					{
						nRndUser = DSMisc.GetRnd( 0,oGoodConnectsThisCycle.Count-1 );
						oUser = (User)oGoodConnectsThisCycle.GetByIndex( nRndUser );
						if( oUser != null && oUnusedUsersThisCycle.ContainsKey( oUser.GUID ) == true )
						{
							oUnusedUsersThisCycle.Remove( oUser.GUID );
							m_oBuoy.MyUser.Buddies[ nNextIndex ] = oUser.GUID;
							oUpdateThread = new IDBuoyUpdateThread( m_oBuoy,oUser );
							m_oUsersBeingTiedTo.Add( oUser.GUID,oUpdateThread );
							RaiseUpdateUserEvent( oUser,false,false );
							nNextIndex++;
						}
					}
					//We can only do this if we have connections left
					if( oGoodConnectsThisCycle.Count > 0 )
					{
						nRndUser = DSMisc.GetRnd( 0,oGoodConnectsThisCycle.Count-1 );
						oUser = (User)oGoodConnectsThisCycle.GetByIndex( nRndUser );
						if( oUser != null && oUnusedUsersThisCycle.ContainsKey( oUser.GUID ) == true )
						{
							oUnusedUsersThisCycle.Remove( oUser.GUID );
							m_oBuoy.MyUser.Buddies[ nNextIndex ] = oUser.GUID;
							oUpdateThread = new IDBuoyUpdateThread( m_oBuoy,oUser );
							m_oUsersBeingTiedTo.Add( oUser.GUID,oUpdateThread );
							RaiseUpdateUserEvent( oUser,false,false );
						}
					}
					oGoodConnectsThisCycle.Clear();
					//Now add 1 random parent... i.e. list master support. 
					oaCatagorizedUsers = CatigorizeListMasters( out nMyLayer );
					//-1 means we didn't find us and 0 means were the top, in both cases we can't a parent.
					if( nMyLayer > 0 )
					{
						nRndUser = DSMisc.GetRnd( 0,oaCatagorizedUsers[ nMyLayer-1 ].Count-1 );

						//Add my random parent
						oUser = (User)oaCatagorizedUsers[ nMyLayer-1 ].GetByIndex( nRndUser );
						if( oUser != null && oUnusedUsersThisCycle.ContainsKey( oUser.GUID ) == true )
						{
							oUnusedUsersThisCycle.Remove( oUser.GUID );
							oUpdateThread = new IDBuoyUpdateThread( m_oBuoy,oUser );
							m_oUsersBeingTiedTo.Add( oUser.GUID,oUpdateThread );
							RaiseUpdateUserEvent( oUser,false,false );
						}
					}
					/*//Clean up after ourselves
					for( int i=0 ; i<oaCatagorizedUsers.Length ; i++ )
					{
						oaCatagorizedUsers[ i ].Clear();
						oaCatagorizedUsers[ i ] = null;
					}*/
					oaCatagorizedUsers = null;
					//Now add a collection of 2 peers we haven't connected to in a while.  
					LaunchNewTieInAttemptsForOldestUsers( oUnusedUsersThisCycle,
														(int)DSMisc.Max( 2,m_cMAXSIMULCONNECTS-m_oUsersBeingTiedTo.Count ) );

					//////////////////////////////////////////////////////////////////////////////////////////////////
				}
				else
				{
					SaveSettings();
					Thread.Sleep( m_nTiming_FailedCycleDelay );
					oGoodConnectsThisCycle.Clear();
				}
			}
		}
		public bool ResolvePendingTieInAttempts( DSSortedList oGoodConnectsThisCycle )
		{
			IDBuoyUpdateThread oUpdateThread = null;
			bool bTieInSuccessful = false;


			WriteDebug( 2,"Resolving Pending Tie-In Attempts" );

			//Do we have any hookins were in the middle of?
			if( m_oHookinsBeingTiedTo.Count > 0 )
			{
				//Lets check up on our pending connections
				for( int nUserIndex=0 ; nUserIndex<m_oHookinsBeingTiedTo.Count ; nUserIndex++ )
				{
					if( nUserIndex>=m_oHookinsBeingTiedTo.Count ){ break; }

					oUpdateThread = (IDBuoyUpdateThread)m_oHookinsBeingTiedTo.GetByIndex( nUserIndex );
					WriteDebug( 3,"Update update attempt for Hookin=" + oUpdateThread.ConnectTo.Name + " IP=" + oUpdateThread.ConnectTo.IP + " Port=" + oUpdateThread.ConnectTo.Port.ToString() );

					if( oUpdateThread.UpdateDone == true )
					{
						//Was it successful?
						if( oUpdateThread.ConnectedSuccessfully == true )
						{
							WriteDebug( 4,"Is done and was successful" );
							m_oHookinPoints.Remove( oUpdateThread.ConnectTo.GUID );
							RaiseUpdateUserEvent( oUpdateThread.ConnectTo,true,true );
						}
						else
						{
							WriteDebug( 4,"Is done and failed: " + oUpdateThread.FailureMessage );
							oUpdateThread.ConnectTo.TimeOfLatestInfo = DateTime.Now;
							RaiseUpdateUserEvent( oUpdateThread.ConnectTo,false,false );
						}

						//Remove this guy, were done tracking him
						m_oHookinsBeingTiedTo.RemoveAt( nUserIndex );
					}
				}
			}

			//Do we have any users were in the middle of connecting to?
			if( m_oUsersBeingTiedTo.Count > 0 )
			{
				//Lets check up on our pending connections
				for( int nUserIndex=0 ; nUserIndex<m_oUsersBeingTiedTo.Count ; nUserIndex++ )
				{
					if( nUserIndex>=m_oUsersBeingTiedTo.Count ){ break; }

					oUpdateThread = (IDBuoyUpdateThread)m_oUsersBeingTiedTo.GetByIndex( nUserIndex );
					WriteDebug( 3,"Update update attempt for Name=" + oUpdateThread.ConnectTo.Name + " IP=" + oUpdateThread.ConnectTo.IP + " Port=" + oUpdateThread.ConnectTo.Port.ToString() );

					if( oUpdateThread.UpdateDone == true )
					{
						//Remove this guy, were done tracking him
						m_oUsersBeingTiedTo.RemoveAt( nUserIndex );

						//Was it successful?
						if( oUpdateThread.ConnectedSuccessfully == true && oGoodConnectsThisCycle.Contains( oUpdateThread.ConnectTo.GUID ) == false )
						{
							WriteDebug( 4,"Is done and was successful" );

							//Record this user as a successful tie-in
							oGoodConnectsThisCycle.Add( oUpdateThread.ConnectTo.GUID,oUpdateThread.ConnectTo );
							RaiseUpdateUserEvent( oUpdateThread.ConnectTo,false,false );
						}
						else
						{
							WriteDebug( 4,"Is done and failed: " + oUpdateThread.FailureMessage );
						}
					}
				}
			}


			return( bTieInSuccessful );
		}

		public void LaunchNewTieInAttemptsForOldestUsers( DSSortedList oUnusedUsersThisCycle,int nNumPeers )
		{
			User oUser = null;
			IDBuoyUpdateThread oUpdateThread = null;
			System.Collections.ArrayList oaSortedUsers = null;


			oaSortedUsers = GetAscSortedUserList_ByLastConnectTime( oUnusedUsersThisCycle,nNumPeers );

			for( int nCount=0 ; nCount<nNumPeers && nCount<oaSortedUsers.Count ; nCount++ )
			{
				//Find us the oldest user we haven't talked to
				oUser = (User)oaSortedUsers[ nCount ];
				
				//Kick off a checkup on that user
				oUpdateThread = new IDBuoyUpdateThread( m_oBuoy,oUser );
				//Add our user
				oUnusedUsersThisCycle.Remove( oUser.GUID );
				m_oUsersBeingTiedTo.Add( oUser.GUID,oUpdateThread );
				RaiseUpdateUserEvent( oUser,false,false );
			}
		}
		public void LaunchNewTieInAttempts( DSSortedList oGoodConnectsThisCycle,
											DSSortedList oUnusedUsersThisCycle,DSSortedList oUnusedHookinsThisCycle )
		{
			int nNewUserIndex = 0;
			TimeSpan tdDiff = TimeSpan.Zero;
			User oUser = null;
			IDBuoyUpdateThread oUpdateThread = null;
			System.Collections.ArrayList oaSortedUsers = null;


			WriteDebug( 1,"Launch new Tie-in attempts" );

			oaSortedUsers = GetAscSortedUserList_ByLastConnectTime( oUnusedUsersThisCycle,oUnusedUsersThisCycle.Count );

			//This means its time to kick off X more connections.  If we reach the end of the list then we've tried
			//everybody once and thats good enough.
			while( oUnusedUsersThisCycle.Count > 0 && m_oUsersBeingTiedTo.Count + m_oHookinsBeingTiedTo.Count < m_nMaxSimultaniousConns )
			{
				oUser = (User)oaSortedUsers[ nNewUserIndex ];

				oUnusedUsersThisCycle.Remove( oUser.GUID );
					
				//If we failed to connect so far then anyone will do.  But if we succeded then only check up on the unavailable people
				if( ( oGoodConnectsThisCycle.Count == 0 || oUser.Status == enumStatus.Unknown ) && oUser != m_oBuoy.MyUser )
				{
					WriteDebug( 2,"Attempting to connect to " + oUser.Name + " at " + oUser.IP + ", " + oUser.Port.ToString() + " (" + oUser.GUID + ")" );

					//Kick off a checkup on that user
					oUpdateThread = new IDBuoyUpdateThread( m_oBuoy,oUser );						
					oUnusedUsersThisCycle.Remove( oUser.GUID );
					//Add our user
					m_oUsersBeingTiedTo.Add( oUser.GUID,oUpdateThread );
					//RaiseUpdateUserEvent( oUser,false,false );
				}

				nNewUserIndex++;
			}


			//Do we have any open connections?  If so then check in with our hookin points
			while( oUnusedHookinsThisCycle.Count > 0 && m_oUsersBeingTiedTo.Count + m_oHookinsBeingTiedTo.Count < m_nMaxSimultaniousConns )
			{
				//Get our user and remove them from the potentials
				oUser = (User)oUnusedHookinsThisCycle.GetByIndex( 0 );
				oUnusedHookinsThisCycle.Remove( oUser.GUID );
				tdDiff = DateTime.Now - oUser.TimeILastTriedToConnectedToThem;

				if( tdDiff.TotalMilliseconds >= m_nTiming_DelayBetweenHookinTries )
				{
					WriteDebug( 2,"Attempting to connect to hookin at " + oUser.IP + ", " + oUser.Port.ToString() + " (" + oUser.GUID + ")" );

					//Kick off a checkup on that unused hookin point
					oUpdateThread = new IDBuoyUpdateThread( m_oBuoy,oUser );					
					//Add our user
					m_oHookinsBeingTiedTo.Add( oUser.GUID,oUpdateThread );
					//RaiseUpdateUserEvent( oUser,false,true );
				}
			}
		}

		//The purpose of this function is to layout who is whom's parent.  A parent structure is a tyep of list master
		//implimentation which allows quicky network syncs.  We could rate people on several characteristics, but for now
		//its the amount of time online only.
		public System.Collections.SortedList[] CatigorizeListMasters( out int nMyLayer )
		{
			User oLoopUser = null;
			System.Collections.Stack oGroupSize = new System.Collections.Stack();
			System.Collections.ArrayList oaUsers = null;
			System.Collections.SortedList[] oaCatagorized = null;
			long nUsers = m_oBuoy.Users.Count;
			long nParentSize = 0;
			long nThisLayerSize = 0;
			int nLayer = 0;
			int nUserIndex = 0;


			nMyLayer = -1;

			//Calculate how many layers we'll have
			while( nUsers > 0 )
			{
				nParentSize = (long)Math.Ceiling( (double)nUsers / 10.0 );
				nThisLayerSize = nUsers - nParentSize;
				if( nThisLayerSize == 0 )
				{
					nThisLayerSize = nUsers;
				}
				oGroupSize.Push( nThisLayerSize );
				nUsers -= nThisLayerSize;
			}

			//Sort our list for easy breakup
			oaUsers = GetAscSortedUserList_ByTimeOnline( m_oBuoy.Users );

			//Spit out our users to their appropreate layer
			nLayer = 0;
			nUserIndex = oaUsers.Count-1;
			oaCatagorized = new System.Collections.SortedList[ oaUsers.Count ];
			while( oGroupSize.Count > 0 )
			{
				nThisLayerSize = (long)oGroupSize.Pop();
				oaCatagorized[ nLayer ] = new SortedList();
				for( int i=0 ; i<nThisLayerSize ; i++ )
				{
					oLoopUser = (User)oaUsers[ nUserIndex ];
					nUserIndex--;
					oaCatagorized[ nLayer ].Add( oLoopUser.GUID,oLoopUser );

					if( m_oBuoy.MyUser != null && oLoopUser.GUID == m_oBuoy.MyUser.GUID )
					{
						nMyLayer = nLayer;
					}
				}
				nLayer++;
			}

			//Clean up our users
			oaUsers.Clear();
			oaUsers = null;


			return( oaCatagorized );
		}
		public System.Collections.ArrayList GetAscSortedUserList_ByLastConnectTime( DSSortedList oaUsers,int nNumToFind )
		{
			DSSortedList oSource = oaUsers.Clone();
			System.Collections.ArrayList oaSortedUsers = new System.Collections.ArrayList();
			DateTime dtOldest = DateTime.MaxValue;
			User oUser = null;
			int nOldestIndex = -1;
			int nTempNumToFind = nNumToFind;


			while( oSource.Count > 0 && nTempNumToFind > 0 )
			{
				//Find the oldest item
				nOldestIndex = -1;
				for( int i=0 ; i<oSource.Count ; i++ )
				{
					oUser = (User)oSource.GetByIndex( i );
					if( oUser.TimeILastTriedToConnectedToThem < dtOldest || nOldestIndex == -1 )
					{
						nOldestIndex = i;
						dtOldest = oUser.TimeILastTriedToConnectedToThem;
					}
				}

				//Now add our new oldest record
				oaSortedUsers.Add( oSource.GetByIndex( nOldestIndex ) );
				oSource.RemoveAt( nOldestIndex );

				nTempNumToFind--;
			}

			
			return( oaSortedUsers );
		}
		public System.Collections.ArrayList GetAscSortedUserList_ByTimeOnline( DSSortedList oaUsers )
		{
			DSSortedList oSource = oaUsers.Clone();
			System.Collections.ArrayList oaSortedUsers = new System.Collections.ArrayList();
			User oUser = null;
			double nOldest = 0;
			int nOldestIndex = -1;


			while( oSource.Count > 0 )
			{
				//Find the oldest item
				nOldestIndex = -1;
				for( int i=0 ; i<oSource.Count ; i++ )
				{
					oUser = (User)oSource.GetByIndex( i );
					if( oUser.TimeOnline < nOldest || nOldestIndex == -1 )
					{
						nOldestIndex = i;
						nOldest = oUser.TimeOnline;
					}
				}

				//Now add our new oldest record
				oaSortedUsers.Add( oSource.GetByIndex( nOldestIndex ) );
				oSource.RemoveAt( nOldestIndex );
			}

			
			return( oaSortedUsers );
		}


		private void WriteDebug( int nTabs, string sMessage )
		{
			if( m_sDebugFileName.Length == 0 )
			{
				m_sDebugFileName = DSMisc.GetDevelopmentAppPath() + "SMNDebug_" + DateTime.Now.ToString( "yyyyMMdd_hhmm" ) + ".log";
			}

			DSMisc.WriteToDebugFile( m_sDebugFileName, m_bDebugMode, true, nTabs, sMessage );
		}



		#region Properties
		public DSSortedList KnownUsers
		{
			get
			{
				return( m_oBuoy.Users );
			}
		}
		public DSSortedList HookinPoints
		{
			get
			{
				return( m_oHookinPoints );
			}
		}
		public int MaxSimultaniousConns
		{
			get
			{
				return( m_nMaxSimultaniousConns );
			}
			set
			{
				m_nMaxSimultaniousConns = value;
			}
		}
		public User MyUser
		{
			get
			{
				return( m_oBuoy.MyUser );
			}
		}
		public bool DebugMode
		{
			get
			{
				return( m_bDebugMode );
			}
			set
			{
				m_bDebugMode = value;
			}
		}
		#endregion
	}
}
