/* $Id$ */
/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * 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 
 */
#include <pjsua-lib/pjsua.h>
#include <pjsua-lib/pjsua_internal.h>
//#include <pjmedia/symbian_sound_aps.h>
#include "ua.h"

#define THIS_FILE	"symbian_ua.cpp"
#define CON_LOG_LEVEL	3 // console log level
#define FILE_LOG_LEVEL	4 // logfile log level

//
// Basic config.
//
#define SIP_PORT	5060


//
// Destination URI (to make call, or to subscribe presence)
//
#define SIP_DST_URI	"<sip:100@pjsip.lab>"

//
// Account
//
#define HAS_SIP_ACCOUNT	0	// 1 to enable registration
#define SIP_DOMAIN	"pjsip.lab"
#define SIP_USER	"400"
#define SIP_PASSWD	"400"

//
// Outbound proxy for all accounts
//
#define SIP_PROXY	NULL
//#define SIP_PROXY	"<sip:192.168.0.8;lr>"

//
// SIP transports
//
#define ENABLE_SIP_UDP	1
#define ENABLE_SIP_TCP	0 // experimental
#define ENABLE_SIP_TLS	0 // experimental

//
// Configure nameserver if DNS SRV is to be used with both SIP
// or STUN (for STUN see other settings below)
//
#define NAMESERVER	NULL
//#define NAMESERVER	"192.168.0.2"

//
// STUN server
#if 0
	// Use this to have the STUN server resolved normally
#   define STUN_DOMAIN	NULL
#   define STUN_SERVER	"stun.pjsip.org"
#elif 0
	// Use this to have the STUN server resolved with DNS SRV
#   define STUN_DOMAIN	"pjsip.org"
#   define STUN_SERVER	NULL
#else
	// Use this to disable STUN
#   define STUN_DOMAIN	NULL
#   define STUN_SERVER	NULL
#endif

//
// Use ICE?
//
#define USE_ICE		1

//
// Use SRTP?
//
#define USE_SRTP 	PJSUA_DEFAULT_USE_SRTP

//
// Set QoS on transports? Yes!
// As an example, we set SIP transports DSCP value to CS3 (DSCP
// value 24 or 0x18), for no reason, and tag RTP/RTCP packets 
// with VOICE type.
//
#define SIP_QOS_DSCP	0x18
#define RTP_QOS_TYPE	PJ_QOS_TYPE_VOICE


//
// Globals
//
static pjsua_acc_id g_acc_id = PJSUA_INVALID_ID;
static pjsua_call_id g_call_id = PJSUA_INVALID_ID;
static pjsua_buddy_id g_buddy_id = PJSUA_INVALID_ID;


/* Callback called by the library upon receiving incoming call */
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
			     pjsip_rx_data *rdata)
{
    pjsua_call_info ci;

    PJ_UNUSED_ARG(acc_id);
    PJ_UNUSED_ARG(rdata);

    if (g_call_id != PJSUA_INVALID_ID) {
    	pjsua_call_answer(call_id, PJSIP_SC_BUSY_HERE, NULL, NULL);
    	return;
    }
    
    pjsua_call_get_info(call_id, &ci);

    PJ_LOG(3,(THIS_FILE, "Incoming call from %.*s!!",
			 (int)ci.remote_info.slen,
			 ci.remote_info.ptr));

    g_call_id = call_id;
    
    /* Automatically answer incoming calls with 180/Ringing */
    pjsua_call_answer(call_id, 180, NULL, NULL);
}

/* Callback called by the library when call's state has changed */
static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
{
    pjsua_call_info ci;

    PJ_UNUSED_ARG(e);

    pjsua_call_get_info(call_id, &ci);
    
    if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
    	if (call_id == g_call_id)
    	    g_call_id = PJSUA_INVALID_ID;
    } else if (ci.state != PJSIP_INV_STATE_INCOMING) {
    	if (g_call_id == PJSUA_INVALID_ID)
    	    g_call_id = call_id;
    }
    
    PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", call_id,
			 (int)ci.state_text.slen,
			 ci.state_text.ptr));
}

/* Callback called by the library when call's media state has changed */
static void on_call_media_state(pjsua_call_id call_id)
{
    pjsua_call_info ci;

    pjsua_call_get_info(call_id, &ci);

    if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
	// When media is active, connect call to sound device.
	pjsua_conf_connect(ci.conf_slot, 0);
	pjsua_conf_connect(0, ci.conf_slot);
    }
}


/* Handler on buddy state changed. */
static void on_buddy_state(pjsua_buddy_id buddy_id)
{
    pjsua_buddy_info info;
    pjsua_buddy_get_info(buddy_id, &info);

    PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
	      (int)info.uri.slen,
	      info.uri.ptr,
	      (int)info.status_text.slen,
	      info.status_text.ptr));
}


/* Incoming IM message (i.e. MESSAGE request)!  */
static void on_pager(pjsua_call_id call_id, const pj_str_t *from, 
		     const pj_str_t *to, const pj_str_t *contact,
		     const pj_str_t *mime_type, const pj_str_t *text)
{
    /* Note: call index may be -1 */
    PJ_UNUSED_ARG(call_id);
    PJ_UNUSED_ARG(to);
    PJ_UNUSED_ARG(contact);
    PJ_UNUSED_ARG(mime_type);

    PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s",
	      (int)from->slen, from->ptr,
	      (int)text->slen, text->ptr));
}


/* Received typing indication  */
static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
		      const pj_str_t *to, const pj_str_t *contact,
		      pj_bool_t is_typing)
{
    PJ_UNUSED_ARG(call_id);
    PJ_UNUSED_ARG(to);
    PJ_UNUSED_ARG(contact);

    PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
	      (int)from->slen, from->ptr,
	      (is_typing?"is typing..":"has stopped typing")));
}


/* Call transfer request status. */
static void on_call_transfer_status(pjsua_call_id call_id,
				    int status_code,
				    const pj_str_t *status_text,
				    pj_bool_t final,
				    pj_bool_t *p_cont)
{
    PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s",
	      call_id, status_code,
	      (int)status_text->slen, status_text->ptr,
	      (final ? "[final]" : "")));

    if (status_code/100 == 2) {
	PJ_LOG(3,(THIS_FILE, 
	          "Call %d: call transfered successfully, disconnecting call",
		  call_id));
	pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL);
	*p_cont = PJ_FALSE;
    }
}


/* NAT detection result */
static void on_nat_detect(const pj_stun_nat_detect_result *res) 
{
    if (res->status != PJ_SUCCESS) {
	pjsua_perror(THIS_FILE, "NAT detection failed", res->status);
    } else {
	PJ_LOG(3, (THIS_FILE, "NAT detected as %s", res->nat_type_name));
    }    
}

/* Notification that call is being replaced. */
static void on_call_replaced(pjsua_call_id old_call_id,
			     pjsua_call_id new_call_id)
{
    pjsua_call_info old_ci, new_ci;

    pjsua_call_get_info(old_call_id, &old_ci);
    pjsua_call_get_info(new_call_id, &new_ci);

    PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by "
			 "call %d with %.*s",
			 old_call_id, 
			 (int)old_ci.remote_info.slen, old_ci.remote_info.ptr,
			 new_call_id,
			 (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
}

/*
 * Transport status notification
 */
static void on_transport_state(pjsip_transport *tp, 
                               pjsip_transport_state state,
                               const pjsip_transport_state_info *info)
{
    char host_port[128];

    pj_ansi_snprintf(host_port, sizeof(host_port), "[%.*s:%d]",
		     (int)tp->remote_name.host.slen,
		     tp->remote_name.host.ptr,
		     tp->remote_name.port);

    switch (state) {
    case PJSIP_TP_STATE_CONNECTED:
	{
	    PJ_LOG(3,(THIS_FILE, "SIP transport %s is connected to %s", 
		     tp->type_name, host_port));
	}
	break;

    case PJSIP_TP_STATE_DISCONNECTED:
	{
	    char buf[100];

	    snprintf(buf, sizeof(buf), "SIP transport %s is disconnected from %s",
		     tp->type_name, host_port);
	    pjsua_perror(THIS_FILE, buf, info->status);
	}
	break;

    default:
	break;
    }

#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0

    if (!pj_ansi_stricmp(tp->type_name, "tls") && info->ext_info &&
	state == PJSIP_TP_STATE_CONNECTED) 
    {
	pjsip_tls_state_info *tls_info = (pjsip_tls_state_info*)info->ext_info;
	pj_ssl_sock_info *ssl_sock_info = (pj_ssl_sock_info*)
					  tls_info->ssl_sock_info;
	char buf[2048];

	/* Dump server TLS certificate */
	pj_ssl_cert_info_dump(ssl_sock_info->remote_cert_info, "  ",
			      buf, sizeof(buf));
	PJ_LOG(4,(THIS_FILE, "TLS cert info of %s:\n%s", host_port, buf));
    }

#endif
}


//#include<e32debug.h>

/* Logging callback */
static void log_writer(int level, const char *buf, int len)
{
    static wchar_t buf16[PJ_LOG_MAX_SIZE];

    PJ_UNUSED_ARG(level);
    
    pj_ansi_to_unicode(buf, len, buf16, PJ_ARRAY_SIZE(buf16));

    TPtrC16 aBuf((const TUint16*)buf16, (TInt)len);
    //RDebug::Print(aBuf);
    console->Write(aBuf);
    
}

/*
 * app_startup()
 *
 * url may contain URL to call.
 */
static pj_status_t app_startup()
{
    pj_status_t status;

    /* Redirect log before pjsua_init() */
    pj_log_set_log_func(&log_writer);
    
    /* Set log level */
    pj_log_set_level(CON_LOG_LEVEL);

    /* Create pjsua first! */
    status = pjsua_create();
    if (status != PJ_SUCCESS) {
    	pjsua_perror(THIS_FILE, "pjsua_create() error", status);
    	return status;
    }

    /* Init pjsua */
    pjsua_config cfg;
    pjsua_logging_config log_cfg;
    pjsua_media_config med_cfg;

    pjsua_config_default(&cfg);
    cfg.max_calls = 2;
    cfg.thread_cnt = 0; // Disable threading on Symbian
    cfg.use_srtp = USE_SRTP;
    cfg.srtp_secure_signaling = 0;
    
    cfg.cb.on_incoming_call = &on_incoming_call;
    cfg.cb.on_call_media_state = &on_call_media_state;
    cfg.cb.on_call_state = &on_call_state;
    cfg.cb.on_buddy_state = &on_buddy_state;
    cfg.cb.on_pager = &on_pager;
    cfg.cb.on_typing = &on_typing;
    cfg.cb.on_call_transfer_status = &on_call_transfer_status;
    cfg.cb.on_call_replaced = &on_call_replaced;
    cfg.cb.on_nat_detect = &on_nat_detect;
    cfg.cb.on_transport_state = &on_transport_state;
    
    if (SIP_PROXY) {
	    cfg.outbound_proxy_cnt = 1;
	    cfg.outbound_proxy[0] = pj_str(SIP_PROXY);
    }
    
    if (NAMESERVER) {
	    cfg.nameserver_count = 1;
	    cfg.nameserver[0] = pj_str(NAMESERVER);
    }
    
    if (NAMESERVER && STUN_DOMAIN) {
	    cfg.stun_domain = pj_str(STUN_DOMAIN);
    } else if (STUN_SERVER) {
	    cfg.stun_host = pj_str(STUN_SERVER);
    }
    
    
    pjsua_logging_config_default(&log_cfg);
    log_cfg.level = FILE_LOG_LEVEL;
    log_cfg.console_level = CON_LOG_LEVEL;
    log_cfg.cb = &log_writer;
    log_cfg.log_filename = pj_str("C:\\data\\symbian_ua.log");

    pjsua_media_config_default(&med_cfg);
    med_cfg.thread_cnt = 0; // Disable threading on Symbian
    med_cfg.has_ioqueue = PJ_FALSE;
    med_cfg.clock_rate = 8000;
    med_cfg.audio_frame_ptime = 40;
    med_cfg.ec_tail_len = 0;
    med_cfg.enable_ice = USE_ICE;
    med_cfg.snd_auto_close_time = 0; // wait for 0 seconds idle before sound dev get auto-closed
    //med_cfg.no_vad = PJ_TRUE;
    
    status = pjsua_init(&cfg, &log_cfg, &med_cfg);
    if (status != PJ_SUCCESS) {
	    pjsua_perror(THIS_FILE, "pjsua_init() error", status);
	    pjsua_destroy();
	    return status;
    }
    
    /* Adjust Speex priority and enable only the narrowband */
    {
        pj_str_t codec_id = pj_str("speex/8000");
        pjmedia_codec_mgr_set_codec_priority( 
        	pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
        	&codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);

        codec_id = pj_str("speex/16000");
        pjmedia_codec_mgr_set_codec_priority( 
        	pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
        	&codec_id, PJMEDIA_CODEC_PRIO_DISABLED);

        codec_id = pj_str("speex/32000");
        pjmedia_codec_mgr_set_codec_priority( 
        	pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
        	&codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
    }

    
    pjsua_transport_config tcfg;
    pjsua_transport_id tid;

#if ENABLE_SIP_UDP
    /* Add UDP transport. */
    pjsua_transport_config_default(&tcfg);
    tcfg.port = SIP_PORT;
    if (SIP_QOS_DSCP) {
	tcfg.qos_params.flags |= PJ_QOS_PARAM_HAS_DSCP;
	tcfg.qos_params.dscp_val = SIP_QOS_DSCP;
    }
    status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &tcfg, &tid);
    if (status != PJ_SUCCESS) {
	    pjsua_perror(THIS_FILE, "Error creating UDP transport", status);
	    pjsua_destroy();
	    return status;
    }
#endif
    
#if ENABLE_SIP_TCP
    /* Add TCP transport */
    pjsua_transport_config_default(&tcfg);
    tcfg.port = SIP_PORT;
    if (SIP_QOS_DSCP) {
	tcfg.qos_params.flags |= PJ_QOS_PARAM_HAS_DSCP;
	tcfg.qos_params.dscp_val = SIP_QOS_DSCP;
    }
    status = pjsua_transport_create(PJSIP_TRANSPORT_TCP, &tcfg, &tid);
    if (status != PJ_SUCCESS) {
	    pjsua_perror(THIS_FILE, "Error creating TCP transport", status);
	    pjsua_destroy();
	    return status;
    }
#endif
    
#if ENABLE_SIP_TLS
    /* Add TLS transport */
    pjsua_transport_config_default(&tcfg);
    tcfg.port = SIP_PORT + 1;
    if (SIP_QOS_DSCP) {
	tcfg.qos_params.flags |= PJ_QOS_PARAM_HAS_DSCP;
	tcfg.qos_params.dscp_val = SIP_QOS_DSCP;
	tcfg.tls_setting.qos_params = tcfg.qos_params;
    }
    status = pjsua_transport_create(PJSIP_TRANSPORT_TLS, &tcfg, &tid);
    if (status != PJ_SUCCESS) {
	    pjsua_perror(THIS_FILE, "Error creating TLS transport", status);
	    pjsua_destroy();
	    return status;
    }
#endif
    
    /* Add account for the transport */
    pjsua_acc_add_local(tid, PJ_TRUE, &g_acc_id);

#if DISABLED_FOR_TICKET_1185
    /* Create media transports */
    pjsua_transport_config mtcfg;
    pjsua_transport_config_default(&mtcfg);
    mtcfg.port = 4000;
    mtcfg.qos_type = RTP_QOS_TYPE;
    status = pjsua_media_transports_create(&mtcfg);
    if (status != PJ_SUCCESS) {
    	pjsua_perror(THIS_FILE, "Error creating media transports", status);
    	pjsua_destroy();
    	return status;
    }
#endif
    
    /* Initialization is done, now start pjsua */
    status = pjsua_start();
    if (status != PJ_SUCCESS) {
    	pjsua_perror(THIS_FILE, "Error starting pjsua", status);
    	pjsua_destroy();
    	return status;
    }

    /* Register to SIP server by creating SIP account. */
    if (HAS_SIP_ACCOUNT) {
	pjsua_acc_config cfg;

	pjsua_acc_config_default(&cfg);
	cfg.id = pj_str("sip:" SIP_USER "@" SIP_DOMAIN);
	cfg.reg_uri = pj_str("sip:" SIP_DOMAIN);
	cfg.cred_count = 1;
	cfg.cred_info[0].realm = pj_str("*");
	cfg.cred_info[0].scheme = pj_str("digest");
	cfg.cred_info[0].username = pj_str(SIP_USER);
	cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
	cfg.cred_info[0].data = pj_str(SIP_PASSWD);

	status = pjsua_acc_add(&cfg, PJ_TRUE, &g_acc_id);
	if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "Error adding account", status);
		pjsua_destroy();
		return status;
	}
    }

    if (SIP_DST_URI) {
    	pjsua_buddy_config bcfg;
    
    	pjsua_buddy_config_default(&bcfg);
    	bcfg.uri = pj_str(SIP_DST_URI);
    	bcfg.subscribe = PJ_FALSE;
    	
    	pjsua_buddy_add(&bcfg, &g_buddy_id);
    }
    return PJ_SUCCESS;
}


////////////////////////////////////////////////////////////////////////////
/*
 * The interractive console UI
 */
#include <e32base.h>

class ConsoleUI : public CActive 
{
public:
    ConsoleUI(CConsoleBase *con);
    ~ConsoleUI();

    // Run console UI
    void Run();

    // Stop
    void Stop();
    
protected:
    // Cancel asynchronous read.
    void DoCancel();

    // Implementation: called when read has completed.
    void RunL();
    
private:
    CConsoleBase *con_;
};


ConsoleUI::ConsoleUI(CConsoleBase *con) 
: CActive(EPriorityStandard), con_(con)
{
    CActiveScheduler::Add(this);
}

ConsoleUI::~ConsoleUI() 
{
    Stop();
}

// Run console UI
void ConsoleUI::Run() 
{
    con_->Read(iStatus);
    SetActive();
}

// Stop console UI
void ConsoleUI::Stop() 
{
    Cancel();
}

// Cancel asynchronous read.
void ConsoleUI::DoCancel() 
{
    con_->ReadCancel();
}

static void PrintMainMenu() 
{
    const char *menu =
	    "\n\n"
	    "Main Menu:\n"
	    "  d    Enable/disable codecs\n"
	    "  m    Call " SIP_DST_URI "\n"
	    "  a    Answer call\n"
	    "  g    Hangup all calls\n"
   	    "  t    Toggle audio route\n"
#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0
   	    "  j    Toggle loopback audio\n"
#endif
   	    "up/dn  Increase/decrease output volume\n"
	    "  s    Subscribe " SIP_DST_URI "\n"
	    "  S    Unsubscribe presence\n"
	    "  o    Set account online\n"
	    "  O    Set account offline\n"
	    "  w    Quit\n";
    
    PJ_LOG(3, (THIS_FILE, menu));
}

static void PrintCodecMenu() 
{
    const char *menu = 
	    "\n\n"
	    "Codec Menu:\n"
	    "  a    Enable all codecs\n"
#if PJMEDIA_HAS_PASSTHROUGH_CODECS && PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR
	    "  d    Enable only AMR\n"
#endif
#if PJMEDIA_HAS_PASSTHROUGH_CODECS && PJMEDIA_HAS_PASSTHROUGH_CODEC_G729
	    "  g    Enable only G.729\n"
#endif
#if PJMEDIA_HAS_PASSTHROUGH_CODECS && PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC
	    "  j    Enable only iLBC\n"
#endif
	    "  m    Enable only Speex\n"
	    "  p    Enable only GSM\n"
	    "  t    Enable only PCMU\n"
	    "  w    Enable only PCMA\n";
    
    PJ_LOG(3, (THIS_FILE, menu));
}

static void HandleMainMenu(TKeyCode kc) {
    switch (kc) {
    
    case EKeyUpArrow:
    case EKeyDownArrow:
	{
	    unsigned vol;
	    pj_status_t status;
	    
	    status = pjsua_snd_get_setting(
			     PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, &vol);
	    if (status == PJ_SUCCESS) {
		if (kc == EKeyUpArrow)
		    vol = PJ_MIN(100, vol+10);
		else
		    vol = (vol>=10 ? vol-10 : 0);
		status = pjsua_snd_set_setting(
				    PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
				    &vol, PJ_TRUE);
	    }

	    if (status == PJ_SUCCESS) {
		PJ_LOG(3,(THIS_FILE, "Output volume set to %d", vol));
	    } else {
		pjsua_perror(THIS_FILE, "Error setting volume", status);
	    }
	}
	break;
    
    case 't':
	{
	    pjmedia_aud_dev_route route;
	    pj_status_t status;
	    
	    status = pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, 
					   &route);
	    
	    if (status == PJ_SUCCESS) {
		if (route == PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER)
		    route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
		else
		    route = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;

		status = pjsua_snd_set_setting(
				    PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
				    &route, PJ_TRUE);
	    }

	    if (status != PJ_SUCCESS)
		pjsua_perror(THIS_FILE, "Error switch audio route", status);
	}
	break;
	
    case 'j':
	{
	    static pj_bool_t loopback_active = PJ_FALSE;
	    if (!loopback_active)
		pjsua_conf_connect(0, 0);
	    else
		pjsua_conf_disconnect(0, 0);
	    loopback_active = !loopback_active;
	}
	break;
	
    case 'm':
	if (g_call_id != PJSUA_INVALID_ID) {
		PJ_LOG(3,(THIS_FILE, "Another call is active"));	
		break;
	}

	if (pjsua_verify_url(SIP_DST_URI) == PJ_SUCCESS) {
		pj_str_t dst = pj_str(SIP_DST_URI);
		pjsua_call_make_call(g_acc_id, &dst, 0, NULL,
				     NULL, &g_call_id);
	} else {
		PJ_LOG(3,(THIS_FILE, "Invalid SIP URI"));
	}
	break;
    case 'a':
	if (g_call_id != PJSUA_INVALID_ID)
		pjsua_call_answer(g_call_id, 200, NULL, NULL);
	break;
    case 'g':
	pjsua_call_hangup_all();
	break;
    case 's':
    case 'S':
	if (g_buddy_id != PJSUA_INVALID_ID)
		pjsua_buddy_subscribe_pres(g_buddy_id, kc=='s');
	break;
    case 'o':
    case 'O':
	pjsua_acc_set_online_status(g_acc_id, kc=='o');
	break;
	    
    default:
	PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed", kc, kc));
	break;
    }

    PrintMainMenu();
}

static void HandleCodecMenu(TKeyCode kc) {
    const pj_str_t ID_ALL = {"*", 1};
    pj_str_t codec = {NULL, 0};
    
    if (kc == 'a') {
	pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_NORMAL);
	PJ_LOG(3,(THIS_FILE, "All codecs activated"));
    } else {
	switch (kc) {
	case 'd':
	    codec = pj_str("AMR");
	    break;
	case 'g':
	    codec = pj_str("G729");
	    break;
	case 'j':
	    codec = pj_str("ILBC");
	    break;
	case 'm':
	    codec = pj_str("SPEEX/8000");
	    break;
	case 'p':
	    codec = pj_str("GSM");
	    break;
	case 't':
	    codec = pj_str("PCMU");
	    break;
	case 'w':
	    codec = pj_str("PCMA");
	    break;
	default:
	    PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed", kc, kc));
	    break;
	}

	if (codec.slen) {
	    pj_status_t status;
	    
	    pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_DISABLED);
		
	    status = pjsua_codec_set_priority(&codec, 
					      PJMEDIA_CODEC_PRIO_NORMAL);
	    if (status == PJ_SUCCESS)
		PJ_LOG(3,(THIS_FILE, "%s activated", codec.ptr));
	    else
		PJ_LOG(3,(THIS_FILE, "Failed activating %s, err=%d", 
			  codec.ptr, status));
	}
    }
}

// Implementation: called when read has completed.
void ConsoleUI::RunL() 
{
    enum {
	MENU_TYPE_MAIN = 0,
	MENU_TYPE_CODEC = 1
    };
    static int menu_type = MENU_TYPE_MAIN;
    TKeyCode kc = con_->KeyCode();
    pj_bool_t reschedule = PJ_TRUE;
    
    if (menu_type == MENU_TYPE_MAIN) {
	if (kc == 'w') {
	    CActiveScheduler::Stop();
	    reschedule = PJ_FALSE;
	} else if (kc == 'd') {
	    menu_type = MENU_TYPE_CODEC;
	    PrintCodecMenu();
	} else {
	    HandleMainMenu(kc);
	}
    } else {
	HandleCodecMenu(kc);
	
	menu_type = MENU_TYPE_MAIN;
	PrintMainMenu();
    }
    
    if (reschedule)
	Run();
}

#if 0
// IP networking related testing
static pj_status_t test_addr(void)
{
	int af;
	unsigned i, count;
	pj_addrinfo ai[8];
	pj_sockaddr ifs[8];
	const pj_str_t *hostname;
	pj_hostent he;
	pj_status_t status;
	
	pj_log_set_log_func(&log_writer);
	
	status = pj_init();
	if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "pj_init() error", status);
		return status;
	}
	
	af = pj_AF_INET();
	
#if 0
	pj_in_addr in_addr;
	pj_str_t aa = pj_str("1.1.1.1");
	in_addr = pj_inet_addr(&aa);
	char *the_addr = pj_inet_ntoa(in_addr);
	PJ_LOG(3,(THIS_FILE, "IP addr=%s", the_addr));

	aa = pj_str("192.168.0.15");
	in_addr = pj_inet_addr(&aa);
	the_addr = pj_inet_ntoa(in_addr);
	PJ_LOG(3,(THIS_FILE, "IP addr=%s", the_addr));

	aa = pj_str("2.2.2.2");
	in_addr = pj_inet_addr(&aa);
	the_addr = pj_inet_ntoa(in_addr);
	PJ_LOG(3,(THIS_FILE, "IP addr=%s", the_addr));
	
	return -1;
#endif
	
	// Hostname
	hostname = pj_gethostname();
	if (hostname == NULL) {
		status = PJ_ERESOLVE;
		pjsua_perror(THIS_FILE, "pj_gethostname() error", status);
		goto on_return;
	}
	
	PJ_LOG(3,(THIS_FILE, "Hostname: %.*s", hostname->slen, hostname->ptr));
	
	// Gethostbyname
	status = pj_gethostbyname(hostname, &he);
	if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "pj_gethostbyname() error", status);
	} else {
		PJ_LOG(3,(THIS_FILE, "gethostbyname: %s", 
				  pj_inet_ntoa(*(pj_in_addr*)he.h_addr)));
	}
	
	// Getaddrinfo
	count = PJ_ARRAY_SIZE(ai);
	status = pj_getaddrinfo(af, hostname, &count, ai);
	if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "pj_getaddrinfo() error", status);
	} else {
		for (i=0; i<count; ++i) {
			char ipaddr[PJ_INET6_ADDRSTRLEN+2];
			PJ_LOG(3,(THIS_FILE, "Addrinfo: %s", 
					  pj_sockaddr_print(&ai[i].ai_addr, ipaddr, sizeof(ipaddr), 2)));
		}
	}
	
	// Enum interface
	count = PJ_ARRAY_SIZE(ifs);
	status = pj_enum_ip_interface(af, &count, ifs);
	if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "pj_enum_ip_interface() error", status);
	} else {
		for (i=0; i<count; ++i) {
			char ipaddr[PJ_INET6_ADDRSTRLEN+2];
			PJ_LOG(3,(THIS_FILE, "Interface: %s", 
					  pj_sockaddr_print(&ifs[i], ipaddr, sizeof(ipaddr), 2)));
		}
	}

	// Get default iinterface
	status = pj_getdefaultipinterface(af, &ifs[0]);
	if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "pj_getdefaultipinterface() error", status);
	} else {
		char ipaddr[PJ_INET6_ADDRSTRLEN+2];
		PJ_LOG(3,(THIS_FILE, "Default IP: %s", 
				  pj_sockaddr_print(&ifs[0], ipaddr, sizeof(ipaddr), 2)));
	}
	
	// Get default IP address
	status = pj_gethostip(af, &ifs[0]);
	if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "pj_gethostip() error", status);
	} else {
		char ipaddr[PJ_INET6_ADDRSTRLEN+2];
		PJ_LOG(3,(THIS_FILE, "Host IP: %s", 
				  pj_sockaddr_print(&ifs[0], ipaddr, sizeof(ipaddr), 2)));
	}
	
	status = -1;
	
on_return:
	pj_shutdown();
	return status;
}
#endif


#include <es_sock.h>

#if 0
// Force network connection to use the first IAP, 
// this is useful for debugging on emulator without GUI. 
// Include commdb.lib & apengine.lib in symbian_ua.mmp file
// if this is enabled.

#include <apdatahandler.h>

inline void ForceUseFirstIAP() 
{
    TUint32 rank = 1;
    TUint32 bearers;
    TUint32 prompt;
    TUint32 iap;

    CCommsDatabase* commDb = CCommsDatabase::NewL(EDatabaseTypeIAP);
    CleanupStack::PushL(commDb);

    CApDataHandler* apDataHandler = CApDataHandler::NewLC(*commDb);
    
    TCommDbConnectionDirection direction = ECommDbConnectionDirectionOutgoing;
    apDataHandler->GetPreferredIfDbIapTypeL(rank, direction, bearers, prompt, iap);
    prompt = ECommDbDialogPrefDoNotPrompt;
    apDataHandler->SetPreferredIfDbIapTypeL(rank, direction, bearers, (TCommDbDialogPref)prompt, iap, ETrue);
    CleanupStack::PopAndDestroy(2); // apDataHandler, commDb
}

static void SelectIAP() 
{
    ForceUseFirstIAP();
}

#else

static void SelectIAP() 
{
}

#endif


// Class CConnMon to monitor network connection (RConnection). Whenever
// the connection is down, it will notify PJLIB and restart PJSUA-LIB.
class CConnMon : public CActive {
public:
    static CConnMon* NewL(RConnection &conn, RSocketServ &sserver) {
	CConnMon *self = new (ELeave) CConnMon(conn, sserver);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
    }
    
    void Start() {
	conn_.ProgressNotification(nif_progress_, iStatus);
	SetActive();
    }
    
    void Stop() {
	Cancel();
    }
    
    ~CConnMon() { Stop(); }
    
private:
    CConnMon(RConnection &conn, RSocketServ &sserver) : 
	CActive(EPriorityHigh), 
	conn_(conn), 
	sserver_(sserver)
    {
	CActiveScheduler::Add(this);
    }
    
    void ConstructL() {}

    void DoCancel() {
	conn_.CancelProgressNotification();
    }

    void RunL() {
	int stage = nif_progress_().iStage;
	
	if (stage == KLinkLayerClosed) {
	    pj_status_t status;
	    TInt err;

	    // Tell pjlib that connection is down.
	    pj_symbianos_set_connection_status(PJ_FALSE);
	    
	    PJ_LOG(3, (THIS_FILE, "RConnection closed, restarting PJSUA.."));
	    
	    // Destroy pjsua
	    pjsua_destroy();
	    PJ_LOG(3, (THIS_FILE, "PJSUA destroyed."));

	    // Reopen the connection
	    err = conn_.Open(sserver_);
	    if (err == KErrNone)
		err = conn_.Start();
	    if (err != KErrNone) {
		CActiveScheduler::Stop();
		return;
	    }

	    // Reinit Symbian OS param before pj_init()
	    pj_symbianos_params sym_params;
	    pj_bzero(&sym_params, sizeof(sym_params));
	    sym_params.rsocketserv = &sserver_;
	    sym_params.rconnection = &conn_;
	    pj_symbianos_set_params(&sym_params);

	    // Reinit pjsua
	    status = app_startup();
	    if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "app_startup() error", status);
		CActiveScheduler::Stop();
		return;
	    }
	    
	    PJ_LOG(3, (THIS_FILE, "PJSUA restarted."));
	    PrintMainMenu();
	}
	
	Start();
    }
    
    RConnection& conn_;
    RSocketServ& sserver_;
    TNifProgressBuf nif_progress_;
};

////////////////////////////////////////////////////////////////////////////
int ua_main() 
{
    RSocketServ aSocketServer;
    RConnection aConn;
    TInt err;
    pj_symbianos_params sym_params;
    pj_status_t status;

    SelectIAP();
    
    // Initialize RSocketServ
    if ((err=aSocketServer.Connect(32)) != KErrNone)
    	return PJ_STATUS_FROM_OS(err);
    
    // Open up a connection
    if ((err=aConn.Open(aSocketServer)) != KErrNone) {
	aSocketServer.Close();
	return PJ_STATUS_FROM_OS(err);
    }
    
    if ((err=aConn.Start()) != KErrNone) {
    	aSocketServer.Close();
    	return PJ_STATUS_FROM_OS(err);
    }
    
    // Set Symbian OS parameters in pjlib.
    // This must be done before pj_init() is called.
    pj_bzero(&sym_params, sizeof(sym_params));
    sym_params.rsocketserv = &aSocketServer;
    sym_params.rconnection = &aConn;
    pj_symbianos_set_params(&sym_params);
    
    // Initialize pjsua
    status  = app_startup();
    //status = test_addr();
    if (status != PJ_SUCCESS) {
    	aConn.Close();
    	aSocketServer.Close();
	return status;
    }

    
    // Run the UI
    ConsoleUI *con = new ConsoleUI(console);
    
    con->Run();
    PrintMainMenu();

    // Init & start connection monitor
    CConnMon *connmon = CConnMon::NewL(aConn, aSocketServer);
    connmon->Start();

    CActiveScheduler::Start();
    
    delete connmon;
    delete con;

    // Dump memory statistics
    PJ_LOG(3,(THIS_FILE, "Max heap usage: %u.%03uMB",
	      pjsua_var.cp.peak_used_size / 1000000,
	      (pjsua_var.cp.peak_used_size % 1000000)/1000));
    
    // check max stack usage
#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK!=0
	pj_thread_t* this_thread = pj_thread_this();
	if (!this_thread)
	    return status;
	
	const char* max_stack_file;
	int max_stack_line;
	status = pj_thread_get_stack_info(this_thread, &max_stack_file, &max_stack_line);
	
	PJ_LOG(3,(THIS_FILE, "Max stack usage: %u at %s:%d", 
		  pj_thread_get_stack_max_usage(this_thread), 
		  max_stack_file, max_stack_line));
#endif
	
    // Shutdown pjsua
    pjsua_destroy();
    
    // Close connection and socket server
    aConn.Close();
    aSocketServer.Close();
    
    return status;
}

