/* libobby - Network text editing library
 * Copyright (C) 2005 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_CHAT_HPP_
#define _OBBY_CHAT_HPP_

#include <ctime>
#include <list>
#include <sigc++/signal.h>
#include <sigc++/connection.h>
#include "user.hpp"
#include "user_table.hpp"
//#include "document_info.hpp"

namespace obby
{

/** Handles obby chat messages.
 */
class chat
{
public:
	/** Base class for chat messages.
	 */
	class message: private net6::non_copyable
	{
	public:
		message(const std::string& text,
		        std::time_t timestamp);
		message(const serialise::object& obj,
		        const user_table& user_table);
		virtual ~message();

		virtual void serialise(serialise::object& obj) const;

		const std::string& get_text() const;
		std::time_t get_timestamp() const;

		std::string format_timestamp(const char* format) const;

		virtual std::string repr() const = 0;
	protected:
		std::string m_text;
		std::time_t m_timestamp;
	};

	/** Message sent by a user.
	 */
	class user_message: public message
	{
	public:
		user_message(const std::string& text,
		             std::time_t timestamp,
		             const user& from);
		user_message(const serialise::object& obj,
		             const user_table& user_table);

		virtual void serialise(serialise::object& obj) const;

		const user& get_user() const;
		virtual std::string repr() const;
	protected:
		const user& m_user;
	};

	/** @brief Emote message by /me command.
	 */
	class emote_message: public user_message
	{
	public:
		emote_message(const std::string& text,
		              std::time_t timestamp,
		              const user& from);
		emote_message(const serialise::object& obj,
		              const user_table& user_table);

		virtual std::string repr() const;
	};

	/** Message sent by server.
	 */
	class server_message: public message
	{
	public:
		server_message(const std::string& text,
		               std::time_t timestamp);
		server_message(const serialise::object& obj,
		               const user_table& user_table);

		virtual std::string repr() const;
	};

	/** Base system notice message.
	 */
	class system_message: public message
	{
	public:
		system_message(const std::string& text,
		               std::time_t timestamp);
		system_message(const serialise::object& obj,
		               const user_table& user_table);

		virtual std::string repr() const;
	};

	typedef ptr_iterator<
		message,
		std::list<message*>,
		std::list<message*>::const_iterator
	> message_iterator;

	typedef sigc::signal<void, const message&>
		signal_message_type;

	/** Chat constructor.
	 * @param buffer Buffer owning this chat.
	 * TODO: Just take user_table and connect to user_table's signals
	 * @param max_messages How many messages to store before deleting
	 * old ones.
	 */
	template<typename Buffer>
	chat(const Buffer& buffer, unsigned int max_messages);

	~chat();

	/** Serialises the chat history to a serialisation object.
	 * @param obj Object to serialise to.
	 */
	void serialise(serialise::object& obj) const;

	/** Deserialises a chat history from a serialisation object.
	 * @param obj Object to deserialise from.
	 * @param user_table user_table where to read user information from.
	 */
	void deserialise(const serialise::object& obj,
	                 const user_table& user_table);

	/** Clears the whole history.
	 */
	void clear();

	/** Adds a new user message to the history.
	 */
	void add_user_message(const std::string& text,
	                      const user& from);

	/** @brief Adds a new emote message to the history.
	 */
	void add_emote_message(const std::string& text,
	                       const user& from);

	/** Adds a new server message to the history.
	 */
	void add_server_message(const std::string& text);

	/** Returns an iterator pointing at the beginning of the chat history.
	 */
	message_iterator message_begin() const;

	/** Returns an iterator pointing at the end of the chat history.
	 */
	message_iterator message_end() const;

	/** Signal that will be emitted if a user message arrived.
	 */
	signal_message_type message_event() const;
protected:
	void add_message(message* msg);

	void on_sync_init(unsigned int);
	void on_sync_final();

	void on_user_join(const user& user);
	void on_user_part(const user& user);

	template<typename DocumentInfo>
	void on_document_insert(DocumentInfo& document);

	template<typename DocumentInfo>
	void on_document_remove(DocumentInfo& document);

	unsigned int m_max_messages;
	std::list<message*> m_messages;

	signal_message_type m_signal_message;

	sigc::connection m_user_join_conn;
	sigc::connection m_user_part_conn;
	sigc::connection m_document_insert_conn;
	sigc::connection m_document_remove_conn;
};

template<typename Buffer>
chat::chat(const Buffer& buffer, unsigned int max_messages):
	m_max_messages(max_messages)
{
	typedef typename Buffer::document_info_type document_info_type;

	buffer.sync_init_event().connect(
		sigc::mem_fun(*this, &chat::on_sync_init) );
	buffer.sync_final_event().connect(
		sigc::mem_fun(*this, &chat::on_sync_final) );
	m_user_join_conn = buffer.user_join_event().connect(
		sigc::mem_fun(*this, &chat::on_user_join) );
	m_user_part_conn = buffer.user_part_event().connect(
		sigc::mem_fun(*this, &chat::on_user_part) );

	m_document_insert_conn = buffer.document_insert_event().connect(
		sigc::mem_fun(
			*this,
			&chat::on_document_insert<document_info_type>
		)
	);

	m_document_remove_conn = buffer.document_remove_event().connect(
		sigc::mem_fun(
			*this,
			&chat::on_document_remove<document_info_type>
		)
	);
}

template<typename DocumentInfo>
void chat::on_document_insert(DocumentInfo& document)
{
	const user* user = document.get_owner();
	std::string localised_str;

	// The document has no owner, it was created by the server.
	if(user != NULL)
	{
		obby::format_string str(
			_("%0% has created a new document: %1%") );
		str << (*user).get_name() << document.get_title();
		localised_str = str.str();
	}
	else
	{
		obby::format_string str(_("A new document was created: %0%") );
		str << document.get_title();
		localised_str = str.str();
	}

	add_message(new system_message(localised_str, std::time(NULL)) );
}

template<typename DocumentInfo>
void chat::on_document_remove(DocumentInfo& document)
{
	obby::format_string str(_("Document %0% has been removed"));
	str << document.get_title();
	add_message(new system_message(str.str(), std::time(NULL)));
}

} // namespace obby

#endif // _OBBY_CHAT_HPP_

