/*
 * COPYRIGHT (c) 1998-1999 Jan-Henrik Haukeland, http://www.tildeslash.com/
 */

package ts.mail;

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

import ts.mail.DefaultMailModel;
import ts.mail.Mime;
import ts.config.Constant;
import ts.config.Config;
import ts.biff.SafeTerminate;
import ts.biff.MailUtil;


/**  
 * This Class is a FetchAgent that connects to a POP3 server to
 * fetch mail. The class extends the AbstractMailServer, and use 
 * the super class to connect to the server.
 * <P>
 * The class does <I>not</I> retrieve mail messages from the POP3 
 * server, it only <I>STAT</I> and <I>TOP</I> the server. If the 
 * server does not support the TOP command an exception is thrown.
 * <P>
 * The <b>getMail</b> method implements the FetchAgents interface
 * method getMail. It returns the content of the POP3 servers mail
 * drop as a Vector of MailModel objects, where one MailModel 
 * represents one mail message from the mailbox file.
 *
 * <H3>Limitations:</H3>
 * The APOP command is not supported. Mostly because most POP3 
 * servers doesn't support it.
 *
 * @see DefaultMailModel
 * @see FetchAgent
 * @see MailParser
 *
 * @author  Jan-Henrik Haukeland &lt;hauk@tildeslash.com&gt;
 * @version $Id: Pop3Agent.java,v 1.11 2001/08/13 12:48:32 hauk Exp $ 
 */
public class Pop3Agent extends AbstractMailServer implements FetchAgent
{

  /** Protocol used by this class */
  protected final String PROTOCOL= "pop3";

  /** Default portnumber for the POP3 protocol */
  protected static final int DEFAULT_PORT= 110;

  /** The POP3 error status indicators */
  protected static final String POP3_ERROR= "-ERR";

  /** The POP3 ok status indicators */
  protected static final String POP3_OK= "+OK";


  /** Username at pop3 server */
  protected  String myUserName;

  /** Password at pop3 server */
  protected  String myPassword;
  
  /** Server output stream */
  protected PrintWriter out=   null; 
  
  /** Server input stream */
  protected BufferedReader in= null; 


  /** Maximum lines to read from a mail message */
  private int myMaxumimLines= 0;

  /** Flag for safe termination */
  private boolean canTerminate= true;



  /**
   * Initializes the object with the given values for the mail server,
   * using the default pop3 port.
   * @param aServer The POP3 server address
   * @exception UnknownHostException  Is thrown if no IP address 
   * for the host could be found
   */
  public Pop3Agent(String aServer) throws UnknownHostException
  {
    this(aServer,DEFAULT_PORT);
  }



  /**
   * Initializes the object with the given values for the pop3 server
   * @param aServer The POP3 server address
   * @param aPort   The POP3 server port number
   * @exception UnknownHostException  Is thrown if no IP address 
   * for the host could be found
   */
  public Pop3Agent(String aServer, int aPort) throws UnknownHostException
  {
    super(aServer,aPort);
  }




  /**
   * Implementation of the SafeTerminate interface
   * @return true if it's safe to terminate
   */
  public boolean canTerminate() {

    return canTerminate;

  }



  /**
   * Set username and password for authentication at the pop3 server
   * @param uname a pop3-server username 
   * @param passwd the password that goes along with the uname
   * @exception IllegalArgumentException if uname or passwd is null
   */
  public void setAuth(String uname, String passwd) {
    
    if ( uname == null )
	throw new IllegalArgumentException("Got null value for pop3 username");
    if ( passwd == null )
	throw new IllegalArgumentException("Got null value for pop3 password");
    
    myUserName= uname;
    myPassword= passwd;

  }



  /**
   * Set maximum number of lines to read from a mail-message
   * @param num maximum number of lines to read from a mail message
   * @exception IllegalArgumentException if num lines is less than 0
   */
  public void setNumLines(int num) {
    
    if ( num < 0 )
	throw new IllegalArgumentException("Got negative number of lines "+
					   "to read from a pop3 server");
    myMaxumimLines= num;
    
  }



  // ------------- Implementation of interface methods ----------------- //

  
  /** 
   * Implementation of the getMail method. This method opens a connection
   * to the Mail server and read mail messages. Each message is put 
   * into a MailModel object.
   * @return A Vector with MailModel objects
   * @exception GetMailException Thrown if mail for some reason
   * could not be read 
   */
  public Vector getMail() throws GetMailException {

    Vector v= null;
    
    try {
      
      // Don't let the application terminate when reading mail
      canTerminate= false;

      super.openConnection();
      
      out = new 
	  PrintWriter(super.getOutputStream(), true);
      in = 
	  new BufferedReader(new InputStreamReader(super.getInputStream()));

      // Check to see if server is ready
      isOk();

      // Login to the pop3 server
      authenticate();

      // Get parsed MailModel messages from the server
      v= parseMail();
      
    }catch (GetMailException gm) {
      throw new GetMailException(gm.getStatus(),
				 gm.getMessage());
    }
    catch (Exception e) {
      throw new GetMailException(GetMailException.ERROR,
				 e.getMessage());
    }
    finally {
      // Now it's ok to terminate
      canTerminate= true;
      try {
	// Terminate the pop connection
	out.print("QUIT\r\n");
	out.close();
	in.close();
	super.closeConnection();
	in= null;
	out= null;
      } catch (Exception e) {}
    }

    return v;

  }


  /**
   * Returns this FetchAgents protocol
   * @return The protocol used
   */
  public String getProtocol() {

    return PROTOCOL;

  }


  /**
   * Stop current mail scan
   */
  public void stopScan() {

    try {
      if ( out != null ) {
	out.print("QUIT\r\n");
	out.flush();
	out.close();
      }
      
      if ( in != null ) {
	in.close();
      }
      super.closeConnection();
    } catch (Exception e) {

    }
    finally {
      in= null;
      out= null;
    }

  }


  /**
   * Login to the POP3 server
   * @exception GetMailException Is thrown if error status from server
   */
  protected void authenticate() throws GetMailException {

    try {
      command("USER "+myUserName);
      command("PASS "+myPassword);
    } catch(Exception g) {
      System.out.println("Error: "+g.getMessage());
      g.printStackTrace();
      throw new GetMailException(GetMailException.ERROR,
				 "Could not login to the pop3 server ("+
				 getMailServerName() + ":"+
				 getMailServerPortNumber()+
				 ")\nPlease check your username and password");
    }
  }


  
  /**
   * Stat the server and return the number of messages in the maildrop
   * @return number of messages in the maildrop
   * @exception GetMailException thrown if drop listing format is wrong
   */
  protected int stat() throws GetMailException {

    String r= null;
    try {
      r= command("STAT");
      return Integer.parseInt(r.substring(0,r.indexOf(" ")));
    } catch (NumberFormatException ne) {
      throw new GetMailException(GetMailException.ERROR,
				 "Got mail drop listing garble from "+
				 "Pop3 server: "+r);
    }
  }


  /**
   * Top a message and return the multi-line response from the server
   * in a String. 
   * @param messageid the id of the message to top
   * @return content of mail message that was topped
   * @exception GetMailException thrown if error response from server
   */
  protected String top(int messageid) throws GetMailException {

    command("TOP "+ messageid + " " + myMaxumimLines);
    StringBuffer sb= new StringBuffer();

    try {
      String line;
      while ( (line= in.readLine()) != null && ! line.equals(".") )
      {
	sb.append(line +  System.getProperty("line.separator"));
      }
    }catch (IOException io) {
      throw new GetMailException(GetMailException.ERROR,
				 "In top: "+io.getMessage());
    }

    return sb.toString();

  }
  

  /**
   * Send a command to the POP3 server and return the result string
   * @param action a POP3 protocol recognizable command
   * @return result from pop server
   * @exception GetMailException Thrown if could not write to server
   */
  protected String command(String action) throws GetMailException {

    try {
      out.write(action+"\r\n");
      out.flush();
    } catch (Exception io) {
      throw new GetMailException(GetMailException.ERROR, io.getMessage());
    }
    
    return isOk();

  }
  

  /**
   * Read a line from the server and check the response. The POP3 protocol
   * has no granularity on status from the server. A response indicates 
   * either ok or error. An error response from the server will result 
   * in an exception thrown by this method. If the server respond +OK, 
   * this method return the respons minus the +OK prefix. E.g. after a 
   * STAT command, the server returns <I>+OK 3 64302</I>, this mehtod will
   * return <I>3 64302</I>.
   * @return the respons, minus the status code (i.e. everything after +OK)
   * @exception GetMailException Is thrown if error status from server
   */
  protected String isOk() throws GetMailException { 

    String rv= null;
    try {
      rv= in.readLine();
    }catch (IOException io) {
      throw new GetMailException(GetMailException.ERROR, io.getMessage());
    }

    if ( rv.startsWith(POP3_ERROR) ) {
      throw new GetMailException(GetMailException.ERROR, rv);
    }
    
    try {
      String stat= rv.substring(0,rv.indexOf(" "));
      String msg=  rv.substring(rv.indexOf(" ")).trim();
      return msg;
    } catch ( StringIndexOutOfBoundsException sb ) {
      // Empty
    }

    return rv;

  }



  /**
   * Connect a stream with mail messages from the POP3 server to the
   * MailParsers input stream. This method returns a vector with 
   * MailModels from the parser, when the parser stops.
   * @return a vector of MailModels from the MailParser
   * @exception GetMailException Thrown if error from MailParser
   */
  protected Vector parseMail() throws GetMailException {

    // Get the number of messages in the maildrop
    int num= stat();
    
    // If no mail messages just return
    if ( num == 0 ) {
      return (Vector)null;
    }

    PipedInputStream    iStream= null;
    PipedOutputStream   oStream= null;
    PrintWriter         writer=  null;
    MailParserConnector conn=    null;
    
    try {
      iStream= new PipedInputStream();
      oStream= new PipedOutputStream(iStream);
      writer=  new PrintWriter(oStream, true);
      
      // Connect the writer's output stream to the MailParsers input stream
      conn= new MailParserConnector(iStream);
      
      // Start the parser
      conn.start();
      
      // Write mail messages from the pop server to the parser
      for ( int i= 1; i<=num; i++ ) {
	writer.println("From ?"); // For the mail parser
	writer.println(top(i));
	writer.flush();
      }
      
      // Terminate the parser
      writer.println(Constant.MAGIC_TERMINATOR);
      
      // Wait for the parser thread to die
      conn.join();
    }
    catch(Exception e) {
      throw new GetMailException(GetMailException.WARNING, e.getMessage());
    }
    finally {
      try {
	writer.close();
	oStream.close();
	iStream.close();
      } catch (IOException io) {}
    }

    // Return a vector with MailModels from the parser
    try {
      return conn.getVector();
    } catch(GetMailException ge) {
      throw new GetMailException(ge.getStatus(), ge.getMessage());
    }

  }



  protected class MailParserConnector extends Thread {

    private InputStream  istream;
    private Vector v;
    private GetMailException e;

    public MailParserConnector(InputStream istream) {

      this.istream= istream;

    }


    public synchronized void run() {
      try {
	v= MailParser.parse(istream);
      } catch (GetMailException g) {
	e= g;
      }

    }


    public Vector getVector() throws GetMailException {

      if ( e != null )
	  throw new GetMailException(e.getStatus(),e.getMessage());

      return v;

    }

  }


}

  
