
package org.jgroups.stack;

import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.Address;
import org.jgroups.Global;
import org.jgroups.PhysicalAddress;
import org.jgroups.util.Util;

import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.net.Inet6Address;


/**
 * Network-dependent address (Internet). Generated by the bottommost layer of the protocol
 * stack (UDP). Contains an InetAddress and port.
 * @author Bela Ban
 */
public class IpAddress implements PhysicalAddress {
    private static final long       serialVersionUID=5877146630213185651L;
    private InetAddress             ip_addr;
    private int                     port;
    protected static final Log      log=LogFactory.getLog(IpAddress.class);
    static boolean                  resolve_dns;
    protected int                   size=-1;

    static {
        /* Trying to get value of resolve_dns. PropertyPermission not granted if
        * running in an untrusted environment  with JNLP */
        try {
            String tmp=Util.getProperty(new String[]{Global.RESOLVE_DNS, "resolve.dns"}, null, null, "false");
            resolve_dns=Boolean.valueOf(tmp);
        }
        catch (SecurityException ex){
            resolve_dns=false;
        }
    }



    // Used only by Externalization
    public IpAddress() {
    }

    public IpAddress(String i, int p) throws UnknownHostException {
        port=p;
        ip_addr=InetAddress.getByName(i);
    }



    public IpAddress(InetAddress i, int p) {
        ip_addr=i; port=p;
        if(this.ip_addr == null)
            setAddressToLocalHost();
    }


    private void setAddressToLocalHost() {
        try {
            ip_addr=InetAddress.getLocalHost();  // get first NIC found (on multi-homed systems)
        }
        catch(Exception e) {
            ip_addr=null;
        }
        if(ip_addr == null) {
            try {
                ip_addr=InetAddress.getByName(null);
            }
            catch(UnknownHostException e) {
                log.error("exception: " + e);
            }
        }
    }

    public IpAddress(int port) {
        this(port, true);
    }

    public IpAddress(int port, boolean set_default_host) {
        this.port=port;
        if(set_default_host)
            setAddressToLocalHost();
    }


    public IpAddress(InetSocketAddress sock_addr) {
        port=sock_addr.getPort();
        ip_addr=sock_addr.getAddress();
    }



    public final InetAddress  getIpAddress()               {return ip_addr;}
    public final int          getPort()                    {return port;}


    /**
     * implements the java.lang.Comparable interface
     * @see java.lang.Comparable
     * @param o - the Object to be compared
     * @return a negative integer, zero, or a positive integer as this object is less than,
     *         equal to, or greater than the specified object.
     * @exception java.lang.ClassCastException - if the specified object's type prevents it
     *            from being compared to this Object.
     */
    public final int compareTo(Address o) {
        int   h1, h2, rc; // added Nov 7 2005, makes sense with canonical addresses

        if(this == o) return 0;
        if(!(o instanceof IpAddress))
            throw new ClassCastException("comparison between different classes: the other object is " +
                    (o != null? o.getClass() : o));
        IpAddress other = (IpAddress) o;
        if(ip_addr == null)
            if (other.ip_addr == null) return port < other.port ? -1 : (port > other.port ? 1 : 0);
            else return -1;

        h1=ip_addr.hashCode();
        h2=other.ip_addr.hashCode();
        rc=h1 < h2? -1 : h1 > h2? 1 : 0;
        return rc != 0 ? rc : port < other.port ? -1 : (port > other.port ? 1 : 0);
    }


    public final boolean equals(Object obj) {
        if(this == obj) return true; // added Nov 7 2005, makes sense with canonical addresses

        if(!(obj instanceof IpAddress))
            return false;
        IpAddress other=(IpAddress)obj;
        boolean sameIP;
        if(this.ip_addr != null)
            sameIP=this.ip_addr.equals(other.ip_addr);
        else
            sameIP=(other.ip_addr == null);
        return sameIP && (this.port == other.port);
    }




    public final int hashCode() {
        return ip_addr != null ? ip_addr.hashCode() + port : port;
    }




    public String toString() {
        StringBuilder sb=new StringBuilder();

        if(ip_addr == null)
            sb.append("<null>");
        else {
            if(ip_addr.isMulticastAddress())
                sb.append(ip_addr.getHostAddress());
            else {
                String host_name;
                if(resolve_dns) {
                    host_name=ip_addr.getHostName();
                }
                else {
                    host_name=ip_addr.getHostAddress();
                }
                sb.append(host_name);
            }
        }
        sb.append(":").append(port);
        return sb.toString();
    }


    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        try {
            readFrom(in);
        }
        catch(Exception e) {
            throw new IOException(e);
        }
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        try {
            writeTo(out);
        }
        catch(Exception e) {
            throw new IOException(e);
        }
    }

    public void writeTo(DataOutput out) throws Exception {
        if(ip_addr != null) {
            byte[] address=ip_addr.getAddress();  // 4 bytes (IPv4) or 16 bytes (IPv6)
            out.writeByte(address.length); // 1 byte
            out.write(address, 0, address.length);
            if(ip_addr instanceof Inet6Address)
                out.writeInt(((Inet6Address)ip_addr).getScopeId());
        }
        else {
            out.writeByte(0);
        }
        out.writeShort(port);
    }

    public void readFrom(DataInput in) throws Exception {
        int len=in.readByte();
        if(len > 0 && (len != Global.IPV4_SIZE && len != Global.IPV6_SIZE))
            throw new IOException("length has to be " + Global.IPV4_SIZE + " or " + Global.IPV6_SIZE + " bytes (was " +
                    len + " bytes)");
        byte[] a = new byte[len]; // 4 bytes (IPv4) or 16 bytes (IPv6)
        in.readFully(a);
        if(len == Global.IPV6_SIZE) {
            int scope_id=in.readInt();
            this.ip_addr=Inet6Address.getByAddress(null, a, scope_id);
        }
        else {
            this.ip_addr=InetAddress.getByAddress(a);
        }

        // changed from readShort(): we need the full 65535, with a short we'd only get up to 32K !
        port=in.readUnsignedShort();
    }

    public int size() {
        if(size >= 0)
            return size;
        // length (1 bytes) + 4 bytes for port
        int tmp_size=Global.BYTE_SIZE+ Global.SHORT_SIZE;
        if(ip_addr != null) {
            tmp_size+=ip_addr.getAddress().length; // 4 bytes for IPv4
            if(ip_addr instanceof Inet6Address)
                tmp_size+=Global.INT_SIZE;
        }
        size=tmp_size;
        return tmp_size;
    }

    public IpAddress copy() {
        return new IpAddress(ip_addr, port);
    }



}
