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 long m_nTransferID;
		#endregion

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


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


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

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

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

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

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

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


		public DSFileServer() 
		{
		}

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


		public void StartFileServer( string sServerIP,long nServerPort )
		{
			TcpChannel oServerChannel = null;
			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";
			//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 );
			ChannelServices.RegisterChannel( oServerChannel );
 
			//Step 3: Register the service that publishes DbConnect for remote access in SingleCall mode
			RemotingConfiguration.RegisterWellKnownServiceType( typeof(DSFileServer),"DSFileServer",WellKnownObjectMode.Singleton );

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

		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 long StartFileTransfer( string sUniqueFileName )
		{
			RaiseFileTransferStartedEvent( m_nNextUniqueFileTransferID,0 );
			return( m_nNextUniqueFileTransferID++ );
		}
		public DSSharedFile GetSharedFile( string sUniqueFileName )
		{
			DSSharedFile oSharedFile = null;
			DSSharedFile oLoopSharedFile = null;


			for( int nIndex=0 ; nIndex<m_oSharedFiles.Count ; nIndex++ )
			{
				oSharedFile = (DSSharedFile)m_oSharedFiles[ nIndex ];
				if( oSharedFile.UniqueFileName == sUniqueFileName )
				{
					oLoopSharedFile = oSharedFile;
					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,nPerc );
				}
				else
				{
					RaiseFileTransferUpdateEvent( nUniqueTransferID,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 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 Thread m_oFileTransferThread = null;
		private ArrayList m_oFileTransfersInProgress = new ArrayList();
		private DSFileServer m_oFileServer = null;

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

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

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

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

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


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


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


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

					for( int nTran=0 ; nTran<oLocalArrayCopy.Count ; nTran++ )
					{
						oLoopFileTran = (DSFileTransfer)m_oFileTransfersInProgress[ nTran ];
						oaBytes = m_oFileServer.GetFileChunk( oLoopFileTran.UniqueFileTransferID, oLoopFileTran.UniqueFileName,oLoopFileTran.CurrentPosition );
						oLoopFileTran.WriteFileChunk( oaBytes );
						//oLoopFileTran.CurrentPosition = nNewPosition;

						nPerc = oLoopFileTran.Progress;
						if( nPerc >= 1 )
						{
							oLoopFileTran.FileTransferComplete();

							RaiseFileTransferCompletedEvent( oLoopFileTran.UniqueFileTransferID,1 );
							lock( m_oFileTransfersInProgress )
							{
                                m_oFileTransfersInProgress.Remove( oLoopFileTran );
							}
						}
						else
						{
							RaiseFileTransferUpdateEvent( oLoopFileTran.UniqueFileTransferID,nPerc );
						}
					}

					oLocalArrayCopy = null;
				}
			}
		}


		public void ConnectToFileServer( string sServerIP,long nServerPort )
		{
			TcpChannel oClientChannel = null;
			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
			oClientChannel = new TcpChannel( oProperties,oClientProv,oServerProv );
			ChannelServices.RegisterChannel( oClientChannel );


			//Connect to the server
			m_oFileServer = (DSFileServer)Activator.GetObject( typeof(DSFileServer),"tcp://" + sServerIP + ":" + nServerPort.ToString() + "/DSFileServer" );
		}
		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 );
			oNewFileTran.EndFileSize = oSharedFile.FileSize;

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

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

			RaiseFileTransferStartedEvent( oNewFileTran.UniqueFileTransferID,0 );
		}



		#region Properties
		public ArrayList FileTransfersInprogress
		{
			get
			{
				return( m_oFileTransfersInProgress );
			}
		}
		#endregion
	}
}
