using System;
using System.Collections;
using System.IO;
using System.Threading;

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


namespace DarkStrideToolbox
{
	[Serializable]
	public class DSFileTransferEventArgs : EventArgs 
	{
		#region Properties
		private double m_nProgress;
		private string m_sUniqueFileName;
		private long m_nTransferID;
		#endregion

		public DSFileTransferEventArgs( long nTransferID,string sUniqueFileName,double nProgress)
		{
			m_nTransferID = nTransferID;
			m_sUniqueFileName = sUniqueFileName;
			m_nProgress = nProgress;
		}


		#region Properties
		public double Progress
		{
			get
			{
				return( m_nProgress );
			}
			set
			{
				m_nProgress = value;
			}
		}
		public string UniqueFileName
		{
			get
			{
				return( m_sUniqueFileName );
			}
			set
			{
				m_sUniqueFileName = value;
			}
		}
		public long TransferID
		{
			get
			{
				return( m_nTransferID );
			}
			set
			{
				m_nTransferID = value;
			}
		}
		#endregion
	}
	public class DSFileTransferErrorEventArgs : EventArgs 
	{
		#region Properties
		private double m_nProgress;
		private string m_sUniqueFileName;
		private long m_nTransferID;
		private string m_sError;
		#endregion

		public DSFileTransferErrorEventArgs( long nTransferID,string sUniqueFileName,double nProgress,string sError )
		{
			m_nTransferID = nTransferID;
			m_sUniqueFileName = sUniqueFileName;
			m_nProgress = nProgress;
			m_sError = sError;
		}


		#region Properties
		public double Progress
		{
			get
			{
				return( m_nProgress );
			}
			set
			{
				m_nProgress = value;
			}
		}
		public string UniqueFileName
		{
			get
			{
				return( m_sUniqueFileName );
			}
			set
			{
				m_sUniqueFileName = value;
			}
		}
		public long TransferID
		{
			get
			{
				return( m_nTransferID );
			}
			set
			{
				m_nTransferID = value;
			}
		}
		public string Error
		{
			get
			{
				return( m_sError );
			}
			set
			{
				m_sError = value;
			}
		}
		#endregion
	}


	public class DSFileServer : MarshalByRefObject
	{
		#region Properties
		public System.Collections.ArrayList m_oSharedFiles = new System.Collections.ArrayList();
		private long m_nNextUniqueFileTransferID = 1;

		private TcpChannel m_oServerChannel = null;

		public event EventHandler FileTransferStarted;
		public event EventHandler FileTransferUpdate;
		public event EventHandler FileTransferCompleted;

		protected void RaiseFileTransferStartedEvent( long nTransferID,string sUniqueFileName,double nProgress )
		{
			DSFileTransferEventArgs oArgs = null;

			if( FileTransferStarted != null )
			{
				oArgs = new DSFileTransferEventArgs( nTransferID,sUniqueFileName,nProgress );
				FileTransferStarted( null, oArgs );
			}
		}
		protected void RaiseFileTransferUpdateEvent( long nTransferID,string sUniqueFileName,double nProgress )
		{
			DSFileTransferEventArgs oArgs = null;

			if( FileTransferUpdate != null )
			{
				oArgs = new DSFileTransferEventArgs( nTransferID,sUniqueFileName,nProgress );
				FileTransferUpdate( null, oArgs );
			}
		}
		protected void RaiseFileTransferCompletedEvent( long nTransferID,string sUniqueFileName,double nProgress )
		{
			DSFileTransferEventArgs oArgs = null;

			if( FileTransferCompleted != null )
			{
				oArgs = new DSFileTransferEventArgs( nTransferID,sUniqueFileName,nProgress );
				FileTransferCompleted( null, oArgs );
			}
		}
		#endregion


		public DSFileServer() 
		{
		}

		public override object InitializeLifetimeService()
		{
			return( null );
		}


		public void StartFileServer( string sServerIP,long nServerPort )
		{
			BinaryServerFormatterSinkProvider oServerProv = null;
			BinaryClientFormatterSinkProvider oClientProv = null;
			IDictionary oProperties = null;


			//We'll use the port the user specified in the GUI
			oProperties = new Hashtable();
			oProperties["port"] = nServerPort;
			oProperties["name"] = "Primary File Server Channel (" + nServerPort.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
			m_oServerChannel = DSMisc.GetRegisteredChannel( (string)oProperties["name"] );
			if( m_oServerChannel == null )
			{
				m_oServerChannel = new TcpChannel( oProperties,oClientProv,oServerProv );
				ChannelServices.RegisterChannel( m_oServerChannel );
			}
 
			//Step 3: Register the service that publishes DbConnect for remote access in SingleCall mode
			RemotingConfiguration.RegisterWellKnownServiceType( typeof(DSFileServer),"DSFileServer(" + nServerPort.ToString() + ")",WellKnownObjectMode.Singleton );

			//Step 4: Setup our singleton buoy object
			RemotingServices.Marshal( this,"DSFileServer(" + nServerPort.ToString() + ")" );
		}

		public void StopFileServer()
		{
			DSSharedFile oFile = null;


			if( m_oServerChannel != null )
			{
				ChannelServices.UnregisterChannel( m_oServerChannel );
				m_oServerChannel = null;


				while( m_oSharedFiles.Count > 0 )
				{
					oFile = (DSSharedFile)m_oSharedFiles[ 0 ];
					oFile.ReleaseFileDetails();
					m_oSharedFiles.RemoveAt( 0 );
				}
			}
		}
		public void HostFile( string sPathToFile,string sUniqueFileName,string sFileDescription )
		{
			DSSharedFile oNewFile = null;


			oNewFile = new DSSharedFile();
			oNewFile.FilePath = sPathToFile;
			oNewFile.UniqueFileName = sUniqueFileName;
			oNewFile.FileDescription = sFileDescription;

			oNewFile.GetFileDetails();

			m_oSharedFiles.Add( oNewFile );
		}

		public void StopHostingFile( string sUniqueFileName )
		{
			DSSharedFile oFile = null;


			for( int i=0 ; i<m_oSharedFiles.Count ; i++ )
			{
				if( i >= m_oSharedFiles.Count ){ break; }
				oFile = (DSSharedFile)m_oSharedFiles[ i ];

				if( oFile.UniqueFileName == sUniqueFileName )
				{
					oFile.ReleaseFileDetails();
					m_oSharedFiles.RemoveAt( i );
				}
			}
		}

		public long StartFileTransfer( string sUniqueFileName )
		{
			RaiseFileTransferStartedEvent( m_nNextUniqueFileTransferID,sUniqueFileName,0 );
			return( m_nNextUniqueFileTransferID++ );
		}
		public DSSharedFile GetSharedFile( string sUniqueFileName )
		{
			DSSharedFile oSharedFile = null;
			DSSharedFile oLoopSharedFile = null;


			for(int nIndex=0 ; nIndex<m_oSharedFiles.Count ; nIndex++ )
			{
				oLoopSharedFile = (DSSharedFile)m_oSharedFiles[ nIndex ];
				if( oLoopSharedFile.UniqueFileName == sUniqueFileName )
				{
					oSharedFile = oLoopSharedFile;
					break;
				}
			}


			return( oSharedFile );
		}
		public byte[] GetFileChunk( long nUniqueTransferID,string sUniqueFileName,int nCurrentPosition )//,out int nNewPosition )
		{
			byte[] oaRetVal = null;
			double nPerc = 0;
			DSSharedFile oSharedFile = null;


			oSharedFile = GetSharedFile( sUniqueFileName );
			if( oSharedFile != null )
			{
				oaRetVal = oSharedFile.GetFileChunk( nCurrentPosition,1024 );//,out nNewPosition );
//				nNewPosition = nCurrentPosition + 1024;
				oSharedFile.UpdateTransferProgress( nUniqueTransferID,nCurrentPosition+oaRetVal.Length );

				nPerc = (double)(nCurrentPosition+oaRetVal.Length)/(double)oSharedFile.FileSize;
				if( nPerc >= 1 )
				{
					RaiseFileTransferCompletedEvent( nUniqueTransferID,sUniqueFileName,nPerc );
				}
				else
				{
					RaiseFileTransferUpdateEvent( nUniqueTransferID,sUniqueFileName,nPerc );
				}
			}
			else
			{
				//nNewPosition = nCurrentPosition;
				throw new System.Exception( "A client has requested the file <" + sUniqueFileName + "> but that file is not currently being served." );
			}


			return( oaRetVal );
		}



		#region Properties
		public ArrayList SharedFileList
		{
			get
			{
				ArrayList oCopyOfShares = null;

				lock( m_oSharedFiles )
				{
					oCopyOfShares = (ArrayList)m_oSharedFiles.Clone();
				}

				return( oCopyOfShares );
			}
		}
		#endregion
	}


	[Serializable]
	public class DSSharedFile
	{
		#region Properties
		private long m_nFileSize = 0;
		private FileStream m_oFileStream = null;
		private string m_sFilePath = "";
		private string m_sUniqueFileName = "";
		private string m_sFileDescription = "";
		private SortedList m_oTransfersInProgress = new SortedList();
		#endregion


		public DSSharedFile()
		{
		}

		public void GetFileDetails()
		{
			FileInfo oFileInfo = null;

			try
			{
				m_oFileStream = new FileStream( m_sFilePath,FileMode.Open,FileAccess.Read );

				oFileInfo = new FileInfo( m_sFilePath );
				m_nFileSize = oFileInfo.Length;
			}
			catch( System.IO.FileNotFoundException oEx )
			{
				throw new System.IO.FileNotFoundException( "Unable to open file <" + m_sFilePath + "> or unable to lock file for the file server.",oEx );
			}
		}

		public void ReleaseFileDetails()
		{
			if( m_oFileStream != null )
			{
				m_oFileStream.Close();
				m_oFileStream = null;
			}
		}
		public void UpdateTransferProgress( long nUniqueTransferID,int nCurrentPosition )
		{
			int nIndex = 0;
			DSFileTransfer oTransfer = null;

			lock( m_oTransfersInProgress )
			{
				nIndex = m_oTransfersInProgress.IndexOfKey( nUniqueTransferID );
				if( nIndex != -1 )
				{
					oTransfer = (DSFileTransfer)m_oTransfersInProgress.GetByIndex( nIndex );
				}
				else
				{
					oTransfer = new DSFileTransfer();
					oTransfer.UniqueFileTransferID = nUniqueTransferID;					
					m_oTransfersInProgress.Add( nUniqueTransferID,oTransfer );
				}
				oTransfer.CurrentPosition = nCurrentPosition;

				//Did we just finish this transfer?
				if( oTransfer.CurrentPosition == oTransfer.EndFileSize )
				{
					m_oTransfersInProgress.Remove( nUniqueTransferID );
				}
			}
		}
		public byte[] GetFileChunk( int nCurrentPosition,int nAmountToGrab )//,out int nNewPosition )
		{
			int nResult = 0;
			byte[] oaBytes = new byte[ nAmountToGrab ];
			byte[] oaTempBytes = null;

			m_oFileStream.Seek( nCurrentPosition,System.IO.SeekOrigin.Begin );
			nResult = m_oFileStream.Read( oaBytes,0,oaBytes.Length );

			if( nResult < nAmountToGrab )
			{
				oaTempBytes = new byte[ nResult ];
				for( int i=0 ; i<nResult ; i++ )
				{
					oaTempBytes[ i ] = oaBytes[ i ];
				}
				oaBytes = oaTempBytes;
			}
			//nNewPosition = nCurrentPosition + nAmountToGrab;

			return( oaBytes );
		}



		#region Properties
		public long FileSize
		{
			get
			{
				return( m_nFileSize );
			}
			set
			{
				m_nFileSize = value;
			}
		}
		public string FilePath
		{
			get
			{
				return( m_sFilePath );
			}
			set
			{
				m_sFilePath = value;
			}
		}
		public string FileDescription
		{
			get
			{
				return( m_sFileDescription );
			}
			set
			{
				m_sFileDescription = value;
			}
		}
		public string UniqueFileName
		{
			get
			{
				return( m_sUniqueFileName );
			}
			set
			{
				m_sUniqueFileName = value;
			}
		}
		public SortedList TransfersInProgress
		{
			get
			{
				SortedList oCopyOfTransfers = null;

				lock( m_oTransfersInProgress )
				{
					oCopyOfTransfers = (SortedList)m_oTransfersInProgress.Clone();
				}

				return( oCopyOfTransfers );
			}

		}
		#endregion
	}


	[Serializable]
	public class DSFileTransfer
	{
		#region Properties
		private FileStream m_oFileStream = null;
		private long m_nUniqueFileTransferID = -1;
		private int m_nCurrentPosition = 0;
		private string m_sUniqueFileName = "";
		private string m_sDestinationPath = "";
		private long m_nEndFileSize = 0;
		#endregion


		public DSFileTransfer()
		{
		}

		public void CreateDestinationFile()
		{
			m_oFileStream = new FileStream( m_sDestinationPath,FileMode.Create,FileAccess.Write );
		}

		public void WriteFileChunk( byte[] oaBytes )
		{
			m_oFileStream.Write( oaBytes,0,oaBytes.Length );
			m_nCurrentPosition += oaBytes.Length;
		}

		public void FileTransferComplete()
		{
			m_oFileStream.Close();
		}



		#region Properties
		public long UniqueFileTransferID
		{
			get
			{
				return( m_nUniqueFileTransferID );
			}
			set
			{
				m_nUniqueFileTransferID = value;
			}
		}
		public int CurrentPosition
		{
			get
			{
				return( m_nCurrentPosition );
			}
			set
			{
				m_nCurrentPosition = value;
			}
		}
		public string UniqueFileName
		{
			get
			{
				return( m_sUniqueFileName );
			}
			set
			{
				m_sUniqueFileName = value;
			}
		}
		public string DestinationPath
		{
			get
			{
				return( m_sDestinationPath );
			}
			set
			{
				m_sDestinationPath = value;
			}
		}
		public long EndFileSize
		{
			get
			{
				return( m_nEndFileSize );
			}
			set
			{
				m_nEndFileSize = value;
			}
		}
		public double Progress
		{
			get
			{
				return( (double)m_nCurrentPosition / (double)m_nEndFileSize );
			}
		}
		#endregion
	}


	public class DSFileClient
	{
		#region Properties
		private bool m_bShuttingDown = false;
		private Thread m_oFileTransferThread = null;
		private ArrayList m_oFileTransfersInProgress = new ArrayList();
		private DSFileServer m_oFileServer = null;
		private TcpChannel m_oClientChannel = null;

		public event EventHandler FileTransferStarted;
		public event EventHandler FileTransferUpdate;
		public event EventHandler FileTransferCompleted;
		public event EventHandler FileTransferFailed;

		protected void RaiseFileTransferStartedEvent( long nTransferID,string sUniqueFileName,double nProgress )
		{
			DSFileTransferEventArgs oArgs = null;

			if( FileTransferStarted != null )
			{
				oArgs = new DSFileTransferEventArgs( nTransferID,sUniqueFileName,nProgress );
				FileTransferStarted( null, oArgs );
			}
		}
		protected void RaiseFileTransferUpdateEvent( long nTransferID,string sUniqueFileName,double nProgress )
		{
			DSFileTransferEventArgs oArgs = null;

			if( FileTransferUpdate != null )
			{
				oArgs = new DSFileTransferEventArgs( nTransferID,sUniqueFileName,nProgress );
				FileTransferUpdate( null, oArgs );
			}
		}
		protected void RaiseFileTransferCompletedEvent( long nTransferID,string sUniqueFileName,double nProgress )
		{
			DSFileTransferEventArgs oArgs = null;

			if( FileTransferCompleted != null )
			{
				oArgs = new DSFileTransferEventArgs( nTransferID,sUniqueFileName,nProgress );
				FileTransferCompleted( null, oArgs );
			}
		}
		protected void RaiseFileTransferFailedEvent( long nTransferID,string sUniqueFileName,double nProgress,string sFailureMessage )
		{
			DSFileTransferErrorEventArgs oArgs = null;

			if( FileTransferFailed != null )
			{
				oArgs = new DSFileTransferErrorEventArgs( nTransferID,sUniqueFileName,nProgress,sFailureMessage );
				FileTransferFailed( null, oArgs );
			}
		}		
		#endregion


		public DSFileClient()
		{
			m_oFileTransferThread = new Thread( new ThreadStart( this.FileTransferLoop ) );
			m_oFileTransferThread.IsBackground = true;
			m_oFileTransferThread.Start();
		}
		~DSFileClient()
		{
			Dispose();
		}
		public void Dispose()
		{
			m_bShuttingDown = true;

			if( m_oFileTransferThread != null )
			{
				//m_oFileTransferThread.Suspend();
				//m_oFileTransferThread.Abort();
				m_oFileTransferThread = null;
			}

			if( m_oClientChannel != null )
			{
				try
				{
					ChannelServices.UnregisterChannel( m_oClientChannel );
				}
				catch{}
				m_oFileTransferThread = null;
			}
		}


		private void FileTransferLoop()
		{
			bool bFailed = false;
			double nPerc = 0;
			byte[] oaBytes = null;
			ArrayList oLocalArrayCopy = null;
			DSFileTransfer oLoopFileTran = null;


			try
			{
				while( m_bShuttingDown == false )
				{
					if( m_oFileTransfersInProgress.Count == 0 )
					{
						Thread.Sleep( 1000 );
					}
					else
					{
						lock( m_oFileTransfersInProgress )
						{
							oLocalArrayCopy = (ArrayList)m_oFileTransfersInProgress.Clone();
						}

						for( int nTran=0 ; nTran<oLocalArrayCopy.Count && m_bShuttingDown == false ; nTran++ )
						{
							if( nTran >= oLocalArrayCopy.Count ){ break; }

							oLoopFileTran = (DSFileTransfer)oLocalArrayCopy[ nTran ];

							bFailed = false;
							try
							{
								oaBytes = m_oFileServer.GetFileChunk( oLoopFileTran.UniqueFileTransferID, oLoopFileTran.UniqueFileName,oLoopFileTran.CurrentPosition );
							}
							catch( System.Exception oEx )
							{
								//Fail qietly or make a ruccus?  Either way don't loose our server.
								bFailed = true;
								//throw new System.Exception( "File server connection has been lost.",oEx );

								oLoopFileTran.FileTransferComplete();
	 
								RaiseFileTransferFailedEvent( oLoopFileTran.UniqueFileTransferID,oLoopFileTran.UniqueFileName,oLoopFileTran.Progress,oEx.Message );
								lock( m_oFileTransfersInProgress )
								{
									m_oFileTransfersInProgress.Remove( oLoopFileTran );
								}
							}

							if( bFailed == false )
							{
								oLoopFileTran.WriteFileChunk( oaBytes );
								//oLoopFileTran.CurrentPosition = nNewPosition;

								nPerc = oLoopFileTran.Progress;
								if( nPerc >= 1 )
								{
									oLoopFileTran.FileTransferComplete();
	 
									RaiseFileTransferCompletedEvent( oLoopFileTran.UniqueFileTransferID,oLoopFileTran.UniqueFileName,1 );
									lock( m_oFileTransfersInProgress )
									{
										m_oFileTransfersInProgress.Remove( oLoopFileTran );
									}
								}
								else
								{
									RaiseFileTransferUpdateEvent( oLoopFileTran.UniqueFileTransferID,oLoopFileTran.UniqueFileName,nPerc );
								}
							}
						}

						oLocalArrayCopy = null;
					}
				}
			}
			catch( System.Exception oEx )
			{
				throw new System.Exception( "File server Error.",oEx );
			}
		}


		public void ConnectToFileServer( string sServerIP,long nServerPort )
		{
			BinaryServerFormatterSinkProvider oServerProv = null;
			BinaryClientFormatterSinkProvider oClientProv = null;
			IDictionary oProperties = null;


			//We'll use the port the user specified in the GUI
			oProperties = new Hashtable();
			oProperties["port"] = 0;
			oProperties["name"] = "Primary File Client Channel";
			//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
			m_oClientChannel = DSMisc.GetRegisteredChannel( (string)oProperties["name"] );
			if( m_oClientChannel == null )
			{
				m_oClientChannel = new TcpChannel( oProperties,oClientProv,oServerProv );
				ChannelServices.RegisterChannel( m_oClientChannel );
			}


			//Connect to the server
			m_oFileServer = (DSFileServer)Activator.GetObject( typeof(DSFileServer),"tcp://" + sServerIP + ":" + nServerPort.ToString() + "/DSFileServer(" + nServerPort.ToString() + ")" );
		}
		public ArrayList GetFileList()
		{
			if( m_oFileServer == null )
			{
				throw new System.Exception( "There is not currently a file server connection." );
			}

			return( m_oFileServer.SharedFileList );
		}
		public void StartFileTransfer( string sUniqueFileName,string sDestinationPath )
		{
			DSSharedFile oSharedFile = null;
			DSFileTransfer oNewFileTran = null;


			if( m_oFileServer == null )
			{
				throw new System.Exception( "There is not currently a file server connection." );
			}

			oNewFileTran = new DSFileTransfer();			
			oNewFileTran.UniqueFileName = sUniqueFileName;
			oNewFileTran.DestinationPath = sDestinationPath;

			oSharedFile = m_oFileServer.GetSharedFile( sUniqueFileName );

			if( oSharedFile == null )
			{
				throw new System.Exception( "DSFileServer.StartFileTransfer Error:  Unable to find file <" + sUniqueFileName + ">." );
			}
			else
			{
				oNewFileTran.EndFileSize = oSharedFile.FileSize;

				oNewFileTran.CreateDestinationFile();
				oNewFileTran.UniqueFileTransferID = m_oFileServer.StartFileTransfer( sUniqueFileName );

				lock( m_oFileTransfersInProgress )
				{
					m_oFileTransfersInProgress.Add( oNewFileTran );
				}

				RaiseFileTransferStartedEvent( oNewFileTran.UniqueFileTransferID,sUniqueFileName,0 );
			}
		}



		#region Properties
		public ArrayList FileTransfersInprogress
		{
			get
			{
				return( m_oFileTransfersInProgress );
			}
		}
		public bool IsAFileTransfersInProgress
		{
			get
			{
				DSFileTransfer oFileTran = null;

				if( m_oFileTransfersInProgress.Count == 0 )
				{
					return( false );
				}
				else if( m_oFileTransfersInProgress.Count == 1 )
				{
					oFileTran = (DSFileTransfer)m_oFileTransfersInProgress[ 0 ];
					return( oFileTran.Progress < 1 );
				}
				else
				{
					return( true );
				}
			}
		}
		#endregion
	}
}
