/*
* Conditions Of Use
*
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), an agency of the Federal Government.
* Pursuant to title 15 Untied States Code Section 105, works of NIST
* employees are not subject to copyright protection in the United States
* and are considered to be in the public domain.  As a result, a formal
* license is not needed to use the software.
*
* This software is provided by NIST as a service and is expressly
* provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY.  NIST does not warrant or make any representations
* regarding the use of the software or the results thereof, including but
* not limited to the correctness, accuracy, reliability or usefulness of
* the software.
*
* Permission to use this software is contingent upon your acceptance
* of the terms of this agreement
*
* .
*
*/
/***************************************************************************
 * Product of NIST/ITL Advanced Networking Technologies Division(ANTD).   *
 **************************************************************************/
package gov.nist.core;

import java.net.*;

/*
 * IPv6 Support added by Emil Ivov (emil_ivov@yahoo.com)<br/>
 * Network Research Team (http://www-r2.u-strasbg.fr))<br/>
 * Louis Pasteur University - Strasbourg - France<br/>
 *
 * Frank Feif reported a bug.
 *
 *
 */
/**
 * Stores hostname.
 * @version 1.2
 *
 * @author M. Ranganathan
 * @author Emil Ivov <emil_ivov@yahoo.com> IPV6 Support. <br/>
 *
 *
 *

 * Marc Bednarek <bednarek@nist.gov> (Bugfixes).<br/>
 *
 */
public class Host extends GenericObject {

    /**
     * Determines whether or not we should tolerate and strip address scope
     * zones from IPv6 addresses. Address scope zones are sometimes returned
     * at the end of IPv6 addresses generated by InetAddress.getHostAddress().
     * They are however not part of the SIP semantics so basically this method
     * determines whether or not the parser should be stripping them (as
     * opposed simply being blunt and throwing an exception).
     */
    private boolean stripAddressScopeZones = false;

    private static final long serialVersionUID = -7233564517978323344L;
    protected static final int HOSTNAME = 1;
    protected static final int IPV4ADDRESS = 2;
    protected static final int IPV6ADDRESS = 3;

    /** hostName field
     */
    protected String hostname;

    /** address field
     */

    protected int addressType;

    private InetAddress inetAddress;

    /** default constructor
     */
    public Host() {
        addressType = HOSTNAME;

        stripAddressScopeZones
            = Boolean.getBoolean("gov.nist.core.STRIP_ADDR_SCOPES");
    }

    /** Constructor given host name or IP address.
     */
    public Host(String hostName) throws IllegalArgumentException {
        if (hostName == null)
            throw new IllegalArgumentException("null host name");

        stripAddressScopeZones
            = Boolean.getBoolean("gov.nist.core.STRIP_ADDR_SCOPES");

        setHost(hostName, IPV4ADDRESS);
    }

    /** constructor
     * @param name String to set
     * @param addrType int to set
     */
    public Host(String name, int addrType) {
        stripAddressScopeZones
            = Boolean.getBoolean("gov.nist.core.STRIP_ADDR_SCOPES");

        setHost(name, addrType);
    }

    /**
     * Return the host name in encoded form.
     * @return String
     */
    public String encode() {
        return encode(new StringBuffer()).toString();
    }

    public StringBuffer encode(StringBuffer buffer) {
        if (addressType == IPV6ADDRESS && !isIPv6Reference(hostname)) {
            buffer.append('[').append(hostname).append(']');
        } else {
            buffer.append(hostname);
        }
        return buffer;
    }

    /**
     * Compare for equality of hosts.
     * Host names are compared by textual equality. No dns lookup
     * is performed.
     * @param obj Object to set
     * @return boolean
     */
    public boolean equals(Object obj) {
        if ( obj == null ) return false;
        if (!this.getClass().equals(obj.getClass())) {
            return false;
        }
        Host otherHost = (Host) obj;
        return otherHost.hostname.equals(hostname);

    }

    /** get the HostName field
     * @return String
     */
    public String getHostname() {
        return hostname;
    }

    /** get the Address field
     * @return String
     */
    public String getAddress() {
        return hostname;
    }

    /**
     * Convenience function to get the raw IP destination address
     * of a SIP message as a String.
     * @return String
     */
    public String getIpAddress() {
        String rawIpAddress = null;
        if (hostname == null)
            return null;
        if (addressType == HOSTNAME) {
            try {
                if (inetAddress == null)
                    inetAddress = InetAddress.getByName(hostname);
                rawIpAddress = inetAddress.getHostAddress();
            } catch (UnknownHostException ex) {
                dbgPrint("Could not resolve hostname " + ex);
            }
        } else {
            rawIpAddress = hostname;
        }
        return rawIpAddress;
    }

    /**
     * Set the hostname member.
     * @param h String to set
     */
    public void setHostname(String h) {
        setHost(h, HOSTNAME);
    }

    /** Set the IP Address.
     *@param address is the address string to set.
     */
    public void setHostAddress(String address) {
        setHost(address, IPV4ADDRESS);
    }

    /**
     * Sets the host address or name of this object.
     *
     * @param host that host address/name value
     * @param type determines whether host is an address or a host name
     */
    private void setHost(String host, int type){
        //set inetAddress to null so that it would be reinited
        //upon next call to getInetAddress()
        inetAddress = null;

        if (isIPv6Address(host))
            addressType = IPV6ADDRESS;
        else
            addressType = type;

        // Null check bug fix sent in by jpaulo@ipb.pt
        if (host != null){
            hostname = host.trim();

            //if this is an FQDN, make it lowercase to simplify processing
            if(addressType == HOSTNAME)
                hostname = hostname.toLowerCase();

            //remove address scope zones if this is an IPv6 address as they
            //are not allowed by the RFC
            int zoneStart = -1;
            if(addressType == IPV6ADDRESS
                && stripAddressScopeZones
                && (zoneStart = hostname.indexOf('%'))!= -1){

                hostname = hostname.substring(0, zoneStart);
            }
        }
    }

    /**
     * Set the address member
     * @param address address String to set
     */
    public void setAddress(String address) {
        this.setHostAddress(address);
    }

    /** Return true if the address is a DNS host name
     *  (and not an IPV4 address)
     *@return true if the hostname is a DNS name
     */
    public boolean isHostname() {
        return addressType == HOSTNAME;
    }

    /** Return true if the address is a DNS host name
     *  (and not an IPV4 address)
     *@return true if the hostname is host address.
     */
    public boolean isIPAddress() {
        return addressType != HOSTNAME;
    }

    /** Get the inet address from this host.
     * Caches the inet address returned from dns lookup to avoid
     * lookup delays.
     *
     *@throws UnkownHostexception when the host name cannot be resolved.
     */
    public InetAddress getInetAddress() throws java.net.UnknownHostException {
        if (hostname == null)
            return null;
        if (inetAddress != null)
            return inetAddress;
        inetAddress = InetAddress.getByName(hostname);
        return inetAddress;

    }

    //----- IPv6
    /**
     * Verifies whether the <code>address</code> could
     * be an IPv6 address
     */
    private boolean isIPv6Address(String address) {
        return (address != null && address.indexOf(':') != -1);
    }

    /**
     * Verifies whether the ipv6reference, i.e. whether it enclosed in
     * square brackets
     */
    public static boolean isIPv6Reference(String address) {
        return address.charAt(0) == '['
            && address.charAt(address.length() - 1) == ']';
    }
    
    @Override
    public int hashCode() {
        return this.getHostname().hashCode();
        
    }
}
