/*
 * bitdht/bdpeer.cc
 *
 * BitDHT: An Flexible DHT library.
 *
 * Copyright 2010 by Robert Fernie
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License Version 3 as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 *
 * Please report all bugs and problems to "bitdht@lunamutt.com".
 *
 */


#include "bitdht/bdpeer.h"
#include "util/bdnet.h"
#include "util/bdrandom.h"
#include "util/bdstring.h"
#include "bitdht/bdiface.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <iostream>
#include <iomanip>

/**
 * #define BITDHT_DEBUG 1
**/

void bdSockAddrInit(struct sockaddr_in *addr)
{
	memset(addr, 0, sizeof(struct sockaddr_in));
	addr->sin_family = AF_INET;
}

bdId::bdId()
{
	/* blank everything */
	bdSockAddrInit(&addr);
	memset(&id.data, 0, BITDHT_KEY_LEN);
}


bdId::bdId(bdNodeId in_id, struct sockaddr_in in_addr)
{
	/* this bit is to ensure the address is valid for windows / osx */
	bdSockAddrInit(&addr);
	addr.sin_addr.s_addr = in_addr.sin_addr.s_addr;
	addr.sin_port = in_addr.sin_port;

	for(int i = 0; i < BITDHT_KEY_LEN; i++)
	{
		id.data[i] = in_id.data[i];
	}
};


void bdZeroNodeId(bdNodeId *id)
{
	uint32_t *a_data = (uint32_t *) id->data;
	for(int i = 0; i < BITDHT_KEY_INTLEN; i++)
	{
		a_data[i] = 0;
	}
	return;
}


int operator<(const bdNodeId &a, const bdNodeId &b)
{
#if 0
	std::cerr <<  "operator<(");
	bdPrintNodeId(std::cerr, &a);
	std::cerr <<  ",";
	bdPrintNodeId(std::cerr, &b);
	std::cerr <<  ")" << std::endl;
#endif
	
	uint8_t *a_data = (uint8_t *) a.data;
	uint8_t *b_data = (uint8_t *) b.data;
	for(int i = 0; i < BITDHT_KEY_LEN; i++)	
	{
		if (*a_data < *b_data)
		{
			//fprintf(stderr, "Return 1, at i = %d\n", i);
			return 1;
		}
		else if (*a_data > *b_data)
		{
			//fprintf(stderr, "Return 0, at i = %d\n", i);
			return 0;
		}
		a_data++;
		b_data++;
	}
	//fprintf(stderr, "Return 0, at i = KEYLEN\n");
	return 0;
}

#if 0
int operator<(const struct sockaddr_in &a, const struct sockaddr_in &b)
{
	/* else NodeIds the same - check id addresses */
	if (a.sin_addr.s_addr < b.sin_addr.s_addr)
		return 1;
	if (b.sin_addr.s_addr > a.sin_addr.s_addr)
		return 0;

	if (a.sin_port < b.sin_port)
		return 1;

	return 0;
}
#endif

int operator<(const bdId &a, const bdId &b)
{
	if (a.id < b.id)
		return 1;
	if (b.id < a.id)
		return 0;

	/* else NodeIds the same - check id addresses */
	if (a.addr.sin_addr.s_addr < b.addr.sin_addr.s_addr)
		return 1;
	if (b.addr.sin_addr.s_addr > a.addr.sin_addr.s_addr)
		return 0;

	if (a.addr.sin_port < b.addr.sin_port)
		return 1;

	return 0;
}


int operator==(const bdNodeId &a, const bdNodeId &b)
{
	uint8_t *a_data = (uint8_t *) a.data;
	uint8_t *b_data = (uint8_t *) b.data;
	for(int i = 0; i < BITDHT_KEY_LEN; i++)	
	{
		if (*a_data < *b_data)
		{
			return 0;
		}
		else if (*a_data > *b_data)
		{
			return 0;
		}
		a_data++;
		b_data++;
	}
	return 1;
}

int operator==(const bdId &a, const bdId &b)
{
	if (!(a.id == b.id))
		return 0;

	if ((a.addr.sin_addr.s_addr == b.addr.sin_addr.s_addr) &&
	    (a.addr.sin_port == b.addr.sin_port))
	{
		return 1;
	}
	return 0;
}



bdBucket::bdBucket()
{
	return;
}

bdSpace::bdSpace(bdNodeId *ownId, bdDhtFunctions *fns)
	:mOwnId(*ownId), mFns(fns)
{
	/* make some space for data */
	buckets.resize(mFns->bdNumBuckets());

	mAttachTS = 0;
	mAttachedFlags = 0;
	mAttachedCount = 0;

	return;
}

	/* empty the buckets */
int     bdSpace::clear()
{
	std::vector<bdBucket>::iterator it;
        /* iterate through the buckets, and sort by distance */
        for(it = buckets.begin(); it != buckets.end(); it++)
	{
		it->entries.clear();
	}
	return 1;
}

int     bdSpace::setAttachedFlag(uint32_t withflags, int count)
{
	mAttachedFlags = withflags;
	mAttachedCount = count;
	mAttachTS = 0;
	return 1;
}

int bdSpace::find_nearest_nodes_with_flags(const bdNodeId *id, int number, 
		std::list<bdId> /* excluding */, 
		std::multimap<bdMetric, bdId> &nearest, uint32_t with_flags)
{
	std::multimap<bdMetric, bdId> closest;
	std::multimap<bdMetric, bdId>::iterator mit;

	bdMetric dist;
	mFns->bdDistance(id, &(mOwnId), &dist);

#ifdef DEBUG_BD_SPACE
	int bucket = mFns->bdBucketDistance(&dist);

	std::cerr << "bdSpace::find_nearest_nodes(NodeId:";
	mFns->bdPrintNodeId(std::cerr, id);

	std::cerr << " Number: " << number;
	std::cerr << " Query Bucket #: " << bucket;
	std::cerr << std::endl;
#endif

	std::vector<bdBucket>::iterator it;
	std::list<bdPeer>::iterator eit;
	/* iterate through the buckets, and sort by distance */
	for(it = buckets.begin(); it != buckets.end(); it++)
	{
		for(eit = it->entries.begin(); eit != it->entries.end(); eit++) 
		{
			if ((!with_flags) || ((with_flags & eit->mPeerFlags) == with_flags))
			{
			  mFns->bdDistance(id, &(eit->mPeerId.id), &dist);
			  closest.insert(std::pair<bdMetric, bdId>(dist, eit->mPeerId));

#if 0
			  std::cerr << "Added NodeId: ";
			  bdPrintNodeId(std::cerr, &(eit->mPeerId.id));
			  std::cerr << " Metric: ";
			  bdPrintNodeId(std::cerr, &(dist));
			  std::cerr << std::endl;
#endif
			}
		}
	}

	/* take the first number of nodes */
	int i = 0;
	for(mit = closest.begin(); (mit != closest.end()) && (i < number); mit++, i++)
	{
		mFns->bdDistance(&(mOwnId), &(mit->second.id), &dist);

#ifdef DEBUG_BD_SPACE
		int iBucket = mFns->bdBucketDistance(&(mit->first));

		std::cerr << "Closest " << i << ": ";
		mFns->bdPrintNodeId(std::cerr, &(mit->second.id));
		std::cerr << " Bucket:        " << iBucket;
		std::cerr << std::endl;
#endif


#if 0
		std::cerr << "\tNodeId: ";
		mFns->bdPrintNodeId(std::cerr, &(mit->second.id));
		std::cerr << std::endl;

		std::cerr << "\tOwn Id: ";
		mFns->bdPrintNodeId(std::cerr, &(mOwnId));
		std::cerr << std::endl;

		std::cerr << "     Us Metric: ";
		mFns->bdPrintNodeId(std::cerr, &dist);
		std::cerr << " Bucket:        " << oBucket;
		std::cerr << std::endl;

		std::cerr << "\tFindId: ";
		mFns->bdPrintNodeId(std::cerr, id);
		std::cerr << std::endl;

		std::cerr << "     Id Metric: ";
		mFns->bdPrintNodeId(std::cerr, &(mit->first));
		std::cerr << " Bucket:        " << iBucket;
		std::cerr << std::endl;
#endif

		nearest.insert(*mit);
	}

#ifdef DEBUG_BD_SPACE
	std::cerr << "#Nearest: " << (int) nearest.size();
	std::cerr << " #Closest: " << (int) closest.size();
	std::cerr << " #Requested: " << number;
	std::cerr << std::endl << std::endl;
#endif

	return 1;
}

int bdSpace::find_nearest_nodes(const bdNodeId *id, int number,
                		std::multimap<bdMetric, bdId> &nearest)
{
	std::list<bdId> excluding;
	uint32_t with_flag = 0;

	return find_nearest_nodes_with_flags(id, number, excluding, nearest, with_flag);
}


/* This is much cheaper than find nearest... we only look in the one bucket
 */

int bdSpace::find_node(const bdNodeId *id, int number, std::list<bdId> &matchIds, uint32_t with_flags)
{
	bdMetric dist;
	mFns->bdDistance(id, &(mOwnId), &dist);
	int buckno = mFns->bdBucketDistance(&dist);

	std::cerr << "bdSpace::find_node(NodeId:";
	mFns->bdPrintNodeId(std::cerr, id);
	std::cerr << ")";

	std::cerr << " Number: " << number;
	std::cerr << " Bucket #: " << buckno;
	std::cerr << std::endl;
#ifdef DEBUG_BD_SPACE
#endif

	bdBucket &buck = buckets[buckno];

	std::list<bdPeer>::iterator eit;
	int matchCount = 0;
	for(eit = buck.entries.begin(); eit != buck.entries.end(); eit++) 
	{
		std::cerr << "bdSpace::find_node() Checking Against Peer: ";
		mFns->bdPrintId(std::cerr, &(eit->mPeerId));
		std::cerr << " withFlags: " << eit->mPeerFlags;
		std::cerr << std::endl;

		if ((!with_flags) || ((with_flags & eit->mPeerFlags) == with_flags))
		{
			if (*id == eit->mPeerId.id)
			{
		  		matchIds.push_back(eit->mPeerId);
				matchCount++;

				std::cerr << "bdSpace::find_node() Found Matching Peer: ";
			  	mFns->bdPrintId(std::cerr, &(eit->mPeerId));
				std::cerr << " withFlags: " << eit->mPeerFlags;
			  	std::cerr << std::endl;
			}
		}
		else
		{
			if (*id == eit->mPeerId.id)
			{
		  		//matchIds.push_back(eit->mPeerId);
				//matchCount++;

				std::cerr << "bdSpace::find_node() Found (WITHOUT FLAGS) Matching Peer: ";
			  	mFns->bdPrintId(std::cerr, &(eit->mPeerId));
				std::cerr << " withFlags: " << eit->mPeerFlags;
			  	std::cerr << std::endl;
			}
		}
	}

	std::cerr << "bdSpace::find_node() Found " << matchCount << " Matching Peers";
	std::cerr << std::endl << std::endl;
#ifdef DEBUG_BD_SPACE
#endif

	return matchCount;
}

/* even cheaper again... no big lists */
int bdSpace::find_exactnode(const bdId *id, bdPeer &peer)
{
	bdMetric dist;
	mFns->bdDistance(&(id->id), &(mOwnId), &dist);
	int buckno = mFns->bdBucketDistance(&dist);

#ifdef DEBUG_BD_SPACE
	std::cerr << "bdSpace::find_exactnode(Id:";
	mFns->bdPrintId(std::cerr, id);
	std::cerr << ")";

	std::cerr << " Bucket #: " << buckno;
	std::cerr << std::endl;
#endif

	bdBucket &buck = buckets[buckno];

	std::list<bdPeer>::iterator eit;
	for(eit = buck.entries.begin(); eit != buck.entries.end(); eit++) 
	{
		if (*id == eit->mPeerId)
		{
#ifdef DEBUG_BD_SPACE
			std::cerr << "bdSpace::find_exactnode() Found Matching Peer: ";
		  	mFns->bdPrintId(std::cerr, &(eit->mPeerId));
			std::cerr << " withFlags: " << eit->mPeerFlags;
		  	std::cerr << std::endl;
#endif

			peer = (*eit);
			return 1;
		}
	}
#ifdef DEBUG_BD_SPACE
	std::cerr << "bdSpace::find_exactnode() WARNING Failed to find Matching Peer: ";
	std::cerr << std::endl;
#endif

	return 0;
}



int bdSpace::clean_node_flags(uint32_t flags)
{
	std::cerr << "bdSpace::clean_node_flags(" << flags << ")";
	std::cerr << std::endl;

	int count = 0;
	std::vector<bdBucket>::iterator bit;
	for(bit = buckets.begin(); bit != buckets.end(); bit++)
	{
		std::list<bdPeer>::iterator eit;
		for(eit = bit->entries.begin(); eit != bit->entries.end(); eit++) 
		{
			if (flags & eit->mPeerFlags)
			{	
				std::cerr << "bdSpace::clean_node_flags() Found Match: ";
		  		mFns->bdPrintId(std::cerr, &(eit->mPeerId));
				std::cerr << " withFlags: " << eit->mPeerFlags;
				std::cerr << std::endl;

				count++;
				eit->mPeerFlags &= ~flags;
			}
		}
	}
	std::cerr << "bdSpace::clean_node_flags() Cleaned " << count << " Matching Peers";
	std::cerr << std::endl;

	return count;
}



#define BITDHT_ATTACHED_SEND_PERIOD 	17

int	bdSpace::scanOutOfDatePeers(std::list<bdId> &peerIds)
{
	/* 
	 * 
	 */
	bool doAttached = (mAttachedCount > 0);
	uint32_t attachedCount = 0;

	std::map<bdMetric, bdId> closest;
	std::map<bdMetric, bdId>::iterator mit;

	std::vector<bdBucket>::iterator it;
	std::list<bdPeer>::iterator eit;
	time_t ts = time(NULL);

	/* iterate through the buckets, and sort by distance */
	for(it = buckets.begin(); it != buckets.end(); it++)
	{
		for(eit = it->entries.begin(); eit != it->entries.end(); ) 
		{
			bool added = false;
			if (doAttached)
			{
				if (eit->mExtraFlags & BITDHT_PEER_EXFLAG_ATTACHED)
				{
					/* add to send list, if we haven't pinged recently */
					if ((ts - eit->mLastSendTime > BITDHT_ATTACHED_SEND_PERIOD ) &&
						(ts - eit->mLastRecvTime > BITDHT_ATTACHED_SEND_PERIOD ))
					{
						peerIds.push_back(eit->mPeerId);
						eit->mLastSendTime = ts;
						added = true;
					}
					attachedCount++;
				}
			}
				

			/* timeout on last send time! */
			if ((!added) && (ts - eit->mLastSendTime > BITDHT_MAX_SEND_PERIOD ))
			{
				/* We want to ping a peer iff:
		 	 	 * 1) They are out-of-date: mLastRecvTime is too old.
			 	 * 2) They don't have 0x0001 flag (we haven't received a PONG) and never sent.
			 	 */
				if ((ts - eit->mLastRecvTime > BITDHT_MAX_SEND_PERIOD ) || 
					!(eit->mPeerFlags & BITDHT_PEER_STATUS_RECV_PONG))
				{
					peerIds.push_back(eit->mPeerId);
					eit->mLastSendTime = ts;
				}
			}


			/* we also want to remove very old entries (should it happen here?) 
			 * which are not pushed out by newer entries (will happen in for closer buckets)
			 */

			bool discard = false;
			/* discard very old entries */
			if (ts - eit->mLastRecvTime > BITDHT_DISCARD_PERIOD)
			{
				discard = true;
			}
		
			/* discard peers which have not responded to anything (ie have no flags set) */
			/* changed into have not id'ed themselves, as we've added ping to list of flags. */
			if ((ts - eit->mFoundTime > BITDHT_MAX_RESPONSE_PERIOD ) &&
				!(eit->mPeerFlags & BITDHT_PEER_STATUS_RECV_PONG))
			{
				discard = true;
			}
			

			/* INCREMENT */
			if (discard)
			{	
				eit = it->entries.erase(eit);
			}
			else
			{
				eit++;
			}
		}
	}

#define ATTACH_UPDATE_PERIOD	600

	if ((ts - mAttachTS > ATTACH_UPDATE_PERIOD) || (attachedCount != mAttachedCount))
	{
		//std::cerr << "Updating ATTACH Stuff";
		//std::cerr << std::endl;
		updateAttachedPeers(); /* XXX TEMP HACK to look at stability */
		mAttachTS = ts;
	}

	return (peerIds.size());
}


/* Attached should be changed to preferentially choose the ones we want 
 * If we have RELAYS enabled - then we want them to attach to the RELAYS
 * Not sure about this yet!
 *
 * Have to think about it a bit more. (and redesign code to handle multiple passes)
 */

int	bdSpace::updateAttachedPeers()
{
	/* 
	 * 
	 */
	bool doAttached = (mAttachedCount > 0);
	uint32_t attachedCount = 0;

	// Must scan through - otherwise we can never remove Attached state.
	// It is only once every 10 minutes or so!
#if 0
	if (!doAttached)
	{
		return 0;
	}
#endif

	std::map<bdMetric, bdId> closest;
	std::map<bdMetric, bdId>::iterator mit;

	std::vector<bdBucket>::iterator it;
	std::list<bdPeer>::reverse_iterator eit;


	/* skip the first bucket, as we don't want to ping ourselves! */	
	it = buckets.begin();
	if (it != buckets.end())
	{
		it++;
	}

	/* iterate through the buckets (sorted by distance) */
	for(; it != buckets.end(); it++)
	{
		/* start from the back, as these are the most recently seen (and more likely to be the old ATTACHED) */
		for(eit = it->entries.rbegin(); eit != it->entries.rend(); eit++) 
		{
			if (doAttached)
			{
				if ((eit->mPeerFlags & mAttachedFlags) == mAttachedFlags)
				{
					/* flag as attached */
					eit->mExtraFlags |= BITDHT_PEER_EXFLAG_ATTACHED;

					/* inc count, and cancel search if we've found them */
					attachedCount++;
					if (attachedCount >= mAttachedCount)
					{
						doAttached = false;
					}
				}
				else
				{
					eit->mExtraFlags &= ~BITDHT_PEER_EXFLAG_ATTACHED;
				}
			}
			else
			{
				eit->mExtraFlags &= ~BITDHT_PEER_EXFLAG_ATTACHED;
			}
		}
	}
	return 1;
}




/* Called to add or update peer.
 * sorts bucket lists by lastRecvTime.
 * updates requested node.
 */

/* peer flags
 * order is important!
 * higher bits = more priority.
 * BITDHT_PEER_STATUS_RECVPING
 * BITDHT_PEER_STATUS_RECVPONG
 * BITDHT_PEER_STATUS_RECVNODES
 * BITDHT_PEER_STATUS_RECVHASHES
 * BITDHT_PEER_STATUS_DHT_ENGINE  (dbXXxx)
 * BITDHT_PEER_STATUS_DHT_APPL    (XXRSxx)
 * BITDHT_PEER_STATUS_DHT_VERSION (XXxx50)
 * 
 */

int     bdSpace::add_peer(const bdId *id, uint32_t peerflags)
{
	/* find the peer */
	bool add = false;
	time_t ts = time(NULL);
	
#ifdef DEBUG_BD_SPACE
	fprintf(stderr, "bdSpace::add_peer()\n");
#endif

	/* calculate metric */
	bdMetric met;
	mFns->bdDistance(&(mOwnId), &(id->id), &met);
	int bucket = mFns->bdBucketDistance(&met);

#ifdef DEBUG_BD_SPACE
	fprintf(stderr, "peer:");
	mFns->bdPrintId(std::cerr, id);	
	fprintf(stderr, " bucket: %d", bucket);
	fprintf(stderr, "\n");
#endif

	/* select correct bucket */
	bdBucket &buck =  buckets[bucket];


	std::list<bdPeer>::iterator it;

	/* calculate the score for this new peer */
	uint32_t minScore = peerflags;

	/* loop through ids, to find it */
	for(it = buck.entries.begin(); it != buck.entries.end(); it++)
	{
                /* similar id check */
                if (mFns->bdSimilarId(id, &(it->mPeerId)))
                {
			bdPeer peer = *it;
			it = buck.entries.erase(it);

			peer.mLastRecvTime = ts;
			peer.mPeerFlags |= peerflags; /* must be cumulative ... so can do online, replynodes, etc */

			/* also update port from incoming id, as we have definitely recved from it */
			if (mFns->bdUpdateSimilarId(&(peer.mPeerId), id))
			{
				/* updated it... must be Unstable */
				peer.mExtraFlags |= BITDHT_PEER_EXFLAG_UNSTABLE;
			}

			buck.entries.push_back(peer);

#ifdef DEBUG_BD_SPACE
			std::cerr << "Peer already in bucket: moving to back of the list" << std::endl;
#endif

			return 1;
		}
		
		/* find lowest score */
		if (it->mPeerFlags < minScore)
		{
			minScore = it->mPeerFlags;
		}
	}

	/* not in the list! */

	if (buck.entries.size() < mFns->bdNodesPerBucket())
	{
#ifdef DEBUG_BD_SPACE
		std::cerr << "Bucket not full: allowing add" << std::endl;
#endif
		add = true;
	}
	else 
	{
		/* check head of list */
		bdPeer &peer = buck.entries.front();
		if (ts - peer.mLastRecvTime >  BITDHT_MAX_RECV_PERIOD)
		{
#ifdef DEBUG_BD_SPACE
			std::cerr << "Dropping Out-of-Date peer in bucket" << std::endl;
#endif
			buck.entries.pop_front();
			add = true;
		}
		else if (peerflags > minScore)
		{
			/* find one to drop */
			for(it = buck.entries.begin(); it != buck.entries.end(); it++)
			{
				if (it->mPeerFlags == minScore)
				{
					/* delete low priority peer */
					it = buck.entries.erase(it);
					add = true;
					break;
				}
			}

#ifdef DEBUG_BD_SPACE
			std::cerr << "Inserting due to Priority: minScore: " << minScore 
				<< " new Peer Score: " << peerscore <<  << std::endl;
#endif
		}
		else
		{
#ifdef DEBUG_BD_SPACE
			std::cerr << "No Out-Of-Date peers in bucket... dropping new entry" << std::endl;
#endif
		}
	}

	if (add)
	{
		bdPeer newPeer;

		newPeer.mPeerId = *id;
		newPeer.mLastRecvTime = ts;
		newPeer.mLastSendTime = 0; // ts; //????
		newPeer.mFoundTime = ts;
		newPeer.mPeerFlags = peerflags;
		newPeer.mExtraFlags = 0;

		buck.entries.push_back(newPeer);

#ifdef DEBUG_BD_SPACE
		/* useful debug */
		std::cerr << "bdSpace::add_peer() Added Bucket[";
		std::cerr << bucket << "] Entry: ";
		mFns->bdPrintId(std::cerr, id);
		std::cerr << std::endl;
#endif
	}
	return add;
}

        /* flag peer */
bool    bdSpace::flagpeer(const bdId *id, uint32_t flags, uint32_t ex_flags)
{
#ifdef DEBUG_BD_SPACE
	fprintf(stderr, "bdSpace::flagpeer()\n");
#endif

	/* calculate metric */
	bdMetric met;
	mFns->bdDistance(&(mOwnId), &(id->id), &met);
	int bucket = mFns->bdBucketDistance(&met);


	/* select correct bucket */
	bdBucket &buck =  buckets[bucket];

	/* loop through ids, to find it */
	std::list<bdPeer>::iterator it;
	for(it = buck.entries.begin(); it != buck.entries.end(); it++)
	{
                /* similar id check */
		if (mFns->bdSimilarId(id, &(it->mPeerId)))
		{
#ifdef DEBUG_BD_SPACE
			fprintf(stderr, "peer:");
			mFns->bdPrintId(std::cerr, id);	
			fprintf(stderr, " bucket: %d", bucket);
			fprintf(stderr, "\n");
			fprintf(stderr, "Original Flags: %x Extra: %x\n", 
				it->mPeerFlags, it->mExtraFlags);
#endif
			it->mPeerFlags |= flags;
			it->mExtraFlags |= ex_flags;

#ifdef DEBUG_BD_SPACE
			fprintf(stderr, "Updated Flags: %x Extra: %x\n",
				it->mPeerFlags, it->mExtraFlags);
#endif
		}
	}
	return true;
}

/* print tables.
 */

int     bdSpace::printDHT()
{
	std::map<bdMetric, bdId> closest;
	std::map<bdMetric, bdId>::iterator mit;

	std::vector<bdBucket>::iterator it;
	std::list<bdPeer>::iterator eit;

	/* iterate through the buckets, and sort by distance */
	int i = 0;

#ifdef BITDHT_DEBUG
	fprintf(stderr, "bdSpace::printDHT()\n");
	for(it = buckets.begin(); it != buckets.end(); it++, i++)
	{
		if (it->entries.size() > 0)
		{
			fprintf(stderr, "Bucket %d ----------------------------\n", i);
		}

		for(eit = it->entries.begin(); eit != it->entries.end(); eit++) 
		{
			bdMetric dist;
			mFns->bdDistance(&(mOwnId), &(eit->mPeerId.id), &dist);

			fprintf(stderr, " Metric: ");
			mFns->bdPrintNodeId(std::cerr, &(dist));
			fprintf(stderr, " Id: ");
			mFns->bdPrintId(std::cerr, &(eit->mPeerId));
			fprintf(stderr, " PeerFlags: %08x", eit->mPeerFlags);
			fprintf(stderr, "\n");
		}
	}
#endif

	fprintf(stderr, "--------------------------------------\n");
	fprintf(stderr, "DHT Table Summary --------------------\n");
	fprintf(stderr, "--------------------------------------\n");

	/* little summary */
	unsigned long long sum = 0;
	unsigned long long no_peers = 0;
	uint32_t count = 0;
	bool doPrint = false;
	bool doAvg = false;

	i = 0;
	for(it = buckets.begin(); it != buckets.end(); it++, i++)
	{
		int size = it->entries.size();
		int shift = BITDHT_KEY_BITLEN - i;
		bool toBig = false;

		if (shift > BITDHT_ULLONG_BITS - mFns->bdBucketBitSize() - 1)
		{
			toBig = true;
			shift = BITDHT_ULLONG_BITS - mFns->bdBucketBitSize() - 1;
		}
		unsigned long long no_nets = ((unsigned long long) 1 << shift);

		/* use doPrint so it acts as a single switch */
		if (size && !doAvg && !doPrint)
		{	
			doAvg = true;
		}

		if (size && !doPrint)
		{	
			doPrint = true;
		}

		if (size == 0)
		{
			/* reset counters - if empty slot - to discount outliers in average */
			sum = 0;
			no_peers = 0;
			count = 0;
		}

		if (doPrint)
		{
			if (size)
				fprintf(stderr, "Bucket %d: %d peers: ", i, size);
#ifdef BITDHT_DEBUG
			else
				fprintf(stderr, "Bucket %d: %d peers: ", i, size);
#endif
		}
		if (toBig)
		{
			if (size)
			{
				if (doPrint)
					bd_fprintf(stderr, "Estimated NetSize >> %llu\n", no_nets);
			}
			else
			{
#ifdef BITDHT_DEBUG
				if (doPrint)
					bd_fprintf(stderr, " Bucket = Net / >> %llu\n", no_nets);
#endif
			}
		}
		else
		{
			no_peers = no_nets * size;
			if (size)
			{	
				if (doPrint)
					bd_fprintf(stderr, "Estimated NetSize = %llu\n", no_peers);
			}
			else
			{

#ifdef BITDHT_DEBUG
				if (doPrint)
					bd_fprintf(stderr, " Bucket = Net / %llu\n", no_nets);
#endif

			}
		}
		if (doPrint && doAvg && !toBig)
		{
			if (size == mFns->bdNodesPerBucket())
			{
				/* last average */
				doAvg = false;
			}
			if (no_peers != 0)
			{
				sum += no_peers;
				count++;	
#ifdef BITDHT_DEBUG
				fprintf(stderr, "Est: %d: %llu => %llu / %d\n", 
					i, no_peers, sum, count);
#endif
			}
		}
			
	}
	if (count == 0)
	{
		fprintf(stderr, "Zero Network Size (count = 0)\n");
	}
	else
	{
		bd_fprintf(stderr, "Estimated Network Size = (%llu / %d) = %llu\n", sum, count, sum / count);
	}

	return 1;
}


int     bdSpace::getDhtBucket(const int idx, bdBucket &bucket)
{
	if ((idx < 0) || (idx > (int) buckets.size() - 1 ))
	{
		return 0;
	}
	bucket = buckets[idx];
	return 1;
}

uint32_t  bdSpace::calcNetworkSize()
{
	std::vector<bdBucket>::iterator it;

	/* little summary */
	unsigned long long sum = 0;
	unsigned long long no_peers = 0;
	uint32_t count = 0;
	bool doPrint = false;
	bool doAvg = false;

	int i = 0;
	for(it = buckets.begin(); it != buckets.end(); it++, i++)
	{
		int size = it->entries.size();
		int shift = BITDHT_KEY_BITLEN - i;
		bool toBig = false;

		if (shift > BITDHT_ULLONG_BITS - mFns->bdBucketBitSize() - 1)
		{
			toBig = true;
			shift = BITDHT_ULLONG_BITS - mFns->bdBucketBitSize() - 1;
		}
		unsigned long long no_nets = ((unsigned long long) 1 << shift);

		/* use doPrint so it acts as a single switch */
		if (size && !doAvg && !doPrint)
		{	
			doAvg = true;
		}

		if (size && !doPrint)
		{	
			doPrint = true;
		}

		if (size == 0)
		{
			/* reset counters - if empty slot - to discount outliers in average */
			sum = 0;
			no_peers = 0;
			count = 0;
		}

		if (!toBig)
		{
			no_peers = no_nets * size;
		}

		if (doPrint && doAvg && !toBig)
		{
			if (size == mFns->bdNodesPerBucket())
			{
				/* last average */
				doAvg = false;
			}
			if (no_peers != 0)
			{
				sum += no_peers;
				count++;	
			}
		}
	}


	uint32_t NetSize = 0;
	if (count != 0)
	{
		NetSize = sum / count;
	}

	//std::cerr << "bdSpace::calcNetworkSize() : " << NetSize;
	//std::cerr << std::endl;

	return NetSize;
}

uint32_t  bdSpace::calcNetworkSizeWithFlag(uint32_t withFlag)
{
	std::vector<bdBucket>::iterator it;

	/* little summary */
	unsigned long long sum = 0;
	unsigned long long no_peers = 0;
	uint32_t count = 0;
	uint32_t totalcount = 0;
	bool doPrint = false;
	bool doAvg = false;

	int i = 0;
	for(it = buckets.begin(); it != buckets.end(); it++, i++)
	{
		int size = 0;
		std::list<bdPeer>::iterator lit;
		for(lit = it->entries.begin(); lit != it->entries.end(); lit++)
		{
			if (withFlag & lit->mPeerFlags)
			{
				size++;
			}
		}

		totalcount += size;

		int shift = BITDHT_KEY_BITLEN - i;
		bool toBig = false;

		if (shift > BITDHT_ULLONG_BITS - mFns->bdBucketBitSize() - 1)
		{
			toBig = true;
			shift = BITDHT_ULLONG_BITS - mFns->bdBucketBitSize() - 1;
		}
		unsigned long long no_nets = ((unsigned long long) 1 << shift);


		/* use doPrint so it acts as a single switch */
		if (size && !doAvg && !doPrint)
		{	
			doAvg = true;
		}

		if (size && !doPrint)
		{	
			doPrint = true;
		}

		if (size == 0)
		{
			/* reset counters - if empty slot - to discount outliers in average */
			sum = 0;
			no_peers = 0;
			count = 0;
		}

		if (!toBig)
		{
			no_peers = no_nets * size;
		}

		if (doPrint && doAvg && !toBig)
		{
			if (size == mFns->bdNodesPerBucket())
			{
				/* last average */
				doAvg = false;
			}
			if (no_peers != 0)
			{
				sum += no_peers;
				count++;	
			}
		}
	}


	uint32_t NetSize = 0;
	if (count != 0)
	{
		NetSize = sum / count;
	}

	//std::cerr << "bdSpace::calcNetworkSize() : " << NetSize;
	//std::cerr << std::endl;

	return NetSize;
}


uint32_t  bdSpace::calcSpaceSize()
{
	std::vector<bdBucket>::iterator it;

	/* little summary */
	uint32_t totalcount = 0;
	for(it = buckets.begin(); it != buckets.end(); it++)
	{
		totalcount += it->entries.size();
	}
	return totalcount;
}

uint32_t  bdSpace::calcSpaceSizeWithFlag(uint32_t withFlag)
{
	std::vector<bdBucket>::iterator it;

	/* little summary */
	uint32_t totalcount = 0;
	
	it = buckets.begin();
	if (it != buckets.end())
	{
		it++; /* skip own bucket! */
	}
	for(; it != buckets.end(); it++)
	{
		int size = 0;
		std::list<bdPeer>::iterator lit;
		for(lit = it->entries.begin(); lit != it->entries.end(); lit++)
		{
			if (withFlag & lit->mPeerFlags)
			{
				size++;
			}
		}
		totalcount += size;
	}
	return totalcount;
}

        /* special function to enable DHT localisation (i.e find peers from own network) */
bool bdSpace::findRandomPeerWithFlag(bdId &id, uint32_t withFlag)
{
	std::vector<bdBucket>::iterator it;
	uint32_t totalcount = calcSpaceSizeWithFlag(withFlag);

	if(totalcount == 0)
		return false ;

	uint32_t rnd = bdRandom::random_u32() % totalcount;
	uint32_t i = 0;
	uint32_t buck = 0;

	//std::cerr << "bdSpace::findRandomPeerWithFlag()";
	//std::cerr << std::endl;

	it = buckets.begin();
	if (it != buckets.end())
	{
		it++; /* skip own bucket! */
		buck++;
	}
	for(; it != buckets.end(); it++, buck++)
	{
		std::list<bdPeer>::iterator lit;
		for(lit = it->entries.begin(); lit != it->entries.end(); lit++)
		{
			if (withFlag & lit->mPeerFlags)
			{
				if (i == rnd)
				{
#ifdef BITDHT_DEBUG
					std::cerr << "bdSpace::findRandomPeerWithFlag() found #" << i;
					std::cerr << " in bucket #" << buck;
					std::cerr << std::endl;
#endif
					/* found */
					id = lit->mPeerId;
					return true;
				}
				i++;
			}
		}
	}
	std::cerr << "bdSpace::findRandomPeerWithFlag() failed to find " << rnd << " / " << totalcount;
	std::cerr << std::endl;
#ifdef BITDHT_DEBUG
#endif

	return false;
}

