//
// Mailator.java
//

import java.util.Vector;
import java.util.Hashtable;
import java.io.Reader;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileReader;
import java.io.StringReader;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.net.UnknownHostException;


/**
  * This class shows a simple usage of the SMTP class, allowing
  * parameters from the command line or commands at the prompt.
  * <br><pre>
  *    -server <SMTP server>
  *    -to <email address>
  *    -from <email address>
  *    -subject <subject line>
  *    -message <message contents>
  *    -messageFile <text file containing message body>
  * </pre>
  * Note that option <code>-to</code> can be used more than once
  * to specify several recipients.<br>
  * <br>
  * Only the <code>-server</code> and <code>-to</code> options are
  * mandatory. The rest can be left unset.
  * <br><br>example:
  * <pre>
  *    java Mailator -server mail.arg.com -from miself@arg.com -to a@b.com -to friends@haha.com -subject "Read this!" -message "Hello there, hope you enjoy".
  * </pre>
  * <b>Important:</b> the SMTP standard defines a maximum line width
  * of 1024 characters. This class doesn't perform any kind of break
  * lines insertion, it just sends the data as defined in the file,
  * so it is user responsability to wrap these lines to be less than
  * 1024 columns wide. Not taking care of this can make the message
  * to appear cut lines in undesired places.
  *
  * @see SMTP
  */
public class Mailator {

   /**
     * The SMTP server
     */
   protected String _server;

   /**
     * The sender of this email
     */
   protected String _sender;

   /**
     * The recipients the mail will be sent to.
     */
   protected Vector _recipients;

   /**
     * Subject field of the message.
     */
   protected String _subject;

   /**
     * The data of the message
     */
   protected Reader _messageData;

   /**
     * The message to pass to the SMTP class. This string will
     * contain the following fields in this order:
     * <pre>
     * From: <_sender>
     * To: <_recipients.elementAt(0)>, <_recipients.elementAt(1)>,
     *     <_recipients.elementAt(2)> ...
     * Subject: _subject
     * _messageData
     * </pre>
     *
     * This attribute is created in the send method.
     *
     * @see sendMail
     */
   protected StringBuffer _composedMessage;

   /**
     * The connection (SMTP object) that will be used to send the
     * message.
     *
     * @see SMTP
     */
   protected SMTP _smtpConnection;

   /**
     * Flag to know when to stop.
     */
   protected boolean _quit;

   /**
     * Command indexes
     */
   protected final static int SERVER_COMMAND        = 0;
   protected final static int FROM_COMMAND          = 1;
   protected final static int TO_COMMAND            = 2;
   protected final static int SUBJECT_COMMAND       = 3;
   protected final static int MESSAGE_COMMAND       = 4;
   protected final static int MESSAGE_FILE_COMMAND  = 5;
   protected final static int SEND_COMMAND          = 6;
   protected final static int ECHO_COMMAND          = 7;
   protected final static int HELP_COMMAND          = 8;
   protected final static int QUIT_COMMAND          = 9;
   protected final static int LAST_COMMAND          = 10;
   protected final static int UNKNOWN_COMMAND       = 11;

   /**
     * Command list
     */
   protected final static String[] _commands = {
      "server",
      "from",
      "to",
      "subject",
      "message",
      "messageFile",
      "send",
      "echo",
      "help",
      "quit"
   };

   /**
     * Fast access to indexes.
     */
   protected Hashtable _commandToCommandIndex;

   /**
     * This constructor initializes the fields with null values. It's
     * a way for not to repeat initializations in constructors, but 
     * be able to use this as a method more than as a constructor
     * (this is more flexible).
     */
   protected void MailatorConstructor () {
      _sender = null;
      _recipients = new Vector();
      _subject = null;
      _messageData = null;
      _composedMessage = new StringBuffer();
      _smtpConnection = null;
      _commandToCommandIndex = new Hashtable();
      _quit = false;
      for (int i = 0; i < LAST_COMMAND; i++) {
         _commandToCommandIndex.put(_commands[i], new Integer(i));

      }
   }

   /**
     * The normal constructor. Initializes the attributes to null or
     * valid empty objects (i.e. Vectors and StringBuffers), and
     * creates the SMTP object.
     *
     * @see _smtpConnection
     */
   public Mailator () {
      MailatorConstructor();
   }

   /**
     * Sets the SMTP server
     */
   public void setServer (String server) {
      _server = server;
   }

   /**
     * Sets the _sender field adding the "<>" brackets to the
     * email address. This is neccesary to 
     */
   public void setSender (String sender) {
      _sender = sender;
   }

   /**
     * Adds a new recipient to the list of recipients.
     *
     * @param recipient The new recipient email address, in the
     *                  normal form: "abd@cde.com", just that.
     */
   public void addRecipient (String recipient) {
      _recipients.addElement(recipient);
   }

   /**
     * Sets the message subject
     */
   public void setSubject (String subject) {
      _subject = subject;
   }

   /**
     * Sets the message data reader which will be used to
     * create the message body and the complete message in
     * the _composedMessage attribute from the send method.
     *
     * @see _composedMessage
     */
   public void setMessageData (Reader messageData) {
      _messageData = messageData;
   }

   /**
     * Check whether the system has finished.
     *
     * @see _quit
     */
   public boolean isFinished () {
      return _quit;
   }

   /**
     * Method that sends the e-mail once the from, to, subject and
     * data fields has been set.
     *
     * @throws UnknownHostException SMTP server is not valid.
     * @throws IOException unable to communicate with the server.
     * @trhows Exception something is missing is the message
     *                   configuration, tipically <code>to</code>
     *                   and <code>from</code> parameters.
     */
   public void sendMail () throws UnknownHostException, IOException, Exception {
      // Check for mandatory parameters
      if (_server == null)
         throw new Exception("Missing \"server\" parameter.");
      if (_recipients.size() == 0)
         throw new Exception("Missing \"to\" parameter. At least one recipient needed.");

      System.out.println("Sending...");

      // Create the connection
      _smtpConnection = new SMTP(_server);

      // Update the necessary fields "From":
      _smtpConnection.setSender(_sender);
      _composedMessage.append("From: <");
      if (_sender != null)
         _composedMessage.append(_sender);

      // To:
      _composedMessage.append(">\r\nTo: ");
      String aux;
      for (int i = 0; i < _recipients.size(); i++) {
         aux = (String) _recipients.elementAt(i);
         _smtpConnection.addRecipient(aux);
         _composedMessage.append(aux);
         _composedMessage.append(" <");
         _composedMessage.append(aux);
         // Check the comman separator insertion
         if (i != (_recipients.size() - 1))
            _composedMessage.append(">,\r\n    ");
         else
            // Here, no comma, it's the last
            _composedMessage.append(">\r\n");
      }

      // Subject:
      _composedMessage.append("Subject: ");
      if (_subject != null)
         _composedMessage.append(_subject);
      _composedMessage.append("\r\n");
      _composedMessage.append("\r\n");

      // And, finally the body, let's use a BufferedReader to
      // improve performance.
      if (_messageData != null) {
         BufferedReader auxReader = new BufferedReader(_messageData);
         String strAux;
         while ((strAux = auxReader.readLine()) != null) {
            _composedMessage.append(strAux);
            _composedMessage.append("\r\n");
         }
      }
      else {
         _composedMessage.append("\r\n");
      }

      _smtpConnection.setData(new StringReader(_composedMessage.toString()));

      // Now. Let's send.
      _smtpConnection.send();
      System.out.println("Sent.");
   }

   /**
     * this method process the command given in the argument
     * calling the corresponding methods in the SMTP object.
     *
     * @param command
     * @return <code>true</code> if the command could be
     *         executed and the application may continue<br>
     *         <code>false</code> if an error has raised
     * @throws Exception if the error is critic
     */
   public boolean processCommand (String command) throws Exception {

      // Check we have a valid command
      command = command.trim();
      if (command.length() == 0)
         return true;

      // This String objects will help us to decide which action
      // shall be taken and the parameters to pass.
      String firstCommand;
      String arguments;
      // Check whether we have arguments or not
      int index = command.indexOf(' ');
      if (index == -1) {
         // No args
         firstCommand = command;
         arguments = null;
      }
      else {
         // Yes, we have arguments
         firstCommand = command.substring(0, index).trim();
         arguments = command.substring(index).trim();
      }    

      // Define which action shall be performed
      switch (getCommandIdentifier(firstCommand)) {        
         case SERVER_COMMAND:
            if (arguments == null) {
               System.out.println("Argument expected");
               break;
            }
            setServer(arguments);
            break;

         case FROM_COMMAND:
            if (arguments == null) {
               System.out.println("Argument expected");
               break;
            }
            setSender(arguments);
            break;

         case TO_COMMAND:
            if (arguments == null) {
               System.out.println("Argument expected");
               break;
            }
            addRecipient(arguments);
            break;

         case SUBJECT_COMMAND:
            if (arguments == null) {
               System.out.println("Argument expected");
               break;
            }
            setSubject(arguments);
            break;

         case MESSAGE_COMMAND:
            if (arguments == null) {
               System.out.println("Argument expected");
               break;
            }
            setMessageData(new StringReader(arguments));
            break;

         case MESSAGE_FILE_COMMAND:
            if (arguments == null) {
               System.out.println("Argument expected");
               break;
            }
            setMessageData(new FileReader(arguments));
            break;

         case SEND_COMMAND:
            sendMail();
            _quit = true;
            break;

         case ECHO_COMMAND:
            if (arguments == null) {
               System.out.println("Argument expected");
               break;
            }
            System.out.println(arguments);
            break;

         case HELP_COMMAND:
            showHelp();
            break;

         case QUIT_COMMAND:
            _quit = true;
            return true;

         case UNKNOWN_COMMAND:
            System.out.println("Unknown command " + firstCommand);
            return true;

      }  // End of switch over the different commands

      return true;
      
   }

   /**
     * returns the command identifier
     *
     * @see _commandToCommandIndex
     */
   public int getCommandIdentifier (String command) {
      Object identifier = _commandToCommandIndex.get(command);
      if (identifier == null)
         return UNKNOWN_COMMAND;
      else
         return ((Integer) identifier).intValue();
   }

   /**
     * Shows the arguments list and a description of each.
     */
   public void showHelp () {
      System.out.println("Usage: java Mailator -[param1] [arg1] -[param2] -[arg2] ...");
      System.out.println("or use the command line parser with the same parameters without the");
      System.out.println("preceeding '-' before each command");
      System.out.println("Where the param may be:");
      System.out.println("   server <SMTP server>");
      System.out.println("   to <email address>");
      System.out.println("   from <email address>");
      System.out.println("   subject <subject line>");
      System.out.println("   message <message contents>");
      System.out.println("   messageFile <text file containing message body>");
      System.out.println("   help <show this help>");
      System.out.println("   send <sends the message and quits>");
      System.out.println("   quit <quit>");
      System.out.println("");
      System.out.println("examples:");
      System.out.println("   java -help");
      System.out.println("   java -from sb@sw.com -to friend@place.com -subject Hello -message Hi there.");
   }

   /**
     * A simple application to test. This main function just
     * processes the command line arguments or the displays a
     * prompt instead.
     */
   public static void main (String[] args) {
      try {
         // This variable is true if the user wants to use a
         // prompt (if no arguments defined).
         boolean promptMode;
         // The commands source. From here (no matter where the
         // commands come from) the args will be taken.
         BufferedReader input;

         // Check whether user asks for command line arguments or
         // prompt mode. This will define the value of prompt mode
         // and imput.
         if (args.length == 0) {
            promptMode = true;
            input = new BufferedReader(new InputStreamReader(System.in));
         }
         else {
            // This will be the formatted arguments sequence. The
            // arguments will form lines as if they were typed (will
            // be separated by "\r\n" characters.
            StringBuffer argumentsSequence = new StringBuffer();

            // Take arguments from the command line removing the 
            // preceding "-".
            String strAux;
            boolean insideACommand = false;
            for (int i = 0; i < args.length; i++) {
               strAux = args[i];

               // Is it a new command?
               if ((strAux.length() > 1) && (strAux.charAt(0) == '-')) {
                  // Close the previous command if needed
                  if (insideACommand)
                     argumentsSequence.append("\r\n");
                  argumentsSequence.append(strAux.substring(1));
                  insideACommand = true;
               }
               else {
                  // If not, if were not already in a command we should
                  // raise an error.
                  if (insideACommand && (!strAux.equals("-"))) {
                     argumentsSequence.append(' ');
                     argumentsSequence.append(strAux);
                  }
                  else {
                     // Oops, we should advise this is not nice.
                     argumentsSequence = new StringBuffer("echo ");
                     argumentsSequence.append(strAux);
                     argumentsSequence.append(" : not a valid argument\r\nhelp\r\nquit");
                     break;
                  }
               }
            }
            argumentsSequence.append("\r\n");

            // update promptMode and create the input object
            promptMode = false;
            argumentsSequence.append("send\r\n");
            input = new BufferedReader(new StringReader(argumentsSequence.toString()));
         }

         // Loop over the different commands 
         Mailator mailator = new Mailator();
         String s;
         while ((s = input.readLine()) != null) {
            if (mailator.processCommand(s) == false) {
               System.out.println("Failed to process command");
               System.exit(-1);
            }

            if (mailator.isFinished())
               break;
         }

      } catch (Exception e) {
         System.out.println(e.getMessage());
         System.exit(-1);
      }

      // Return a success code
      System.exit(0);
   }

}  // End of the class