//
// Copyright (C) 2001-2022 Graeme Walker <graeme_walker@users.sourceforge.net>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
// ===
///
/// \file gmultiserver.h
///

#ifndef G_NET_MULTISERVER_H
#define G_NET_MULTISERVER_H

#include "gdef.h"
#include "gevent.h"
#include "gserver.h"
#include "gtimer.h"
#include "ginterfaces.h"
#include "gexception.h"
#include <memory>
#include <utility> // std::pair<>
#include <vector>

namespace GNet
{
	class MultiServer ;
	class MultiServerImp ;
}

//| \class GNet::MultiServer
/// A server that listens on more than one address using a facade
/// pattern to multiple GNet::Server instances. Supports dynamic
/// server instantiation based on available network interface
/// addresses (see GNet::InterfacesHandler).
///
class GNet::MultiServer : private InterfacesHandler
{
public:
	G_EXCEPTION( NoListeningAddresses , tx("no listening addresses") ) ;
	G_EXCEPTION( InvalidName , tx("invalid address or interface name") ) ;
	G_EXCEPTION( InvalidFd , tx("invalid listening file descriptor number") ) ;
	using AddressList = std::vector<Address> ;

	struct ServerInfo /// A structure used in GNet::MultiServer::newPeer().
	{
		ServerInfo() ;
		Address m_address ; ///< The server address that the peer connected to.
	} ;

	MultiServer( ExceptionSink listener_exception_sink , const G::StringArray & listen_list ,
		unsigned int port , const std::string & server_type ,
		ServerPeer::Config server_peer_config , Server::Config server_config ) ;
			///< Constructor. The server listens on inherited file descriptors
			///< formatted like "#3", specific local addresses (eg. "127.0.0.1")
			///< and addresses from named interfaces ("eth0").
			///<
			///< Listens on "0.0.0.0" and "::" if the listen list is
			///< empty.
			///<
			///< Throws if there are no addresses in the list and the
			///< GNet::Interfaces implementation is not active().

	~MultiServer() override ;
		///< Destructor.

	bool hasPeers() const ;
		///< Returns true if peers() is not empty.

	std::vector<std::weak_ptr<ServerPeer>> peers() ;
		///< Returns the list of ServerPeer-derived objects.

	std::unique_ptr<ServerPeer> doNewPeer( ExceptionSinkUnbound , ServerPeerInfo && , const ServerInfo & ) ;
		///< Pseudo-private method used by the pimple class.

protected:
	virtual std::unique_ptr<ServerPeer> newPeer( ExceptionSinkUnbound , ServerPeerInfo && , ServerInfo ) = 0 ;
		///< A factory method which new()s a ServerPeer-derived
		///< object. See GNet::Server for the details.

	void serverCleanup() ;
		///< Should be called from all derived classes' destructors
		///< so that peer objects can use their Server objects
		///< safely during their own destruction.

	void serverReport() const ;
		///< Writes to the system log a summary of the underlying server
		///< objects and their addresses.

private: // overrides
	void onInterfaceEvent( const std::string & ) override ; // GNet::InterfacesHandler

public:
	MultiServer( const MultiServer & ) = delete ;
	MultiServer( MultiServer && ) = delete ;
	MultiServer & operator=( const MultiServer & ) = delete ;
	MultiServer & operator=( MultiServer && ) = delete ;

private:
	friend class GNet::MultiServerImp ;
	void parse( const G::StringArray & , unsigned int port ,
		AddressList & , std::vector<int> & ,
		G::StringArray & , G::StringArray & , G::StringArray & ) ;
	static int parseFd( const std::string & ) ;
	bool gotServerFor( const Address & ) const ;
	void onInterfaceEventTimeout() ;
	static bool match( const Address & , const Address & ) ;
	static std::string displayString( const Address & ) ;

private:
	using ServerPtr = std::unique_ptr<MultiServerImp> ;
	using ServerList = std::vector<ServerPtr> ;
	ExceptionSink m_es ;
	unsigned int m_port ;
	std::string m_server_type ;
	ServerPeer::Config m_server_peer_config ;
	Server::Config m_server_config ;
	Interfaces m_if ;
	G::StringArray m_if_names ;
	ServerList m_server_list ;
	Timer<MultiServer> m_interface_event_timer ;
} ;

//| \class GNet::MultiServerImp
/// A GNet::Server class used in GNet::MultiServer.
///
class GNet::MultiServerImp : public GNet::Server
{
public:
	MultiServerImp( MultiServer & , ExceptionSink , const Address & , ServerPeer::Config , Server::Config ) ;
		///< Constructor.

	MultiServerImp( MultiServer & , ExceptionSink , Descriptor , ServerPeer::Config , Server::Config ) ;
		///< Constructor.

	~MultiServerImp() override ;
		///< Destructor.

	std::unique_ptr<ServerPeer> newPeer( ExceptionSinkUnbound , ServerPeerInfo&& ) final ;
		///< Called by the base class to create a new ServerPeer.

	void cleanup() ;
		///< Calls GNet::Server::serverCleanup().

public:
	MultiServerImp( const MultiServerImp & ) = delete ;
	MultiServerImp( MultiServerImp && ) = delete ;
	MultiServerImp & operator=( const MultiServerImp & ) = delete ;
	MultiServerImp & operator=( MultiServerImp && ) = delete ;

private:
	MultiServer & m_ms ;
} ;

#endif
