/*
    Copyright (C) 2005-2009  Michel de Boer <michel@twinklephone.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, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file
 * Threads communicate by passing events via event queues.
 */

#ifndef _EVENTS_H
#define _EVENTS_H

#include <queue>
#include "protocol.h"
#include "timekeeper.h"
#include "stun/stun.h"
#include "audio/audio_codecs.h"
#include "audio/rtp_telephone_event.h"
#include "parser/sip_message.h"
#include "sockets/socket.h"
#include "sockets/url.h"
#include "threads/mutex.h"
#include "threads/sema.h"

using namespace std;

// Forward declarations
class t_userintf;

/** Different types of events. */
enum t_event_type {
	EV_QUIT,		/**< Generic quit event */
	EV_NETWORK,		/**< Network event, eg. SIP message from/to network */
	EV_USER,		/**< User event, eg. SIP message from/to user  */
	EV_TIMEOUT,		/**< Timer expiry */
	EV_FAILURE,		/**< Failure, eg. transport failure */
	EV_START_TIMER,		/**< Start timer */
	EV_STOP_TIMER,		/**< Stop timer */
	EV_ABORT_TRANS,		/**< Abort transaction */
	EV_STUN_REQUEST,	/**< Outgoing STUN request */
	EV_STUN_RESPONSE,	/**< Received STUN response */
	EV_NAT_KEEPALIVE,	/**< Send a NAT keep alive packet */
	EV_ICMP,		/**< ICMP error */
	EV_UI,			/**< User interface event */
	EV_ASYNC_RESPONSE,	/**< Response on an asynchronous question */
	EV_BROKEN_CONNECTION,	/**< Persitent connection to SIP proxy broken */
	EV_TCP_PING,		/**< Send a TCP ping (double CRLF) */
	EV_FN_CALL,		/**< Queued function call */
};

/** Abstract parent class for all events */
class t_event {
public:
	virtual ~t_event() {}
	
	/** Get the type of this event. */
	virtual t_event_type get_type(void) const = 0;
};


/** 
 * Generic quit event.
 * The quit event instructs a thread to exit gracefully.
 */
class t_event_quit : public t_event {
public:
	virtual ~t_event_quit();
	virtual t_event_type get_type(void) const;
};


/** 
 * Network events.
 * A network event is a SIP message going from the transaction manager
 * to the network or v.v.
 */
class t_event_network : public t_event {
private:
	/** The SIP message. */
	t_sip_message	*msg;

public:
	unsigned int	src_addr; /**< Source IP address of the SIP message (host order). */
	unsigned short	src_port; /**< Source port of the SIP message (host order). */
	unsigned int	dst_addr; /**< Destination IP address of the SIP message (host order). */
	unsigned short	dst_port; /**< Destination port of the SIP message (host order). */
	string		transport; /**< Transport protocol */

	/**
	 * Constructor.
	 * The event will keep a copy of the SIP message.
	 * @param m [in] The SIP message.
	 */
	t_event_network(t_sip_message *m);
	
	~t_event_network();
	
	t_event_type get_type(void) const;
	
	/** 
	 * Get the SIP message.
	 * @return Pointer to the SIP message inside this event.
	 */
	t_sip_message *get_msg(void) const;
};


/**
 * User events.
 * A user event is a SIP message going from the user to the
 * transaction manager or v.v.
 */
class t_event_user : public t_event {
private:
	t_sip_message	*msg;	/**< The SIP message. */
	unsigned short	tuid;	/**< Transaction user id. */
	unsigned short	tid;	/**< Transaction id. */

	/**
	 * Transaction id that is the target of the CANCEL message.
	 * Only set if tid is a CANCEL transaction and the event
	 * is sent towards the user.
	 */
	unsigned short	tid_cancel_target;
	
	/** User profile of the user sending/receiving the SIP message. */
	t_user		*user_config;

public:
	/** Constructor.
	 * @param u [in] User profile.
	 * @param m [in] SIP message
	 * @param _tuid [in] Transaction user id associated with this message.
	 * @param _tid [in] Transaction id of the transaction for this message.
	 */
	t_event_user(t_user *u, t_sip_message *m, unsigned short _tuid,
		unsigned short _tid);
		
	/** Constructor for CANCEL request towards the user.
	 * @param u [in] User profile.
	 * @param m [in] SIP message.
	 * @param _tuid [in] Transaction user id associated with this message.
	 * @param _tid [in] Transaction id of the transaction for this message.
	 * @param _tid_cancel_target [in] Id of the target transaction of a CANCEL request.
	 */
	t_event_user(t_user *u, t_sip_message *m, unsigned short _tuid,
		unsigned short _tid, unsigned short _tid_cancel_target);
		
	~t_event_user();
	
	t_event_type get_type(void) const;
	
	/** 
	 * Get the SIP message.
	 * @return Pointer to the SIP message inside this event.
	 */
	t_sip_message *get_msg(void) const;
	
	/** Get transaction user id. */
	unsigned short get_tuid(void) const;
	
	/** Get transaction id. */
	unsigned short get_tid(void) const;
	
	/** Get the CANCEL target transaction id. */
	unsigned short get_tid_cancel_target(void) const;
	
	/**
	 * Get the user profile.
	 * @return Pointer to the user profile inside the event.
	 */
	t_user *get_user_config(void) const;
};


/**
 * Time out events.
 * Expiration of a timer is signalled by a time out event.
 */
class t_event_timeout : public t_event {
private:
	/**
	 * The epxired timer.
	 * @note Timer pointer will be deleted upon destruction of the object.
	 */
	t_timer		*timer;

public:
	/**
	 * Constructor.
	 * @param t [in] The expired timer.
	 * @note The event will keep a copy of the timer.
	 */
	t_event_timeout(t_timer *t);
	
	~t_event_timeout();
	
	t_event_type get_type(void) const;
	
	/**
	 * Get the timer from the event.
	 * @return The timer.
	 */
	t_timer *get_timer(void) const;
};

/**
 * Failure events.
 */
class t_event_failure : public t_event {
private:
	t_failure	failure;	/**< Type of failure. */
	
	/**
	 * Indicates if the tid value is populated. If the tid value is not
	 * populated, then the branch and cseq_method are populated.
	 */
	bool		tid_populated;
	
	unsigned short	tid;		/**< Id of transaction that failed. */
	
	string		branch;		/**< Branch parameter of SIP message that failed. */
	t_method	cseq_method;	/**< CSeq method of SIP message that failed. */
public:
	/**
	 * Constructor.
	 * @param f [in] Type of failure.
	 * @param _tid [in] Transaction id.
	 */
	t_event_failure(t_failure f, unsigned short _tid);
	
	/** Constructor */
	t_event_failure(t_failure f, const string &_branch, const t_method &_cseq_method);
	
	t_event_type get_type(void) const;
	
	/**
	 * Get the type of failure.
	 * @return Type of failure.
	 */
	t_failure get_failure(void) const;
	
	/**
	 * Get the transaction id.
	 * @return Transaction id.
	 */
	unsigned short get_tid(void) const;
	
	/** Get branch parameter. */
	string get_branch(void) const;
	
	/** Get CSeq method. */
	t_method get_cseq_method(void) const;
	
	/** Check if tid is populated. */
	bool is_tid_populated(void) const;
};


/**
 * Start timer event.
 * A start timer event instructs the time keeper to start a timer.
 */
class t_event_start_timer : public t_event {
private:
	t_timer		*timer;		/**< The timer to start. */

public:
	/**
	 * Constructor.
	 * @param t [in] The timer to start.
	 */
	t_event_start_timer(t_timer *t);
	
	
	t_event_type get_type(void) const;
	
	/**
	 * Get the timer.
	 * @return Timer.
	 */
	t_timer *get_timer(void) const;
};


/**
 * Stop timer event
 * A stop timer event instructs the time keeper to stop a timer.
 */
class t_event_stop_timer : public t_event {
private:
	/** Id of the timer to stop. */
	unsigned short	timer_id;

public:
	/**
	 * Constructor.
	 * @param id [in] Id of the timer to stop.
	 */
	t_event_stop_timer(unsigned short id);
	
	t_event_type get_type(void) const;
	
	/**
	 * Get the timer id.
	 * @return Timer id.
	 */
	unsigned short get_timer_id(void) const;
};


/**
 * Abort transaction event.
 * With an abort transaction event, the requester asks the transaction
 * manager to abort a pending transaction.
 */
class t_event_abort_trans : public t_event {
private:
	unsigned short	tid; /**< Id of the transaction to abort. */
public:
	/**
	 * Constructor.
	 * @param _tid [in] Transaction id.
	 */
	t_event_abort_trans(unsigned short _tid);
	
	t_event_type get_type(void) const;
	
	/**
	 * Get transaction id.
	 * @return Transaction id.
	 */
	unsigned short get_tid(void) const;
};


/** STUN event types. */
enum t_stun_event_type {
	TYPE_STUN_SIP,		/**< Request to open a port for SIP. */
	TYPE_STUN_MEDIA,	/**< Request to open a port for media. */
};	

/**
 * STUN request event.
 */
class t_event_stun_request : public t_event {
private:
	StunMessage		*msg;		/**< STUN request to send. */
	unsigned short		tuid;		/**< Transaction user id. */
	unsigned short		tid;		/**< Transaction id. */
	t_stun_event_type	stun_event_type; /**< Type of STUN event. */
	t_user			*user_config;	/**< User profile associated with this request. */

public:
	unsigned int	dst_addr;	/**< Destination address of request (host order). */
	unsigned short	dst_port;	/**< Destination port of request (host order). */
	unsigned short	src_port;	/**< Source port for media event type (host order). */

	/** Constructor. */
	t_event_stun_request(t_user *u, StunMessage *m, t_stun_event_type ev_type,
		unsigned short _tuid, unsigned short _tid);
		
	~t_event_stun_request();
	
	t_event_type get_type(void) const;
	
	/** Get STUN message. */
	StunMessage *get_msg(void) const;
	
	/** Get transaction user id. */
	unsigned short get_tuid(void) const;
	
	/** Get transaction id. */
	unsigned short get_tid(void) const;
	
	/** Get STUN event type. */
	t_stun_event_type get_stun_event_type(void) const;
	
	/** Get user profile. */
	t_user *get_user_config(void) const;
};


/**
 * STUN response event.
 */
class t_event_stun_response : public t_event {
private:
	StunMessage	*msg;		/**< STUN request to send. */
	unsigned short	tuid;		/**< Transaction user id. */
	unsigned short	tid;		/**< Transaction id. */

public:
	/** Constructor. */
	t_event_stun_response(StunMessage *m, unsigned short _tuid,
		unsigned short _tid);
		
	~t_event_stun_response();
	
	t_event_type get_type(void) const;
	
	/** Get STUN message. */
	StunMessage *get_msg(void) const;
	
	/** Get transaction user id. */
	unsigned short get_tuid(void) const;
	
	/** Get transaction id. */
	unsigned short get_tid(void) const;
};


/**
 * NAT keep alive event.
 * Request to send a NAT keep alive message.
 */
class t_event_nat_keepalive : public t_event {
public:
	unsigned int	dst_addr;	/**< Destination address for keepalive (host order) */
	unsigned short	dst_port;	/**< Destination port (host order) */
	
	t_event_type get_type(void) const;
};


/**
 * ICMP event.
 * This event signals the reception of an ICMP error.
 */
class t_event_icmp : public t_event {
private:
	t_icmp_msg	icmp;	/**< The received ICMP message. */

public:
	/**
	 * Constructor.
	 * @param m [in] ICMP message.
	 */
	t_event_icmp(const t_icmp_msg &m);
	
	t_event_type get_type(void) const;
	
	/**
	 * Get the ICMP message.
	 * @return ICMP message.
	 */
	t_icmp_msg get_icmp(void) const;
};


/** User interface callback types. */
enum t_ui_event_type {
	TYPE_UI_CB_DISPLAY_MSG,			/**< Display a message */
	TYPE_UI_CB_DTMF_DETECTED,		/**< DTMF tone detected */
	TYPE_UI_CB_SEND_DTMF,			/**< Sending DTMF */
	TYPE_UI_CB_RECV_CODEC_CHANGED,		/**< Codec changed */
	TYPE_UI_CB_LINE_STATE_CHANGED,		/**< Line state changed */
	TYPE_UI_CB_LINE_ENCRYPTED,		/**< Line is now encrypted */
	TYPE_UI_CB_SHOW_ZRTP_SAS,		/**< Show the ZRTP SAS */
	TYPE_UI_CB_ZRTP_CONFIRM_GO_CLEAR,	/**< ZRTP Confirm go-clear */
	TYPE_UI_CB_QUIT				/**< Quit the user interface */
};

/** Display message priorities. */
enum t_msg_priority {
	MSG_NO_PRIO,
	MSG_INFO,
	MSG_WARNING,
	MSG_CRITICAL
};

/**
 * User interface event.
 * Send a user interface callback to the user interface.
 * Most callbacks are called directly as a function call.
 * Sometimes an asynchronous callback is needed. That's where
 * this event is used for.
 */
class t_event_ui : public t_event {
private:
	t_ui_event_type	type;	/**< User interface callback type. */
	
	/** @name Parameters for call back functions */
	//@{
	int		line;		/**< Line number. */
	t_audio_codec	codec;		/**< Audio codec. */
	t_dtmf_ev	dtmf_event;	/**< DTMF event. */
	bool		encrypted;	/**< Encryption indication. */
	string		cipher_mode;	/**< Cipher mode (algorithm name). */
	string		zrtp_sas;	/**< ZRTP SAS/ */
	t_msg_priority	msg_priority;	/**< Priority of a display message. */
	string		msg;		/**> Message to display. */
	//@}

public:
	/**
	 * Constructor.
	 * @param _type [in] Type of callback.
	 */
	t_event_ui(t_ui_event_type _type);
	
	t_event_type get_type(void) const;
	
	/** @name Set parameters for call back functions */
	//@{
	void set_line(int _line);
	void set_codec(t_audio_codec _codec);
	void set_dtmf_event(t_dtmf_ev _dtmf_event);
	void set_encrypted(bool on);
	void set_cipher_mode(const string &_cipher_mode);
	void set_zrtp_sas(const string &sas);
	void set_display_msg(const string &_msg, t_msg_priority &_msg_priority);
	//@}
	
	/**
	 * Call the callback function.
	 * @param user_intf [in] The user interface that receives the callback.
	 */
	void exec(t_userintf *user_intf);
};


/**
 * Asynchronous response event.
 * A user interface can open an asynchronous message box to request
 * information from the user. Via this event the user interface signals
 * the response from the user.
 */
class t_event_async_response : public t_event {
public:
	/** Response type */
	enum t_response_type {
		RESP_REFER_PERMISSION	/**< Response on permission to refer question */
	};
	
private:
	t_response_type		response_type;	/**< Response type. */
	bool			bool_response;	/**< Boolean response. */
	
public:
	/**
	 * Constructor.
	 * @param type [in] The response type.
	 */
	t_event_async_response(t_response_type type);
	
	t_event_type get_type(void) const;
	
	/**
	 * Set the boolean response.
	 * @param b [in] The response.
	 */
	void set_bool_response(bool b);
	
	/**
	 * Get response type.
	 * @return Response type.
	 */
	t_response_type get_response_type(void) const;
	
	/**
	 * Get boolean response.
	 * @return The response.
	 */
	bool get_bool_response(void) const;
};

/**
 * Broken connection event.
 * A persistent connection to a SIP proxy is broken. With this event
 * the transport layer signals the transaction layer that a connection
 * is broken.
 */
class t_event_broken_connection : public t_event {
private:
	/** The user URI (AoR) that the connection was associated with. */
	t_url		user_uri_;
	
public:
	/** Constructor */
	t_event_broken_connection(const t_url &url);
	
	t_event_type get_type(void) const;
	
	/**
	 * Get the user URI.
	 * @return The user URI.
	 */
	t_url get_user_uri(void) const;
};

/**
 * TCP ping event.
 * Send a TCP ping (double CRLF).
 */
class t_event_tcp_ping : public t_event {
private:
	/** The user URI (AoR) for which the ping must be sent. */
	t_url		user_uri_;
	
	unsigned int	dst_addr_;	/**< Destination address for ping (host order) */
	unsigned short	dst_port_;	/**< Destination port (host order) */
	
public:
	/** Constructor */
	t_event_tcp_ping(const t_url &url, unsigned int dst_addr, unsigned short dst_port);
	
	t_event_type get_type(void) const;
	
	/** @name Getters */
	//@{
	t_url get_user_uri(void) const;
	unsigned int get_dst_addr(void) const;
	unsigned short get_dst_port(void) const;
	//@}
};

class t_event_fncall : public t_event {
public:
	t_event_fncall(std::function<void()> fn);
	void invoke();
	virtual t_event_type get_type(void) const override;
private:
	std::function<void()> m_fn;
};

/**
 * Event queue.
 * An event queue is the communication pipe between multiple
 * threads. Multiple threads write events into the queue and
 * one thread reads the events from the queue and processes them
 * Access to the queue is protected by a mutex. A semaphore is
 * used to synchronize the reader with the writers of the queue.
 */
class t_event_queue {
private:
	queue<t_event *>	ev_queue;	/**< Queue of events. */
	t_mutex			mutex_evq;	/**< Mutex to protect access to the queue. */
	t_semaphore		sema_evq;	/**< Semephore counting the number of events. */

	/**
	 * Semaphore to signal an interrupt.
	 * Will be posted when the interrupt method is called.
	 */
	t_semaphore		sema_caught_interrupt;

public:
	/** Constructor. */
	t_event_queue();
	
	~t_event_queue();

	/**
	 * Push an event into the queue.
	 * @param e [in] Event
	 */
	void push(t_event *e);
	
	/** Push a quit event into the queue. */
	void push_quit(void);

	/** Push a queued function call */
	void push_fncall(std::function<void()> fn);

	/**
	 * Create a network event and push it into the queue.
	 * @param m [in] SIP message.
	 * @param ipaddr [in] Destination address of the message (host order).
	 * @param port [in] Port of the message (host order).
	 */
	void push_network(t_sip_message *m, const t_ip_port &ip_port);

	/**
	 * Create a user event and push it into the queue.
	 * The user event must be associated with a user profile.
	 * @param user_config [in] The user profile.
	 * @param m [in] SIP message.
	 * @param tuid [in] Transaction user id.
	 * @param tid [in] Transaction id.
	 */
	void push_user(t_user *user_config, t_sip_message *m, unsigned short tuid,
		unsigned short tid);
		
	/**
	 * Create a user event and push it into the queue.
	 * The user event must be unrelated to a particular user profile.
	 * @param m [in] SIP message.
	 * @param tuid [in] Transaction user id.
	 * @param tid [in] Transaction id.
	 */
	void push_user(t_sip_message *m, unsigned short tuid,
		unsigned short tid);

	/**
	 * Create a cancel event for a user.
	 * @param user_config [in] The user profile.
	 * @param m [in] SIP message.
	 * @param tuid [in] Transaction user id.
	 * @param tid [in] Transaction id.
	 */
	void push_user_cancel(t_user *user_config, t_sip_message *m, unsigned short tuid,
		unsigned short tid, unsigned short target_tid);
		
	/**
	 * Create a cancel event for a user.
	 * @param m [in] SIP message.
	 * @param tuid [in] Transaction user id.
	 * @param tid [in] Transaction id.
	 */
	void push_user_cancel(t_sip_message *m, unsigned short tuid,
		unsigned short tid, unsigned short target_tid);

	/**
	 * Create a timeout event and push it into the queue.
	 * @param t [in] The timer that expired.
	 */
	void push_timeout(t_timer *t);

	/**
	 * Create failure event and push it into the queue.
	 * @param f [in] Type of failure.
	 * @param tid [in] Transaction id of failed transaction.
	 */
	void push_failure(t_failure f, unsigned short tid);
	
	/**
	 * Create failure event and push it into the queue.
	 * @param f [in] Type of failure.
	 * @param branch [in] Branch parameter of failed transaction.
	 * @param cseq_method [in] CSeq method of failed transaction.
	 */
	void push_failure(t_failure f, const string &branch, const t_method &cseq_method);

	/**
	 * Create a start timer event.
	 * @param t [in] Timer to start.
	 */
	void push_start_timer(t_timer *t);

	/**
	 * Create a stop timer event.
	 * @param timer_id [in] Timer id of timer to stop.
	 */
	void push_stop_timer(unsigned short timer_id);

	/**
	 * Create an abort transaction event.
	 * @param tid [in] Transaction id of transaction to abort.
	 */
	void push_abort_trans(unsigned short tid);
	
	/**
	 * Create a STUN request event.
	 * @param user_config [in] The user profile associated with the request.
	 * @param m [in] STUN request.
	 * @param ev_type [in] Type of STUN event.
	 * @param tuid [in] Transaction user id.
	 * @param tid [in] Transaction id.
	 * @param ipaddr [in] Destination address (host order)
	 * @param port [in] Destination port (host order)
	 * @param src_port [in] Source port of media. This must only be passed for
	 *   a media STUN event.
	 */
	void push_stun_request(t_user *user_config, StunMessage *m, t_stun_event_type ev_type,
		unsigned short tuid, unsigned short tid,
		unsigned long ipaddr, unsigned short port, unsigned short src_port = 0);
		
	/**
	 * Create a STUN response event.
	 * @param m [in] STUN response.
	 * @param tuid [in] Transaction user id.
	 * @param tid [in] Transaction id.
	 */
	void push_stun_response(StunMessage *m,
		unsigned short tuid, unsigned short tid);
		
	/**
	 * Create a NAT keepalive event.
	 * @param ipaddr [in] Destination address (host order)
	 * @param port [in] Destination port (host order)
	 */
	void push_nat_keepalive(unsigned long ipaddr, unsigned short port);
	
	/**
	 * Create ICMP event.
	 * @param m [in] ICMP message.
	 */
	void push_icmp(const t_icmp_msg &m);
	
	/**
	 * Create a REFER pemission response event.
	 * @param permission [in] Permission allowed?.
	 */
	void push_refer_permission_response(bool permission);
	
	/**
	 * Create a broken connection event.
	 * @param user_uri [in] The user URI (AoR) associated with the connection.
	 */
	void push_broken_connection(const t_url &user_uri);
	
	/**
	 * Create a TCP ping event.
	 * @param user_uri [in] The user URI (AoR) for which the TCP ping must be sent.
	 * @param dst_addr [in] The destination IPv4 address for the ping.
	 * @param dst_port [in] The destination TCP port for the ping.
	 */
	void push_tcp_ping(const t_url &user_uri, unsigned int dst_addr, unsigned short dst_port);

	/**
	 * Pop an event from the queue. 
	 * If the queue is empty then the thread will be blocked until an 
	 * event arrives.
	 * @return The popped event.
	 */
	t_event *pop(void);

	/**
	 * Pop an event from the queue.
	 * Same method as above, but this one can be interrupted by
	 * calling the method interrupt.
	 * @param interrupted [out] When the pop operation is interrupted this
	 * parameter is set to true. Otherwise it is false.
	 * @return NULL, when interrupted.
	 * @return The popped event, otherwise.
	 */
	t_event *pop(bool &interrupted);

	/**
	 * Send an interrupt. 
	 * This will cause the interruptable pop to return.
	 * A non-interruptable pop will ignore the interrupt.
	 * If pop is currently not suspending the thread execution then the
	 * next call to pop will catch the interrupt.
	 */
	void interrupt(void);
};

#endif
