package com.limegroup.gnutella.dht;

import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.limewire.mojito.KUID;
import org.limewire.mojito.concurrent.DHTExecutorService;
import org.limewire.mojito.routing.Bucket;
import org.limewire.mojito.routing.ClassfulNetworkCounter;
import org.limewire.mojito.routing.Contact;
import org.limewire.mojito.routing.ContactFactory;
import org.limewire.mojito.routing.RouteTable;
import org.limewire.mojito.routing.Vendor;
import org.limewire.mojito.routing.Version;
import org.limewire.mojito.settings.KademliaSettings;
import org.limewire.mojito.util.CollectionUtils;
import org.limewire.mojito.util.FixedSizeHashMap;

class PassiveLeafRouteTable implements RouteTable {
    
    private static final long serialVersionUID = 2378400850935282184L;

    private final Bucket bucket;
    
    private final Contact localNode;
    
    public PassiveLeafRouteTable(Vendor vendor, Version version) {
        localNode = ContactFactory.createLocalContact(vendor, version, true);
        bucket = new BucketImpl(this, KademliaSettings.REPLICATION_PARAMETER.getValue());
    }
    
    public synchronized void add(Contact node) {
        if (isLocalNode(node)) {
            return;
        }
        
        if (node.isFirewalled()) {
            return;
        }
        
        ClassfulNetworkCounter counter = bucket.getClassfulNetworkCounter();
        if (counter == null || counter.isOkayToAdd(node)) {
            bucket.addActiveContact(node);
        }
    }

    public synchronized void addRouteTableListener(RouteTableListener l) {
    }

    public synchronized void removeRouteTableListener(RouteTableListener l) {
    }
    
    public synchronized void clear() {
        bucket.clear();
    }

    public synchronized Contact get(KUID nodeId) {
        if (nodeId.equals(localNode.getNodeID())) {
            return localNode;
        }
        
        return bucket.get(nodeId);
    }

    public synchronized Collection<Contact> getActiveContacts() {
        List<Contact> nodes = new ArrayList<Contact>(bucket.getActiveContacts());
        nodes.add(localNode);
        return nodes;
    }

    public synchronized Bucket getBucket(KUID nodeId) {
        return bucket;
    }

    public synchronized Collection<Bucket> getBuckets() {
        return Collections.singleton(bucket);
    }

    public synchronized Collection<Contact> getCachedContacts() {
        return bucket.getCachedContacts();
    }

    public synchronized Collection<Contact> getContacts() {
        return bucket.getActiveContacts();
    }

    public synchronized Contact getLocalNode() {
        return localNode;
    }

    public synchronized Collection<KUID> getRefreshIDs(boolean bootstrapping) {
        return Collections.emptySet();
    }

    public synchronized void handleFailure(KUID nodeId, SocketAddress address) {
        bucket.remove(nodeId);
    }

    public synchronized boolean isLocalNode(Contact node) {
        return localNode.equals(node);
    }

    public synchronized void purge(long elapsedTimeSinceLastContact) {
    }

    public synchronized void purge(PurgeMode first, PurgeMode... rest) {
    }
    
    public synchronized Collection<Contact> select(KUID nodeId, int count, SelectMode mode) {
        Collection<Contact> nodes = bucket.select(nodeId, count);
        if (nodes.size() >= count || mode == SelectMode.ALIVE) {
            return nodes;
        }
        
        nodes = new ArrayList<Contact>(nodes);
        nodes.add(localNode);
        return nodes;
    }

    public synchronized Contact select(KUID nodeId) {
        Contact c = bucket.select(nodeId);
        if (c != null) {
            return c;
        }
        return localNode;
    }

    public synchronized void setContactPinger(ContactPinger pinger) {
    }
    
    public void setNotifier(DHTExecutorService e){}

    public synchronized int size() {
        return bucket.size() + 1;
    }
    
    @Override
    public synchronized String toString() {
        StringBuilder buffer = new StringBuilder();
        buffer.append(getLocalNode()).append("\n");
        buffer.append(bucket);
        return buffer.toString();
    }
    
    private static class BucketImpl implements Bucket {
        
        private static final long serialVersionUID = 7625655390844705296L;
        
        private final RouteTable routeTable;
        
        private final int k;
        
        private final Map<KUID, Contact> map;
        
        private final ClassfulNetworkCounter counter;
        
        public BucketImpl(RouteTable routeTable, int k) {
            this.routeTable = routeTable;
            this.k = k;
            
            counter = new ClassfulNetworkCounter(this);
            
            map = new FixedSizeHashMap<KUID, Contact>(k, 0.75f, true, k) {
                private static final long serialVersionUID = 4026436727356877846L;

                @Override
                protected boolean removeEldestEntry(Entry<KUID, Contact> eldest) {
                    if (super.removeEldestEntry(eldest)) {
                        getClassfulNetworkCounter().decrementAndGet(eldest.getValue());
                        return true;
                    }
                    return false;
                }
            };
        }
        
        public ClassfulNetworkCounter getClassfulNetworkCounter() {
            return counter;
        }

        public boolean isLocalNode(Contact node) {
            return routeTable.isLocalNode(node);
        }
        
        public void addActiveContact(Contact node) {
            updateContact(node);
        }

        public Contact updateContact(Contact node) {
            Contact existing = map.remove(node.getNodeID());
            if (existing != null) {
                getClassfulNetworkCounter().decrementAndGet(existing);
            }
            
            Contact previous = map.put(node.getNodeID(), node);
            assert (previous == null);
            getClassfulNetworkCounter().incrementAndGet(node);
            
            return existing;
        }
        
        public Contact addCachedContact(Contact node) {
            throw new UnsupportedOperationException();
        }

        public void clear() {
            map.clear();
        }

        public boolean contains(KUID nodeId) {
            return map.containsKey(nodeId);
        }

        public boolean containsActiveContact(KUID nodeId) {
            return contains(nodeId);
        }

        public boolean containsCachedContact(KUID nodeId) {
            return false;
        }

        public Contact get(KUID nodeId) {
            return map.get(nodeId);
        }

        public Contact getActiveContact(KUID nodeId) {
            return get(nodeId);
        }

        public Collection<Contact> getActiveContacts() {
            return new ArrayList<Contact>(map.values());
        }

        public Collection<Contact> getCachedContacts() {
            return Collections.emptySet();
        }

        public int getActiveSize() {
            return map.size();
        }
        
        public int getCacheSize() {
            return getCachedContacts().size();
        }
        
        public KUID getBucketID() {
            return KUID.MINIMUM;
        }

        public Contact getCachedContact(KUID nodeId) {
            return null;
        }
        
        public int getDepth() {
            return 0;
        }

        public Contact getLeastRecentlySeenActiveContact() {
            Contact lrs = null;
            for (Contact c : map.values()) {
                lrs = c;
            }
            return lrs;
        }

        public Contact getLeastRecentlySeenCachedContact() {
            return null;
        }

        public Contact getMostRecentlySeenActiveContact() {
            for (Contact c : map.values()) {
                return c;
            }
            return null;
        }

        public Contact getMostRecentlySeenCachedContact() {
            return null;
        }

        public long getTimeStamp() {
            return Long.MAX_VALUE;
        }

        public boolean isActiveFull() {
            return false;
        }

        public int getMaxActiveSize() {
            return k;
        }

        public boolean isCacheFull() {
            return false;
        }

        public boolean isRefreshRequired() {
            return false;
        }

        public boolean isTooDeep() {
            return false;
        }

        public void purge() {
        }

        public boolean remove(KUID nodeId) {
            Contact node = map.remove(nodeId);
            if (node != null) {
                getClassfulNetworkCounter().decrementAndGet(node);
                return true;
            }
            return false;
        }

        public boolean removeActiveContact(KUID nodeId) {
            return remove(nodeId);
        }

        public boolean removeCachedContact(KUID nodeId) {
            return false;
        }

        public Collection<Contact> select(KUID nodeId, int count) {
            Contact[] nodes = new Contact[Math.min(count, map.size())];
            int index = 0;
            for (Contact c : map.values()) {
                if (index >= nodes.length) {
                    break;
                }
                
                nodes[index++] = c;
            }
            return Arrays.asList(nodes);
        }

        public Contact select(KUID nodeId) {
            for (Contact c : map.values()) {
                return c;
            }
            return null;
        }

        public int size() {
            return getActiveSize() + getCacheSize();
        }

        public List<Bucket> split() {
            throw new UnsupportedOperationException();
        }

        public void touch() {
        }

        @Override
        public String toString() {
            return CollectionUtils.toString(map.values());
        }
    }
}
