/*
    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, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// State of active phone user

#ifndef _PHONE_USER_H
#define _PHONE_USER_H

#include <string>
#include <list>
#include "auth.h"
#include "protocol.h"
#include "service.h"
#include "transaction_layer.h"
#include "user.h"
#include "im/msg_session.h"
#include "mwi/mwi.h"
#include "mwi/mwi_dialog.h"
#include "parser/request.h"
#include "parser/response.h"
#include "stun/stun.h"
#include "presence/buddy.h"

using namespace std;
using namespace im;

// Forward declarations
class t_client_request;
class t_presence_epa;

class t_phone_user {
private:
	t_user			*user_config;

	// State
	// Indicates that this user is active. A non-active user
	// is not removed from the user list as some transactions may
	// still need the user info after the user got de-activated.
	bool			active;

	// Requests outside a dialog
	t_client_request	*r_options;
	t_client_request	*r_register;
	t_client_request	*r_deregister;
	t_client_request	*r_query_register;
	t_client_request	*r_message;
	
	// STUN request
	t_client_request	*r_stun;
	
	/**
	 * Pending MESSAGE requests.
	 * Twinkle will only send one message at a time per user.
	 * This satisfies the requirements in RFC 3428 8.
	 * NOTE: as an optimization a message queue per destination
	 * could be kept. This way a pending message for one destination
	 * will not block a message for another destination.
	 */
	list<t_request *> pending_messages;

	/** @name Registration data */
	//@{
	string			register_call_id;  /**< Call-ID for REGISTER requests. */
	unsigned long		register_seqnr;    /**< Last seqnr issued. */
	bool			is_registered;     /**< Indicates if user is registered. */
	unsigned long		registration_time; /**< Expiration in seconds */
	bool			last_reg_failed;   /** Indicates if last registration failed. */
	
	/** Destination of last REGISTER */
	t_ip_port		register_ip_port;
	
	/** Service Route, collected from REGISTER responses */
	list<t_route>		service_route;
	//@}
	
	// A STUN request can be triggered by the following events:
	//
	// * Registration
	// * MWI subscription.
	//
	// These events should take place after the STUN transaction has
	// finished. The following indicators indicate which events should
	// take place.
	bool		register_after_stun;
	bool		mwi_subscribe_after_stun;
	bool		stun_binding_inuse_registration;
	bool		stun_binding_inuse_mwi;
	
	// Authorizor
	t_auth			authorizor;
	
	// MWI dialog
	t_mwi_dialog		*mwi_dialog;
	
	/**
	 * Indicates if MWI must be automatically resubscribed to, if the
	 * subscription terminates with a reason telling that resubscription
	 * is possible.
	 */
	bool			mwi_auto_resubscribe;
	
	/** Buddy list for presence susbcriptions. */
	t_buddy_list		*buddy_list;
	
	/** Event publication agent for presence */
	t_presence_epa		*presence_epa;
	
	/**
	 * Resend the request: a new sequence number will be assigned and a new via
	 * header created (new transaction).
	 * @param req [in] The request to resend.
	 * @param is_register [in] indicates if this request is a register
	 * @param cr [in] is the current client request wrapper for this request.
	 * @note In case of a REGISTER, the internal register seqnr will be increased.
	 */
	void resend_request(t_request *req, bool is_register, t_client_request *cr);

	/** @name Handle responses. */
	//@{
	/**
	 * Process a repsonse on a registration request. 
	 * @param r [in] The response.
	 * @param re_register [out] Indicates if an automatic re-registration needs
	 * to be done.
	 */
	void handle_response_register(t_response *r, bool &re_register);
	
	/**
	 * Process a response on a de-registration request.
	 * @param r [in] The response.
	 */
	void handle_response_deregister(t_response *r);
	
	/**
	 * Process a response on a registration query request.
	 * @param r [in] The response.
	 */
	void handle_response_query_register(t_response *r);

	/**
	 * Process a response on an OPTIONS request.
	 * @param r [in] The response.
	 */
	void handle_response_options(t_response *r);
	
	/**
	 * Process a response on a MESSAGE request.
	 * @param r [in] The response.
	 */
	void handle_response_message(t_response *r);
	//@}
	
	/** Send a NAT keep alive packet. */
	void send_nat_keepalive(void);
	
	/** Send a TCP ping packet. */
	void send_tcp_ping(void);
	
	/** Handle MWI dialog termination. */
	void cleanup_mwi_dialog(void);
	
	/** Cleanup registration data for STUN and NAT keep alive. */
	void cleanup_registration_data(void);
	
public:
	/** @name Timers */
	//@{
	unsigned short		id_registration;	/**< Registration timeout */
	unsigned short		id_nat_keepalive;	/**< NAT keepalive interval */
	unsigned short		id_tcp_ping;		/**< TCP ping timer */
	unsigned short		id_resubscribe_mwi; 	/**< MWI re-subscribe after failure */
	//@}
	
	/** Supplementary services */
	t_service	*service;
	
	/** Message Waiting Indication data. */
	t_mwi		mwi;
	
	/** @name STUN data */
	//@{
	bool		use_stun; 		/**< Indicates if STUN must be used. */
	bool		use_nat_keepalive; 	/**< Send NAT keepalive ? */
	unsigned long	stun_public_ip_sip; 	/**< Public IP for SIP */
	unsigned short	stun_public_port_sip; 	/**< Public port for SIP */
	
	/** Number of presence subscriptions using the STUN binding. */
	unsigned short	stun_binding_inuse_presence;	
	
	/**< Subscribe to presence after STUN transaction completed. */
	bool		presence_subscribe_after_stun;	
	//@}
	
	/** 
	 * The constructor will create a copy of profile.
	 * @param profile [in] User profile of this phone user.
	 */
	t_phone_user(const t_user &profile);
	
	/** Destructor. */
	~t_phone_user();
	
	/** Send STUN request. */
	void send_stun_request(void);
	
	/** Cleanup STUN data if not in use anymore. */
	void cleanup_stun_data(void);
	
	/** Stop sending NAT keep alives when not necessary anymore. */
	void cleanup_nat_keepalive(void);
	
	/** 
	 * Synchronize the sending of NAT keep alives with the user config.
	 * Start sending if keep alives are enabled but currently not being
	 * sent.
	 */
	void sync_nat_keepalive(void);
	
	/** Stop sending TCP ping packets when not necessary anumore. */
	void cleanup_tcp_ping(void);
	
	/** @name Getters */
	//@{
	t_user *get_user_profile(void);
	t_buddy_list *get_buddy_list(void);
	t_presence_epa *get_presence_epa(void);
	//@}
	
	// Handle responses for out-of-dialog requests
	void handle_response_out_of_dialog(t_response *r, t_tuid tuid, t_tid tid);
	void handle_response_out_of_dialog(StunMessage *r, t_tuid tuid);
	
	/**
	 * Send a registration, de-registration or query registration request.
	 * @param register_type [in] Type of registration request.
	 * @param re_register [in] Indicates if this registration request is a re-registration.
	 * @param expires [in] Epxiry time to put in registration request.
	 * @note If needed a STUN request is sent before doing a registration.
	 */
	void registration(t_register_type register_type, bool re_register,
					unsigned long expires = 0);
					
	// OPTIONS outside dialog
	void options(const t_url &to_uri, const string &to_display = "");
	
	/** @name MWI */
	//@{
	/** Subscribe to MWI. */
	void subscribe_mwi(void);
	
	/** Unsusbcribe to MWI. */
	void unsubscribe_mwi(void);
	
	/**
	 * Check if an MWI subscription is established.
	 * @return true, if an MWI subscription is established.
	 * @return false, otherwise
	 */
	bool is_mwi_subscribed(void) const;
	
	/**
	 * Check if an MWI dialog does exist.
	 * @return true, if there is no MWI subscription dialog.
	 * @return false, otherwise
	 */
	bool is_mwi_terminated(void) const;
	
	/**
	 * Process an unsollicited NOTIFY for MWI.
	 * @param r [in] The NOTIFY request.
	 * @param tid [in] Transaction identifier of the NOTIFY transaction.
	 */
	void handle_mwi_unsollicited(t_request *r, t_tid tid);
	//@}
	
	/** @name Presence */
	//@{
	/** Subscribe to presence of buddies in buddy list. */
	void subscribe_presence(void);
	
	/** Unsusbcribe to presence of buddies in buddy list. */
	void unsubscribe_presence(void);
	
	/**
	 * Check if all presence subscriptions are terminated.
	 * @return true, if all presence subscriptions are terminated.
	 * @return false, otherwise
	 */
	bool is_presence_terminated(void) const;
	
	/**
	 * Publish presence.
	 * @param basic_state [in] The basic presence state to publish.
	 */
	void publish_presence(t_presence_state::t_basic_state basic_state);
	
	/** Unpublish presence. */
	void unpublish_presence(void);
	//@}
	
	/**
	 * Send a text message.
	 * @param to_uri [in] Destination URI of recipient.
	 * @param to_display [in] Display name of recipient.
	 * @param msg [in] The message to send.
	 * @return True if sending succeeded, otherwise false.
	 */
	bool send_message(const t_url &to_uri, const string &to_display, const t_msg &message);
	
	/**
	 * @param to_uri [in] Destination URI of recipient.
	 * @param to_display [in] Display name of recipient.
	 * @param state [in] Message composing state.
	 * @param refresh [in] The refresh interval in seconds (when state is active).
	 * @return True if sending succeeded, false otherwise.
	 * @note For the idle state, the value of refresh has no meaning.
	 */
	bool send_im_iscomposing(const t_url &to_uri, const string &to_display, 
			const string &state, time_t refresh);
	
	/**
	 * Process incoming MESSAGE request.
	 * @param r [in] The MESSAGE request.
	 * @param tid [in] Transaction id of the request transaction.
	 */
	void recvd_message(t_request *r, t_tid tid);
	
	/**
	 * Process incoming NOTIFY reqeust.
	 * @param r [in] The NOTIFY request.
	 * @param tid [in] Transaction id of the request transaction.
	 */
	void recvd_notify(t_request *r, t_tid tid);
	
	/** @name Process timeouts */
	//@{
	/** 
	 * Process phone timer expiry.
	 * @param timer [in] Type of expired phone timer.
	 */
	void timeout(t_phone_timer timer);
	
	/**
	 * Proces subscribe timer expiry.
	 * @param timer [in] Type of expired subscribe timer.
	 * @param id_timer [in] Id of expired timer.
	 */
	void timeout_sub(t_subscribe_timer timer, t_object_id id_timer);
	
	/**
	 * Proces publish timer expiry.
	 * @param timer [in] Type of expired subscribe timer.
	 * @param id_timer [in] Id of expired timer.
	 */
	void timeout_publish(t_publish_timer timer, t_object_id id_timer);
	//@}
	
	/** Handle a broken persistent connection. */
	void handle_broken_connection(void);
	
	/** Match subscribe timeout with a subcription
	 * @param timer [in] Type of expired subscribe timer.
	 * @param id_timer [in] Id of expired timer.
	 * @return True if timer matches a subscription owned by the phone user.
	 * @return False, otherwise.
	 */
	bool match_subscribe_timer(t_subscribe_timer timer, t_object_id id_timer) const;
	
	/** Match publish timeout with a subcription
	 * @param timer [in] Type of expired publish timer.
	 * @param id_timer [in] Id of expired timer.
	 * @return True if timer matches a publication owned by the phone user.
	 * @return False, otherwise.
	 */
	bool match_publish_timer(t_publish_timer timer, t_object_id id_timer) const;
	
	/**
	 * Start the re-subscribe timer after an MWI subscription failure.
	 * @param duration [in] Duration before trying a re-subscribe (s)
	 */
	void start_resubscribe_mwi_timer(unsigned long duration);
	
	/** Stop MWI re=subscribe timer. */
	void stop_resubscribe_mwi_timer(void);
	
	/**
	 * Create request. 
	 * Headers that are the same for each request
	 * are already populated: Via, From, Max-Forwards, User-Agent.
	 * All possible destinations for a failover are calculated.
	 * @param m [in] Request method.
	 * @param request_uri [in] Request-URI.
	 * @return The created request.
	 */
	t_request *create_request(t_method m, const t_url &request_uri) const;
	
	// Create a response to an OPTIONS request
	// Argument 'in-dialog' indicates if the OPTIONS response is
	// sent within a dialog.
	t_response *create_options_response(t_request *r,
					bool in_dialog = false) const;
	
	// Get registration status
	bool get_is_registered(void) const;
	bool get_last_reg_failed(void) const;
	
	/**
	 * Get local IP address for SIP.
	 * @param auto_ip [in] IP address to use if no IP address has been determined through
	 *                     some NAT procedure.
	 * @return The IP address.
	 */
	string get_ip_sip(const string &auto_ip) const;
	
	/**
	 * Get local port for SIP.
	 * @return SIP port.
	 */ 
	unsigned short get_public_port_sip(void) const;
	
	/** 
	 * Get the service route.
	 * @return The service route.
	 */
	list<t_route> get_service_route(void) const;

	// Try to match message with phone user
	bool match(t_response *r, t_tuid tuid) const;
	bool match(t_request *r) const;
	bool match(StunMessage *r, t_tuid tuid) const;
	
	/**
	 * Authorize the request based on the challenge in the response
	 * @param r [inout] The request to be authorized.
	 * @param resp [in] The response containing the challenge (401/407).
	 * @param True if authorization succeeds, false otherwise.
	 * @post On successful return the request r contains the correct authorization
	 * header (based on 401/407 response).
	 */
	bool authorize(t_request *r, t_response *resp);
	
	/**
	 * Resend the request: a new sequence number will be assigned and a new via
	 * header created (new transaction).
	 * @param req [in] The request to resend.
	 * @param cr [in] is the current client request wrapper for this request.
	 * @note In case of a REGISTER, the internal register seqnr will be increased.
	 */
	void resend_request(t_request *req, t_client_request *cr);
	
	/**
	 * Remove cached credentials for a particular realm.
	 * @param realm [in] The realm.
	 */
	void remove_cached_credentials(const string &realm);
	
	/**
	 * Check if this phone user is active.
	 * @return True if phone user is active, false otherwise.
	 */
	bool is_active(void) const;
	
	/**
	 * Activate phone user.
	 * @param user [in] The user profile of the user.
	 * @note The passed user profile will replace the current user profile
	 * owned by phone user. During the deactivated state the profile may
	 * have been update.
	 */
	void activate(const t_user &user);
	
	/** Deactivate phone user. */
	void deactivate(void);
};

#endif
