/* libobby - Network text editing library
 * Copyright (C) 2005, 2006 0x539 dev group
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef _OBBY_CLIENT_BUFFER_HPP_
#define _OBBY_CLIENT_BUFFER_HPP_

#include <net6/default_accumulator.hpp>
#include <net6/client.hpp>
#include "error.hpp"
#include "command.hpp"
#include "local_buffer.hpp"
#include "client_document_info.hpp"

namespace obby
{

/** Buffer to establish a connection to a basic_server_buffer.
 */
template<typename Document, typename Selector>
class basic_client_buffer:
	virtual public basic_local_buffer<Document, Selector>
{
public:
	struct connection_settings {
		std::string name;
		obby::colour colour;
		std::string global_password;
		std::string user_password;
	};

	// Document info
	typedef typename basic_local_buffer<Document, Selector>::
		base_document_info_type base_document_info_type;

	typedef basic_client_document_info<Document, Selector>
		document_info_type;

	// Network
	typedef typename basic_local_buffer<Document, Selector>::
		base_net_type base_net_type;

	typedef net6::basic_client<Selector> net_type;

	// Signal
	typedef net6::default_accumulator<bool, false> login_accumulator;

	typedef sigc::signal<void>
		signal_welcome_type;
	typedef sigc::signal<void, login::error>
		signal_login_failed_type;
	typedef sigc::signal<void>
		signal_close_type;

	typedef typename sigc::signal<bool, connection_settings&>
		::template accumulated<login_accumulator>
		signal_prompt_name_type;
	typedef typename sigc::signal<bool, connection_settings&>
		::template accumulated<login_accumulator>
		signal_prompt_colour_type;
	typedef typename sigc::signal<bool, connection_settings&>
		::template accumulated<login_accumulator>
		signal_prompt_global_password_type;
	typedef typename sigc::signal<bool, connection_settings&>
		::template accumulated<login_accumulator>
		signal_prompt_user_password_type;

	/** Creates a new client_buffer that is not connected to anywhere.
	 */
	basic_client_buffer();

	/** Connects to the given host where a obby server is assumed to be
	 * running. After the connection has been established, signal_welcome
	 * will be emitted after the server sent us some initial data
	 * At this point the login function may be used to login as a user
	 * with a given colour.
	 * TODO: Ask username and colour parameters already here and login
	 * implicitly after having called connect().
	 *
	 * @param hostname Host name to connect to. If hostname is not an IP
	 * address, a DNS lookup will be performed.
	 * @param port Port to connect to. 6522 is the default obby port.
	 */
	void connect(const std::string& hostname, unsigned int port = 6522);

	/** Connects to the given address where a obby server is assumed to be
	 * running. After the connection has been established, signal_welcome
	 * will be emitted after the server sent us some initial data
	 * At this point the login function may be used to login as a user
	 * with a given colour.
	 * TODO: Ask username and colour parameters already here and login
	 * implicitly after having called connect().
	 *
	 * @param address Address to connect to.
	 */
	void connect(const net6::address& addr);

	/** Disconnects from a server. Note that documents and users are
	 * still available until reconnection. get_self() will still return
	 * the local user. is_logged_in() will returns false since the
	 * connection is lost.
	 */
	void disconnect();

	/** Checks if we are currently connected to an obby session.
	 */
	//bool is_connected() const;

	/** Requests encryption of the connection.
	 */
	//void request_encryption();

	/** Sends a login request for this client. If either the login request
	 * failed because of name or colour are already in use or a password
	 * is required, prompt_*_event() will be emitted. It may be used to
	 * choose another name/colour or password. Returning false from those
	 * signal handlers tell obby to abort the login process,
	 * signal_login_failed will _not_ be emitted in this case.
	 *
	 * TODO: Take connection_settings?
	 * @param name User name for this client.
	 * @param colour User colour.
	 */
	void login(const std::string& name,
	           const obby::colour& colour);

	/** Returns TRUE if the client is already logged in.
	 */
	bool is_logged_in() const;

	/** Requests a new document at the server and sync its initial
	 * contents. signal_document_insert will be emitted if the server
	 * authorised the creation process.
	 */
	virtual void document_create(const std::string& title,
	                             const std::string& encoding,
	                             const std::string& content = "");

	/** Requests the deletion of a document at the server.
	 * signal_document_remove will be emitted if the server
	 * authorized the deletion.
	 */
	virtual void document_remove(base_document_info_type& doc);

	/** Looks for a document with the given ID which belongs to the user
	 * with the given owner ID. Note that we do not take a real user object
	 * here because the ID is enough and one might not have a user object
	 * to the corresponding ID. So a time-consuming lookup is obsolete.
	 */
	document_info_type* document_find(unsigned int owner_id,
	                                  unsigned int id) const;

	/** Returns the local user.
	 */
	virtual const user& get_self() const;

	/** Returns the name of the local user even if the login process has
	 * not already completed.
	 */
	virtual const std::string& get_name() const;

	/** Sends a global message to all users.
	 */
	virtual void send_message(const std::string& message);

	/** Sends a command to the server and asks for execution.
	 */
	virtual void send_command(const command_query& query);

	/** Set user password.
	 */
	virtual void set_password(const std::string& password);

	/** Set user colour.
	 */
	virtual void set_colour(const colour& colour);

	/** @brief Send keepalives to the server, if enabled.
	 *
	 * With this option enabled, the client sends keepalive packets
	 * to the server when the connection is otherwise idle to make sure
	 * that the connection has not gone away.
	 */
	void set_enable_keepalives(bool enable);

	/** Signal which will be emitted after the first packet, the welcome
	 * packet, is received. This is a good place to perform a call to
	 * the login function. Note that you cannot login earlier because the
	 * server's public key is not known at that time.
	 */
	signal_welcome_type welcome_event() const;

	/** Signal which will be emitted if the connection to the server
	 * has been lost.
	 */
	signal_close_type close_event() const;

	/** Signal which will be emitted if a login request did not succeed.
	 */
	signal_login_failed_type login_failed_event() const;

	/** Signal which will be emitted if the name is already in use.
	 */
	signal_prompt_name_type prompt_name_event() const;

	/** Signal which will be emitted if the colour is already in use.
	 */
	signal_prompt_colour_type prompt_colour_event() const;

	/** Signal which will be emitted if a global password is required.
	 */
	signal_prompt_global_password_type prompt_global_password_event() const;

	/** Signal which will be emitted if a user password is required.
	 */
	signal_prompt_user_password_type prompt_user_password_event() const;

protected:
	/** Registers the signal handlers for the net6::client object. It may
	 * be used by derived classes to register these signal handlers.
	 */
	void register_signal_handlers();

        /** Creates a new document info object according to the type of buffer.
	 */
	virtual base_document_info_type*
	new_document_info(const user* owner,
	                  unsigned int id,
	                  const std::string& title,
	                  unsigned int suffix,
	                  const std::string& encoding);

        /** Creates a new document info object according to the type of buffer.
	 */
	virtual base_document_info_type*
	new_document_info(const user* owner,
	                  unsigned int id,
	                  const std::string& title,
	                  const std::string& encoding,
	                  const std::string& content);

        /** Creates a new document info object according to the type of buffer.
	 */
	virtual base_document_info_type*
	new_document_info(const net6::packet& pack);

	/** Creates the underlaying net6 network object corresponding to the
	 * buffer's type.
	 * TODO: Make server_buffer's and host_buffer's new_net parameterless
	 * and call open() appropriately
	 */
	virtual base_net_type* new_net();

	/** net6 signal handlers.
	 */
	void on_join(const net6::user& user6, const net6::packet& pack);
	void on_part(const net6::user& user6, const net6::packet& pack);
	void on_close();
	void on_encrypted();
	void on_data(const net6::packet& pack);
	void on_login_failed(net6::login::error error);
	void on_login_extend(net6::packet& pack);

	/** Executes a given network packet.
	 */
	virtual bool execute_packet(const net6::packet& pack);

	/** Welcome handling.
	 */
	virtual void on_net_welcome(const net6::packet& pack);

	/** Document commands.
	 */
	virtual void on_net_document_create(const net6::packet& pack);
	virtual void on_net_document_remove(const net6::packet& pack);

	/** Messaging commands.
	 */
	virtual void on_net_message(const net6::packet& pack);
	virtual void on_net_emote_message(const net6::packet& pack);

	/** User colour commands.
	 */
	virtual void on_net_user_colour(const net6::packet& pack);
	virtual void on_net_user_colour_failed(const net6::packet& pack);

	/** Synchronisation commands.
	 */
	virtual void on_net_sync_init(const net6::packet& pack);
	virtual void on_net_sync_usertable_user(const net6::packet& pack);
	virtual void on_net_sync_doclist_document(const net6::packet& pack);
	virtual void on_net_sync_final(const net6::packet& pack);

	/** Forwarding commands.
	 */
	virtual void on_net_document(const net6::packet& pack);

	/** Commands.
	 */
	virtual void on_net_command_result(const net6::packet& pack);

	void on_command_emote(const command_query& query,
	                      const command_result& result);

	/** @brief Closes the session.
	 */
	virtual void session_close();

	/** @brief Implementation of session_close() that does not call
	 * a base function.
	 */
	void session_close_impl();

	const user* m_self;

	connection_settings m_settings;
	bool m_enable_keepalives;

	signal_welcome_type m_signal_welcome;
	signal_close_type m_signal_close;
	signal_login_failed_type m_signal_login_failed;

	signal_prompt_name_type m_signal_prompt_name;
	signal_prompt_colour_type m_signal_prompt_colour;
	signal_prompt_global_password_type m_signal_prompt_global_password;
	signal_prompt_user_password_type m_signal_prompt_user_password;
private:
	/** This function provides access to the underlaying net6::basic_client
	 * object.
	 */
	net_type& net6_client();

	/** This function provides access to the underlaying net6::basic_client
	 * object.
	 */
	const net_type& net6_client() const;
};

typedef basic_client_buffer<obby::document, net6::selector> client_buffer;

template<typename Document, typename Selector>
basic_client_buffer<Document, Selector>::basic_client_buffer():
	basic_local_buffer<Document, Selector>(), m_self(NULL),
	m_enable_keepalives(false)
{
	const command_queue& queue =
		basic_local_buffer<Document, Selector>::m_command_queue;

	queue.result_event("me").connect(
		sigc::mem_fun(*this, &basic_client_buffer::on_command_emote)
	);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	connect(const std::string& hostname,
                unsigned int port)
{
	if(basic_buffer<Document, Selector>::is_open() )
	{
		throw std::logic_error(
			"obby::basic_client_buffer::connect:\n"
			"Connection already established"
		);
	}

	// Create connection object
	basic_buffer<Document, Selector>::m_net.reset(new_net() );

	// Register signal handlers
	register_signal_handlers();

	// Make sure that the signal handlers to the net6::client object
	// are registered before the connection is made, otherwise we may
	// lose a welcome packet if the remote site sends the packet between
	// the connection and the signal handler registration (which _may_
	// occur with connections to localhost).
	try
	{
		net6_client().connect(
			net6::ipv4_address::create_from_hostname(
				hostname,
				port
			)
		);
	}
	catch(net6::error& e)
	{
		if(!IPV6_ENABLED) throw e;

		net6_client().connect(
			net6::ipv6_address::create_from_hostname(
				hostname,
				port
			)
		);
	}

	net6_client().set_enable_keepalives(m_enable_keepalives);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	connect(const net6::address& addr)
{
	if(basic_buffer<Document, Selector>::is_open() )
	{
		throw std::logic_error(
			"obby::basic_client_buffer::connect:\n"
			"Connection already established"
		);
	}

	// Create connection object
	basic_buffer<Document, Selector>::m_net.reset(new_net() );

	// Register signal handlers
	register_signal_handlers();

	// Connect
	net6_client().connect(addr);

	// Set keepalive
	net6_client().set_enable_keepalives(m_enable_keepalives);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::disconnect()
{
	if(!basic_buffer<Document, Selector>::is_open() )
	{
		throw std::logic_error(
			"obby::basic_client_buffer::disconnect:\n"
			"Client is not connected"
		);
	}

	// Close session
	session_close();
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::login(const std::string& name,
                                                    const colour& colour)
{
	m_settings.name = name;
	m_settings.colour = colour;

	net6_client().login(name);
}

template<typename Document, typename Selector>
bool basic_client_buffer<Document, Selector>::is_logged_in() const
{
	// TODO: Return true or false when being disconnected and still
	// having an old session?
	//
	// Some functions like document_remove depend on the current
	// behavior. Change them accordingly if you change something here!
	if(basic_buffer<Document, Selector>::m_net.get() == NULL) return false;
	return net6_client().is_logged_in();
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	document_create(const std::string& title,
	                const std::string& encoding,
	                const std::string& content)
{
	// TODO: Allow this and create the document just locally?
	if(!is_logged_in() )
	{
		throw std::logic_error(
			"obby::basic_client_buffer::document_create:\n"
			"Cannot create document without being logged in"
		);
	}

	// TODO: m_doc_counter does not belong into the base class
	unsigned int id = ++ basic_buffer<Document, Selector>::m_doc_counter;
	// Create document
	base_document_info_type* info =
		new_document_info(m_self, id, title, encoding, content);
	// Add document to list
	basic_buffer<Document, Selector>::document_add(*info);
	// Tell server
	net6::packet request_pack("obby_document_create");
	request_pack << id << title << encoding << content;
	net6_client().send(request_pack);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	document_remove(base_document_info_type& document)
{
	if(is_logged_in() )
	{
		// Send remove request, remove document on server reply
		// to ensure synchronisation
		net6::packet request_pack("obby_document_remove");
		request_pack << &document;
		net6_client().send(request_pack);
	}
	else
	{
		// Delete document
		basic_buffer<Document, Selector>::document_delete(document);
	}
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::document_info_type*
basic_client_buffer<Document, Selector>::
	document_find(unsigned int owner_id,
	              unsigned int id) const
{
	return dynamic_cast<document_info_type*>(
		basic_buffer<Document, Selector>::document_find(owner_id, id)
	);
}

template<typename Document, typename Selector>
const obby::user& basic_client_buffer<Document, Selector>::get_self() const
{
	if(m_self == NULL)
	{
		throw std::logic_error(
			"obby::basic_client_buffer::get_self:\n"
			"Client is not logged in"
		);
	}

	return *m_self;
}

template<typename Document, typename Selector>
const std::string& basic_client_buffer<Document, Selector>::get_name() const
{
	// TODO: Do we still need this?
	if(m_self == NULL) return m_settings.name;
	return basic_local_buffer<Document, Selector>::get_name();
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	send_message(const std::string& message)
{
	if(is_logged_in() )
	{
		// Wait for server response to ensure synchronisation
		net6::packet message_pack("obby_message");
		message_pack << message;
		net6_client().send(message_pack);
	}
	else
	{
		// If we have never been connected to a session we have no
		// user that might send this message
		if(m_self == NULL)
		{
			throw std::logic_error(
				"obby::basic_client_buffer::send_message:\n"
				"No self user available. Probably the client "
				"buffer never has been connected to a session."
			);
		}

		basic_buffer<Document, Selector>::m_chat.add_user_message(
			message, get_self()
		);
	}
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	send_command(const command_query& query)
{
	if(!is_logged_in() )
	{
		throw std::logic_error(
			"obby::basic_client_buffer::send_command:\n"
			"Cannot send command without being logged in"
		);
	}

	basic_local_buffer<Document, Selector>::m_command_queue.query(query);

	net6::packet pack("obby_command_query");
	query.append_packet(pack);

	net6_client().send(pack);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	set_password(const std::string& password)
{
	if(is_logged_in() )
	{
		net6::packet password_pack("obby_user_password");
		password_pack << password;
		net6_client().send(password_pack);
	}
	else
	{
		throw std::logic_error(
			"obby::basic_client_buffer::set_password:\n"
			"Cannot set password without being logged in"
		);
	}
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::set_colour(const colour& colour)
{
	if(is_logged_in() )
	{
		net6::packet colour_pack("obby_user_colour");
		colour_pack << colour;
		net6_client().send(colour_pack);
	}
	else
	{
		throw std::logic_error(
			"obby::basic_client_buffer::::set_colour:\n"
			"Cannot change colour without being logged in"
		);
	}
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::set_enable_keepalives(bool enable)
{
	if(m_enable_keepalives == enable) return;

	m_enable_keepalives = enable;
	if(basic_buffer<Document, Selector>::m_net.get() != NULL)
		net6_client().set_enable_keepalives(enable);
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::signal_welcome_type
basic_client_buffer<Document, Selector>::welcome_event() const
{
	return m_signal_welcome;
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::signal_login_failed_type
basic_client_buffer<Document, Selector>::login_failed_event() const
{
	return m_signal_login_failed;
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::signal_prompt_name_type
basic_client_buffer<Document, Selector>::prompt_name_event() const
{
	return m_signal_prompt_name;
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::signal_prompt_colour_type
basic_client_buffer<Document, Selector>::prompt_colour_event() const
{
	return m_signal_prompt_colour;
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::
	signal_prompt_global_password_type
basic_client_buffer<Document, Selector>::prompt_global_password_event() const
{
	return m_signal_prompt_global_password;
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::
	signal_prompt_user_password_type
basic_client_buffer<Document, Selector>::prompt_user_password_event() const
{
	return m_signal_prompt_user_password;
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::signal_close_type
basic_client_buffer<Document, Selector>::close_event() const
{
	return m_signal_close;
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::on_join(const net6::user& user6,
                                                      const net6::packet& pack)
{
	unsigned int id =
		pack.get_param(3).net6::parameter::as<unsigned int>();
	colour colour =
		pack.get_param(4).net6::parameter::as<obby::colour>();

	// Add user
	const user* new_user = basic_buffer<Document, Selector>::
		m_user_table.add_user(id, user6, colour);

	// The first joining user is the local one
	if(m_self == NULL) m_self = new_user;

	basic_buffer<Document, Selector>::user_join(*new_user);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::on_part(const net6::user& user6,
                                                      const net6::packet& pack)
{
	// Find user
	const user* cur_user =
		basic_buffer<Document, Selector>::m_user_table.find(
			user6,
			user::flags::CONNECTED,
			user::flags::NONE
		);

	// Should never happen
	if(cur_user == NULL)
	{
		format_string str("User %0% is not connected");
		str << user6.get_id();
		throw net6::bad_value(str.str() );
	}

	basic_buffer<Document, Selector>::user_part(*cur_user);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::on_close()
{
	// Disconnect
	disconnect();
	// TODO: Emit signal_close in the disconnect() function?
	m_signal_close.emit();
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::on_encrypted()
{
	// Login now possible
	m_signal_welcome.emit();
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::on_data(const net6::packet& pack)
{
	if(!execute_packet(pack) )
	{
		throw net6::bad_value(
			"Unexpected command: " + pack.get_command()
		);
	}
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_login_failed(net6::login::error error)
{
	if(error == net6::login::ERROR_NAME_IN_USE)
	{
		if(m_signal_prompt_name.emit(m_settings) )
			login(m_settings.name, m_settings.colour);
	}
	else if(error == login::ERROR_COLOUR_IN_USE)
	{
		if(m_signal_prompt_colour.emit(m_settings) )
			login(m_settings.name, m_settings.colour);
	}
	else if(error == login::ERROR_WRONG_GLOBAL_PASSWORD)
	{
		if(m_signal_prompt_global_password.emit(m_settings) )
			login(m_settings.name, m_settings.colour);
	}
	else if(error == login::ERROR_WRONG_USER_PASSWORD)
	{
		if(m_signal_prompt_user_password.emit(m_settings) )
			login(m_settings.name, m_settings.colour);
	}
	else
	{
		m_signal_login_failed.emit(error);
	}
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_login_extend(net6::packet& pack)
{
	// Add user colour and, if given, (hashed) passwords.
	pack << m_settings.colour;
	if(!m_settings.global_password.empty() ||
	   !m_settings.user_password.empty() )
	{
		pack << m_settings.global_password;
		if(!m_settings.user_password.empty() )
			pack << m_settings.user_password;
	}
}

template<typename Document, typename Selector>
bool basic_client_buffer<Document, Selector>::
	execute_packet(const net6::packet& pack)
{
	// TODO: std::map<> from command to function
	if(pack.get_command() == "obby_welcome")
		{ on_net_welcome(pack); return true; }

	if(pack.get_command() == "obby_document_create")
		{ on_net_document_create(pack); return true; }

	if(pack.get_command() == "obby_document_remove")
		{ on_net_document_remove(pack); return true; }

	if(pack.get_command() == "obby_message")
		{ on_net_message(pack); return true; }

	if(pack.get_command() == "obby_emote_message")
		{ on_net_emote_message(pack); return true; }

	if(pack.get_command() == "obby_user_colour")
		{ on_net_user_colour(pack); return true; }

	if(pack.get_command() == "obby_user_colour_failed")
		{ on_net_user_colour_failed(pack); return true; }

	if(pack.get_command() == "obby_sync_init")
		{ on_net_sync_init(pack); return true; }

	if(pack.get_command() == "obby_sync_usertable_user")
		{ on_net_sync_usertable_user(pack); return true; }

	if(pack.get_command() == "obby_sync_doclist_document")
		{ on_net_sync_doclist_document(pack); return true; }

	if(pack.get_command() == "obby_sync_final")
		{ on_net_sync_final(pack); return true; }

	if(pack.get_command() == "obby_document")
		{ on_net_document(pack); return true; }

	if(pack.get_command() == "obby_command_result")
		{ on_net_command_result(pack); return true; }

	return false;
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_welcome(const net6::packet& pack)
{
	// Get the OBBY version the server is running and compare to the
	// version of this library.
	unsigned long server_version =
		pack.get_param(0).net6::parameter::as<unsigned long>();

	if(server_version != PROTOCOL_VERSION)
	{
		on_login_failed(login::ERROR_PROTOCOL_VERSION_MISMATCH);
		return;
	}

	// Emit welcome signal to indicate that the user may now perform a
	// login() call.
	//
	// Do no longer emit signal welcome here since the login has to be
	// performed after the connection has been encrypted since
	// obby 0.4.0.
	//m_signal_welcome.emit();
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_document_create(const net6::packet& pack)
{
	// Get owner, id and title
	const user* owner = pack.get_param(0).net6::parameter::as<const user*>(
		::serialise::hex_context_from<const user*>(
			basic_buffer<Document, Selector>::get_user_table()
		)
	);

	unsigned int id =
		pack.get_param(1).net6::parameter::as<unsigned int>();
	const std::string& title =
		pack.get_param(2).net6::parameter::as<std::string>();
	unsigned int suffix =
		pack.get_param(3).net6::parameter::as<unsigned int>();
	const std::string& encoding =
		pack.get_param(4).net6::parameter::as<std::string>();

	// Get owner ID
	unsigned int owner_id = (owner == NULL ? 0 : owner->get_id() );

	// Document owner must not be the local user because if we created the
	// document, the create_document request is not sent back to us.
	if(owner == m_self)
	{
		format_string str("Owner of document %0%/%1% is self");
		str << owner_id << id;
		throw net6::bad_value(str.str() );
	}

	// Is there already such a document?
	if(document_find(owner_id, id) )
	{
		format_string str("Document %0%/%1% exists already");
		str << owner_id << id;
		throw net6::bad_value(str.str() );
	}

	// Add new document
	base_document_info_type* info =
		new_document_info(owner, id, title, suffix, encoding);

		basic_buffer<Document, Selector>::document_add(*info);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_document_remove(const net6::packet& pack)
{
	// Get document to remove
	document_info_type& doc = dynamic_cast<document_info_type&>(
		*pack.get_param(0).net6::parameter::as<
			base_document_info_type*
		>(::serialise::hex_context_from<base_document_info_type*>(
			*this
		))
	);

	// Emit unsubscribe singal for users who were subscribed to this doc
	// TODO: Do this is in document_delete!
	for(typename document_info_type::user_iterator user_iter =
		doc.user_begin();
	    user_iter != doc.user_end();
	    ++ user_iter)
	{
		doc.unsubscribe_event().emit(*user_iter);
	}

	// Delete document
	basic_buffer<Document, Selector>::document_delete(doc);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_message(const net6::packet& pack)
{
	const user* writer = pack.get_param(0).net6::parameter::as<const user*>(
		::serialise::hex_context_from<const user*>(
			basic_buffer<Document, Selector>::get_user_table()
		)
	);

	const std::string& message =
		pack.get_param(1).net6::parameter::as<std::string>();

	// Valid user id indicates that the message comes from a user, otherwise
	// the server sent the message directly
	if(writer != NULL)
	{
		basic_buffer<Document, Selector>::m_chat.add_user_message(
			message,
			*writer
		);
	}
	else
	{
		basic_buffer<Document, Selector>::m_chat.add_server_message(
			message
		);
	}
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_emote_message(const net6::packet& pack)
{
	const user* writer = pack.get_param(0).net6::parameter::as<const user*>(
		::serialise::hex_context_from<const user*>(
			basic_buffer<Document, Selector>::get_user_table()
		)
	);

	const std::string& message =
		pack.get_param(1).net6::parameter::as<std::string>();

	if(writer == NULL)
	{
		throw std::logic_error(
			"obby::basic_client_buffer::on_net_emote_message:\n"
			"Server cannot send emote messages"
		);
	}

	basic_buffer<Document, Selector>::m_chat.add_emote_message(
		message,
		*writer
	);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_user_colour(const net6::packet& pack)
{
	const user* from = pack.get_param(0).net6::parameter::as<const user*>(
		::serialise::hex_context_from<const user*>(
			basic_buffer<Document, Selector>::get_user_table()
		)
	);

	basic_buffer<Document, Selector>::m_user_table.set_user_colour(
		*from,
		pack.get_param(1).net6::parameter::as<obby::colour>()
	);

	// TODO: user::set_colour should emit the signal
	basic_buffer<Document, Selector>::m_signal_user_colour.emit(*from);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_user_colour_failed(const net6::packet& pack)
{
	basic_local_buffer<Document, Selector>::
		m_signal_user_colour_failed.emit();
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_sync_init(const net6::packet& pack)
{
	// Login was successful, synchronisation begins. Clear users and
	// documents from old session that have been kept to be able to still
	// access them while being disconnected.
	basic_buffer<Document, Selector>::m_user_table.clear();
	basic_buffer<Document, Selector>::document_clear();
	m_self = NULL;

	basic_buffer<Document, Selector>::m_signal_sync_init.emit(
		pack.get_param(0).net6::parameter::as<unsigned int>()
	);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_sync_usertable_user(const net6::packet& pack)
{
	// User that was already in the obby session, but isn't anymore.

	// Extract data from packet
	unsigned int id =
		pack.get_param(0).net6::parameter::as<unsigned int>();
	const std::string& name =
		pack.get_param(1).net6::parameter::as<std::string>();
	colour colour =
		pack.get_param(2).net6::parameter::as<obby::colour>();

	// Add user into user table
	basic_buffer<Document, Selector>::m_user_table.add_user(
		id, name, colour
	);

	// TODO: Emit user_join_signal. Should be done automatically by the
	// function call above
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_sync_doclist_document(const net6::packet& pack)
{
	// Get data from packet
	const user* owner = pack.get_param(0).net6::parameter::as<const user*>(
		::serialise::hex_context_from<const user*>(
			basic_buffer<Document, Selector>::get_user_table()
		)
	);

	unsigned int id =
		pack.get_param(1).net6::parameter::as<unsigned int>();

	// Get document owner ID
	unsigned int owner_id = (owner == NULL ? 0 : owner->get_id() );

	// Initialise document counter to the document's ID to produce
	// unique IDs even when rejoining a session.
	if(owner_id == m_self->get_id() &&
	   id >= basic_buffer<Document, Selector>::m_doc_counter)
	{
		basic_buffer<Document, Selector>::m_doc_counter = id + 1;
	}

	// Check for duplicates, should not happen
	if(document_find(owner_id, id) != NULL)
	{
		format_string str("Document %0%/%1% exists already");
		str << owner_id << id;
		throw net6::bad_value(str.str() );
	}

	// Create document_info from packet
	base_document_info_type* info = new_document_info(pack);
	// Add to buffer
	basic_buffer<Document, Selector>::document_add(*info);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_sync_final(const net6::packet& pack)
{
	basic_buffer<Document, Selector>::m_signal_sync_final.emit();
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_document(const net6::packet& pack)
{
	// Get document, forward packet
	document_info_type& info = dynamic_cast<document_info_type&>(
		*pack.get_param(0).net6::parameter::as<
			base_document_info_type*
		>(::serialise::hex_context_from<base_document_info_type*>(
			*this
		))
	);

	// TODO: Rename this function. Think about providing a signal that may
	// be emitted.
	info.on_net_packet(document_packet(pack) );
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_net_command_result(const net6::packet& pack)
{
	unsigned int index = 0;
	command_result result(pack, index);

	basic_local_buffer<Document, Selector>::m_command_queue.result(result);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::
	on_command_emote(const command_query& query,
	                 const command_result& result)
{
	basic_buffer<Document, Selector>::m_chat.add_emote_message(
		query.get_paramlist(),
		*m_self
	);
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::session_close()
{
	session_close_impl();
	basic_local_buffer<Document, Selector>::session_close_impl();
	basic_buffer<Document, Selector>::session_close_impl();
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::session_close_impl()
{
	// Reset passwords to prevent using them for the next connection
	m_settings.global_password = m_settings.user_password = "";
}

template<typename Document, typename Selector>
void basic_client_buffer<Document, Selector>::register_signal_handlers()
{
	net6_client().join_event().connect(
		sigc::mem_fun(*this, &basic_client_buffer::on_join) );
	net6_client().part_event().connect(
		sigc::mem_fun(*this, &basic_client_buffer::on_part) );
	net6_client().close_event().connect(
		sigc::mem_fun(*this, &basic_client_buffer::on_close) );
	net6_client().encrypted_event().connect(
		sigc::mem_fun(*this, &basic_client_buffer::on_encrypted) );
	net6_client().data_event().connect(
		sigc::mem_fun(*this, &basic_client_buffer::on_data) );
	net6_client().login_failed_event().connect(
		sigc::mem_fun(*this, &basic_client_buffer::on_login_failed) );
	net6_client().login_extend_event().connect(
		sigc::mem_fun(*this, &basic_client_buffer::on_login_extend) );
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::base_document_info_type*
basic_client_buffer<Document, Selector>::
	new_document_info(const user* owner,
			  unsigned int id,
			  const std::string& title,
			  unsigned int suffix,
			  const std::string& encoding)
{
	// Create client_document_info, according to client_buffer
	return new document_info_type(
		*this, net6_client(), owner, id, title, suffix, encoding
	);
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::base_document_info_type*
basic_client_buffer<Document, Selector>::
	new_document_info(const user* owner,
	                  unsigned int id,
	                  const std::string& title,
	                  const std::string& encoding,
	                  const std::string& content)
{
	return new document_info_type(
		*this, net6_client(), owner, id, title, encoding, content
	);
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::base_document_info_type*
basic_client_buffer<Document, Selector>::
	new_document_info(const net6::packet& pack)
{
	return new document_info_type(*this, net6_client(), pack);
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::base_net_type*
basic_client_buffer<Document, Selector>::new_net()
{
	// Connect to remote host
	return new net_type;
}

template<typename Document, typename Selector>
typename basic_client_buffer<Document, Selector>::net_type&
basic_client_buffer<Document, Selector>::net6_client()
{
	return dynamic_cast<net_type&>(
		*basic_buffer<Document, Selector>::m_net.get()
	);
}

template<typename Document, typename Selector>
const typename basic_client_buffer<Document, Selector>::net_type&
basic_client_buffer<Document, Selector>::net6_client() const
{
	return dynamic_cast<const net_type&>(
		*basic_buffer<Document, Selector>::m_net.get()
	);
}

} // namespace obby

#endif // _OBBY_CLIENT_BUFFER_HPP_
