package com.limegroup.gnutella;

import java.io.IOException;
import java.net.InetAddress;

import com.limegroup.gnutella.filters.SpamFilter;
import com.limegroup.gnutella.util.DataUtils;
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.vendor.SimppVM;
import com.limegroup.gnutella.messages.vendor.StatisticVendorMessage;
import com.limegroup.gnutella.messages.vendor.UDPCrawlerPong;
import com.limegroup.gnutella.settings.ApplicationSettings;
import com.limegroup.gnutella.statistics.SentMessageStatHandler;
import com.limegroup.gnutella.util.NetworkUtils;

/**
 * This class is an implementation of <tt>ReplyHandler</tt> that is 
 * specialized for handling UDP messages.
 */
public final class UDPReplyHandler implements ReplyHandler {

	/**
	 * Constant for the <tt>InetAddress</tt> of the host to reply to.
	 */
	private final InetAddress IP;

	/**
	 * Constant for the port of the host to reply to.
	 */
	private final int PORT;

	/**
	 * Constant for the <tt>UDPService</tt>.
	 */
	private static final UDPService UDP_SERVICE = UDPService.instance();
    
    /**
     * Used to filter messages that are considered spam.
     * With the introduction of OOB replies, it is important
     * to check UDP replies for spam too.
     *
     * Uses one static instance instead of creating a new
     * filter for every single UDP message.
     */
    private static volatile SpamFilter _personalFilter =
        SpamFilter.newPersonalFilter();
	
	/**
	 * Constructor that sets the ip and port to reply to.
	 *
	 * @param ip the <tt>InetAddress</tt> to reply to
	 * @param port the port to reply to
	 */
	public UDPReplyHandler(InetAddress ip, int port) {
	    if(!NetworkUtils.isValidPort(port))
	        throw new IllegalArgumentException("invalid port: " + port);
	    if(!NetworkUtils.isValidAddress(ip))
	        throw new IllegalArgumentException("invalid ip: " + ip);
	       
		IP   = ip;
		PORT = port;
	}
    
    /**
     * Sets the new personal spam filter to be used for all UDPReplyHandlers.
     */
    public static void setPersonalFilter(SpamFilter filter) {
        _personalFilter = filter;
    }

	
	/**
	 * Sends the <tt>PingReply</tt> via a UDP datagram to the IP and port
	 * for this handler.<p>
	 *
	 * Implements <tt>ReplyHandler</tt>.
	 *
	 * @param hit the <tt>PingReply</tt> to send
	 * @param handler the <tt>ReplyHandler</tt> to use for sending the reply
	 */
	public void handlePingReply(PingReply pong, ReplyHandler handler) {
        UDP_SERVICE.send(pong, IP, PORT);
		SentMessageStatHandler.UDP_PING_REPLIES.addMessage(pong);
	}

	/**
	 * Sends the <tt>QueryReply</tt> via a UDP datagram to the IP and port
	 * for this handler.<p>
	 *
	 * Implements <tt>ReplyHandler</tt>.
	 *
	 * @param hit the <tt>QueryReply</tt> to send
	 * @param handler the <tt>ReplyHandler</tt> to use for sending the reply
	 */
	public void handleQueryReply(QueryReply hit, ReplyHandler handler) {
        UDP_SERVICE.send(hit, IP, PORT);
		SentMessageStatHandler.UDP_QUERY_REPLIES.addMessage(hit);
	}

	/**
	 * Sends the <tt>QueryRequest</tt> via a UDP datagram to the IP and port
	 * for this handler.<p>
	 *
	 * Implements <tt>ReplyHandler</tt>.
	 *
	 * @param request the <tt>QueryRequest</tt> to send
	 * @param handler the <tt>ReplyHandler</tt> to use for sending the reply
	 */
	public void handlePushRequest(PushRequest request, ReplyHandler handler) {
        UDP_SERVICE.send(request, IP, PORT);
		SentMessageStatHandler.UDP_PUSH_REQUESTS.addMessage(request);
	}

	public void countDroppedMessage() {}

	public boolean isPersonalSpam(Message m) {
        return !_personalFilter.allow(m);
	}

	public boolean isOpen() {
		return true;
	}

	public int getNumMessagesReceived() {
		return 0;
	}

	public boolean isOutgoing() {
		return false;
	}

	// inherit doc comment
	public boolean isKillable() {
		return false;
	}

	/**
	 * Implements <tt>ReplyHandler</tt>.  This always returns <tt>false</tt>
	 * for UDP reply handlers, as leaves are always connected via TCP.
	 *
	 * @return <tt>false</tt>, as all leaves are connected via TCP, so
	 *  directly connected leaves will not have <tt>UDPReplyHandler</tt>s
	 */
	public boolean isSupernodeClientConnection() {
		return false;
	}

	/**
	 * Implements <tt>ReplyHandler</tt> interface.  Always returns 
	 * <tt>false</tt> because leaves are connected via TCP, not UDP.
	 *
	 * @return <tt>false</tt>, since leaves never maintain their connections
	 *  via UDP, only TCP
	 */
	public boolean isLeafConnection() {
		return false;
	}

	/**
	 * Returns whether or not this connection is a high-degree connection,
	 * meaning that it maintains a high number of intra-Ultrapeer connections.
	 * In the case of UDP reply handlers, this always returns <tt>false<tt>.
	 *
	 * @return <tt>false</tt> because, by definition, a UDP 'connection' is not
	 *  a connection at all
	 */
	public boolean isHighDegreeConnection() {
		return false;
	}

    /**
     * Returns <tt>false</tt> since UDP reply handlers are not TCP 
     * connections in the first place.
     *
     * @return <tt>false</tt>, since UDP handlers are not connections in
     *  the first place, and therefore cannot use Ultrapeer query routing
     */
    public boolean isUltrapeerQueryRoutingConnection() {
        return false;
    }


    /**
     * Returns <tt>false</tt>, as this node is not  a "connection"
     * in the first place, and so could never have sent the requisite
     * headers.
     *
     * @return <tt>false</tt>, as this node is not a real connection
     */
    public boolean isGoodUltrapeer() {
        return false;
    }

    /**
     * Returns <tt>false</tt>, as this node is not  a "connection"
     * in the first place, and so could never have sent the requisite
     * headers.
     *
     * @return <tt>false</tt>, as this node is not a real connection
     */
    public boolean isGoodLeaf() {
        return false;
    }

    /**
     * Returns <tt>false</tt>, since we don't know whether a host 
     * communicating via UDP supports pong caching or not.
     *
     * @return <tt>false</tt> since we don't know if this node supports
     *  pong caching or not
     */
    public boolean supportsPongCaching() {
        return false;
    }

    /**
     * Returns whether or not to allow new pings from this <tt>ReplyHandler</tt>.
     * Since this ping is over UDP, we'll always allow it.
     *
     * @return <tt>true</tt> since this ping is received over UDP
     */
    public boolean allowNewPings() {
        return true;
    }

    /**
     * sends a Vendor Message to the host/port in this reply handler by UDP
     * datagram.
     */
    public void handleStatisticVM(StatisticVendorMessage m) throws IOException {
        UDPService.instance().send(m, IP, PORT);
    }
    
    /**
     * As of now there is no need to send SimppMessages via UDP, 
     */ 
    public void handleSimppVM(SimppVM simppVM) {
        //This should never happen. But if it does, ignore it and move on
        return;
    }
    


    // inherit doc comment
    public InetAddress getInetAddress() {
        return IP;
    }
    
    /**
     * Retrieves the host address.
     */
    public String getAddress() {
        return IP.getHostAddress();
    }

    /**
     * Returns <tt>false</tt> to indicate that <tt>UDPReplyHandler</tt>s 
     * should never be considered stable, due to data loss over UDP and lack
     * of knowledge as to whether the host is still alive.
     *
     * @return <tt>false</tt> since UDP handler are never stable
     */
    public boolean isStable() {
        return false;
    }

    /**
     * implementation of interface. this is not used.
     */
    public String getLocalePref() {
        return ApplicationSettings.DEFAULT_LOCALE.getValue();
    }

	/**
	 * Overrides toString to print out more detailed information about
	 * this <tt>UDPReplyHandler</tt>
	 */
	public String toString() {
		return ("UDPReplyHandler:\r\n"+
				IP.toString()+"\r\n"+
				PORT+"\r\n");
	}
	
	/**
	 * sends the response through udp back to the requesting party
	 */
	public void handleUDPCrawlerPong(UDPCrawlerPong m) {
		UDPService.instance().send(m, IP, PORT);
	}
	
	public void reply(Message m) {
		UDPService.instance().send(m, IP,PORT);
	}
	
	public int getPort() {
		return PORT;
	}
	
	public byte[] getClientGUID() {
	    return DataUtils.EMPTY_GUID;
	}
}
