/* 
 * 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 EventManager manager;
    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() {
        try {
            server = new ServerSocket();
            server.setSoTimeout(1000);
            server.bind(new InetSocketAddress(host, port));
            manager = new EventManager();
            serverThread = new Thread(this);
            serverThread.start();
            managerThread = new Thread(manager);
            managerThread.start();
        } catch(IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while(true) {
            try {
                Socket socket = server.accept();
                Connection connection = new SocketConnection(this, socket);
                connection.start();
            } catch(SocketTimeoutException e) {
            } 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;
                    }
                }
            }
        }
    }
}

@@ -3305,24 +3305,28 @@
             
+    
 case DATA_RE
@@ -3329,16 +3329,20 @@
 A_READ:%0A
+    
         
@@ -3389,16 +3389,17 @@
 e%3C?, ?%3E)
+ 
 event.ge
@@ -3425,32 +3425,36 @@
                 
+ 
+   
 handler.readData
@@ -3466,16 +3466,17 @@
 nection)
+ 
 read.get
@@ -3519,32 +3519,36 @@
                 
+    
 break;%0A         
@@ -3550,32 +3550,36 @@
                 
+ 
+   
 case CLIENT_CONN
@@ -3575,32 +3575,36 @@
 CLIENT_CONNECT:%0A
+    
                 
@@ -3631,32 +3631,33 @@
 add((Connection)
+ 
 event.getSecond(
@@ -3676,32 +3676,36 @@
                 
+ 
+   
 handler.clientCo
@@ -3714,32 +3714,33 @@
 ect((Connection)
+ 
 event.getSecond(
@@ -3759,32 +3759,36 @@
                 
+ 
+   
 break;%0A         
@@ -3790,32 +3790,36 @@
                 
+ 
+   
 case CLIENT_DISC
@@ -3818,32 +3818,36 @@
 ENT_DISCONNECT:%0A
+    
                 
@@ -3883,32 +3883,33 @@
 ect((Connection)
+ 
 event.getSecond(
@@ -3928,32 +3928,36 @@
                 
+  
+  
 connections.remo
@@ -3963,32 +3963,33 @@
 ove((Connection)
+ 
 event.getSecond(
@@ -4008,32 +4008,36 @@
                 
+  
+  
 break;%0A         
@@ -4039,32 +4039,36 @@
                 
+  
+  
 case CLIENT_LOST
@@ -4069,16 +4069,20 @@
 T_LOST:%0A
+    
         
@@ -4120,32 +4120,33 @@
 ost((Connection)
+ 
 event.getSecond(
@@ -4165,32 +4165,36 @@
                 
+  
+  
 connections.remo
@@ -4208,16 +4208,17 @@
 nection)
+ 
 event.ge
@@ -4221,32 +4221,112 @@
 t.getSecond());%0A
+                            break;%0A                        case DATA_WRITE:%0A    
                 

fix indent of switch statement and add space after casts in socket server