using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace Network
{
	/// 
	/// FtpClient class performs the actions of a simple ftp client
	/// 
	public class FtpClient : AFtpClient
	{
		private bool bConnectionOpen = false;
		private bool bStreamReady = false;
		private string m_sUsername = "";
		private string m_sPassword = "";
		private string m_sHost = "";
		private int m_iPort = 21;

		private TcpClient m_tcpClient = null;
		private NetworkStream m_commandStream = null;
		private StreamReader m_comRead = null;

		/// 
		/// Initializes the ftp client
		/// 
		/// Hostname of the remote machine
		/// User name of the remote machine account
		/// Password of the remote machine account
		public FtpClient(string sHost, string sUser, string sPassword)
		{
			m_sHost = sHost;
			m_sUsername = sUser;
			m_sPassword = sPassword;
		}
		/// 
		/// Initializes the ftp client
		/// 
		/// Hostname of the remote machine
		public FtpClient(string sHost)
		{
			m_sHost = sHost;
		}

		/// 
		/// Initializes the ftp client
		/// 
		/// Hostname of the remote machine
		/// Port of the remote machine
		public FtpClient(string sHost, int iPort)
		{
			m_sHost = sHost;
			m_iPort = iPort;
		}

		public FtpClient()
		{
		}

		/// 
		/// Username of the remote machine account
		/// 
		public override string Username
		{
			get
			{
				return m_sUsername;
			}
			set
			{
				m_sUsername = value;
			}
		}

		/// 
		/// Password of the remote machine account
		/// 
		public override string Password
		{
			get
			{
				return m_sPassword;
			}
			set
			{
				m_sPassword = value;
			}
		}

		/// 
		/// Hostname of the remote machine
		/// 
		public override string Host
		{
			get
			{
				return m_sHost;
			}
			set
			{
				m_sHost = value;
			}
		}

		/// 
		/// FTP port of the remote machine. This property is set to 21 by default
		/// 
		public override int Port
		{
			get
			{
				return m_iPort;
			}
			set
			{
				m_iPort = value;
			}
		}

		/// 
		/// Opens the connection to the remote machine
		/// 
		public override void Open()
		{
			if (bConnectionOpen)
			{
				throw new FtpClientException(1,"Connection already open");
			}

			try 
			{
				m_tcpClient = new TcpClient(m_sHost, m_iPort);
				m_tcpClient.ReceiveBufferSize = 4096; // alocate a 4kb buffer (for extra large MOTDs)
			} 
			catch (SocketException e) 
			{
				throw new FtpClientException(e.ErrorCode,"FtpClient cannot establish a connection");
			}

			m_commandStream = m_tcpClient.GetStream();      // get the command stream
			m_comRead = new StreamReader(m_commandStream);  // now we can read the stream
			//m_comWrite = new StreamWriter(m_commandStream,System.Text.Encoding.ASCII); // and write to it(hmmm .. in beta 2 that is
			// we just successfully connected so the server welcomes us with a 220 response
			string sOut = m_comRead.ReadLine();
			if (sOut.Substring(0,3) != "220") { throw new FtpClientException(3, "Unrecognized response on connect"); }
			WriteToStream("USER " + m_sUsername); // send our user name
			// the server must reply with 331
			sOut = m_comRead.ReadLine();
			if (sOut.Substring(0,3) != "331") { throw new FtpClientException(4, "User does not exist on the remote machine, or anonymous access is blocked"); }
			WriteToStream("PASS " + m_sPassword); // send our password
			sOut = m_comRead.ReadLine();
			// the server must reply with 230, which is a successful login
			// after that the server's MOTD/disclaimer might follow, so we
			// will async-read the stream to the end after we get the first response line
			// to the end of it, this is needed for really slow connections(this still happens:)
			// because we can't issue commands to the server until it sends us everything
			if (sOut.Substring(0,3) != "230") { throw new FtpClientException(5, "Password is incorrect for this user"); }
			if (m_commandStream.DataAvailable) 
			{
				m_commandStream.BeginRead(new byte[4096],0,4096,new AsyncCallback(ReadEnd),null);
			} 
			else 
			{
				bStreamReady = true;
			}
			bConnectionOpen = true;
		}

		/// 
		/// Sets the current remote directory
		/// 
		/// Directory name
		public override void SetCurrentDirectory(string sDirectory)
		{
			if (!bConnectionOpen)
			{
				throw new FtpClientException(6,"Connection not open");
			}
			while (!bStreamReady)
			{
				// wait for the server to become ready for more commands
				System.Threading.Thread.Sleep(200);
			}
			WriteToStream("CWD " + sDirectory); // send the command to change directory
			string sOut = m_comRead.ReadLine();
			// server must reply with 250, else the directory does not exist
			if (sOut.Substring(0,3) != "250") { throw new FtpClientException(7, "Remote directory does not exist"); }
		}

		/// 
		/// Gets a file from the ftp server, if sRemoteFilename contains a mask only the
		/// first file matching the mask is received.
		/// 
		/// Full filename of the local file [Path+Name]
		/// Remote file name
		/// Transfer mode constant
		public override void ReceiveFile(string sLocalFilename, string sRemoteFilename, TransferMode mode)
		{
			// create a new file
			FileStream fStream = new FileStream(sLocalFilename,FileMode.Create,FileAccess.ReadWrite,FileShare.Read,1024,false);
			string sOut = null;
			// set mode
			switch (mode)
			{
				case TransferMode.Ascii:
				{
					WriteToStream("TYPE A");
					sOut = m_comRead.ReadLine(); // consume the return message
					break;
				}
				case TransferMode.Binary:
				{
					WriteToStream("TYPE I");
					sOut = m_comRead.ReadLine(); // consume the return message
					break;
				}
			}

			// get a list of IP addresses for this machine
			IPHostEntry ipThis = Dns.GetHostByName(Dns.GetHostName());
			Random r = new Random();
			int port = 0;
			bool bIPFound = false;
			// we will try all IP addresses assigned to this machine
			// the first one that the remote machine likes will be chosen
			for(int i=0;i0)
				{
					bytesRead = xfer.Receive(bData,0,1024,SocketFlags.None);
					fStream.Write(bData,0,bytesRead);
				}
				fStream.Close();
				xfer.Shutdown(SocketShutdown.Both);
				xfer.Close();
				conn.Stop();
				conn = null;
				xfer = null;
				fStream = null;
				sOut = m_comRead.ReadLine(); // consume the "226 Transfer Complete" response
			} 
			catch (Exception e) 
			{
				throw e; // propagate the exception
			}
		}

		/// 
		/// Puts a file to the ftp server, if sRemoteFilename contains a mask only the
		/// first file matching the mask is received.
		/// 
		/// Full filename of the local file [Path+Name]
		/// Remote file name
		/// Transfer mode constant
		public override void SendFile(string sLocalFilename, string sRemoteFilename, TransferMode mode)
		{
			// create a new file
			FileStream fStream = new FileStream(sLocalFilename,FileMode.Open,FileAccess.Read,FileShare.Read,1024,false);
			string sOut = null;
			// set mode
			switch (mode)
			{
				case TransferMode.Ascii:
				{
					WriteToStream("TYPE A");
					sOut = m_comRead.ReadLine(); // consume the return message
					break;
				}
				case TransferMode.Binary:
				{
					WriteToStream("TYPE I");
					sOut = m_comRead.ReadLine(); // consume the return message
					break;
				}
			}

			// get a list of IP addresses for this machine
			IPHostEntry ipThis = Dns.GetHostByName(Dns.GetHostName());
			Random r = new Random();
			int port = 0;
			bool bIPFound = false;
			// we will try all IP addresses assigned to this machine
			// the first one that the remote machine likes will be chosen
			for(int i=0;i0)
				{
					bytesRead = fStream.Read(bData,0,1024);
					xfer.Send(bData,0,bytesRead,SocketFlags.None);
				}
				fStream.Close();
				xfer.Shutdown(SocketShutdown.Both);
				xfer.Close();
				conn.Stop();
				conn = null;
				xfer = null;
				fStream = null;
				sOut = m_comRead.ReadLine(); // consume the "226 Transfer Complete" response
			} 
			catch (Exception e) 
			{
				throw e; // propagate the exception
			}
		}
		/// 
		/// Closes the connection to remote host
		/// 
		public void Close()
		{
			m_comRead.Close();
			m_commandStream.Close();
			m_tcpClient.Close();
			bConnectionOpen = false;
			bStreamReady = false;
		}

		private void WriteToStream(string command)
		{
			// this is interesting, in Beta 2 of .NET I was able to use the
			// StreamWriter to write to the stream. Now I am not, the stream becomes
			// blocked, so this method writes a byte array directly to the stream
			m_commandStream.Write(System.Text.Encoding.ASCII.GetBytes(command+"\n"),0,command.Length+1);
		}

		private void ReadEnd(IAsyncResult ar)
		{
			bStreamReady = true;
		}
	}
}