/* 
 * 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 <pjsip-simple/mwi.h>
#include <pjsip-simple/errno.h>
#include <pjsip-simple/evsub_msg.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_dialog.h>
#include <pj/assert.h>
#include <pj/guid.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/string.h>


#define THIS_FILE                   "mwi.c"

 /*
 * MWI module (mod-mdi)
 */
static struct pjsip_module mod_mwi = 
{
    NULL, NULL,                     /* prev, next.                      */
    { "mod-mwi", 7 },               /* Name.                            */
    -1,                             /* Id                               */
    PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority                         */
    NULL,                           /* load()                           */
    NULL,                           /* start()                          */
    NULL,                           /* stop()                           */
    NULL,                           /* unload()                         */
    NULL,                           /* on_rx_request()                  */
    NULL,                           /* on_rx_response()                 */
    NULL,                           /* on_tx_request.                   */
    NULL,                           /* on_tx_response()                 */
    NULL,                           /* on_tsx_state()                   */
};


/*
 * This structure describe an mwi agent (both client and server)
 */
typedef struct pjsip_mwi
{
    pjsip_evsub         *sub;           /**< Event subscribtion record.     */
    pjsip_dialog        *dlg;           /**< The dialog.                    */
    pjsip_evsub_user     user_cb;       /**< The user callback.             */

    /* These are for server subscriptions */
    pj_pool_t           *body_pool;     /**< Pool to save message body      */
    pjsip_media_type     mime_type;     /**< MIME type of last msg body     */
    pj_str_t             body;          /**< Last sent message body         */
} pjsip_mwi;


/*
 * Forward decl for evsub callbacks.
 */
static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
                                    pjsip_event *event);
static void mwi_on_evsub_rx_refresh( pjsip_evsub *sub, 
                                     pjsip_rx_data *rdata,
                                     int *p_st_code,
                                     pj_str_t **p_st_text,
                                     pjsip_hdr *res_hdr,
                                     pjsip_msg_body **p_body);
static void mwi_on_evsub_rx_notify( pjsip_evsub *sub, 
                                    pjsip_rx_data *rdata,
                                    int *p_st_code,
                                    pj_str_t **p_st_text,
                                    pjsip_hdr *res_hdr,
                                    pjsip_msg_body **p_body);
static void mwi_on_evsub_client_refresh(pjsip_evsub *sub);
static void mwi_on_evsub_server_timeout(pjsip_evsub *sub);


/*
 * Event subscription callback for mwi.
 */
static pjsip_evsub_user mwi_user = 
{
    &mwi_on_evsub_state,
    &mwi_on_evsub_tsx_state,
    &mwi_on_evsub_rx_refresh,
    &mwi_on_evsub_rx_notify,
    &mwi_on_evsub_client_refresh,
    &mwi_on_evsub_server_timeout,
};


/*
 * Some static constants.
 */
static const pj_str_t STR_EVENT          = { "Event", 5 };
static const pj_str_t STR_MWI            = { "message-summary", 15 };
static const pj_str_t STR_APP_SIMPLE_SMS = { "application/simple-message-summary", 34};

/*
 * Init mwi module.
 */
PJ_DEF(pj_status_t) pjsip_mwi_init_module( pjsip_endpoint *endpt,
                                           pjsip_module *mod_evsub)
{
    pj_status_t status;
    pj_str_t accept[1];

    /* Check arguments. */
    PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL);

    /* Must have not been registered */
    PJ_ASSERT_RETURN(mod_mwi.id == -1, PJ_EINVALIDOP);

    /* Register to endpoint */
    status = pjsip_endpt_register_module(endpt, &mod_mwi);
    if (status != PJ_SUCCESS)
        return status;

    accept[0] = STR_APP_SIMPLE_SMS;

    /* Register event package to event module. */
    status = pjsip_evsub_register_pkg( &mod_mwi, &STR_MWI, 
                                       PJSIP_MWI_DEFAULT_EXPIRES, 
                                       PJ_ARRAY_SIZE(accept), accept);
    if (status != PJ_SUCCESS) {
        pjsip_endpt_unregister_module(endpt, &mod_mwi);
        return status;
    }

    return PJ_SUCCESS;
}


/*
 * Get mwi module instance.
 */
PJ_DEF(pjsip_module*) pjsip_mwi_instance(void)
{
    return &mod_mwi;
}


/*
 * Create client subscription.
 */
PJ_DEF(pj_status_t) pjsip_mwi_create_uac( pjsip_dialog *dlg,
                                          const pjsip_evsub_user *user_cb,
                                          unsigned options,
                                          pjsip_evsub **p_evsub )
{
    pj_status_t status;
    pjsip_mwi *mwi;
    pjsip_evsub *sub;

    PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);

    PJ_UNUSED_ARG(options);

    pjsip_dlg_inc_lock(dlg);

    /* Create event subscription */
    status = pjsip_evsub_create_uac( dlg,  &mwi_user, &STR_MWI, 
                                     options, &sub);
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Create mwi */
    mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi);
    mwi->dlg = dlg;
    mwi->sub = sub;
    if (user_cb)
        pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user));

    /* Attach to evsub */
    pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi);

    *p_evsub = sub;

on_return:
    pjsip_dlg_dec_lock(dlg);
    return status;
}


/*
 * Create server subscription.
 */
PJ_DEF(pj_status_t) pjsip_mwi_create_uas( pjsip_dialog *dlg,
                                          const pjsip_evsub_user *user_cb,
                                          pjsip_rx_data *rdata,
                                          pjsip_evsub **p_evsub )
{
    pjsip_accept_hdr *accept;
    pjsip_event_hdr *event;
    pjsip_evsub *sub;
    pjsip_mwi *mwi;
    char obj_name[PJ_MAX_OBJ_NAME];
    pj_status_t status;

    /* Check arguments */
    PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);

    /* Must be request message */
    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
                     PJSIP_ENOTREQUESTMSG);

    /* Check that request is SUBSCRIBE */
    PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
                                      &pjsip_subscribe_method)==0,
                     PJSIP_SIMPLE_ENOTSUBSCRIBE);

    /* Check that Event header contains "mwi" */
    event = (pjsip_event_hdr*)
            pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
    if (!event) {
        return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
    }
    if (pj_stricmp(&event->event_type, &STR_MWI) != 0) {
        return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT);
    }

    /* Check that request contains compatible Accept header. */
    accept = (pjsip_accept_hdr*)
             pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
    if (accept) {
        unsigned i;
        for (i=0; i<accept->count; ++i) {
            if (pj_stricmp(&accept->values[i], &STR_APP_SIMPLE_SMS)==0) {
                break;
            }
        }

        if (i==accept->count) {
            /* Nothing is acceptable */
            return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
        }

    } else {
        /* No Accept header. 
         * Assume client supports "application/simple-message-summary" 
        */
    }

    /* Lock dialog */
    pjsip_dlg_inc_lock(dlg);


    /* Create server subscription */
    status = pjsip_evsub_create_uas( dlg, &mwi_user, rdata, 0, &sub);
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Create server mwi subscription */
    mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi);
    mwi->dlg = dlg;
    mwi->sub = sub;
    if (user_cb)
        pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user));

    pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "mwibd%p", dlg->pool);
    mwi->body_pool = pj_pool_create(dlg->pool->factory, obj_name, 
                                    512, 512, NULL);

    /* Attach to evsub */
    pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi);

    /* Done: */
    *p_evsub = sub;

on_return:
    pjsip_dlg_dec_lock(dlg);
    return status;
}


/*
 * Forcefully terminate mwi.
 */
PJ_DEF(pj_status_t) pjsip_mwi_terminate( pjsip_evsub *sub,
                                         pj_bool_t notify )
{
    return pjsip_evsub_terminate(sub, notify);
}

/*
 * Create SUBSCRIBE
 */
PJ_DEF(pj_status_t) pjsip_mwi_initiate( pjsip_evsub *sub,
                                        pj_uint32_t expires,
                                        pjsip_tx_data **p_tdata)
{
    return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires, 
                                p_tdata);
}


/*
 * Accept incoming subscription.
 */
PJ_DEF(pj_status_t) pjsip_mwi_accept( pjsip_evsub *sub,
                                      pjsip_rx_data *rdata,
                                      int st_code,
                                      const pjsip_hdr *hdr_list )
{
    return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
}

/*
 * Create message body and attach it to the (NOTIFY) request.
 */
static pj_status_t mwi_create_msg_body( pjsip_mwi *mwi, 
                                        pjsip_tx_data *tdata)
{
    pjsip_msg_body *body;
    pj_str_t dup_text;

    PJ_ASSERT_RETURN(mwi->mime_type.type.slen && mwi->body.slen, PJ_EINVALIDOP);
    
    /* Clone the message body and mime type */
    pj_strdup(tdata->pool, &dup_text, &mwi->body);

    /* Create the message body */
    body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
    pjsip_media_type_cp(tdata->pool, &body->content_type, &mwi->mime_type);
    body->data = dup_text.ptr;
    body->len = (unsigned)dup_text.slen;
    body->print_body = &pjsip_print_text_body;
    body->clone_data = &pjsip_clone_text_data;

    /* Attach to tdata */
    tdata->msg->body = body;

    return PJ_SUCCESS;
}


/*
 * Create NOTIFY
 */
PJ_DEF(pj_status_t) pjsip_mwi_notify(  pjsip_evsub *sub,
                                       pjsip_evsub_state state,
                                       const pj_str_t *state_str,
                                       const pj_str_t *reason,
                                       const pjsip_media_type *mime_type,
                                       const pj_str_t *body,
                                       pjsip_tx_data **p_tdata)
{
    pjsip_mwi *mwi;
    pjsip_tx_data *tdata;
    pj_status_t status;
    
    /* Check arguments. */
    PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL);

    /* Get the mwi object. */
    mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
    PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP);

    /* Make sure both mime_type & body are-being/have-been set. */
    PJ_ASSERT_RETURN((mime_type && body) ||
                     (!body &&
                      mwi->mime_type.type.slen && mwi->body.slen),
                     PJ_EINVAL);

    /* Lock object. */
    pjsip_dlg_inc_lock(mwi->dlg);

    /* Create the NOTIFY request. */
    status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata);
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Update the cached message body */
    if (mime_type && body) {
        pj_pool_reset(mwi->body_pool);
        pjsip_media_type_cp(mwi->body_pool, &mwi->mime_type, mime_type);
        pj_strdup(mwi->body_pool, &mwi->body, body);
    }

    /* Create message body */
    status = mwi_create_msg_body( mwi, tdata );
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Done. */
    *p_tdata = tdata;

on_return:
    pjsip_dlg_dec_lock(mwi->dlg);
    return status;
}


/*
 * Create NOTIFY that reflect current state.
 */
PJ_DEF(pj_status_t) pjsip_mwi_current_notify( pjsip_evsub *sub,
                                              pjsip_tx_data **p_tdata )
{
    pjsip_mwi *mwi;
    pjsip_tx_data *tdata;
    pj_status_t status;
    
    /* Check arguments. */
    PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL);

    /* Get the mwi object. */
    mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
    PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP);

    /* Lock object. */
    pjsip_dlg_inc_lock(mwi->dlg);

    /* Create the NOTIFY request. */
    status = pjsip_evsub_current_notify( sub, &tdata);
    if (status != PJ_SUCCESS)
        goto on_return;


    /* Create message body to reflect the mwi status. */
    status = mwi_create_msg_body( mwi, tdata );
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Done. */
    *p_tdata = tdata;

on_return:
    pjsip_dlg_dec_lock(mwi->dlg);
    return status;
}


/*
 * Send request.
 */
PJ_DEF(pj_status_t) pjsip_mwi_send_request( pjsip_evsub *sub,
                                             pjsip_tx_data *tdata )
{
    return pjsip_evsub_send_request(sub, tdata);
}

/*
 * This callback is called by event subscription when subscription
 * state has changed.
 */
static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
{
    pjsip_mwi *mwi;

    mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
    PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});

    if (mwi->user_cb.on_evsub_state)
        (*mwi->user_cb.on_evsub_state)(sub, event);

    if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
        if (mwi->body_pool) {
            pj_pool_release(mwi->body_pool);
            mwi->body_pool = NULL;
        }
    }
}

/*
 * Called when transaction state has changed.
 */
static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
                                     pjsip_event *event)
{
    pjsip_mwi *mwi;

    mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
    PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});

    if (mwi->user_cb.on_tsx_state)
        (*mwi->user_cb.on_tsx_state)(sub, tsx, event);
}


/*
 * Called when SUBSCRIBE is received.
 */
static void mwi_on_evsub_rx_refresh( pjsip_evsub *sub, 
                                     pjsip_rx_data *rdata,
                                     int *p_st_code,
                                     pj_str_t **p_st_text,
                                     pjsip_hdr *res_hdr,
                                     pjsip_msg_body **p_body)
{
    pjsip_mwi *mwi;

    mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
    PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});

    if (mwi->user_cb.on_rx_refresh) {
        (*mwi->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
                                       res_hdr, p_body);

    } else {
        /* Implementors MUST send NOTIFY if it implements on_rx_refresh */
        pjsip_tx_data *tdata;
        pj_str_t timeout = { "timeout", 7};
        pj_status_t status;

        if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
            status = pjsip_mwi_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
                                       NULL, &timeout, NULL, NULL, &tdata);
        } else {
            status = pjsip_mwi_current_notify(sub, &tdata);
        }

        if (status == PJ_SUCCESS)
            pjsip_mwi_send_request(sub, tdata);
    }
}


/*
 * Called when NOTIFY is received.
 */
static void mwi_on_evsub_rx_notify( pjsip_evsub *sub, 
                                     pjsip_rx_data *rdata,
                                     int *p_st_code,
                                     pj_str_t **p_st_text,
                                     pjsip_hdr *res_hdr,
                                     pjsip_msg_body **p_body)
{
    pjsip_mwi *mwi;

    mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
    PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});

    /* Just notify application. */
    if (mwi->user_cb.on_rx_notify) {
        (*mwi->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text, 
                                     res_hdr, p_body);
    }
}

/*
 * Called when it's time to send SUBSCRIBE.
 */
static void mwi_on_evsub_client_refresh(pjsip_evsub *sub)
{
    pjsip_mwi *mwi;

    mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
    PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});

    if (mwi->user_cb.on_client_refresh) {
        (*mwi->user_cb.on_client_refresh)(sub);
    } else {
        pj_status_t status;
        pjsip_tx_data *tdata;

        status = pjsip_mwi_initiate(sub, PJSIP_EXPIRES_NOT_SPECIFIED, &tdata);
        if (status == PJ_SUCCESS)
            pjsip_mwi_send_request(sub, tdata);
    }
}

/*
 * Called when no refresh is received after the interval.
 */
static void mwi_on_evsub_server_timeout(pjsip_evsub *sub)
{
    pjsip_mwi *mwi;

    mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
    PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});

    if (mwi->user_cb.on_server_timeout) {
        (*mwi->user_cb.on_server_timeout)(sub);
    } else {
        pj_status_t status;
        pjsip_tx_data *tdata;
        pj_str_t reason = { "timeout", 7 };

        status = pjsip_mwi_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
                                   NULL, &reason, NULL, NULL, &tdata);
        if (status == PJ_SUCCESS)
            pjsip_mwi_send_request(sub, tdata);
    }
}

