package jp.ac.naka.ec.dht;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FindPeer implements Runnable{

	//public static final String MCAST_ADDRESS = "224.0.1.1";
	//public static final String BROADCAST_P = "224.0.1.1";
	static final String REQUEST_MESSAGE = "find_peer";
	static final String RESPONSE_MESSAGE = "ack";
	public static final int BROADCAST_PORT = 9674;
	static final int EXPIRES = 5000;
	
	private List<LookupListener> listeners = new ArrayList<LookupListener>();

	boolean success = false;

	private static FindPeer fp = new FindPeer();

	private FindPeer() {

	}

	public static FindPeer getInstance() {
		return fp;
	}
	
	public void findOtherPeer() throws IOException {
		success = false;
		MulticastSocket socket = new MulticastSocket();
		InetAddress broadcastAddress = getBroadcastAddress();
		byte buf[] = REQUEST_MESSAGE.getBytes();
		DatagramPacket packet = new DatagramPacket(buf, buf.length,
				broadcastAddress, BROADCAST_PORT);
		socket.send(packet);
		//this.start();
		ExecutorService service = Executors.newSingleThreadExecutor();
		
		service.execute(new Runnable() {
			public void run() {
				try {
					Thread.sleep(EXPIRES);
					if (!success) {
						success = true;
						LookupEvent evt = new LookupEvent(this);
						evt.setStatus(LookupEvent.FAIL);
						dispatchEvent(evt);
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		socket.close();
		
	}

	public void run() {
		
		byte buf[] = REQUEST_MESSAGE.getBytes();
		DatagramSocket sock = null;
		
		try {
			try {
				sock = new DatagramSocket(BROADCAST_PORT);
			} catch (SocketException e1) {
				e1.printStackTrace();
				success = true;
				// ローカル環境
				LookupEvent evt = new LookupEvent(this);
				evt.setStatus(LookupEvent.SUCCESS);
				evt.setPeerAddress(InetAddress.getLocalHost());
				dispatchEvent(evt);
				
				return;
			}
			//sock.joinGroup(mcastAddress);
			while (true) {
				DatagramPacket packet = new DatagramPacket(buf, buf.length);
				sock.receive(packet);
				
				String data = new String(packet.getData(), 0, packet
						.getLength());
				
				InetAddress addr = packet.getAddress();
				//int port = packet.getPort();
				System.out.println("Response from :" + addr.getHostAddress() + ", " + data);
				if (data.equals(REQUEST_MESSAGE)) {
					byte[] res_buf = RESPONSE_MESSAGE.getBytes();
					// 返信
					Random rand = new Random();
					try {
						Thread.sleep(rand.nextInt(10) * 100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					DatagramPacket response = new DatagramPacket(res_buf, res_buf.length, addr, BROADCAST_PORT);
					sock.send(response);
				} else if (data.equals(RESPONSE_MESSAGE)) {
					if (success)
						continue;
					success = true;
					LookupEvent evt = new LookupEvent(this);
					evt.setStatus(LookupEvent.SUCCESS);
					evt.setPeerAddress(addr);
					dispatchEvent(evt);
				} else {
					System.err.println("Invalid Request Message :" + data);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (sock != null)
				sock.close();
		}
	}

	public InetAddress getBroadcastAddress() throws UnknownHostException {
		InetAddress local = InetAddress.getLocalHost();
		String temp = local.getHostAddress();
		int index = temp.lastIndexOf(".");
		InetAddress ret = InetAddress.getByName(temp.substring(0, index)
				+ ".255");
		return ret;

	}

	public boolean addLookupListener(LookupListener listener) {
		boolean ret = listeners.add(listener);
		return ret;
	}

	public boolean removeLookupListener(LookupListener listener) {
		return listeners.remove(listener);
	}

	boolean dispatchEvent(LookupEvent evt) {
		boolean ret = false;
		for (LookupListener listener : listeners) {
			switch (evt.getStatus()) {
			case LookupEvent.SUCCESS:
				listener.findPeer(evt);
				break;
			case LookupEvent.FAIL:
				listener.peerNotFound(evt);
			}
			ret = true;
		}
		return ret;
	}
}
