/* Copyright (c) 1997-2004
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
   http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.de

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   $Project: polymake $$Id: SelectorThread.java,v 1.7 2005/11/18 17:09:50 wotzlaw Exp $
*/

package de.tuberlin.polymake.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.Pipe;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

/**
 * This class is the heart of the polymake java interface. It implements a Thread 
 * listening to multiple channels. A Selector
 * is used to wake the Thread up and process the input of the two channels.
 * The input will be treated according to the run method.
 *
 * @author Thilo Schr&ouml;der
 * @version 1.0
 */
public class SelectorThread extends Thread {

    /** Selector to listen to socket and pipe simultanously. */
    protected Selector selector = null;
    
    /** Channel to communicate with polymake-server. */
    protected SocketChannel polyChannel = null;

    /** SelectionKey to check read access of socket. */
    protected SelectionKey polyKey = null;

    /** Reader to read from socket. */
    protected BufferedReader polyReader = null;

    /** Encoding used for socket communication. */
    protected Charset charset = null;
    
   /** mapping clientChannels to PolymakeControls */
    protected HashMap clientControlMap = new HashMap(8);

    /**
     * Describe variable <code>pipeControlMap</code> here.
     *
     */
    protected HashMap pipeControlMap = new HashMap(8);

    /**
     * Describe variable <code>pipeClientMap</code> here.
     *
     */
    protected HashMap pipeClientMap = new HashMap(8);

    /** Buffer to read from pipe. */
    protected ByteBuffer dbuf = ByteBuffer.allocateDirect(8);

    /**
     * Describe variable <code>staticGeometryKey</code> here.
     *
     */
    protected SelectionKey staticGeometryKey = null;

    /**
     * Creates a new <code>SelectorThread</code> instance.
     *
     * @param psChannel a <code>SocketChannel</code> value
     * @param charset a <code>String</code> value
     * @exception IOException if an error occurs
     */
    public SelectorThread(SocketChannel psChannel, String charset) throws IOException {
	super("SelectorThread");

	selector = Selector.open();
	
	if(psChannel != null) {
	    polyChannel = psChannel;
	    polyReader = new BufferedReader( Channels.newReader(polyChannel,charset));

	}
	this.charset = Charset.forName(charset);
		
	setPriority(Thread.NORM_PRIORITY);
    }

    /**
     * Describe <code>registerControl</code> method here.
     *
     * @param pc a <code>PolymakeControl</code> value
     * @param sc a <code>SocketChannel</code> value
     * @param p a <code>Pipe</code> value
     * @return a <code>SelectionKey</code> value
     * @exception IOException if an error occurs
     */
    public SelectionKey registerControl(PolymakeControl pc, SocketChannel sc, Pipe p ) throws IOException {
	SelectionKey pk = p.source().register(selector, SelectionKey.OP_READ);
	SelectionKey sk = sc.register(selector,SelectionKey.OP_READ);
	clientControlMap.put(sk,pc);
	pipeControlMap.put(pk,pc);
	pipeClientMap.put(pk,sk);
	return sk;
    }

    /**
     * Describe <code>registerControl</code> method here.
     *
     * @param pc a <code>StaticGeometryControl</code> value
     * @param p a <code>Pipe</code> value
     * @exception IOException if an error occurs
     */
    public void registerControl(PolymakeControl pc, Pipe p) throws IOException {
	SelectionKey pk = p.source().register(selector, SelectionKey.OP_READ);
	if(staticGeometryKey == null) {
	    staticGeometryKey = pk;
	}
	pipeControlMap.put(pk,pc);
	pipeClientMap.put(pk,null);
    }

    /**
     * Describe <code>run</code> method here.
     *
     */
    public void run() {
	try {
	    polyKey = polyChannel.register(selector, SelectionKey.OP_READ);
	    while (true) {
		if(System.getProperty("polymake.debug") != null) {
		    System.out.println("java: selecting");
		}
		selector.select();
		Set selectedKeys = selector.selectedKeys();
		Iterator it = selectedKeys.iterator();
		while (it.hasNext()) {
		    SelectionKey actKey = (SelectionKey) it.next();
		    it.remove();
		    if (pipeControlMap.containsKey(actKey)) {
			if(System.getProperty("polymake.debug") != null) {
			    System.out.println("controlPipe...");
			}
			
			dbuf.clear();
			((Pipe.SourceChannel)actKey.channel()).read(dbuf);
			dbuf.flip();
			PolymakeControl pc = (PolymakeControl)pipeControlMap.get(actKey);
			SelectionKey clientKey = (SelectionKey)pipeClientMap.get(actKey);
			if(!pc.isAlive()) {
			    pipeControlMap.remove(actKey);
			    pipeClientMap.remove(actKey);
			    if(clientKey != null && clientControlMap.containsKey(clientKey)) {
				clientControlMap.remove(clientKey);
				clientKey.channel().close();
			    }
			    if(pipeClientMap.size() == 0) {
				System.exit(0);
			    }
			    continue;
			}
			if(clientKey != null) {
			    SocketChannel clientChannel = (SocketChannel)clientKey.channel();
			    String msg = pc.getMessage();
			    if(System.getProperty("polymake.debug") != null) {
				System.out.println("jv>:\n" + msg + "sent.");
			    }
			    if(!pc.getAnswer()) {
				clientChannel.write(charset.encode(msg));
				pc.popMessage();
			    } else {
				clientChannel.write(charset.encode(msg));
			    }			
			}    
		    } else if (clientControlMap.containsKey(actKey)) {
			if(System.getProperty("polymake.debug") != null) {
			    System.out.println("clientChannel...");
			}
			((PolymakeControl)clientControlMap.get(actKey)).update();
		    } else if (polyKey.equals(actKey)) {
			if(System.getProperty("polymake.debug") != null) {
			    System.out.println("polyChannel...");
			}
			((PolymakeControl)pipeControlMap.get(staticGeometryKey)).update();
		    }
		}
		Thread.yield();
	    }
	} catch (IOException e) {
	    System.err.println("SelectorThread: caught IOException in IOThread");
	    e.printStackTrace(System.err);
	}
    }
    
    /** 
     * Close the channels and exit application, if ALL_ClOSED was received.
     * 
     * @throws IOException if any problems closing the channels
     */
    protected void cleanUp() throws IOException{
	if( System.getProperty("polymake.debug") != null ){
	    System.out.print("java exiting...");
	}
	if(polyChannel != null) {
	    polyChannel.close();
	}
	if( System.getProperty("polymake.debug") != null ){
	    System.out.println("done!");
	}
	System.exit(0);
    }
}
