package org.greenstone.gatherer.util;

/** Verbatim copy of what's in GS3's org.greenstone.server */

import java.io.IOException;
import java.net.ServerSocket;
import java.net.BindException;

// When needing to use sockets, this class can be used to find an available port
public class PortFinder {

    // Port numbers range from 0 to 65,535. RESERVED PORTS are below 1025
    // see http://www.gnu.org/software/hello/manual/libc/Ports.html
    public static final int MAX_PORT = 65535;
    public static final int PORTS_RESERVED = 1024;

    // Considering a limited range of ports that will be reused (circular buffer)
    public final int PORT_BASE;
    public final int PORT_BLOCK_SIZE;

    // Keep track of what port numbers we have checked for availability
    private int nextFreePort;

    // The socket port number that we will use
    private int port = -1;


    public PortFinder() {
	this( PORTS_RESERVED+1, (MAX_PORT - PORTS_RESERVED) );
    }

    public PortFinder(int base, int blocksize) {
	PORT_BASE = base;
	PORT_BLOCK_SIZE = blocksize;

	nextFreePort = PORT_BASE;
    }

    /** Circular buffer. Modifies the value of nextFreePort (the buffer index). */
    private void incrementNextFreePort() {
	int offset = nextFreePort - PORT_BASE;
	offset = (offset + 1) % PORT_BLOCK_SIZE;
	nextFreePort = PORT_BASE + offset;
    } 

    /** Finds the first available port in the range specified during Constructor call.
     *  @return the number of an available port.	
     */
    public int findPortInRange(boolean verbose) throws Exception {	
	try {
	    boolean foundFreePort = false;
	    for(int i = 0; i < PORT_BLOCK_SIZE; i++) {
		
		if(isPortAvailable(nextFreePort, verbose)) {
		    foundFreePort = true;
		    break;
		    
		} else {
		    incrementNextFreePort();
		}
	    }
	    
	    if(foundFreePort) {
		// Free port number currently found becomes the port number of the socket
		// that will be used
		this.port = nextFreePort;
		incrementNextFreePort();
		
	    } else {
		throw new Exception("Cannot find an available port in the range " 
				    + PORT_BASE + "-" + (PORT_BASE+PORT_BLOCK_SIZE));
	    }
	    
	} catch(IOException e) {
	    System.err.println("Error when trying to find an available port. In PortFinder.findPort() " + e);
	}
	return port;
    }

    /** @return true if the portnumber is in the valid range. */
    public static boolean isValidPortNumber(int portNum) {
	return (portNum >= 0 && portNum <= MAX_PORT);
    }

    /** @return true if the portnumber is in the useable range. */
    public static boolean isAssignablePortNumber(int portNum) {
	return (portNum > PORTS_RESERVED && portNum <= MAX_PORT);
    }

    /** Finds an available port.
     * @return the number of an available port.
     */
    public static int findAnyFreePort() throws Exception {	
	ServerSocket tmpSocket = null;
	int portNumber = -1;
	try {
	    // Creates a server socket, bound to the specified port.
	    // A port of 0 creates a socket on any free port. 
	    tmpSocket = new ServerSocket(0);
	    portNumber = tmpSocket.getLocalPort();
	    tmpSocket.close();
	} catch(Exception e) {
	    System.err.println("Unable to find a free port or close it. Got Exception: " + e);
	    tmpSocket = null;
	}
	return portNumber;
    }

    /** @return true if the portnum is available for use */
    public static boolean isPortAvailable(int portnum, boolean verbose) {
	ServerSocket tmpSocket = null;
	try {
	    tmpSocket = new ServerSocket(portnum);
	    tmpSocket.close();
	    //if(verbose) {
	    //System.err.println("Port " + portnum + " not yet in use.");
	    //}
	    return true;
	    
	} catch(BindException ex){
	    // "Signals that an error occurred while attempting to bind a 
	    // socket to a local address and port. Typically, the port is 
	    // in use, or the requested local address could not be assigned."
	    if(verbose) {
		System.err.println("Port " + portnum + " already in use or can't be assigned.");
	    }
	    tmpSocket = null;
	    return false;	    
	} catch(Exception ex) { 
	    // Some other problem creating or closing the server socket
	    System.err.println("Problem creating or closing server socket at port " + portnum);
	    tmpSocket = null;
	    return false;
	}
    }
}