// SMTPSender.java	(C) K. J. Turner, P. K. Johnson	04/03/06

// Simple Mail Transfer Protocol sender (client)

package protocol;

import java.util.Vector;
import support.*;

public class SMTPSender implements ProtocolEntity {

  private PDU pduReceived;
  private PDU pduSent;
  private ProtocolEntity peer;
  private Medium medium;
  private String name;

  final static String clientData = "DATA";
  final static String clientFrom = "MAIL FROM: sender";
  final static String clientHello = "HELO client";
  final static String clientMail = "Mail message";
  final static String clientQuit = "QUIT";
  final static String clientRecipient = "RCPT TO: recipient";
  final static String invalidRecipient = "550 Recipient invalid";
  final static String invalidSender = "550 Sender invalid";
  final static String okRecipient = "250 Recipient OK";
  final static String okSender = "250 Sender OK";
  final static String serverAccept = "250 Message accepted";
  final static String serverClosing = "221 Server Closing";
  final static String serverHello = "250 Server hello to client";
  final static String serverReady = "220 Server ready";
  final static String serverSend = "354 Send mail";
  final static String tcpConnect = "TCP connect";
  final static String tcpDisconnect = "TCP disconnect";

  int state;					// protocol state

  final static int idle = 0;			// no connection
  final static int waitReady = 1;		// await server ready
  final static int waitHello = 2;		// await server hello
  final static int ready = 3;			// ready for mail
  final static int waitSender = 4;		// await sender answer
  final static int waitRecipient = 5;		// await recip answer
  final static int waitSend = 6;		// await server send
  final static int waitAccept = 7;		// await server accept
  final static int waitClose = 8;		// await server closing

  int person = 1;				// sender/recipient
  int recipients;				// number of recipients

  public SMTPSender(Medium m, String name) {
    this.name = name;
    medium = m;
  }

  public String getName() {
    return(name);
  }

  public Vector<String> getServices() {
    Vector<String> list = new Vector<String>();
    String pduType;
    if (state == idle)				// waiting to connect?
	list.addElement(tcpConnect);		// connect
    else if (pduReceived != null) {		// non-empty reply?
      pduType = pduReceived.type;		// get PDU type
      if (state == waitReady &&			// awaiting server ready?
	  pduType.equals(serverReady))		// reply server ready?
	list.addElement(clientHello);		// send hello from client
      else if (state == waitHello &&		// awaiting server hello?
	       pduType.equals(serverHello))	// reply server hello?
	state = ready;				// now ready
      else if (state == waitSender) {		// awaiting sender response?
	if (pduType.equals(okSender)) {		// sender is OK?
	  recipients = 0;			// no recipients yet
	  state = waitSend;			// now ready to send
	}
	else if (pduType.equals(invalidSender))	// sender is invalid?
	  state = ready;			// back to ready
      }
      else if (state == waitRecipient &&	// awaiting recipient response?
	       (pduType.equals(okRecipient) ||	// recipient is OK or ...
		pduType.equals(invalidRecipient))) { // not?
	if (pduType.equals(okRecipient))	// recipient is OK?
	  recipients++;				// add to recipients
	state = waitSend;			// still ready to send
      }
      else if (state == waitSend &&		// awaiting server send?
	       pduType.equals(serverSend)) {	// response is server send?
	list.addElement(clientMail);		// send client mail message
	state = waitAccept;			// wait for server accept
      }
      else if (state == waitAccept &&		// awaiting server accept?
	       pduType.equals(serverAccept)) {	// reply is server accept?
	state = ready;				// back to ready
      }
      else if (state == waitClose &&		// awaiting server closing?
	       pduType.equals(serverClosing)) {	// reply is server closing?
	list.addElement(tcpDisconnect);		// disconnect
      }
      if (state == ready) {			// ready?
	list.addElement(clientFrom + person++);	// propose sender
	list.addElement(clientQuit);		// propose quit
      }
      else if (state == waitSend) {		// waiting to send?
	list.addElement(clientRecipient + person++); // propose recipient
	if (recipients > 0)			// recipients?
	  list.addElement(clientData);		// propose to send data
      }
    }
    return(list);
  }

  public void initialise() {
    state = idle;				// initialise state
    pduReceived = null;				// note no PDU received
    person = 1;					// initialise sender/recipient
  }

  public Vector<ProtocolEvent> performService(String s) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    if (s.equals(clientData)) {			// send client data?
      transmitPDU(new PDU(clientData), peer);	// send client data
      state = waitSend;				// await server send
    }
    else if (s.startsWith(clientFrom)) {	// send sender?
      transmitPDU(new PDU(s), peer);		// send sender
      state = waitSender;			// await sender response
    }
    else if (s.equals(clientHello)) {		// send client hello?
      transmitPDU(new PDU(s), peer);		// send client hello
      state = waitHello;			// await server hello
    }
    else if (s.equals(clientMail))		// send client mail message?
      transmitPDU(new PDU(clientMail), peer);	// send client mail message
    else if (s.equals(clientQuit)) {		// send client quit?
      transmitPDU(new PDU(clientQuit), peer);	// send client quit
      state = waitClose;			// await server closing
    }
    else if (s.startsWith(clientRecipient)) {	// send recipient?
      transmitPDU(new PDU(s), peer);		// send recipient
      state = waitRecipient;			// await recipient response
    }
    if (s.equals(tcpConnect)) {			// send TCP connect?
      transmitPDU(new PDU(tcpConnect), peer);	// send TCP connect
      state = waitReady;			// await server ready
    }
    else if (s.equals(tcpDisconnect)) {		// send TCP disconnect?
      transmitPDU(new PDU(tcpDisconnect), peer); // send TCP disconnect
      state = idle;				// back to start
    }
    if (pduSent != null) {			// PDU to send?
      events.addElement(new ProtocolEvent(ProtocolEvent.TRANSMIT, pduSent));
      events.addElement(new ProtocolEvent(ProtocolEvent.RECEIVE, pduSent));
    }
    pduSent = null;				// nullify just in case
    return(events);
  }

  public Vector<ProtocolEvent> receivePDU(PDU pdu) {
    pduReceived = pdu;
    return(new Vector<ProtocolEvent>());
  }

  public void transmitPDU(PDU pdu, ProtocolEntity dest) {
    pdu.setSource(this);
    pdu.setDestination(dest);
    pduSent = pdu;
    this.peer.receivePDU(pdu);
    pduReceived = null;
  }

  public void setPeer(ProtocolEntity peer) {
    this.peer = peer;
  }

}
