package net.i2p.kademlia;
/*
 * free (adj.): unencumbered; not under the control of others
 * Written by jrandom in 2003 and released into the public domain
 * with no warranty of any kind, either expressed or implied.
 * It probably won't make your computer catch on fire, or eat
 * your children, but it might.  Use at your own risk.
 *
 */

import java.util.Collections;
import java.util.Set;

import net.i2p.I2PAppContext;
import net.i2p.data.SimpleDataStructure;
import net.i2p.util.ConcurrentHashSet;

/**
 *  A concurrent implementation using ConcurrentHashSet.
 *  The max size (K) may be temporarily exceeded due to concurrency,
 *  a pending split, or the behavior of the supplied trimmer,
 *  as explained below.
 *  The creator is responsible for splits.
 *
 *  This class has no knowledge of the DHT base used for XORing,
 *  and thus there are no validity checks in add/remove.
 *
 *  The begin and end values are immutable.
 *  All entries in this bucket will have at least one bit different
 *  from us in the range [begin, end] inclusive.
 *  Splits must be implemented by creating two new buckets
 *  and discarding this one.
 *
 *  The keys are kept in a Set and are NOT sorted by last-seen.
 *  Per-key last-seen-time, failures, etc. must be tracked elsewhere.
 *
 *  If this bucket is full (i.e. begin == end &amp;&amp; size == max)
 *  then add() will call KBucketTrimmer.trim() do
 *  (possibly) remove older entries, and indicate whether
 *  to add the new entry. If the trimmer returns true without
 *  removing entries, this KBucket will exceed the max size.
 *
 *  Refactored from net.i2p.router.networkdb.kademlia
 *  @since 0.9.2 in i2psnark, moved to core in 0.9.10
 */
class KBucketImpl<T extends SimpleDataStructure> implements KBucket<T> {
    /**
     *  set of Hash objects for the peers in the kbucket
     */
    private final Set<T> _entries;
    /** include if any bits equal or higher to this bit (in big endian order) */
    private final int _begin;
    /** include if no bits higher than this bit (inclusive) are set */
    private final int _end;
    private final int _max;
    private final KBucketTrimmer<T> _trimmer;
    /** when did we last shake things up */
    private long _lastChanged;
    private final I2PAppContext _context;
    
    /**
     *  All entries in this bucket will have at least one bit different
     *  from us in the range [begin, end] inclusive.
     */
    public KBucketImpl(I2PAppContext context, int begin, int end, int max, KBucketTrimmer<T> trimmer) {
        if (begin > end)
            throw new IllegalArgumentException(begin + " > " + end);
        _context = context;
        _entries = new ConcurrentHashSet<T>(max + 4);
        _begin = begin;
        _end = end;
        _max = max;
        _trimmer = trimmer;
    }
    
    public int getRangeBegin() { return _begin; }

    public int getRangeEnd() { return _end; }

    public int getKeyCount() {
        return _entries.size();
    }
    
    /**
     *  @return an unmodifiable view; not a copy
     */
    public Set<T> getEntries() {
        return Collections.unmodifiableSet(_entries);
    }

    public void getEntries(SelectionCollector<T> collector) {
        for (T h : _entries) {
             collector.add(h);
        }
    }
    
    public void clear() {
        _entries.clear();
    }
    
    /**
     *  Sets last-changed if rv is true OR if the peer is already present.
     *  Calls the trimmer if begin == end and we are full.
     *  If begin != end then add it and caller must do bucket splitting.
     *  @return true if added
     */
    public boolean add(T peer) {
        if (_begin != _end || _entries.size() < _max ||
            _entries.contains(peer) || _trimmer.trim(this, peer)) {
            // do this even if already contains, to call setLastChanged()
            boolean rv = _entries.add(peer);
            setLastChanged();
            return rv;
        }
        return false;
    }
    
    /**
     *  @return if removed. Does NOT set lastChanged.
     */
    public boolean remove(T peer) {
        boolean rv = _entries.remove(peer);
        //if (rv)
        //    setLastChanged();
        return rv;
    }
    
    /**
     *  Update the last-changed timestamp to now.
     */
    public void setLastChanged() {
        _lastChanged = _context.clock().now();
    }

    /**
     *  The last-changed timestamp, which actually indicates last-added or last-seen.
     */
    public long getLastChanged() {
        return _lastChanged;
    }
    
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(1024);
        buf.append(_entries.size());
        buf.append(" entries in (").append(_begin);
        if (_end != _begin)
            buf.append('-').append(_end);
        buf.append(") : ").append(_entries.toString());
        return buf.toString();
    }
}
