package com.limegroup.gnutella.filters;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.io.IP;
import org.limewire.mojito.messages.DHTMessage;

import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.PingReply;
import com.limegroup.gnutella.messages.PushRequest;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryRequest;

abstract class AbstractIPFilter implements IPFilter {

    private static final Log LOG = LogFactory.getLog(AbstractIPFilter.class);
    
    /**
     * marker object for ips which are allowed.
     */
    private static final IP ALLOWED = new IP(new byte[4]);
    
    protected IP extractAddress(Message m) {
        if (m instanceof PingReply) {
            PingReply pr = (PingReply)m;
            return getIP(pr.getAddress());
        } else if (m instanceof QueryReply) {
            QueryReply qr = (QueryReply)m;
            return getIP(qr.getIPBytes());
        } else if (m instanceof PushRequest) {
            PushRequest push=(PushRequest)m;
            return getIP(push.getIP());
        } else if (m instanceof QueryRequest) {
            QueryRequest query = (QueryRequest)m;
            if (query.desiresOutOfBandReplies())
                return getIP(query.getGUID());
            else
                return ALLOWED;
        }
        else if (m instanceof DHTMessage){
            DHTMessage message = (DHTMessage)m;
            InetSocketAddress addr = 
                (InetSocketAddress) message.getContact().getContactAddress();
            if (addr != null && addr.getAddress() instanceof Inet4Address)
                return getIP(addr.getAddress().getAddress());
            // dht messages do not require contact address.
            return ALLOWED;
        } else {
            // we dont want to block other kinds of messages
            return ALLOWED;
        }
    }
    
    private IP getIP(byte [] host) {
        IP ip = null;
        try {
            ip = new IP(host, 0);
        } catch(IllegalArgumentException badHost) {
        }
        return ip;
    }
    
    private IP getIP(String host) {
        IP ip = null;
        try {
            ip = new IP(host);
        } catch (IllegalArgumentException badHost) {
            try {
                if (LOG.isDebugEnabled())
                    LOG.debug("doing dns lookup for "+host);
                InetAddress lookUp = InetAddress.getByName(host);
                host = lookUp.getHostAddress();
                ip = new IP(host);
            } catch(UnknownHostException unknownHost) {
                // could not look up this host.
            } catch(IllegalArgumentException stillBadHost) {
                // couldn't construct IP still.
            }
        }        
        return ip;
    }
    
    /** 
     * Checks if a given Message's host is banned.
     * @return true if this Message's host is allowed, false if it is banned
     *  or we are unable to create correct IP addr out of it.
     */
    public boolean allow(Message m) {
        return allow(extractAddress(m));
    }
    

    public boolean allow(SocketAddress addr) {
        if(!(addr instanceof InetSocketAddress)) {
            return false;
        }
        return allow(((InetSocketAddress)addr).getAddress().getAddress());
    }
    
    /**
     * Checks to see if a given host is banned.
     * @param host the host's IP in byte form.
     */
    public boolean allow(byte[] host) {
        return allow(getIP(host));
    }
    
    public boolean allow(String host) {
        return allow(getIP(host));
    }
    
    public boolean allow(IP ip) {
        return allowAndLog(ip);
    }
    
    private boolean allowAndLog(IP ip) {
        if (ip == ALLOWED) {
            LOG.debug("allowing non-checkable ip.");
            return true;
        }
        
        if (ip == null) {
            LOG.debug("not allowing invalid ip");
            return false;
        }
        
        boolean yes = allowImpl(ip);
        
        if (yes && LOG.isDebugEnabled())
            LOG.debug(this.hashCode()+" allowing "+ip);
        else if (!yes && LOG.isInfoEnabled())
            LOG.info(this.hashCode()+" NOT allowing "+ip);
        
        return yes;
    }
    
    protected abstract boolean allowImpl(IP ip);
}
