import transport.*;

public class SAWSender2 extends Sender implements TimeoutAction {
    
    private static final int S0 = 0;
    private static final int ACK0 = 1;
    private static final int S1 = 2;
    private static final int ACK1 = 3;
    private static final int ACK0FIN = 4; // waiting for ack0, then go to FIN
    private static final int ACK1FIN = 5; // waiting for ack1, then go to FIN
    private static final int FIN = 6; // waiting for final ack, then go to CLOSE
    private static final int CLOSED = 7;

    private static final int SENDER_TIMEOUT_MS = 5000;

    private int state;
    private byte[] data_pkt;
    private int data_pkt_len;

    public SAWSender2() {
	state = S0;
	data_pkt = new byte[MAX_PACKET_SIZE];
	data_pkt_len = 0;
    }

    private void make_packet(int seq, byte[] buffer, int offset, int length) {
	if (length + 1 > MAX_PACKET_SIZE) {
	    data_pkt_len = MAX_PACKET_SIZE;
	} else {
	    data_pkt_len = length + 1;
	}
	data_pkt[0] = (byte)seq;
	for(int i = 1; i < data_pkt_len; ++i)
	    data_pkt[i] = buffer[offset++];
    }

    private static void print_packet(byte[] buffer, int offset, int length) {
	int end = offset + length;
	System.out.print("\n[");
	while(offset < end) 
	    System.out.print(buffer[offset++] + " ");
	System.out.print("]");
    }

    public void unreliableReceive(byte[] buffer, int offset, int length) {
	//	print_packet(buffer, offset, length);
	switch(state) {
	case S0:
	    System.err.println("SAWSender.unreliableReceive: received packet in S0.  Ignoring.");
	    return;

	case S1:
	    System.err.println("SAWSender.unreliableReceive: received packet in S1.  Ignoring.");
	    return;

	case ACK0:
	    System.out.print("ACK0:");
	    if (length > 0 && buffer[offset] == 0) {
		System.out.print("->S1 ");
		state = S1;
		cancelTimeout(this);
		allowDisconnect();
		resumeSender();
	    } else {
		System.out.print("!->ACK0 ");
		cancelTimeout(this);
		setTimeout(SENDER_TIMEOUT_MS, this);
		unreliableSend(data_pkt, 0, data_pkt_len);
	    }
	    return;

	case ACK0FIN:
	    System.out.print("ACK0FIN:");
	    if (length > 0 && buffer[offset] == 0) {
		cancelTimeout(this);
		goToFinState();
	    } else {
		System.out.print("!->ACK1FIN ");
		cancelTimeout(this);
		setTimeout(SENDER_TIMEOUT_MS, this);
		unreliableSend(data_pkt, 0, data_pkt_len);
	    }
	    return;

	case ACK1:
	    System.out.print("ACK1:");
	    if (length > 0 && buffer[offset] == 1) {
		System.out.print("->S0 ");
		state = S0;
		cancelTimeout(this);
		allowDisconnect();
		resumeSender();
	    } else {
		System.out.print("!->ACK1 ");
		cancelTimeout(this);
		setTimeout(SENDER_TIMEOUT_MS, this);
		unreliableSend(data_pkt, 0, data_pkt_len);
	    }
	    return;

	case ACK1FIN:
	    System.out.print("ACK1FIN:");
	    if (length > 0 && buffer[offset] == 1) {
		cancelTimeout(this);
		goToFinState();
	    } else {
		System.out.print("!->ACK1FIN ");
		cancelTimeout(this);
		setTimeout(SENDER_TIMEOUT_MS, this);
		unreliableSend(data_pkt, 0, data_pkt_len);
	    }
	    return;

	case FIN:
	    System.out.print("FIN:");
	    if (length > 0 && buffer[offset] == 2) {
		System.out.print("->CLOSED ");
		state = CLOSED;
		cancelTimeout(this);
		allowDisconnect();
		resumeSender();
	    } else {
		System.out.print("!->FIN ");
		cancelTimeout(this);
		setTimeout(SENDER_TIMEOUT_MS, this);
		unreliableSend(data_pkt, 0, data_pkt_len);
	    }
	    return;
	}
    }

    public void timeoutAction() {
	switch(state) {
	case S0:
	    System.err.println("SAWSender.timeoutAction: timeout in S0.  Ignoring timeout.");
	    return;

	case S1:
	    System.err.println("SAWSender.timeoutAction: timeout in S1.  Ignoring timeout.");
	    return;

	case FIN:
	case ACK0:
	case ACK1:
	case ACK0FIN:
	case ACK1FIN:
	    System.out.print("T! ");
	    cancelTimeout(this);
	    setTimeout(SENDER_TIMEOUT_MS, this);
	    unreliableSend(data_pkt, 0, data_pkt_len);
	}
    }

    public int reliableSend(byte[] buffer, int offset, int length) {
	switch(state) {
	case S0:
	    System.out.print("S0:->ACK0 ");
	    blockSender();
	    blockDisconnect();
	    make_packet(0, buffer, offset, length);
	    state = ACK0;
	    setTimeout(SENDER_TIMEOUT_MS, this);
	    unreliableSend(data_pkt, 0, data_pkt_len);
	    return data_pkt_len - 1;

	case S1:
	    System.out.print("S1:->ACK1 ");
	    blockSender();
	    blockDisconnect();
	    make_packet(1, buffer, offset, length);
	    state = ACK1;
	    setTimeout(SENDER_TIMEOUT_MS, this);
	    unreliableSend(data_pkt, 0, data_pkt_len);
	    return data_pkt_len - 1;

	case ACK0:
	case ACK1:
	case ACK0FIN:
	case ACK1FIN:
	case FIN:
	default:
	    System.err.println("SAWSender.reliableSend: sender should be blocked.  Ignoring request");
	    return 0;
	}
    }

    private void goToFinState() {
	System.out.print("->FIN ");
	blockSender();
	blockDisconnect();
	make_packet(2, null, 0, 0);
	state = FIN;
	setTimeout(SENDER_TIMEOUT_MS, this);
	unreliableSend(data_pkt, 0, data_pkt_len);
    }

    public void close() {
	switch(state) {
	case S0: 
	    System.out.print("S0:");
	    goToFinState();
	    return;

	case S1:
	    System.out.print("S1:");
	    goToFinState();
	    return;

	case ACK0:
	    System.out.print("ACK0:->ACK0FIN ");
	    state = ACK0FIN;
	    return;

	case ACK1:
	    System.out.print("ACK1:->ACK1FIN ");
	    state = ACK1FIN;
	    return;

	case FIN:
	case CLOSED:
	default:
	    System.err.println("SAWSender.closeConnection: connection already closed or in closing state.  Ignoring request");
	    return;
	}
    }
}
