/*
	This file is part of cpp-ethereum.

	cpp-ethereum 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 3 of the License, or
	(at your option) any later version.

	cpp-ethereum 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 cpp-ethereum.  If not, see <http://www.gnu.org/licenses/>.
*/
/** @file EthereumPeer.h
 * @author Gav Wood <i@gavwood.com>
 * @date 2014
 */

#pragma once

#include <mutex>
#include <array>
#include <set>
#include <memory>
#include <utility>
#include <libdevcore/RLP.h>
#include <libdevcore/Guards.h>
#include <libdevcore/RangeMask.h>
#include <libethcore/CommonEth.h>
#include <libp2p/Capability.h>
#include "CommonNet.h"
#include "DownloadMan.h"

namespace dev
{
namespace eth
{

/**
 * @brief The EthereumPeer class
 * @todo Document fully.
 * @todo make state transitions thread-safe.
 */
class EthereumPeer: public p2p::Capability
{
	friend class EthereumHost;

public:
	/// Basic constructor.
	EthereumPeer(p2p::Session* _s, p2p::HostCapabilityFace* _h, unsigned _i);

	/// Basic destructor.
	virtual ~EthereumPeer();

	/// What is our name?
	static std::string name() { return "eth"; }

	/// What is our version?
	static u256 version() { return c_protocolVersion; }

	/// How many message types do we have?
	static unsigned messageCount() { return PacketCount; }

	/// What is the ethereum subprotocol host object.
	EthereumHost* host() const;

private:
	using p2p::Capability::sealAndSend;

	/// Interpret an incoming message.
	virtual bool interpret(unsigned _id, RLP const& _r);

	/// Transition state in a particular direction.
	void transition(Asking _wantState, bool _force = false);

	/// Attempt to begin syncing with this peer; first check the peer has a more difficlult chain to download, then start asking for hashes, then move to blocks.
	void attemptSync();

	/// Abort the sync operation.
	void abortSync();

	/// Clear all known transactions.
	void clearKnownTransactions() { std::lock_guard<std::mutex> l(x_knownTransactions); m_knownTransactions.clear(); }

	/// Update our asking state.
	void setAsking(Asking _g, bool _isSyncing);

	/// Update our syncing requirements state.
	void setNeedsSyncing(h256 _latestHash, u256 _td);
	void resetNeedsSyncing() { setNeedsSyncing(h256(), 0); }

	/// Do we presently need syncing with this peer?
	bool needsSyncing() const { return !!m_latestHash; }

	/// Are we presently syncing with this peer?
	bool isSyncing() const;

	/// Check whether the session should bother grabbing the peer's blocks.
	bool shouldGrabBlocks() const;

	/// Peer's protocol version.
	unsigned m_protocolVersion;
	/// Peer's network id.
	u256 m_networkId;

	/// What, if anything, we last asked the other peer for.
	Asking m_asking = Asking::Nothing;

	/// Whether this peer is in the process of syncing or not. Only one peer can be syncing at once.
	bool m_isSyncing = false;

	/// These are determined through either a Status message or from NewBlock.
	h256 m_latestHash;						///< Peer's latest block's hash that we know about or default null value if no need to sync.
	u256 m_totalDifficulty;					///< Peer's latest block's total difficulty.
	/// Once a sync is started on this peer, they are cleared and moved into m_syncing*.

	/// This is built as we ask for hashes. Once no more hashes are given, we present this to the
	/// host who initialises the DownloadMan and m_sub becomes active for us to begin asking for blocks.
	h256s m_syncingNeededBlocks;				///< The blocks that we should download from this peer.
	h256 m_syncingLatestHash;					///< Peer's latest block's hash, as of the current sync.
	u256 m_syncingTotalDifficulty;				///< Peer's latest block's total difficulty, as of the current sync.

	/// Once we're asking for blocks, this becomes in use.
	DownloadSub m_sub;

	/// Have we received a GetTransactions packet that we haven't yet answered?
	bool m_requireTransactions;

	Mutex x_knownBlocks;
	h256Set m_knownBlocks;					///< Blocks that the peer already knows about (that don't need to be sent to them).
	Mutex x_knownTransactions;
	h256Set m_knownTransactions;			///< Transactions that the peer already knows of.

};

}
}