/* 
 * Copyright (c) 2012, noxan
 * See LICENSE for details.
 */

package com.github.noxan.aves.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import com.github.noxan.aves.net.Connection;
import com.github.noxan.aves.net.SocketConnection;
import com.github.noxan.aves.util.Tuple;


public class SocketServer implements Server, Runnable {
    private String host;
    private int port;

    private BlockingQueue<Tuple<ServerEvent, Object>> serverEvents;
    private ServerHandler handler;

    private Set<Connection> connections;

    private ServerSocket server;
    private Thread serverThread;

    private Thread managerThread;

    public SocketServer(ServerHandler handler) {
        this("0.0.0.0", 1666, handler);
    }

    public SocketServer(String host, int port, ServerHandler handler) {
        this.host = host;
        this.port = port;
        this.handler = handler;
        serverEvents = new LinkedBlockingQueue<Tuple<ServerEvent, Object>>();
        connections = new HashSet<Connection>();
    }

    @Override
    public void start() throws IOException {
        isRunning = true;
        server = new ServerSocket();
        server.setSoTimeout(1000);
        server.bind(new InetSocketAddress(host, port));
        serverThread = new Thread(this);
        serverThread.start();
        managerThread = new Thread(new EventManager());
        managerThread.start();
    }

    @Override
    public void stop() throws IOException {
        server.close();
        try {
            serverThread.join(1000);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        try {
            managerThread.join(1000);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while(true) {
            try {
                Socket socket = server.accept();
                Connection connection = new SocketConnection(this, socket);
                connection.start();
            } catch(SocketTimeoutException ignored) {
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public Set<Connection> getConnections() {
        return Collections.unmodifiableSet(connections);
    }

    public void broadcast(Object data) throws IOException {
        for(Connection connection : connections) {
            connection.write(data);
        }
    }

    public void broadcast(Connection self, Object data) throws IOException {
        for(Connection connection : connections) {
            if(connection != self) {
                connection.write(data);
            }
        }
    }

    @Override
    public String getHost() {
        return host;
    }

    @Override
    public int getPort() {
        return port;
    }

    public void offerEvent(ServerEvent type, Object data) {
        serverEvents.add(new Tuple<ServerEvent, Object>(type, data));
    }

    private class EventManager implements Runnable {
        @Override
        public void run() {
            while(true) {
                Tuple<ServerEvent, Object> event = serverEvents.poll();
                if(event != null) {
                    switch(event.getFirst()) {
                        case DATA_READ:
                            Tuple<?, ?> read = (Tuple<?, ?>) event.getSecond();
                            handler.readData((Connection) read.getFirst(), read.getSecond());
                            break;
                        case CLIENT_CONNECT:
                            connections.add((Connection) event.getSecond());
                            handler.clientConnect((Connection) event.getSecond());
                            break;
                        case CLIENT_DISCONNECT:
                            handler.clientDisconnect((Connection) event.getSecond());
                            connections.remove((Connection) event.getSecond());
                            break;
                        case CLIENT_LOST:
                            handler.clientLost((Connection) event.getSecond());
                            connections.remove((Connection) event.getSecond());
                            break;
                        case DATA_WRITE:
                            break;
                    }
                }
            }
        }
    }
}

@@ -815,16 +815,48 @@
 tions;%0A%0A
+    private boolean isRunning;%0A%0A
     priv
@@ -1324,24 +1324,51 @@
 nection%3E();%0A
+        isRunning = false;%0A
     %7D%0A%0A    @
@@ -1413,32 +1413,151 @@
 s IOException %7B%0A
+        if(isRunning) %7B%0A            throw new IllegalStateException(%22Server is already running%22);%0A        %7D else %7B%0A    
         isRunnin
@@ -1570,24 +1570,28 @@
 ue;%0A        
+   
+ 
 server = new
@@ -1611,24 +1611,28 @@
 ();%0A        
+    
 server.setSo
@@ -1650,24 +1650,28 @@
 0);%0A        
+ 
+   
 server.bind(
@@ -1706,32 +1706,36 @@
 port));%0A        
+  
+  
 serverThread = n
@@ -1747,24 +1747,28 @@
 read(this);%0A
+    
         serv
@@ -1785,32 +1785,36 @@
 tart();%0A        
+    
 managerThread = 
@@ -1845,32 +1845,36 @@
 ger());%0A        
+ 
+   
 managerThread.st
@@ -1876,24 +1876,34 @@
 ad.start();%0A
+        %7D%0A
     %7D%0A%0A    @
@@ -1947,32 +1947,91 @@
 s IOException %7B%0A
+        if(isRunning) %7B%0A            isRunning = false;%0A    
         server.c
@@ -2038,32 +2038,36 @@
 lose();%0A        
+    
 try %7B%0A          
@@ -2060,32 +2060,36 @@
 y %7B%0A            
+  
+  
 serverThread.joi
@@ -2089,32 +2089,36 @@
 ead.join(1000);%0A
+    
         %7D catch(
@@ -2147,32 +2147,36 @@
 ) %7B%0A            
+    
 e.printStackTrac
@@ -2180,34 +2180,42 @@
 race();%0A        
+ 
-%7D%0A
+   %7D%0A    
         try %7B%0A  
@@ -2204,32 +2204,36 @@
           try %7B%0A
+    
             mana
@@ -2254,32 +2254,36 @@
 (1000);%0A        
+    
 %7D catch(Interrup
@@ -2304,32 +2304,36 @@
 ) %7B%0A            
+    
 e.printStackTrac
@@ -2329,32 +2329,134 @@
 ntStackTrace();%0A
+            %7D%0A        %7D else %7B%0A            throw new IllegalStateException(%22Server is not running.%22);%0A
         %7D%0A    %7D%0A

add isrunning boolean to socketserver and call start and stop only in the right state