package com.github.noxan.aves.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import com.github.noxan.aves.net.Connection;
import com.github.noxan.aves.protocol.InputProtocol;
import com.github.noxan.aves.protocol.OutputProtocol;
import com.github.noxan.aves.protocol.ProtocolFactory;
import com.github.noxan.aves.protocol.string.StringProtocolFactory;
import com.github.noxan.aves.util.Tuple;

public class SocketClient implements Client, Connection {
    private String host;
    private int port;
    private Socket socket;
    private ClientHandler handler;

    private boolean isConnected;

    private ProtocolFactory factory;
    private InputProtocol in;
    private OutputProtocol out;

    private Thread inputThread;
    private Thread managerThread;

    private BlockingQueue<Tuple<ClientEvent, Object>> clientEvents;

    public SocketClient(ClientHandler handler) {
        this("localhost", 1666, handler, new StringProtocolFactory());
    }

    public SocketClient(String host, int port, ClientHandler handler, ProtocolFactory factory) {
        this.host = host;
        this.port = port;
        this.handler = handler;
        this.factory = factory;
        this.clientEvents = new LinkedBlockingQueue<Tuple<ClientEvent, Object>>();
        this.isConnected = false;
    }

    @Override
    public void write(Object data) throws IOException {
        if(isConnected) {
            out.write(data);
        } else {
            throw new IllegalStateException("Client is not connected.");
        }
    }

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

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

    @Override
    public void connect() throws IOException {
        if(isConnected) {
            throw new IllegalStateException("Client is already connected.");
        } else {
            socket = new Socket();
            socket.connect(new InetSocketAddress(host, port));
            in = factory.createInputProtocol(socket.getInputStream());
            out = factory.createOutputProtocol(socket.getOutputStream());
            isConnected = true;
            inputThread = new Thread(new InputManager());
            inputThread.start();
            managerThread = new Thread(new EventManager());
            managerThread.start();
            handler.clientConnect();
        }
    }

    @Override
    public void disconnect() {
        isConnected = false;
        try {
            inputThread.join();
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        try {
            managerThread.join(1000);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        try {
            socket.close();
        } catch(IOException e) {
            e.printStackTrace();
        }
        handler.clientDisconnect();
    }

    private void offerEvent(ClientEvent event, Object data) {
        clientEvents.offer(new Tuple<ClientEvent, Object>(event, data));
    }

    private class EventManager implements Runnable {
        @Override
        public void run() {
            while(isConnected || !clientEvents.isEmpty()) {
                Tuple<ClientEvent, Object> event = clientEvents.poll();
                if(event != null) {
                    switch(event.getFirst()) {
                        case DATA_READ:
                            handler.readData(event.getSecond());
                            break;
                        case SERVER_DISCONNECT:
                            handler.serverDisconnect();
                            break;
                        case SERVER_LOST:
                            handler.serverLost();
                            break;
                    }
                }
            }
        }
    }

    private class InputManager implements Runnable {
        @Override
        public void run() {
            while(isConnected) {
                try {
                    Object data = in.read();
                    if(data != null) {
                        offerEvent(ClientEvent.DATA_READ, data);
                    } else {
                        isConnected = false;
                        offerEvent(ClientEvent.SERVER_DISCONNECT, null);
                    }
                } catch(IOException e) {
                    isConnected = false;
                    offerEvent(ClientEvent.SERVER_LOST, null);
                }
            }
        }
    }
}

@@ -2541,24 +2541,54 @@
 connect() %7B%0A
+        if(isConnected) %7B%0A    
         isCo
@@ -2604,32 +2604,36 @@
  false;%0A        
+ 
+   
 try %7B%0A          
@@ -2626,32 +2626,36 @@
 y %7B%0A            
+   
+ 
 inputThread.join
@@ -2654,24 +2654,28 @@
 ead.join();%0A
+    
         %7D ca
@@ -2708,32 +2708,36 @@
 ) %7B%0A            
+    
 e.printStackTrac
@@ -2741,34 +2741,42 @@
 race();%0A        
+   
+ 
 %7D%0A
+    
         try %7B%0A  
@@ -2765,32 +2765,36 @@
           try %7B%0A
+    
             mana
@@ -2815,32 +2815,36 @@
 (1000);%0A        
+   
+ 
 %7D catch(Interrup
@@ -2865,32 +2865,36 @@
 ) %7B%0A            
+    
 e.printStackTrac
@@ -2906,26 +2906,33 @@
         
-%7D%0A
+    %7D%0A   
         
 try %7B%0A  
@@ -2915,38 +2915,43 @@
    %7D%0A           
+ 
 try %7B%0A
+    
             sock
@@ -2962,32 +2962,36 @@
 lose();%0A        
+ 
+   
 %7D catch(IOExcept
@@ -3003,32 +3003,36 @@
 ) %7B%0A            
+   
+ 
 e.printStackTrac
@@ -3036,32 +3036,36 @@
 race();%0A        
+    
 %7D%0A        handle
@@ -3050,32 +3050,36 @@
       %7D%0A        
+ 
+   
 handler.clientDi
@@ -3086,24 +3086,124 @@
 sconnect();%0A
+        %7D else %7B%0A            throw new IllegalStateException(%22Client is not connected.%22);%0A        %7D%0A
     %7D%0A%0A    p

throw illegal state exception if disconnect gets called and socket is not connected