/*
 * Mojito Distributed Hash Table (Mojito DHT)
 * Copyright (C) 2006-2007 LimeWire LLC
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
 
package org.limewire.mojito.routing;

import java.io.Serializable;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.List;

import org.limewire.mojito.KUID;
import org.limewire.mojito.concurrent.DHTExecutorService;
import org.limewire.mojito.concurrent.DHTFutureListener;
import org.limewire.mojito.result.PingResult;


/**
 * Defines the interface for a <a href="http://en.wikipedia.org/wiki/Routing_table">
 * RouteTable</a>.
 */
public interface RouteTable extends Serializable {
    
    /**
     * Selection mode for the RouteTable's select() operation
     */
    public static enum SelectMode {
        /**
         * Selects all Contacts
         */
        ALL,
        
        /**
         * Selects only alive Contacts
         */
        ALIVE,
        
        /**
         * Selects only alive and the local Contacts
         */
        ALIVE_WITH_LOCAL;
    }
    
    /**
     * Rebuild mode for the RouteTable's purge() operation
     */
    public static enum PurgeMode {
        /**
         * Drops all Contacts in the replacement cache
         */
        DROP_CACHE,
        
        /**
         * Deletes all non-alive Contacts from the active
         * RouteTable and fill up the new slots with alive
         * Contacts from the replacement cache
         */
        PURGE_CONTACTS,
        
        /**
         * Merges all Buckets by essentially rebuilding the
         * entire RouteTable
         */
        MERGE_BUCKETS,
        
        /**
         * Marks all Contacts as unknown
         */
        STATE_TO_UNKNOWN,
    }
    
    /**
     * Adds a new Contact or if it's already in the RouteTable updates 
     * its contact information.
     * 
     * @param node the Contact we would like to add
     */
    public void add(Contact node);
    
    /**
     * Returns a Contact from the local RoutingTable if such Contact exists 
     * and null if it doesn't.
     */
    public Contact get(KUID nodeId);
    
    /**
     * Selects the best matching Contact for the provided KUID.
     * This method will guaranteed return a non-null value if the
     * RoutingTable is not empty.
     */
    public Contact select(KUID nodeId);
    
    /**
     * Selects the best matching k Contacts for the provided KUID. The returned
     * Contacts are sorted by their closeness to the lookup Key from closest to
     * least closest Contact. Use {@link org.limewire.mojito.util.BucketUtils#sort(List)}
     * to sort the list from least-recently-seen to most-recently-seen Contact.
     * 
     * @param nodeId the lookup KUID
     * @param count the number of Contact (maybe less if RoutingTable has less than 'count' entries!)
     * @param mode whether or not only alive Contacts should be in the result set
     * @return list of Contacts sorted by closeness
     */
    public Collection<Contact> select(KUID nodeId, int count, SelectMode mode);
    
    /**
     * Notifies the RoutingTable that the Contact with the provided
     * KUID has failed to answer to a request.
     */
    public void handleFailure(KUID nodeId, SocketAddress address);
    
    /**
     * Returns all Contacts as List
     */
    public Collection<Contact> getContacts();
    
    /**
     * Returns Contacts that are actively used for routing
     */
    public Collection<Contact> getActiveContacts();
    
    /**
     * Returns cached Contacts that are in the replacement cache
     */
    public Collection<Contact> getCachedContacts();
    
    /**
     * Returns a Bucket that is nearest (XOR distance) 
     * to the given KUID
     */
    public Bucket getBucket(KUID nodeId);
    
    /**
     * Returns all Buckets as an Collection
     */
    public Collection<Bucket> getBuckets();
    
    /**
     * Returns a List of KUIDs that need to be looked up in order
     * to refresh (or bootstrap) the RouteTable.
     * 
     * @param bootstrapping whether or not this refresh is done during bootstrap
     */
    public Collection<KUID> getRefreshIDs(boolean bootstrapping);
    
    /**
     * Clears all elements from the RoutingTable
     */
    public void clear();
    
    /**
     * Purges all Contacts from the RouteTable whose time stamp 
     * is older than the given time and merges all Buckets
     */
    public void purge(long elapsedTimeSinceLastContact);
    
    /**
     * Purges/Rebuilds the RouteTable based on the given Modes
     */
    public void purge(PurgeMode first, PurgeMode... rest);
    
    /**
     * Returns the number of live and cached Contacts in the Route Table
     */
    public int size();
    
    /**
     * Returns whether or not the given Contact is the local
     * Node
     */
    public boolean isLocalNode(Contact node);
    
    /**
     * Returns the local Node
     */
    public Contact getLocalNode();
    
    /**
     * Sets the RouteTable PingCallback
     */
    public void setContactPinger(ContactPinger pinger);
    
    /**
     * Sets the executor on which to notify for route table events
     */
    public void setNotifier(DHTExecutorService executor);
    
    /**
     * Adds a RouteTableListener
     * 
     * @param l the RouteTableListener to add
     */
    public void addRouteTableListener(RouteTableListener l);
    
    /**
     * Removes a RouteTableListener
     * 
     * @param l the RouteTableListener to remove
     */
    public void removeRouteTableListener(RouteTableListener l);
    
    /**
     * An interface used by the RouteTable to access 
     * external resources.
     */
    public static interface ContactPinger {
        
        /** Sends a PING to the given Node */
        public void ping(Contact node, DHTFutureListener<PingResult> listener);
    }
    
    /**
     * The interface to receive RouteTable events
     */
    public static interface RouteTableListener {
        
        /**
         * Invoked when an event occurs
         * 
         * @param event the event that occurred
         */
        public void handleRouteTableEvent(RouteTableEvent event);
    }
    
    /**
     * Created and fired for various RouteTable events.
     */
    public static class RouteTableEvent {
        
        /**
         * The types of events that may occur
         */
        public static enum EventType {
            
            /** A Contact was added to the RouteTable */
            ADD_ACTIVE_CONTACT,
            
            /** A Contact was added to the replacement cache */
            ADD_CACHED_CONTACT,
            
            /** A Contact was replaced */
            REPLACE_CONTACT,
            
            /** A Contact was updated */
            UPDATE_CONTACT,
            
            /** A Contact was removed */
            REMOVE_CONTACT,
            
            /** A Contact was contacted to check if it's alive */
            CONTACT_CHECK,
            
            /** A Bucket was split */
            SPLIT_BUCKET,
            
            /** The RouteTable was cleared */
            CLEAR;
        }
        
        private final RouteTable routeTable;
        
        private final Bucket bucket;
        
        private final Bucket left;
        
        private final Bucket right;
        
        private final Contact existing;
        
        private final Contact node;
        
        private final EventType type;
        
        private final long timeStamp = System.currentTimeMillis();
        
        public RouteTableEvent(RouteTable routeTable, 
                Bucket bucket, Bucket left, Bucket right,
                Contact existing, Contact node, EventType type) {
            this.routeTable = routeTable;
            this.bucket = bucket;
            this.left = left;
            this.right = right;
            this.existing = existing;
            this.node = node;
            this.type = type;
        }
        
        /**
         * Returns the RouteTable which triggered the Event
         */
        public RouteTable getRouteTable() {
            return routeTable;
        }
        
        /**
         * The Bucket where an Event occurred
         */
        public Bucket getBucket() {
            return bucket;
        }
        
        /**
         * Returns the new left hand Bucket if this is
         * a SPLIT_BUCKET event and null otherwise
         */
        public Bucket getLeftBucket() {
            return left;
        }
        
        /**
         * Returns the new right hand Bucket if this is
         * a SPLIT_BUCKET event and null otherwise
         */
        public Bucket getRightBucket() {
            return right;
        }
        
        /**
         * Returns the existing Contact that was updated.
         * This might be null in certain cases!
         */
        public Contact getExistingContact() {
            return existing;
        }
        
        /**
         * Returns the Contact that was added to the RouteTable
         */
        public Contact getContact() {
            return node;
        }
        
        /**
         * Returns the type of the Event
         */
        public EventType getEventType() {
            return type;
        }
        
        /**
         * Returns the time when this Event occurred
         */
        public long getTimeStamp() {
            return timeStamp;
        }
    }
}
