// NNTP class -- Williams

import java.io.*;
import java.net.*;
import java.util.StringTokenizer;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Date;

public class NewsClient {
    protected Socket skt;
    protected OutputStreamWriter out;
    protected BufferedReader in;
    public String response;
    public int responseCode;
    public int first=-1;
    public int last=-1;
    public int count=-1;

    protected void write(String s) throws IOException {
	//	System.out.println(s);  // debug
        out.write(s+"\r\n");
        out.flush();
    }

    // Write a string with CRLF and check response
    protected boolean output(String s) throws IOException {
	write(s);
	response=in.readLine();
	//	System.out.println(response);  debug
	responseCode=Integer.parseInt(response.substring(0,3));
	return responseCode>=200 && responseCode<=399;

    }

    // Connect, login, and set the status variables
    public boolean connect(String host,String user,String pw,int port)
     throws UnknownHostException, IOException  {
	if (port==0) port=119; // default port
	skt=new Socket(host,port);
	out=new OutputStreamWriter(skt.getOutputStream());
	in=new BufferedReader(new InputStreamReader(skt.getInputStream()));
	response=in.readLine();
	if (!response.substring(0,3).equals("200")
           && !response.substring(0,3).equals("201"))
             return false;
	if (!output("AUTHINFO USER " + user)) return false;
	if (!output("AUTHINFO PASS " + pw)) return false;
	return true;
    }

    // Do a quit 
    public boolean quit() throws IOException {
	boolean rv=true;
	if (!output("QUIT")) rv=false;
	try {
	    skt.close();
	}
	catch (IOException e) {}
	return rv;
    }

     public boolean setGroup(String group) throws IOException {
	 if (!output("GROUP " + group)) return false;
         StringTokenizer token = new StringTokenizer(response);
         token.nextToken(); // skip code
	 count=Integer.parseInt(token.nextToken()); // estimate count
	 first=Integer.parseInt(token.nextToken());  
	 last=Integer.parseInt(token.nextToken());
	 return true;
     }

    // Read a specific message
    public MailMessage readMessage(int n) throws IOException {
	return readMessage(n,"ARTICLE");
    }

    public MailMessage readMessage(int n,String cmd) throws IOException {
	MailMessage mail;
	Hashtable headers=new Hashtable();
	String line;
	StringBuffer bodytext=new StringBuffer();
	boolean body=false;
	if (n==-1) {
	    if (!output(cmd)) return null;
	}
	else {
	    if (!output(cmd + " " + Integer.toString(n))) return null;
	}
	String lastHeader = "";
	// for each line of response...
	while (true) {
	    line=in.readLine();
	    if (line.length()==0 && !body) { 
		body=true; // blank line in headers means body
		continue;
	    }
	    else {
		// check for end
		if (line.length()!=0 && line.charAt(0)=='.' && line.length()==1) break; 
		// check for escaped period
		if (line.length()!=0 && line.charAt(0)=='.') line=line.substring(1);
	    }
	    // if body, just build up lines
	    if (body) {
		bodytext.append(line);
		bodytext.append("\n");
	    }
	    else {  // headers
		// if a wrap-around header, append it
		if (line.charAt(0)==' ' || line.charAt(0)=='\t') {
		    headers.put(lastHeader,
				(String)headers.get(lastHeader)+" "+
				line.trim());
		    continue;
		}
		// otherwise split header line at :
		int ndx=line.indexOf(':');
		if (ndx!=-1) {
		    String key=line.substring(0,ndx).trim();
		    String value=line.substring(ndx+1).trim();
		    lastHeader=key.toLowerCase();
		    // and store in hashtable
		    headers.put(lastHeader,value);
		}
	    }
	}
	// get real message id (header may be blank)
	StringTokenizer token = new StringTokenizer(response);
	// throw away response
	token.nextToken();
	headers.put("X-server-number",token.nextToken());
	headers.put("X-actual-message-id",token.nextToken());
	// ok we got it all I think
	mail=new MailMessage((String)headers.get("from"),
			     (String)headers.get("Newsgroups"),
			     (String)headers.get("cc"),"",
			     (String)headers.get("subject"),
			     bodytext.toString());
	// remove "standard headers"
	headers.remove("from");
	headers.remove("Newsgroups");
	headers.remove("cc");
	headers.remove("subject");
	mail.headers=headers;
	return mail;
    }


    public boolean postMessage(MailMessage msg) throws IOException {
	if (!output("POST")) return false;
	String id=response.substring(response.indexOf('<'));
	id=id.substring(0,id.indexOf('>')+1);
	// Note MS Outlook does not like these headers
	// lower case
	write("From: "+msg.sender);
	write("Newsgroups: "+msg.to);
	write("Subject: "+msg.subject);
	write("Message-ID:"+id);
	if (msg.headers!=null) {
	    for (Enumeration e=msg.headers.keys();
		 e.hasMoreElements();) {
		String hdr = (String) e.nextElement();
		String val = (String) msg.headers.get(hdr);
		write(hdr+": "+val);
	    }
	}
	write("");   // end of headers
	try {
	    String line;
	    // want to split the input into lines so we can
	    // put a CRLF at the end of each
	    BufferedReader rdr = new BufferedReader(
               new StringReader(msg.body+"\n"));
	    do {
		line=rdr.readLine();
		if (line!=null) {
		    // do . escape
		    if (line.length()>0 && line.charAt(0)=='.') 
                      line="."+line;
		    write(line);
		}
	    } while (line!=null);
	}
	catch (Exception e) { }
	return output(".");
    }


    public boolean nextPost() throws IOException {
	if (!output("NEXT")) return false;
	return true;
    }

    public boolean lastPost() throws IOException {
	if (!output("LAST")) return false;
	return true;
    }
    public NewsClient() { }


    public static void readdump(NewsClient news) throws Exception {
	    MailMessage msg=news.readMessage(-1);
	    System.out.println(msg.body);
	    System.out.println("----- Headers -----");
	    Enumeration keys=msg.headers.keys();
	    while (keys.hasMoreElements()) {
		String iKey=(String)keys.nextElement();
		System.out.println(iKey + ":" + msg.headers.get(iKey));
	    }


    }

    // Test main
    public static void main(String args[]) throws Exception {
	NewsClient news= new NewsClient();
	if (news.connect(args[0],args[1],args[2],0)) {
	    // Test posting
	    MailMessage msg = new MailMessage("test@test.org",
					      "alt.test",
					      "Java BB test " + new Date().toString(),
					      "This is just a test\nPlease ignore");
	    System.out.println("Posting = " + news.postMessage(msg));
	    System.out.println(news.response);


	    // read something
	    news.setGroup("alt.test");
	    news.output("STAT " + Integer.toString(news.last));
	    readdump(news);
	    news.nextPost();
	    readdump(news);
	    news.quit();
	}
	else {
	    System.out.println("Error : " + news.response);
	}

    }
}
