import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

import javax.swing.Timer;

public abstract class NQTCPSender {

	//send data 10 bytes at a time
	public static final int PORT = 12345;
	public static final int MAX_DATA_LENGTH = 10;
	public static final int HEADER_LENGTH = 8;
	public static final int MAX_PACKET_LENGTH = HEADER_LENGTH + MAX_DATA_LENGTH;
	public static final int DELAY = 1000; //1 second

	protected DatagramSocket socket;
	protected InetAddress ipAddress;

	protected Timer timer;
	protected volatile boolean done = false;


	public NQTCPSender(InetAddress ip) throws IOException{
		ipAddress = ip;
		
		//set up the socket
		socket = new DatagramSocket();

		//build the receiver thread
		Receiver rcv = new Receiver();
		rcv.start();

		//A timer object for waiting for ACKs
		timer = new Timer(DELAY, new ActionListener(){
			public void actionPerformed(ActionEvent e){
				timeout();
			}
		});
		timer.setRepeats(false);
	}

	public void go() throws IOException{
		Scanner in = new Scanner(System.in);

		System.out.println("Enter some text, to end place a . on a line alone.");

		String line = null;
		while((line = in.nextLine()) != null && !(line.equals("."))){
			//add the new line back on
			line += '\n';
			//break up the line into MAX_DATA_LENGTH pieces
			byte[] data = line.getBytes();

			for(int i = 0; i < data.length; i+= MAX_DATA_LENGTH) {
				int length = MAX_DATA_LENGTH;
				//the remaining text won't fill an entire packet
				if(i + MAX_DATA_LENGTH >= data.length) {
					length = data.length - i;
				}

				sendPacket(data, i, length);
			} 

		}
		done = true;
		socket.close();
		System.out.println("Done!");
	}

	/**
	 * sendPacket: send (some of) the data in the array as a packet.
	 * Create and send a packet with the data described by the parameters.
	 * Update the instance variable sequenceNumber and start the timer.
	 * @param data The data array.
	 * @param start The starting index of the data to be sent.
	 * @param length The number of bytes of data to be sent.
	 */
	public abstract void sendPacket(byte[] data, int start, int length);


	/**
	 * receivePacket: called when an acknowledgement is received.
	 * This method should acknowledge any appropriate packets.
	 * @param ack The Datagram packet received as an acknowledgement. 
	 */
	public abstract void receivePacket(DatagramPacket ack);

	/**
	 * timeout: resend unacknowledged packets and restart the timer.
	 */
	public abstract void timeout();



	public void startTimer(){
		System.err.println("Timer started.");
		timer.start();
	}

	public void restartTimer(){
		System.err.println("Timer Restarted.");
		timer.restart();
	}

	public void stopTimer(){
		System.err.println("Timer stopped.");
		timer.stop();
	}

	public boolean isTimerRunning(){
		return timer.isRunning();
	}


	class Receiver extends Thread{
		public void run(){
			try{
				while(!done){
					//listen for packet 
					byte[] buffer = new byte[MAX_PACKET_LENGTH];
					DatagramPacket ack = new DatagramPacket(buffer, 
							buffer.length);
					socket.receive(ack);
					receivePacket(ack);
				}
			}
			catch(IOException ioe){
				//Will enter here if the thread is listening for a packet
				//when we quit.
			}

		}
	}

	public static void main(String[] args) {
		try {
			InetAddress ipAddress = null;
			if(args.length > 0){
				ipAddress = InetAddress.getByName(args[0]);
			}
			else{
				ipAddress = InetAddress.getByName("localhost"); //loopback
			}

			MyNQTCPSender sender = new MyNQTCPSender(ipAddress);
			sender.go();
		}
		catch(IOException ioe) {
			//problem connecting
			System.err.println("Something went wrong.");
			ioe.printStackTrace();
		}
	}

}
