package com.limegroup.gnutella.messagehandlers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.limewire.collection.IntSet;
import org.limewire.inspection.Inspectable;
import org.limewire.security.SecurityToken;

import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.Response;
import com.limegroup.gnutella.URN;

/**
 * A session of OOB result exchange between the local host and a remote host.
 */
class OOBSession implements Inspectable {
    // inspection-related fields
    private final List<Long> responseTimestamps = new ArrayList<Long>(10);
    private final List<Integer> responseCounts = new ArrayList<Integer>(10);
    private final List<Integer> addedResponses = new ArrayList<Integer>(10);
    private final long start;
    private final boolean early;
	// end inspection-related fields
    
    private final SecurityToken token;
    private final IntSet urnHashCodes;
    
    private IntSet responseHashCodes;
    
    private final int requestedResponseCount;
    private final GUID guid;
    
    OOBSession(SecurityToken token, int requestedResponseCount, GUID guid, boolean early) {
        this.token = token;
        this.requestedResponseCount = requestedResponseCount;
        this.urnHashCodes = new IntSet(requestedResponseCount);
        this.guid = guid;
        this.early = early;
        start = System.currentTimeMillis();
	}
    
    GUID getGUID() {
        return guid;
    }
	
    /**
     * Counts the responses uniquely. 
     */
    int countAddedResponses(Response[] responses) {
        int added = 0;
        for (Response response : responses) {
            Set<URN> urns = response.getUrns();
            if (!urns.isEmpty()) {
                added += urnHashCodes.add(urns.iterator().next().hashCode()) ? 1 : 0;
            }
            else {
                // create lazily since responses should have urns
                if (responseHashCodes == null) {
                    responseHashCodes = new IntSet();
                }
                added += responseHashCodes.add(response.hashCode()) ? 1 : 0;
            }
        }
        
        // inspection-related code
        responseTimestamps.add(System.currentTimeMillis());
        responseCounts.add(responses.length);
        addedResponses.add(added);
        // end inspection-related code
        
        return added;
    }
    
    /**
     * Returns the number of results that are still expected to come in.
     */
    final int getRemainingResultsCount() {
        return requestedResponseCount - urnHashCodes.size() - (responseHashCodes != null ? responseHashCodes.size() : 0); 
    }
    
	@Override
    public boolean equals(Object o) {
		if (! (o instanceof OOBSession))
			return false;
		OOBSession other = (OOBSession) o;
		return Arrays.equals(token.getBytes(), other.token.getBytes());
	}
    
    
    public Object inspect() {
        Map<String,Object> ret = new HashMap<String,Object>();
        ret.put("start",start);
        ret.put("urnh",urnHashCodes.size());
        if (responseHashCodes != null)
            ret.put("rhh",responseHashCodes.size());
        ret.put("rrc",requestedResponseCount);
        ret.put("timestamps",responseTimestamps);
        ret.put("rcounts",responseCounts);
        ret.put("added",addedResponses);
        ret.put("early", early);
        return ret;
    }
}