/*
 * Copyright 1999-2010 University of Chicago
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 *
 * See the License for the specific language governing permissions and limitations under the License.
 */
package org.globus.net;

import java.io.IOException;

import org.globus.common.CoGProperties;

/**
 * This class manages the port ranges. It keeps track of which
 * ports are used and which ones are open.
 */
public class PortRange {

    protected static final byte UNUSED = 0;
    protected static final byte USED   = 1;

    private boolean portRange = false;
    private int minPort, maxPort;
    private byte [] ports;

    private static PortRange tcpPortRange = null;
    private static PortRange tcpSourcePortRange = null;
    private static PortRange udpSourcePortRange = null;

    protected PortRange() {
    }

    /**
     * Returns PortRange instance for TCP listening sockets.
     *
     * @see #getTcpInstance()
     */
    public static PortRange getInstance() {
        return getTcpInstance();
    }

    /**
     * Returns PortRange instance for TCP listening sockets.
     * If the tcp.port.range property is set, the class
     * will be initialized with the specified port ranges.
     *
     * @return PortRange the default instace of this class.
     */
    public static synchronized PortRange getTcpInstance() {
        if (tcpPortRange == null) {
            tcpPortRange = new PortRange();
            tcpPortRange.init(CoGProperties.getDefault().getTcpPortRange());
        }
        return tcpPortRange;
    }

    /**
     * Returns PortRange instance for TCP source sockets.
     * If the tcp.source.port.range property is set, the class
     * will be initialized with the specified port ranges.
     *
     * @return PortRange the default instace of this class.
     */
    public static synchronized PortRange getTcpSourceInstance() {
        if (tcpSourcePortRange == null) {
            tcpSourcePortRange = new PortRange();
            tcpSourcePortRange.init(
                  CoGProperties.getDefault().getTcpSourcePortRange());
        }
        return tcpSourcePortRange;
    }

    /**
     * Returns PortRange instance for UDP source sockets.
     * If the udp.source.port.range property is set, the class
     * will be initialized with the specified port ranges.
     *
     * @return PortRange the default instace of this class.
     */
    public static synchronized PortRange getUdpSourceInstance() {
        if (udpSourcePortRange == null) {
            udpSourcePortRange = new PortRange();
            udpSourcePortRange.init(
                  CoGProperties.getDefault().getUdpSourcePortRange());
        }
        return udpSourcePortRange;
    }

    /**
     * Checks if the port range is set.
     *
     * @return true if the port range is set, false otherwise.
     */
    public boolean isEnabled() {
        return portRange;
    }

    /**
     * Returns first available port.
     *
     * @param lastPortNumber port number to start finding the next
     *                       available port from. Set it to 0 if
     *                       called initialy.
     * @return the next available port number from the lastPortNumber.
     * @exception IOException if there is no more free ports available or
     *            if the lastPortNumber is incorrect.
     */
    public synchronized int getFreePort(int lastPortNumber)
        throws IOException {
        int id = 0;
        if (lastPortNumber != 0) {
            id = lastPortNumber - minPort;
            if (id < 0) {
                throw new IOException("Port number out of range.");
            }
        }
        for(int i=id;i<ports.length;i++) {
            if (ports[i] == USED) continue;
            return minPort+i;
        }
        throw new IOException("No free ports available.");
    }

    /**
     * Sets the port number as used.
     *
     * @param portNumber port number
     */
    public void setUsed(int portNumber) {
        setPort(portNumber, USED);
    }

    /**
     * Releases or frees the port number.
     * (Mark it as unused)
     *
     * @param portNumber port number
     */
    public void free(int portNumber) {
        setPort(portNumber, UNUSED);
    }

    /**
     * Checks if the port number if within port range. Does <b>not</b>
     * check if it is a free, but only checks if it is within said range.
     */
    public boolean isInRange(int portNumber) {

        if ((minPort <= portNumber) && (maxPort >= portNumber)) {
            return true;
        }

        return false;
    }

    private synchronized void setPort(int portNumber, byte type) {
        int id = portNumber - minPort;
        if (id < 0) {
            throw new IllegalArgumentException("Port number out of range: " + portNumber);
        }
        ports[id] = type;
    }

    private void init(String portRangeStr) {
        portRange = false;

        if (portRangeStr == null) return ;

        int pos = portRangeStr.indexOf(",");
        if (pos == -1) {
            throw new IllegalArgumentException("Missing comma in the port range property: " +
                                               portRangeStr);
        }

        int min, max;

        try {
            min = Integer.parseInt(portRangeStr.substring(0, pos).trim());
        } catch(Exception e) {
            throw new IllegalArgumentException("The minimum port range value is invalid: " +
                                               e.getMessage());
        }

        try {
            max = Integer.parseInt(portRangeStr.substring(pos+1).trim());
        } catch(Exception e) {
            throw new IllegalArgumentException("The maximum port range value is invalid: " +
                                               e.getMessage());
        }

        if (min >= max) {
            throw new IllegalArgumentException("The minimum port range value is greater then " +
                                               "the maximum port range value.");
        }

        minPort = min;
        maxPort = max;
        ports     = new byte[ maxPort-minPort ];
        portRange = true;
    }

}
