// ABRAService.java	(C) I. A. Robin, K. J. Turner	08/03/06

package protocol;

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

public class ABRAService implements ProtocolEntity {

  // Service state constants
  private static final int DISCONNECTED = 0; // no connection in progress
					     //   or active
  private static final int CALLING = 1;      // connection initiated by local
					     //   user is being established
  private static final int CALLED = 2;       // connection initiated by remote
					     //   user is being established
  private static final int CONNECTED = 3;    // data transfer is possible

  // Service primitives
  public static final String CON_REQ =  "Connection Request";
  public static final String CON_IND =  "Connection Indication";
  public static final String CON_RESP = "Connection Response";
  public static final String CON_CONF = "Connection Confirmation";
  public static final String DAT_REQ  = "Data Request";
  public static final String DAT_IND  = "Data Indication";
  public static final String DIS_REQ  = "Disconnection Request";
  public static final String DIS_IND  = "Disconnection Indication";

  private ProtocolEntity provider;  // protocol providing service
  private Vector providerEvents;
  private PDU pduSent;
  private String name;
  private int state;
  private int block;                // sequence number of data block
  private boolean okToSend;         // OK to send DatReq to provider

  public ABRAService(String name) {
    this.name = name;
    initialise();
  }

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

  public Vector<String> getServices() {
    Vector<String> list = new Vector<String>();
    switch (state) {
      case DISCONNECTED:
	list.addElement("Send " + CON_REQ);
	break;
      case CALLING:
	list.addElement("Send " + DIS_REQ);
	break;
      case CALLED:
	list.addElement("Send " + CON_RESP);
	list.addElement("Send " + DIS_REQ);
	break;
      case CONNECTED:
	if (okToSend)
	  list.addElement("Send " + DAT_REQ + "(D" + block + ")");
	list.addElement("Send " + DIS_REQ);
	break;
    }
    return(list);
  }

  public void initialise() {
    block = 0;
    okToSend = true;
    providerEvents = new Vector();
    state = DISCONNECTED;
  }

  public Vector<ProtocolEvent> performService(String s) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    switch (state) {
      case DISCONNECTED:
	if (s.equals("Send " + CON_REQ)) {
	  transmitPDU(new PDU("ConReq"), provider);
	  events.addElement(
	    new ProtocolEvent(ProtocolEvent.SEND, pduSent));
	  state = CALLING;
	}
      case CALLING:
	if (s.equals("Send " + DIS_REQ)) {
	  transmitPDU(new PDU("DisReq"), provider);
	  events.addElement(
	    new ProtocolEvent(ProtocolEvent.SEND, pduSent));
	  state = DISCONNECTED;
	}
	break;
      case CALLED:
	if (s.equals("Send " + CON_RESP)) {
	  transmitPDU(new PDU("ConResp"), provider);
	  events.addElement(
	    new ProtocolEvent(ProtocolEvent.SEND, pduSent));
	  state = CONNECTED;
	}
	if (s.equals("Send " + DIS_REQ)) {
	  transmitPDU(new PDU("DisReq"), provider);
	  events.addElement(
	    new ProtocolEvent(ProtocolEvent.SEND, pduSent));
	  state = DISCONNECTED;
	}
	break;
      case CONNECTED:
	if (s.startsWith("Send " + DAT_REQ)) {
	  String sdu = "D" + block;
	  transmitPDU(new PDU("DatReq", sdu), provider);
	  block++;
	  events.addElement(
	    new ProtocolEvent(ProtocolEvent.SEND, pduSent));
	  // No state transition
	}
	if (s.equals("Send " + DIS_REQ)) {
	  transmitPDU(new PDU("DisReq"), provider);
	  events.addElement(
	    new ProtocolEvent(ProtocolEvent.SEND, pduSent));
	  state = DISCONNECTED;
	}
    }
    for (Enumeration e = providerEvents.elements(); e.hasMoreElements(); )
      events.addElement((ProtocolEvent) e.nextElement());
    return(events);
  }

  /** Respond to PDU received from underlying protocol */

   public Vector<ProtocolEvent> receivePDU(PDU pdu) {
    Vector<ProtocolEvent> events = new Vector<ProtocolEvent>();
    String pduType = pdu.type;
    switch (state) {
      case DISCONNECTED:
	if (pduType.equals("ConInd")) state = CALLED;
	break;
      case CALLING:
	// Is the ConInd possibility below needed?
	if (pduType.equals("ConConf")
	    || pduType.equals("ConInd")) state = CONNECTED;
	if (pduType.equals("DisInd")) state = DISCONNECTED;
	break;
      case CALLED:
	if (pduType.equals("DisInd")) state = DISCONNECTED;
	break;
      case CONNECTED:
	if (pduType.equals("DisInd")) state = DISCONNECTED;
	break;
    }
    return(events);
  }

  /** Implements flow control by back-pressure. DatReq allowed only when
      okToSend is true */

  public void setOKToSend(boolean ok) {
    okToSend = ok;
  }

  public void setProvider(ProtocolEntity provider) {
    this.provider = provider;
  }

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

}
