import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Vector;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;



public class SimpleChatServer {

	private static final String HOST = "localhost";
	private static final int PORT = 9999;

	private static FileHandler fileHandler;
	private static Logger logger = Logger.getLogger("net.IOCP");

	private Selector selector = null;
	private ServerSocketChannel serverSocketChannel = null;
	private ServerSocket serverSocket = null;

	private Vector room = new Vector();

	public void initServer() {
		try {
			//select open
			selector = Selector.open();

			//Create ServerSocket Channel
			serverSocketChannel = ServerSocketChannel.open();

			//ºñ ºí·ÏÅ· ¸ðµå·Î ¼³Á¤ÇÑ´Ù.
			serverSocketChannel.configureBlocking(false);

			//¼­¹ö¼ÒÄÏ Ã¤³Î°ú ¿¬°áµÈ ¼­¹ö¼ÒÄÏÀ» °¡Á®¿Â´Ù.
			serverSocket = serverSocketChannel.socket();


			// ÁÖ¾îÁø ÆÄ¶ó¹ÌÅÍ¿¡ ÇØ´çÇÏ´Â ÁÖ¼Ò´Ù. Æ÷Æ®·Î ¼­¹ö¼ÒÄÏÀ» ¹ÙÀÎµåÇÑ´Ù.
			InetSocketAddress isa = new InetSocketAddress(HOST, PORT);
			serverSocket.bind(isa);

			//¼­¹ö¼ÒÄÏ Ã¤³ÎÀ» ¼¿·ºÅÍ¿¡ µî·ÏÇÑ´Ù.
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		} catch (IOException ex) {

			log(Level.WARNING, "SimpleChatServer.initServer()", ex);
		}
	}

	public void startServer() {

		info("Server is Started....");

		try {

			while (true) {

				info("¿äÃ»À» ±â´Ù¸®´ÂÁß...");

				//¼¿·ºÅÍÀÇ select() ¸Þ¼Òµå·Î ÁØºñµÈ ÀÌº¥Æ®°¡ ÀÖ´ÂÁö È®ÀÎÇÑ´Ù.
				selector.select();

				//¼¿·ºÅÍÀÇ SeelctedSet¿¡ ÀúÀåµÈ 
				//ÁØºñµÈ ÀÌº¥Æ®µé (SelectionKeyµé) À» ÇÏ³ª¾¿ Ã³¸®ÇÑ´Ù.

				Iterator it = selector.selectedKeys().iterator();

				while (it.hasNext()) {

					SelectionKey key = (SelectionKey)it.next();

					if (key.isAcceptable()) {
						//¼­¹ö¼ÒÄÏ Ã¤³Î¿¡ Å¬¶óÀÌ¾ðÆ®°¡ Á¢¼ÓÀ» ½ÃµµÇÑ °æ¿ì.
						accept(key);
					}
					else if (key.isReadable()) {
						//ÀÌ¹Ì ¿¬°áµÈ Å¬¶óÀÌ¾ðÆ®°¡ ¸Þ½ÃÁö¸¦ º¸³½°æ¿ì
						read(key);
					}
					it.remove();
				}	//end while(iterator)
			}
		} 
		catch (Exception ex) {
			log(Level.WARNING, "SimpleChatServer.startServer()", ex);
		}
	}

	private void accept(SelectionKey key) {
		ServerSocketChannel server = (ServerSocketChannel)key.channel();
		SocketChannel sc;

		try {

			//¼­¹ö¼ÒÄÏ Ã¤³ÎÀÇ accept() ¸Þ¼Òµå·Î ¼­¹ö¼ÒÄÏÀ» »ý¼ºÇÑ´Ù.
			sc = server.accept();
			//»ý¼ºµÈ ¼ÒÄÏÃ¤³ÎÀ» ºñ ºí·ÏÅ·°ú ÀÐ±â ¸ðµå·Î ¼¿·ºÅÍ¿¡ µî·ÏÇÑ´Ù.
			registerChannel(selector, sc, SelectionKey.OP_READ);
			info(sc.toString() + "Å¬¶óÀÌ¾ðÆ®°¡ Á¢¼ÓÇß½À´Ï´Ù.");	
		} 
		catch (ClosedChannelException ex) {
			log(Level.WARNING, "SimpleChatServer.accept()", ex);
		} 
		catch (IOException ex) { 
			log(Level.WARNING, "SimpleChatServer.accept()", ex);
		}
	}

	private void registerChannel(Selector selector, SocketChannel sc, int ops) throws ClosedChannelException, IOException {

		if (sc == null) {
			info("Invalid Connection");
			return ;
		}

		sc.configureBlocking(false);
		sc.register(selector, ops);

		//Ã¤ÆÃ¹æ¿¡ Ãß°¡ÇÑ´Ù.
		addUser(sc);
	}

	private void read(SelectionKey key) {

		//SelectionKey·ÎºÎÅÍ ¼ÒÄÏÃ¤³ÎÀ» ¾ò¾î¿Â´Ù.
		SocketChannel sc = (SocketChannel)key.channel();
		//ByteBuffer¸¦ »ý¼ºÇÑ´Ù.
		ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

		try {

			//¿äÃ»ÇÑ Å¬¶óÀÌ¾ðÆ®ÀÇ ¼ÒÄÏÃ¤³Î·ÎºÎÅÍ µ¥ÀÌÅÍ¸¦ ÀÐ¾îµéÀÎ´Ù.
			int read = sc.read(buffer);
			info(read + "byte ¸¦ ÀÐ¾ú½À´Ï´Ù.");
		}
		catch (IOException ex) {
			try {
				sc.close();
			} catch (IOException e) {}
			
			removeUser(sc);
			info(sc.toString() + " Å¬¶óÀÌ¾ðÆ®°¡ Á¢¼ÓÀ» ÇØÁ¦Çß½À´Ï´Ù.");
		}
		

		try {
			//Å¬¶óÀÌ¾ðÆ®°¡ º¸³½ ¸Þ½ÃÁö¸¦ Ã¤ÆÃ¹æ ¾È¿¡ ¸ðµç »ç¿ëÀÚ¿¡°Ô ºê·ÎµåÄ³½ºÆ® ÇØÁØ´Ù.
			//¸Þ½ÃÁö Ã³¸®±¸°£.. switch  case .. .
			broadcast(buffer);
		} catch (IOException ex) {
			log(Level.WARNING, "SimpleChatServer.broadcast()", ex);
		}
		clearBuffer(buffer);
	}

	private void broadcast(ByteBuffer buffer) throws IOException {

		buffer.flip();

		Iterator iter = room.iterator();

		while (iter.hasNext()) {
			SocketChannel sc = (SocketChannel)iter.next();

			if (sc != null) {
				sc.write(buffer);
				buffer.rewind();
			}
		}
	}

	private void clearBuffer(ByteBuffer buffer) {
		if (buffer != null) {
			buffer.clear();
			buffer = null;
		}
	}

	private void addUser(SocketChannel sc) {
		room.add(sc);
	}

	private void removeUser(SocketChannel sc) { 
		room.remove(sc);
	}

	/**
	 * ·Î±×
	 */

	public void initLog() {
		try {
			fileHandler = new FileHandler("SimpleChatServer.log");
		}
		catch (IOException ex) {
			logger.addHandler(fileHandler);
			logger.setLevel(Level.ALL);
		}
	}

	public void log(Level level, String msg, Throwable error) {

		logger.log(level, msg, error);
	}
	
	public void info(String msg) {
		logger.info(msg);
	}
	
	public static void main(String[] args) {
		
		SimpleChatServer scs = new SimpleChatServer();
		scs.initLog();
		scs.initServer();
		scs.startServer();
	}
}
