/*
 * This file is part of jNAT-PMPlib.
 *
 * jNAT-PMPlib is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * jNAT-PMPlib 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with jNAT-PMPlib.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.tomp2p.natpmp;

import java.net.InetAddress;

/**
 * This class manages a NAT-PMP device. This class is thread-safe.
 *
 * This manages communication with a NAT-PMP device on the network. There are
 * two types of messages that can be sent to the device.
 * {@link ExternalAddressRequestMessage} can be sent to get the external IP of
 * the gateway. {@link MapRequestMessage} can be sent to map a port for a
 * certain amount of time. These two messages can be put into the message queue
 * through the {@link #enqueueMessage(Message)} method.
 *
 * As this class manages a message queue to the NAT-PMP device, it is important
 * to shut it down correctly. Any mapped ports that are no longer desired should
 * be unmapped before shutdown occurs. Refer to {@link #NatPmpDevice(boolean)}
 * for details about the shutdown mechanism.
 * @author flszen
 */
public class NatPmpDevice {
    // Shutdown control instance fields.
    private boolean isShutdown = false;
    private final Object shutdownLock = new Object();

    private MessageQueue messageQueue;

    /**
     * Constructs a new NatPmpDevice.
     *
     * @param shutdownHookEnabled Shutting down existing port mappings is a
     * desired behavior; therefore, this value is required! Refer to
     * {@link #setShutdownHookEnabled(boolean)} for details about what value
     * should be provided here and how it alters the object's behavior.
     *
     * @throws NatPmpException A NatPmpException may be thrown if the local
     * network is not using addresses defined in RFC1918. A NatPmpException may
     * also be thrown if the the network gateway cannot be determined, which may
     * rarely be due to the network not using IPv4. NAT-PMP should only be used 
     * on RFC1918 networks using IPv4.
     *
     * @see #setShutdownHookEnabled(boolean)
     * @see #shutdown()
     */
    public NatPmpDevice(InetAddress gateway) throws NatPmpException {
        // Reject if the gateway is null.
        // This indicates either no gateway or the network is not IPv4.
        // It could also be that the netstat response is not supported.
        if (gateway == null) {
            throw new NatPmpException("The network gateway cannot be located.");
        }

        // Reject if it is not RFC1918.
        if (!gateway.isSiteLocalAddress()) {
            throw new NatPmpException("The network gateway address is not RFC1918 compliant.");
        }

        // Set up messaging queue.
        messageQueue = MessageQueue.createMessageQueue(gateway);
    }

    /**
     * Enqueues a message for sending.
     * @param message The {@link Message} to send.
     * @see #clearQueue()
     */
    public void enqueueMessage(Message message) {
        messageQueue.enqueueMessage(message);
    }
    
    /**
     * Clears the queue of messages to send. If a message is currently sending,
     * it is not interrupted.
     * @see #enqueueMessage(Message)
     */
    public void clearQueue() {
        messageQueue.clearQueue();
    }

    /**
     * Synchronously waits until the queue is empty before returning.
     */
    public void waitUntilQueueEmpty() {
        messageQueue.waitUntilQueueEmpty();
    }

    /**
     * Shuts down this NatPmpDevice. If the shutdown hook is disabled, this
     * method should be called manually at the time port mappings through the
     * NAT-PMP gateway are no longer needed. If the shutdown hook is enabled,
     * this method is called automatically during Java VM shutdown.
     *
     * When this method is called, if the shutdown hook is enabled, it is
     * automatically disabled.
     *
     * It should be noted that when this method is called manually, it blocks
     * until it completes. If it is desired to shutdown asynchronously, the
     * {@link #shutdownAsync(boolean)} method should be called.
     *
     * @see #isShutdown()
     * @see #setShutdownHookEnabled(boolean)
     * @see #shutdownAsync(boolean)
     */
    public void shutdown() {
        synchronized (shutdownLock) {
            // Do the shutdown stuff.
            messageQueue.shutdown();

            // Set the isShutdown flag.
            isShutdown = true;
        }
    }

    /**
     * Flag indicates if this NatPmpDevice is shutdown. This method will block
     * if a shutdown is in progress. If you desire to wait for an asynchronous
     * shutdown to complete, please monitor the returned {@link Thread} instead.
     * @return True if this NatPmpDeviceTest is shutdown, false if it is not.
     * @see #shutdown() 
     */
    public boolean isShutdown() {
        synchronized (shutdownLock) {
            return isShutdown;
        }
    }
}
