package brooklyn.location.geo;

import java.math.BigDecimal;
import java.net.InetAddress;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import brooklyn.entity.Entity;
import brooklyn.location.AddressableLocation;
import brooklyn.location.Location;
import brooklyn.location.basic.AbstractLocation;
import brooklyn.util.internal.BrooklynSystemProperties;

/**
 * Encapsulates geo-IP information for a given host.
 */
public class HostGeoInfo {
    
    /** the IP address */
    public final String address;
    
    public final String displayName;
    
    public final double latitude;
    public final double longitude;

    public static final Logger log = LoggerFactory.getLogger(HostGeoInfo.class);

    public static HostGeoInfo fromIpAddress(InetAddress address) {
        try {
            HostGeoLookup lookup = findHostGeoLookupImpl();
            if (lookup!=null) {
                return lookup.getHostGeoInfo(address);
            }
        } catch (Exception e) {
            if (log.isDebugEnabled())
                log.debug("unable to look up geo DNS info for "+address+": "+e);
        }
        return null;
    }
    
    public static HostGeoInfo fromLocation(Location l) {
        if (l instanceof HasHostGeoInfo) {
            HostGeoInfo result = ((HasHostGeoInfo)l).getHostGeoInfo();
            if (result!=null) return result;
        }
        InetAddress address = findIpAddress(l);
        Object latitude = l.findLocationProperty("latitude");
        Object longitude = l.findLocationProperty("longitude");

        if (address == null) return null;
        if (latitude == null || longitude == null) {
            HostGeoInfo geo = fromIpAddress(address);
            if (geo==null) return null;
            latitude = geo.latitude;
            longitude = geo.longitude;
        }
        if (latitude instanceof BigDecimal) latitude = ((BigDecimal) latitude).doubleValue();
        if (longitude instanceof BigDecimal) longitude = ((BigDecimal) longitude).doubleValue();
        if (!(latitude instanceof Double) || !(longitude instanceof Double))
            throw new IllegalArgumentException("Passed location specifies invalid type of lat/long");
        
        HostGeoInfo result = new HostGeoInfo(address.getHostAddress(), l.getName(), (Double) latitude, (Double) longitude);
        if (l instanceof AbstractLocation) {
            ((AbstractLocation)l).setHostGeoInfo(result);
        }
        return result;
    }
    
    private static HostGeoLookup findHostGeoLookupImpl() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        String type = BrooklynSystemProperties.HOST_GEO_LOOKUP_IMPL.getValue();
        //like utrace because it seems more accurate than geobytes and gives a report of how many tokens are left
        if (type==null) return new UtraceHostGeoLookup();
        if (type.isEmpty()) return null;
        return (HostGeoLookup) Class.forName(type).newInstance();
    }

    public static HostGeoInfo fromEntity(Entity e) {
        for (Location l : e.getLocations()) {
            HostGeoInfo hgi = fromLocation(l);
            if (hgi != null)
                return hgi;
        }
        return null;
    }
    
    public static InetAddress findIpAddress(Location l) {
        if (l == null)
            return null;
        if (l instanceof AddressableLocation)
            return ((AddressableLocation) l).getAddress();
        return findIpAddress(l.getParentLocation());
    }
    
    
    public HostGeoInfo(String address, String displayName, double latitude, double longitude) {
        this.address = address;
        this.displayName = displayName;
        this.latitude = latitude;
        this.longitude = longitude;
    }

    @Override
    public String toString() {
        return "HostGeoInfo["+displayName+": "+address+" at ("+latitude+","+longitude+")]";
    }
    
    @Override
    public boolean equals(Object o) {
        // Slight cheat: only tests the address field.
        return (o instanceof HostGeoInfo) && address.equals(((HostGeoInfo) o).address);
    }
    
    @Override
    public int hashCode() {
        // Slight cheat: only includes the address field.
        return address.hashCode();
    }
    
}

@@ -3654,16 +3654,41 @@
 ayName =
+ displayName==null ? %22%22 :
  display
@@ -4004,12 +4004,15 @@
 nly 
-test
+include
 s th
@@ -4013,38 +4013,100 @@
 des the address 
-field.
++ displayName field (displayName to allow overloading localhost etc)
 %0A        return 
@@ -4172,24 +4172,93 @@
  o).address)
+%0A                && displayName.equals(((HostGeoInfo) o).displayName)
 ;%0A    %7D%0A    
@@ -4355,37 +4355,129 @@
 ess 
-field.%0A        return address
++ displayName field (displayName to allow overloading localhost etc)%0A        return address.hashCode() * 31 + displayName
 .has

improve equals check for host geo to allow multiple localhosts (eg in tests)