/*
 * Copyright (c) 2009 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 *
 */

package com.jscape.util.net;

import com.jscape.util.Assert;

import java.io.IOException;
import java.util.BitSet;

/**
 * @author Alex Usun
 */
public class PortRange {

    public static final int MAX_VALUE = 65535;
    public static final int MIN_VALUE = 1;

    private int lowerBound;
    private int upperBound;
    private BitSet ports;

    /**
     * Creates a new port range.
     *
     * @param lowerBound lower port bound
     * @param upperBound upper port bound
     */
    public PortRange(int lowerBound, int upperBound) {
        Assert.inRangeInclusive(lowerBound, MIN_VALUE, MAX_VALUE, "Bad bounds.");
        Assert.inRangeInclusive(upperBound, MIN_VALUE, MAX_VALUE, "Bad bounds.");
        Assert.greaterOrEqual(upperBound, lowerBound, "Bad bounds.");

        this.lowerBound = lowerBound;
        this.upperBound = upperBound;
        this.ports = new BitSet(upperBound - lowerBound);
    }

    public PortRange() {
        this(MIN_VALUE, MAX_VALUE);
    }

    /**
     * Returns range lower bound.
     *
     * @return range lower bound
     */
    public synchronized int getLowerBound() {
        return this.lowerBound;
    }

    public synchronized void setLowerBound(int value) {
        Assert.inRangeInclusive(value, MIN_VALUE, MAX_VALUE, "Bad bound.");
        Assert.greaterOrEqual(this.upperBound, value, "Bad bound.");

        adjustPorts(value, this.upperBound);

        this.lowerBound = value;
    }

    /**
     * Returns range upper bound.
     *
     * @return range upper bound
     */
    public synchronized int getUpperBound() {
        return this.upperBound;
    }

    public synchronized void setUpperBound(int value) {
        Assert.inRangeInclusive(value, MIN_VALUE, MAX_VALUE, "Bad bound.");
        Assert.greaterOrEqual(value, this.lowerBound, "Bad bound.");

        adjustPorts(this.lowerBound, value);

        this.upperBound = value;
    }

    public synchronized void setBounds(int lowerBound, int upperBound) {
        Assert.inRangeInclusive(lowerBound, MIN_VALUE, MAX_VALUE, "Bad bounds.");
        Assert.inRangeInclusive(upperBound, MIN_VALUE, MAX_VALUE, "Bad bounds.");
        Assert.greaterOrEqual(upperBound, lowerBound, "Bad bounds.");

        adjustPorts(lowerBound, upperBound);

        this.lowerBound = lowerBound;
        this.upperBound = upperBound;
    }

    /**
     * Allocates a port.
     *
     * @return allocated port number
     *
     * @throws PortRange.AllocationException if no available ports found
     */
    public synchronized int allocatePort()
            throws AllocationException {
        int portIndex = this.ports.nextClearBit(0);

        if (portIndex == (this.upperBound - this.lowerBound + 1)) {
            throw new AllocationException("No free port available.");
        }

        this.ports.set(portIndex, true);

        return (this.lowerBound + portIndex);
    }

    /**
     * Releases allocated port.
     *
     * @param port the port to release
     */
    public synchronized void releasePort(int port) {
        int portIndex = port - this.lowerBound;
        this.ports.set(portIndex, false);
    }

    public String toString() {
        return "PortRange{"
                + "lowerBound=" + this.lowerBound
                + ", upperBound=" + this.upperBound
                + ", ports=" + this.ports
                + '}';
    }

    private void adjustPorts(int lowerBound, int upperBound) {
        BitSet ports = new BitSet(upperBound - lowerBound);

        for (int i = 0, size = this.ports.size(); i < size; ++i) {
            if (this.ports.get(i)) {
                int port = this.lowerBound + i;

                if (lowerBound <= port && port <= upperBound) {
                    int bitIndex = port - lowerBound;
                    ports.set(bitIndex, true);
                }
            }
        }

        this.ports = ports;
    }

    public static final class AllocationException
            extends IOException {

        public AllocationException(String message) {
            super(message);
        }

    }

}
