package com.hypefiend.javagamebook.client;

import com.hypefiend.javagamebook.common.*;

import java.nio.*;
import java.nio.channels.*;
import java.util.*;
import java.net.*;
import java.io.*;

import org.apache.log4j.*;

/**
 * NIOEventReader.java
 *
 * Odczytuje obiekty GameEvent z serwera
 * 
 * @author <a href="mailto:bret@hypefiend.com">bret barker</a>
 * @version 1.0
 */
public class NIOEventReader extends Thread {
    /** obiekt rejestracji log4j */
    private Logger log = Logger.getLogger(NIOEventReader.class);

    /** referencja do klienta gry */
    GameClient gameClient;

    /** połączenie z  serwerem */
    private SocketChannel channel;
    
    /** kolejka na przychodzące zdarzenia */
    private EventQueue queue;

    /** selektor do zarządzania połączeniem serwera */
    private Selector selector;
    
    /** nadal działa? */
    private  boolean running;
 
    /** 
     * konstruktor.
     */
    public NIOEventReader(GameClient gc, SocketChannel channel, EventQueue queue) {
        super("NIOEventReader");
        this.gameClient = gc;
        this.queue = queue;
        this.channel = channel;
    }

    /** 
     * jest niemal identyczna z metodą SelectAndRead.select()
     */ 
    public void run() {
        try {
            selector = Selector.open();
            channel.register(selector, SelectionKey.OP_READ, new Attachment());
        }
        catch (ClosedChannelException cce) {
            log.error("wyjątek closedchannelexception w czasie rejestracji kanału w selektorze", cce);
            return;
        }
        catch (IOException ioe) {
            log.error("wyjątek ioexception w czasie rejestracji kanału w selektorze", ioe);
            return;
        }

        running = true;
        while (running) {
            try {
            selector.select();
            Set readyKeys = selector.selectedKeys();
            
            Iterator i = readyKeys.iterator();
            while (i.hasNext()) {
                SelectionKey key = (SelectionKey) i.next();
                i.remove();
                
                SocketChannel channel = (SocketChannel) key.channel();
                Attachment attachment = (Attachment) key.attachment();

                try {
                long nbytes = channel.read(attachment.readBuff);
                if (nbytes == -1) {
                    channel.close();
                    
                    GameEvent event = gameClient.createDisconnectEvent("end-of-stream");
                    queue.enQueue(event);
                }
                
                try {
                    if (attachment.readBuff.position() >= attachment.HEADER_SIZE) {
                    attachment.readBuff.flip();
                    
                    while(attachment.eventReady()) {
                    GameEvent event = getEvent(attachment);
                    queue.enQueue(event);
                    attachment.reset();
                    }
                    attachment.readBuff.compact();
                    }
                }
                catch (IllegalArgumentException e) {
                    log.error("wyjątek illegalargument w czasie analizy przychodzących zdarzeń", e);
                }
                }
                catch (IOException ioe) {
                log.warn("wyjątek IOException w czasie read(), zamykanie kanału:" + channel.socket().getInetAddress());
                channel.close();
                }
            }
            }
            catch (IOException ioe2) {
            log.warn("błąd w czasie select(): " + ioe2.getMessage());
            }
            catch (Exception e) {
            log.error("błąd w czasie select()", e);
            }
        }
    }

    private GameEvent getEvent(Attachment attachment) {
        GameEvent event = null;
        ByteBuffer bb = ByteBuffer.wrap(attachment.payload);

        // wywołanie do klienta gry w celu tworzenia zdarzeń
        event = gameClient.createGameEvent();
        event.read(bb);

        return event;
        }  

        public void shutdown() {
        running = false;
        // wymuszenie odblokowania selektora
        selector.wakeup();
    }
}

