package rene.util.ftp;

import java.io.*;
import java.net.*;
import java.util.*;

/**
An FTP protocol handler. See main for an example.
*/

public class FTP
{	String Server;
	int Port;
	Socket S;
	BufferedReader In;
	PrintWriter Out;
	Answer A;

	/**
	Just denote the server and port (default is 21).
	The connection has to be opened and closed!
	*/
	public FTP (String server, int port)
	{	Server=server; Port=port;
	}

	public FTP (String server)
	{	this(server,21);
	}

	/**
	Open the connection to the server, getting the control data
	streams.
	*/
	public void open () throws IOException,UnknownHostException
	{	S=new Socket(Server,Port);
		In=new BufferedReader(new InputStreamReader(
			new DataInputStream(S.getInputStream())));
		Out=new PrintWriter(S.getOutputStream());
		if (getreply()/100!=2)
			throw new IOException("Illegal reply.");
	}

	/**
	Open the server and connect as user with the password.
	*/
	public void open (String user, String password)
		throws IOException,UnknownHostException
	{	open();
		if (!command("USER "+user))
			throw new IOException("User not accepted.");
		if (!command("PASS "+password))
			throw new IOException("Wrong Password");
	}

	/**
	Close the connection to the server and the control data streams.
	*/
	public void close () throws IOException
	{	send("QUIT");
		In.close();
		Out.close();
		S.close();
	}

	/**
	Wait for reply from the server.
	@return the reply code (1xx,2xx,3xx are OK).
	*/
	public int getreply () throws IOException
	{	A=new Answer();
		A.get(In);
		//System.out.println(A.code());
		//System.out.println(A.text());
		return A.code();
	}

	/**
	Send a command to the server and wait for a direct reply.
	This will set the Answer field for checking.
	@return true if the command succeeded immediately.
	*/
	public boolean command (String s) throws IOException
	{	send(s);
		return (getreply()/100<4);
	}
	
	/**
	This returns the Answer to the previous command. The
	meanings of Answer.code() are:
	<ul>
	<li> 1xx - Another reply will follow
	<li> 2xx - The answer is positive
	<li> 3xx - The answer is positive, but needs more action
	<li> 4xx - The answer is negative, but one can try again
	<li> 5xx - The answer is negative
	</ul>
	A.text() will contain the text, the server sent. In case
	of multiline text, this will be separated by \n.
	*/
	public Answer answer ()
	{	return A; 
	}
	
	/**
	Send a command to the server.
	*/
	public void send (String s) throws IOException
	{	Out.println(s); Out.flush();
	}
	
	/**
	Tell the server to wait for a data connection on a port
	of his discretion. The return of the server is scanned
	for the IP and the port and a Socket is generated.
	@return Socket for the data connection to the server
	*/
	public Socket passive ()
		throws IOException,UnknownHostException, NumberFormatException
	{	if (!command("PASV"))
			throw new IOException("Passive mode not supported.");
		StringTokenizer p=new StringTokenizer(A.text(),"(,)");
		if (!p.hasMoreTokens())
			throw new IOException("Wrong answer from server.");
		else p.nextToken();
		int N[]=new int[4];
		for (int i=0; i<4; i++)
		{	N[i]=Integer.parseInt(p.nextToken());
		}
		int k=Integer.parseInt(p.nextToken());
		int P=k*256+Integer.parseInt(p.nextToken());
		String server=N[0]+"."+N[1]+"."+N[2]+"."+N[3];
		return new Socket(server,P);
	}

	Socket DSocket;

	/**
	Get an input stream to the file. getClose() must
	be called!
	*/	
	public InputStream getFile (String file)
		throws IOException,UnknownHostException
	{	DSocket=passive();
		if (!command("TYPE I"))
			throw new IOException("Type I not supported?");
		send("RETR "+file);
		getreply();
		if (A.code()/100>=4) throw new IOException("Get failed.");
		return DSocket.getInputStream();
	}
	
	/**
	Get an input stream to the list. getClose() must
	be called!
	*/	
	public InputStream getDir (String path)
		throws IOException,UnknownHostException
	{	DSocket=passive();
		if (!command("TYPE A"))
			throw new IOException("Type A not supported?");
		if (!path.equals("")) send("LIST "+path);
		else send("LIST");
		getreply();
		if (A.code()/100>=4) throw new IOException("ls failed.");
		return DSocket.getInputStream();
	}
	
	/**
	Get an input stream to the list. getClose() must
	be called!
	*/	
	public InputStream getLs(String path)
		throws IOException,UnknownHostException
	{	DSocket=passive();
		if (!command("TYPE A"))
			throw new IOException("Type A not supported?");
		if (!path.equals("")) send("NLST "+path);
		else send("NLST");
		getreply();
		if (A.code()/100>=4) throw new IOException("ls failed.");
		return DSocket.getInputStream();
	}
	
	/**
	Close the data socket after a get command and wait for
	the transfer complete message.
	*/
	public void getClose ()
		throws IOException
	{	DSocket.close();
		while (true)
		{	getreply();
			if (A.code()==226) return;
			if (A.code()>=400)
				throw new IOException("Put failed.");
		}
	}

	/**
	Get an output stream to the file. putClose() must
	be called!
	*/
	public OutputStream putFile (String file)
		throws IOException,UnknownHostException
	{	DSocket=passive();
		if (!command("TYPE I"))
			throw new IOException("Type I not supported?");
		send("STOR "+file);
		getreply();
		if (A.code()/100>=4) throw new IOException("Put failed.");
		return DSocket.getOutputStream();
	}

	/**
	Close the date socket after a put command and wait for
	the transfer complete message.
	*/	
	public void putClose ()
		throws IOException
	{	DSocket.close();
		while (true)
		{	getreply();
			if (A.code()==226) return;
			if (A.code()>=400)
				throw new IOException("Put failed.");
		}
	}
	
	/**
	Cange the directory to the specified one.
	*/
	public void changeDirectory (String dir)
		throws IOException
	{	if (!command("CWD "+dir))
			throw new IOException("Directory change failed.");
	}
	
	public Vector getDirectory (String dir)
		throws IOException
	{	Vector v=new Vector();
		try
		{	BufferedReader In=new BufferedReader(
				new InputStreamReader(getDir(dir)));
			while (true)
			{	String s=In.readLine();
				if (s==null) break;
				v.addElement(s);
			}
			In.close();
		}
		catch (Exception e)
		{	throw new IOException("Directory list failed.");
		}
		getClose();
		return v;
	}
	
	public Vector getCurrentDirectory ()
		throws IOException
	{	return getDirectory(".");
	}

	static public void main (String args[])
	{	try
		{	//FTP ftp=new FTP("mathsrv.ku-eichstaett.de");
			//ftp.open("","");
			FTP ftp=new FTP("localhost");
			ftp.open("","");
			testPut(ftp.putFile("test"));
			ftp.putClose();
			testGet(ftp.getFile("test"));
			ftp.getClose();
			Enumeration e=ftp.getCurrentDirectory().elements();
			while (e.hasMoreElements())
			{	System.out.println((String)e.nextElement());
			}
			ftp.close();
		}
		catch (Exception e)
		{	System.out.println(e);
		}	
	}
	
	static void testGet (InputStream in)
		throws IOException
	{	BufferedReader In=new BufferedReader(
			new InputStreamReader(in));
		while (true)
		{	String s=In.readLine();
			if (s==null) break;
			System.out.println(s);
		}
		In.close();
	}

	static void testPut (OutputStream out)
		throws IOException
	{	PrintWriter Out=new PrintWriter(out);
		Out.println("Erste Zeile: Test");
		Out.println("Zweite Zeile: Test");
		Out.print("Dritte Zeile");
		Out.flush();
		Out.close();
	}

}
