/*
 * 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;

/**
 * This class manages an External Address message. This class is thread-safe.
 *
 * After instantiation, this class may be added to the message queue on the
 * {@link NatPmpDevice}. It is important to remove the mapping before shutdown
 * of the {@link NatPmpDevice}. Refer to the parameters of
 * {@link #MapRequestMessage(boolean, int, int, long, com.hoodcomputing.natpmp.MessageResponseInterface) }
 * for details of how to perform this.
 * @author flszen
 */
public class MapRequestMessage extends Message {
    private int internalPort;
    private int requestedExternalPort;
    private Integer externalPort;
    private int requestedPortMappingLifetime;
    private Integer portMappingLifetime;

    /**
     * Constructs a new {@link MapRequestMessage}. If
     * requestedPortMappingLifetime is zero, an existing port mapping is
     * removed. If both internalPort and requestedPortMappingLifetime are zero,
     * then all mappings for this host are removed.
     *
     * @param isTCP A value of true indicates that this is a TCP mapping. A value
     * of false indicates that this is a UDP mapping.
     *
     * @param internalPort The port on which this client is listening. A value
     * of zero, along with a lifetime of zero indicates that all port mappings
     * for this client are to be removed.
     *
     * @param requestedExternalPort The external port to request. This port may
     * not be available. Always check the result of the Message to determine
     * which external port is assigned. A value of zero indicates that the
     * NAT-PMP Gateway should choose a port on its own.
     *
     * @param requestedPortMappingLifetime The time, in seconds, to request the
     * mapping for. A value of zero requests that this mapping is removed.
     * Always check the result of the Message to determine the actual lifetime
     * that was assigned, as it may be different than the requested. The
     * recommended port mapping lifetime is 3600 seconds.
     *
     * The protocol specifies an unsigned integer for this parameter, however
     * Java will not support that as a data type. Therefore, the parameter is
     * given as a long. Do not assign values larger than an unsigned int can
     * handle to this parameter.
     *
     * @param listener The {@link MessageResponseInterface} that will respond
     * to the message result.
     */
    public MapRequestMessage(boolean isTCP, int internalPort, int requestedExternalPort,
            int requestedPortMappingLifetime, MessageResponseInterface listener) {
        super(isTCP ? MessageType.MapTCP : MessageType.MapUDP, listener);

        // Localize.
        this.internalPort = internalPort;
        this.requestedExternalPort = requestedExternalPort;
        this.requestedPortMappingLifetime = requestedPortMappingLifetime;
    }

    byte[] getRequestPayload() {
        byte[] request = new byte[12];
        request[2] = 0; // Reserved
        request[3] = 0; // Reserved
        shortToByteArray(getInternalPort(),request, 4); // Internal Port
        shortToByteArray(getRequestedExternalPort(),request, 6); // Requested External Port
        intToByteArray(getRequestedPortMappingLifetime(),request, 8); // Request Port Mapping Lifetime in Seconds

        return request;
    }

    void parseResponse(byte[] response) throws NatPmpException {
        int returnedInternalPort = shortFromByteArray(response, 8);
        if (returnedInternalPort != internalPort) {
            throw new NatPmpException("The internal port returned from the gateway was not the same as the one sent.");
        }
        internalPort = returnedInternalPort;
        
        externalPort = shortFromByteArray(response, 10);
        portMappingLifetime = intFromByteArray(response, 12);
    }

    byte getOpcode() {
        return getMessageType() == MessageType.MapUDP ? (byte)1 : (byte)2;
    }

    /**
     * Gets the internal port on which mapped data will arrive.
     * @return The port on this host that is listening.
     */
    public int getInternalPort() {
        return internalPort;
    }

    /**
     * Get sthe external port that was requested.
     * @return The requested external port.
     */
    public int getRequestedExternalPort() {
        return requestedExternalPort;
    }

    /**
     * Gets the external port that was assigned.
     * @return The external port that was assigned.
     * @throws NatPmpException Thrown if there was an exception generated during
     * the parsing of the respnse.
     */
    public Integer getExternalPort() throws NatPmpException {
        return externalPort;
    }

    /**
     * Gets the requested port mapping lifetime.
     * @return The requested port mapping lifetime, in seconds.
     */
    public int getRequestedPortMappingLifetime() {
        return requestedPortMappingLifetime;
    }

    /**
     * Gets the assigned port mapping lifetime.
     * @return The assigned port mapping lifetime, in seconds.
     * @throws NatPmpException Thrown if there was an exception generated during
     * the parsing of the respnse.
     */
    public Integer getPortMappingLifetime() throws NatPmpException {
        return portMappingLifetime;
    }
}
