/***************************************************************************
 *   Copyright (C) 2005 by Joris Guisson                                   *
 *   joris.guisson@gmail.com                                               *
 *                                                                         *
 *   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 Street, Fifth Floor, Boston, MA 02110-1301, USA.             *
 ***************************************************************************/
#ifndef DHTKBUCKET_H
#define DHTKBUCKET_H

#include <set>
#include <QList>
#include <util/constants.h>
#include <net/address.h>
#include "key.h"
#include "rpccall.h"
#include "kbucketentry.h"

namespace bt
{
	class File;
	class BEncoder;
}

namespace dht
{
	class RPCServerInterface;
	class KClosestNodesSearch;
	class Task;

	const bt::Uint32 K = 20;
	const bt::Uint32 BUCKET_MAGIC_NUMBER = 0xB0C4B0C4;
	const bt::Uint32 BUCKET_REFRESH_INTERVAL = 15 * 60 * 1000;


	/**
	 * @author Joris Guisson
	 *
	 * A KBucket is just a list of KBucketEntry objects.
	 * The list is sorted by time last seen :
	 * The first element is the least recently seen, the last
	 * the most recently seen.
	 */
	class KBucket : public RPCCallListener
	{
		Q_OBJECT

	public:
		KBucket(RPCServerInterface* srv, const Key & our_id);
		KBucket(const dht::Key & min_key, const dht::Key & max_key, RPCServerInterface* srv, const Key & our_id);
		virtual ~KBucket();
		
		typedef QSharedPointer<KBucket> Ptr;
		
		/// Get the min key
		const dht::Key & minKey() const {return min_key;}
		
		/// Get the max key
		const dht::Key & maxKey() const {return max_key;}

		/// Does the key k lies in in the range of this bucket
		bool keyInRange(const dht::Key & k) const;
		
		/// Are we allowed to split
		bool splitAllowed() const;
		
		class UnableToSplit
		{};
		
		/**
		 * Split the bucket in two new buckets, each containing half the range of the original one.
		 * @return A pair of KBucket's
		 * @throw UnableToSplit if something goes wrong
		 */
		std::pair<KBucket::Ptr, KBucket::Ptr> split() throw (UnableToSplit);
		
		/**
		 * Inserts an entry into the bucket.
		 * @param entry The entry to insert
		 * @return true If the bucket needs to be splitted, false otherwise
		 */
		bool insert(const KBucketEntry & entry);

		/// Get the least recently seen node
		const KBucketEntry & leastRecentlySeen() const {return entries[0];}

		/// Get the number of entries
		bt::Uint32 getNumEntries() const {return entries.count();}

		/// See if this bucket contains an entry
		bool contains(const KBucketEntry & entry) const;

		/**
		 * Find the K closest entries to a key and store them in the KClosestNodesSearch
		 * object.
		 * @param kns The object to storre the search results
		 */
		void findKClosestNodes(KClosestNodesSearch & kns);

		/**
		 * A peer failed to respond
		 * @param addr Address of the peer
		 */
		bool onTimeout(const net::Address & addr);

		/// Check if the bucket needs to be refreshed
		bool needsToBeRefreshed() const;

		/// save the bucket to a file
		void save(bt::BEncoder & enc);

		/// Load the bucket from a file
		void load(bt::BDictNode* dict);

		/// Update the refresh timer of the bucket
		void updateRefreshTimer();

		/// Set the refresh task
		void setRefreshTask(Task* t);

	private:
		virtual void onResponse(RPCCall* c, RPCMsg::Ptr rsp);
		virtual void onTimeout(RPCCall* c);
		void pingQuestionable(const KBucketEntry & replacement_entry);
		bool replaceBadEntry(const KBucketEntry & entry);

	private slots:
		void onFinished(Task* t);
		
	private:
		dht::Key min_key, max_key;
		QList<KBucketEntry> entries, pending_entries;
		RPCServerInterface* srv;
		Key our_id;
		QMap<RPCCall*, KBucketEntry> pending_entries_busy_pinging;
		mutable bt::TimeStamp last_modified;
		Task* refresh_task;
	};
}

template <class T>
inline uint qHash(const T & e)
{
	return e.hash();
}

#endif
