
package com.limegroup.gnutella.messages.vendor;

import com.limegroup.gnutella.ByteOrder;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.messages.BadPacketException;

/**
 * a request for a given ultrapeer's ultrapeer connections.
 * Useful for crawling.
 * 
 * Currently it doesn't do any validation of the source, i.e. we 
 * sent our list of ultrapeer to whomever requests it.
 */
public class UDPCrawlerPing extends VendorMessage {
	
	public static final int VERSION = 1;
	
	public static final int ALL=-1;
	
	/**
	 * the number of requested ultrapeer results.  
	 */
	private int _numberUP;
	
	/**
	 * the number of requested leaf results.  
	 */
	private int _numberLeaves;
	
	/**
	 * a bitmask representing the format of the message.  Extensible with up to 8 features.
	 * Add more to the list below, make sure you update the feature mask.
	 */
	private byte _format;
	
	public static final byte PLAIN = 0x0;
	
	public static final byte CONNECTION_TIME = 0x1 << 0;
	public static final byte LOCALE_INFO = 0x1 << 1;
	public static final byte NEW_ONLY = 0x1 << 2;
	public static final byte USER_AGENT = 0x1 << 3;
    public static final byte NODE_UPTIME = 0x1 << 4;
    public static final byte REPLIES = 0x1 << 5;
	
	//all features OR'd.
    public static final byte FEATURE_MASK = 
    	CONNECTION_TIME |
    	LOCALE_INFO |
    	NEW_ONLY |
    	USER_AGENT |
    	NODE_UPTIME |
    	REPLIES; 
	
	/**
	 * constructs a new ultrapeer request message.
	 * @param guid the guid of the message
	 * @param number the number of ultrapeers desired
	 * @param features the features we want to receive in the pong
	 */
	public UDPCrawlerPing(GUID guid, int numberUP, int numberLeaves, byte features) {
	      super(F_LIME_VENDOR_ID, F_CRAWLER_PING, VERSION,
	            derivePayload(numberUP,numberLeaves, features));
	      setGUID(guid);
	      _numberUP = numberUP;
	      _numberLeaves = numberLeaves;
	      _format = (byte) (features & FEATURE_MASK);
	}
	
	
	
	/**
	 * constructs a new ultrapeer request message, asking for
	 * all ultrapeers and leafs of the other guy.
	 * @param guid the guid of the message
	 */
	public UDPCrawlerPing(GUID guid) {
	      this(guid,ALL,ALL,PLAIN);
	}
	
	private static byte [] derivePayload(int numberUP, int numberLeaves, byte features) {
		//we don't expect to have more than 255 connections soon
		
		if (numberUP > 255)
			numberUP=255;
		if (numberLeaves > 255)
			numberLeaves=255;
		
		//trim the features to the ones we currently support
		features= (byte) (features & FEATURE_MASK);
		
		byte [] temp = new byte [2];
		byte [] payload = new byte[3];
		
		ByteOrder.short2leb((short)numberUP,temp,0);
		payload[0]=temp[0];
		ByteOrder.short2leb((short)numberLeaves,temp,0);
		payload[1] = temp[0];
		
		//the third byte is the requested format
		payload[2]=features;
		
		return payload;
	}
	
	/**
	 * see superclass comment
	 * 
	 * note this does not have upper limit to the number of requested results 
	 * (other than the 255 byte limit).  One day we may have many more connections..
	 */
	protected UDPCrawlerPing(byte[] guid, byte ttl, byte hops,
			 int version, byte[] payload)
			throws BadPacketException {
		super(guid, ttl, hops, F_LIME_VENDOR_ID, F_CRAWLER_PING, version, payload);
		
		//see if the payload is valid
		if (getVersion() == VERSION && (payload == null || payload.length != 3))
			throw new BadPacketException();
		
		//a new version would ideally keep the first 3 bytes the same.
		_numberUP = ByteOrder.ubyte2int(payload[0]);
		_numberLeaves = ByteOrder.ubyte2int(payload[1]);
		_format = payload[2];
		
		//trim the features
		_format =(byte)(_format & FEATURE_MASK);
		
	}
	
	
	/**
	 * @return Returns the number of UP neighbor addresses that were requested
	 * with this message.
	 */
	public int getNumberUP() {
		return _numberUP;
	}
	
	/**
	 * @return Returns the number of Leaf neighbor addresses that were requested
	 * with this message.
	 */
	public int getNumberLeaves() {
		return _numberLeaves;
	}
	
    /**
     * 
     * @return whether the ping is requesting this node's connection uptime.
     */
    public boolean hasNodeUptime() {
        return (byte)(NODE_UPTIME & _format) == NODE_UPTIME;
    }
    
    /**
     * 
     * @return whether the ping is requesting connection uptimes.
     */
    public boolean hasConnectionTime() {
        return (byte)(CONNECTION_TIME & _format) == CONNECTION_TIME;
    }
	
	/**
	 * 
	 * @return whether the ping is requesting locale info
	 */
	public boolean hasLocaleInfo() {
		return (byte)(LOCALE_INFO & _format) == LOCALE_INFO;
	}
	
	/**
	 * 
	 * @return whether the ping wants to receive only connections which
	 * support UDP pinging (useful for crawling)
	 */
	public boolean hasNewOnly() {
		return (byte)(NEW_ONLY & _format) == NEW_ONLY;
	}
	
	/**
	 * 
	 * @return whether the ping wants to receive information about the 
	 * User-Agent strings reported by the connections.
	 */
	public boolean hasUserAgent() {
		return (byte)(USER_AGENT & _format) == USER_AGENT;
	}
	
	/**
	 * @return whether the ping wants the number of replies received
	 * over each connection
	 */
	public boolean hasReplies() {
		return hasFeature(REPLIES);
	}
	
	/**
	 * checks whether ht ping is requesting a specific feature.
	 * @param featureId the byte id of the feature
	 * @return whether the ping is asking for it
	 */
	public boolean hasFeature(byte featureId) {
		return (byte)(featureId & _format) == featureId;
	}
	
	/**
	 * @return Returns the _format.
	 */
	public byte getFormat() {
		return _format;
	}
}
