/* $Id: call.cpp 4885 2014-08-12 12:12:49Z nanang $ */
/*
 * Copyright (C) 2012-2013 Teluu Inc. (http://www.teluu.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
 */
#include <pjsua2/account.hpp>
#include <pjsua2/call.hpp>
#include <pjsua2/endpoint.hpp>
#include <pj/ctype.h>
#include "util.hpp"

using namespace pj;
using namespace std;

#define THIS_FILE		"call.cpp"

///////////////////////////////////////////////////////////////////////////////

#define SDP_BUFFER_SIZE 1024

MathStat::MathStat()
: n(0), max(0), min(0), last(0), mean(0)
{
}

void MathStat::fromPj(const pj_math_stat &prm)
{
    this->n    = prm.n;
    this->max  = prm.max;
    this->min  = prm.min;
    this->last = prm.last;
    this->mean = prm.mean;
}

void RtcpStreamStat::fromPj(const pjmedia_rtcp_stream_stat &prm)
{
    this->update.fromPj(prm.update);
    this->updateCount     = prm.update_cnt;
    this->pkt             = (unsigned)prm.pkt;
    this->bytes           = (unsigned)prm.bytes;
    this->discard         = prm.discard;
    this->loss            = prm.loss;
    this->reorder         = prm.loss;
    this->dup             = prm.dup;
    this->lossPeriodUsec.fromPj(prm.loss_period);
    this->lossType.burst  = prm.loss_type.burst;
    this->lossType.random = prm.loss_type.random;
    this->jitterUsec.fromPj(prm.jitter);
}

void RtcpSdes::fromPj(const pjmedia_rtcp_sdes &prm)
{
    this->cname = pj2Str(prm.cname);
    this->name  = pj2Str(prm.name);
    this->email = pj2Str(prm.email);
    this->phone = pj2Str(prm.phone);
    this->loc   = pj2Str(prm.loc);
    this->tool  = pj2Str(prm.tool);
    this->note  = pj2Str(prm.note);
}

void RtcpStat::fromPj(const pjmedia_rtcp_stat &prm)
{
    this->start.fromPj(prm.start);
    this->txStat.fromPj(prm.tx);
    this->rxStat.fromPj(prm.rx);
    this->rttUsec.fromPj(prm.rtt);
    this->rtpTxLastTs  = prm.rtp_tx_last_ts;
    this->rtpTxLastSeq = prm.rtp_tx_last_seq;
#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
    this->rxIpdvUsec.fromPj(prm.rx_ipdv);
#endif
#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && \
    PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
    this->rxRawJitterUsec.fromPj(prm.rx_raw_jitter);
#endif
    this->peerSdes.fromPj(prm.peer_sdes);
}

void JbufState::fromPj(const pjmedia_jb_state &prm)
{
    this->frameSize    = prm.frame_size;
    this->minPrefetch  = prm.min_prefetch;
    this->maxPrefetch  = prm.max_prefetch;
    this->burst        = prm.burst;
    this->prefetch     = prm.prefetch;
    this->size         = prm.size;
    this->avgDelayMsec = prm.avg_delay;
    this->minDelayMsec = prm.min_delay;
    this->maxDelayMsec = prm.max_delay;
    this->devDelayMsec = prm.dev_delay;
    this->avgBurst     = prm.avg_burst;
    this->lost         = prm.lost;
    this->discard      = prm.discard;
    this->empty        = prm.empty;
}

void SdpSession::fromPj(const pjmedia_sdp_session &sdp)
{
    char buf[SDP_BUFFER_SIZE];
    int len;

    len = pjmedia_sdp_print(&sdp, buf, sizeof(buf));
    wholeSdp = (len > -1? string(buf, len): "");
    pjSdpSession = (void *)&sdp;
}

void MediaEvent::fromPj(const pjmedia_event &ev)
{
    type = ev.type;
    if (type == PJMEDIA_EVENT_FMT_CHANGED) {
        data.fmtChanged.newWidth = ev.data.fmt_changed.new_fmt.det.vid.size.w;
        data.fmtChanged.newHeight = ev.data.fmt_changed.new_fmt.det.vid.size.h;
    }
    pjMediaEvent = (void *)&ev;
}

void MediaTransportInfo::fromPj(const pjmedia_transport_info &info)
{
    char straddr[PJ_INET6_ADDRSTRLEN+10];
    
    pj_sockaddr_print(&info.src_rtp_name, straddr, sizeof(straddr), 3);
    srcRtpName = straddr;
    pj_sockaddr_print(&info.src_rtcp_name, straddr, sizeof(straddr), 3);
    srcRtcpName = straddr;
}

//////////////////////////////////////////////////////////////////////////////

/* Call Audio Media. */
class CallAudioMedia : public AudioMedia
{
public:
    /*
     * Set the conference port identification associated with the
     * call audio media.
     */
    void setPortId(int id);
};


void CallAudioMedia::setPortId(int id)
{
    this->id = id;
}

CallOpParam::CallOpParam(bool useDefaultCallSetting)
: statusCode(pjsip_status_code(0)), reason(""), options(0)
{
    if (useDefaultCallSetting)
        opt = CallSetting(true);
}

CallSendRequestParam::CallSendRequestParam()
: method("")
{
}

CallVidSetStreamParam::CallVidSetStreamParam()
{
#if PJSUA_HAS_VIDEO
    pjsua_call_vid_strm_op_param prm;
    
    pjsua_call_vid_strm_op_param_default(&prm);
    this->medIdx = prm.med_idx;
    this->dir    = prm.dir;
    this->capDev = prm.cap_dev;
#endif
}

CallSetting::CallSetting(pj_bool_t useDefaultValues)
{
    if (useDefaultValues) {
        pjsua_call_setting setting;
    
        pjsua_call_setting_default(&setting);
        fromPj(setting);
    } else {
        flag                = 0;
        reqKeyframeMethod   = 0;
        audioCount          = 0;
        videoCount          = 0;
    }
}

bool CallSetting::isEmpty() const
{
    return (flag == 0 && reqKeyframeMethod == 0 && audioCount == 0 &&
            videoCount == 0);
}

void CallSetting::fromPj(const pjsua_call_setting &prm)
{
    this->flag              = prm.flag;
    this->reqKeyframeMethod = prm.req_keyframe_method;
    this->audioCount        = prm.aud_cnt;
    this->videoCount        = prm.vid_cnt;
}

pjsua_call_setting CallSetting::toPj() const
{
    pjsua_call_setting setting;

    setting.flag                = this->flag;
    setting.req_keyframe_method = this->reqKeyframeMethod;
    setting.aud_cnt             = this->audioCount;
    setting.vid_cnt             = this->videoCount;
    
    return setting;
}


CallMediaInfo::CallMediaInfo()
{
}

void CallMediaInfo::fromPj(const pjsua_call_media_info &prm)
{
    this->index                     = prm.index;
    this->type                      = prm.type;
    this->dir                       = prm.dir;
    this->status                    = prm.status;
    if (this->type == PJMEDIA_TYPE_AUDIO) {
        this->audioConfSlot         = (int)prm.stream.aud.conf_slot;
    } else if (this->type == PJMEDIA_TYPE_VIDEO) {
        this->videoIncomingWindowId = prm.stream.vid.win_in;
        this->videoCapDev           = prm.stream.vid.cap_dev;
    }
}

void CallInfo::fromPj(const pjsua_call_info &pci)
{
    unsigned mi;
    
    id 			= pci.id;
    role                = pci.role;
    accId               = pci.acc_id;
    localUri            = pj2Str(pci.local_info);
    localContact        = pj2Str(pci.local_contact);
    remoteUri           = pj2Str(pci.remote_info);
    remoteContact       = pj2Str(pci.remote_contact);
    callIdString        = pj2Str(pci.call_id);
    setting.fromPj(pci.setting);
    state               = pci.state;
    stateText           = pj2Str(pci.state_text);
    lastStatusCode      = pci.last_status;
    lastReason          = pj2Str(pci.last_status_text);
    connectDuration.fromPj(pci.connect_duration);
    totalDuration.fromPj(pci.total_duration);
    remOfferer          = PJ2BOOL(pci.rem_offerer);
    remAudioCount       = pci.rem_aud_cnt;
    remVideoCount       = pci.rem_vid_cnt;
    
    for (mi = 0; mi < pci.media_cnt; mi++) {
        CallMediaInfo med;
        
        med.fromPj(pci.media[mi]);
        media.push_back(med);
    }
    for (mi = 0; mi < pci.prov_media_cnt; mi++) {
        CallMediaInfo med;
        
        med.fromPj(pci.prov_media[mi]);
        provMedia.push_back(med);
    }
}

void StreamInfo::fromPj(const pjsua_stream_info &info)
{
    char straddr[PJ_INET6_ADDRSTRLEN+10];

    type = info.type;
    if (type == PJMEDIA_TYPE_AUDIO) {
        proto = info.info.aud.proto;
        dir = info.info.aud.dir;
        pj_sockaddr_print(&info.info.aud.rem_addr, straddr, sizeof(straddr), 3);
        remoteRtpAddress = straddr;
        pj_sockaddr_print(&info.info.aud.rem_rtcp, straddr, sizeof(straddr), 3);
        remoteRtcpAddress = straddr;
        txPt = info.info.aud.tx_pt;
        rxPt = info.info.aud.rx_pt;
        codecName = pj2Str(info.info.aud.fmt.encoding_name);
        codecClockRate = info.info.aud.fmt.clock_rate;
        codecParam = info.info.aud.param;
    } else if (type == PJMEDIA_TYPE_VIDEO) {
        proto = info.info.vid.proto;
        dir = info.info.vid.dir;
        pj_sockaddr_print(&info.info.vid.rem_addr, straddr, sizeof(straddr), 3);
        remoteRtpAddress = straddr;
        pj_sockaddr_print(&info.info.vid.rem_rtcp, straddr, sizeof(straddr), 3);
        remoteRtcpAddress = straddr;
        txPt = info.info.vid.tx_pt;
        rxPt = info.info.vid.rx_pt;
        codecName = pj2Str(info.info.vid.codec_info.encoding_name);
        codecClockRate = info.info.vid.codec_info.clock_rate;
        codecParam = info.info.vid.codec_param;
    }
}

void StreamStat::fromPj(const pjsua_stream_stat &prm)
{
    rtcp.fromPj(prm.rtcp);
    jbuf.fromPj(prm.jbuf);
}

///////////////////////////////////////////////////////////////////////////////

struct call_param
{
    pjsua_msg_data      msg_data;
    pjsua_msg_data     *p_msg_data;
    pjsua_call_setting  opt;
    pjsua_call_setting *p_opt;
    pj_str_t            reason;
    pj_str_t           *p_reason;

public:
    /**
     * Default constructors with specified parameters.
     */
    call_param(const SipTxOption &tx_option);
    call_param(const SipTxOption &tx_option, const CallSetting &setting,
               const string &reason_str);
};

call_param::call_param(const SipTxOption &tx_option)
{
    if (tx_option.isEmpty()) {
        p_msg_data = NULL;
    } else {
        tx_option.toPj(msg_data);
        p_msg_data = &msg_data;
    }
    
    p_opt = NULL;
    p_reason = NULL;
}

call_param::call_param(const SipTxOption &tx_option, const CallSetting &setting,
                       const string &reason_str)
{
    if (tx_option.isEmpty()) {
        p_msg_data = NULL;
    } else {
        tx_option.toPj(msg_data);
        p_msg_data = &msg_data;
    }
    
    if (setting.isEmpty()) {
        p_opt = NULL;
    } else {
        opt = setting.toPj();
        p_opt = &opt;
    }
    
    reason = str2Pj(reason_str);
    p_reason = (reason.slen == 0? NULL: &reason);
}

Call::Call(Account& account, int call_id)
: acc(account), id(call_id)
{
    if (call_id != PJSUA_INVALID_ID)
        pjsua_call_set_user_data(call_id, this);
}

Call::~Call()
{
    /* Remove reference to this instance from PJSUA library */
    if (id != PJSUA_INVALID_ID)
	pjsua_call_set_user_data(id, NULL);

    /*
     * If this instance is deleted, also hangup the corresponding call in
     * PJSUA library.
     */
    if (pjsua_get_state() < PJSUA_STATE_CLOSING && isActive()) {
        CallOpParam prm;
        hangup(prm);
    }
}

CallInfo Call::getInfo() const throw(Error)
{
    pjsua_call_info pj_ci;
    CallInfo ci;
    
    PJSUA2_CHECK_EXPR( pjsua_call_get_info(id, &pj_ci) );
    ci.fromPj(pj_ci);
    return ci;
}

bool Call::isActive() const
{
    if (id == PJSUA_INVALID_ID)
        return false;
    
    return (pjsua_call_is_active(id) != 0);
}

int Call::getId() const
{
    return id;
}

Call *Call::lookup(int call_id)
{
    Call *call = (Call*)pjsua_call_get_user_data(call_id);
    if (call)
        call->id = call_id;
    return call;
}

bool Call::hasMedia() const
{
    return (pjsua_call_has_media(id) != 0);
}

Media *Call::getMedia(unsigned med_idx) const
{
    /* Check if the media index is valid and if the media has a valid port ID */
    if (med_idx >= medias.size() ||
        (medias[med_idx] && medias[med_idx]->getType() == PJMEDIA_TYPE_AUDIO &&
         ((AudioMedia *)medias[med_idx])->getPortId() == PJSUA_INVALID_ID))
    {
        return NULL;
    }
    
    return medias[med_idx];
}

pjsip_dialog_cap_status Call::remoteHasCap(int htype,
                                           const string &hname,
                                           const string &token) const
{
    pj_str_t pj_hname = str2Pj(hname);
    pj_str_t pj_token = str2Pj(token);
    
    return pjsua_call_remote_has_cap(id, htype,
                                     (htype == PJSIP_H_OTHER)? &pj_hname: NULL,
                                     &pj_token);
}

void Call::setUserData(Token user_data)
{
    userData = user_data;
}

Token Call::getUserData() const
{
    return userData;
}

pj_stun_nat_type Call::getRemNatType() throw(Error)
{
    pj_stun_nat_type nat;
    
    PJSUA2_CHECK_EXPR( pjsua_call_get_rem_nat_type(id, &nat) );
    
    return nat;
}

void Call::makeCall(const string &dst_uri, const CallOpParam &prm) throw(Error)
{
    pj_str_t pj_dst_uri = str2Pj(dst_uri);
    call_param param(prm.txOption, prm.opt, prm.reason);
    
    PJSUA2_CHECK_EXPR( pjsua_call_make_call(acc.getId(), &pj_dst_uri,
                                            param.p_opt, this,
                                            param.p_msg_data, &id) );
}

void Call::answer(const CallOpParam &prm) throw(Error)
{
    call_param param(prm.txOption, prm.opt, prm.reason);
    
    PJSUA2_CHECK_EXPR( pjsua_call_answer2(id, param.p_opt, prm.statusCode,
                                          param.p_reason, param.p_msg_data) );
}

void Call::hangup(const CallOpParam &prm) throw(Error)
{
    call_param param(prm.txOption, prm.opt, prm.reason);
    
    PJSUA2_CHECK_EXPR( pjsua_call_hangup(id, prm.statusCode, param.p_reason,
                                         param.p_msg_data) );
}

void Call::setHold(const CallOpParam &prm) throw(Error)
{
    call_param param(prm.txOption, prm.opt, prm.reason);
    
    PJSUA2_CHECK_EXPR( pjsua_call_set_hold2(id, prm.options, param.p_msg_data));
}

void Call::reinvite(const CallOpParam &prm) throw(Error)
{
    call_param param(prm.txOption, prm.opt, prm.reason);

    PJSUA2_CHECK_EXPR( pjsua_call_reinvite2(id, param.p_opt, param.p_msg_data));
}

void Call::update(const CallOpParam &prm) throw(Error)
{
    call_param param(prm.txOption, prm.opt, prm.reason);
    
    PJSUA2_CHECK_EXPR( pjsua_call_update2(id, param.p_opt, param.p_msg_data) );
}

void Call::xfer(const string &dest, const CallOpParam &prm) throw(Error)
{
    call_param param(prm.txOption);
    pj_str_t pj_dest = str2Pj(dest);
    
    PJSUA2_CHECK_EXPR( pjsua_call_xfer(id, &pj_dest, param.p_msg_data) );
}

void Call::xferReplaces(const Call& dest_call,
                  const CallOpParam &prm) throw(Error)
{
    call_param param(prm.txOption);
    
    PJSUA2_CHECK_EXPR(pjsua_call_xfer_replaces(id, dest_call.getId(),
                                               prm.options, param.p_msg_data) );
}

void Call::processRedirect(pjsip_redirect_op cmd) throw(Error)
{
    PJSUA2_CHECK_EXPR(pjsua_call_process_redirect(id, cmd));
}

void Call::dialDtmf(const string &digits) throw(Error)
{
    pj_str_t pj_digits = str2Pj(digits);
    
    PJSUA2_CHECK_EXPR(pjsua_call_dial_dtmf(id, &pj_digits));
}

void Call::sendInstantMessage(const SendInstantMessageParam& prm)
    throw(Error)
{
    pj_str_t mime_type = str2Pj(prm.contentType);
    pj_str_t content = str2Pj(prm.content);
    call_param param(prm.txOption);

    PJSUA2_CHECK_EXPR(pjsua_call_send_im(id, &mime_type, &content,
                                         param.p_msg_data, prm.userData) );
}

void Call::sendTypingIndication(const SendTypingIndicationParam &prm)
    throw(Error)
{
    call_param param(prm.txOption);
    
    PJSUA2_CHECK_EXPR(pjsua_call_send_typing_ind(id,
                                                 (prm.isTyping?
                                                  PJ_TRUE: PJ_FALSE),
                                                 param.p_msg_data) );
}

void Call::sendRequest(const CallSendRequestParam &prm) throw(Error)
{
    pj_str_t method = str2Pj(prm.method);
    call_param param(prm.txOption);
    
    PJSUA2_CHECK_EXPR(pjsua_call_send_request(id, &method, param.p_msg_data) );
}

string Call::dump(bool with_media, const string indent) throw(Error)
{
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
    char buffer[1024 * 10];
#else
    char buffer[1024 * 3];
#endif

    PJSUA2_CHECK_EXPR(pjsua_call_dump(id, (with_media? PJ_TRUE: PJ_FALSE),
                                      buffer, sizeof(buffer),
                                      indent.c_str()));
    
    return buffer;
}

int Call::vidGetStreamIdx() const
{
#if PJSUA_HAS_VIDEO
    return pjsua_call_get_vid_stream_idx(id);
#else
    return PJSUA_INVALID_ID;
#endif
}

bool Call::vidStreamIsRunning(int med_idx, pjmedia_dir dir) const
{
#if PJSUA_HAS_VIDEO
    return (pjsua_call_vid_stream_is_running(id, med_idx, dir) == PJ_TRUE);
#else
    PJ_UNUSED_ARG(med_idx);
    PJ_UNUSED_ARG(dir);
    return false;
#endif
}

void Call::vidSetStream(pjsua_call_vid_strm_op op,
                        const CallVidSetStreamParam &param) throw(Error)
{
#if PJSUA_HAS_VIDEO
    pjsua_call_vid_strm_op_param prm;
    
    prm.med_idx = param.medIdx;
    prm.dir = param.dir;
    prm.cap_dev = param.capDev;
    PJSUA2_CHECK_EXPR( pjsua_call_set_vid_strm(id, op, &prm) );
#else
    PJ_UNUSED_ARG(op);
    PJ_UNUSED_ARG(param);
    PJSUA2_RAISE_ERROR(PJ_EINVALIDOP);
#endif
}

StreamInfo Call::getStreamInfo(unsigned med_idx) const throw(Error)
{
    pjsua_stream_info pj_si;
    StreamInfo si;
    
    PJSUA2_CHECK_EXPR( pjsua_call_get_stream_info(id, med_idx, &pj_si) );
    si.fromPj(pj_si);
    return si;
}

StreamStat Call::getStreamStat(unsigned med_idx) const throw(Error)
{
    pjsua_stream_stat pj_ss;
    StreamStat ss;
    
    PJSUA2_CHECK_EXPR( pjsua_call_get_stream_stat(id, med_idx, &pj_ss) );
    ss.fromPj(pj_ss);
    return ss;
}

MediaTransportInfo Call::getMedTransportInfo(unsigned med_idx) const
    throw(Error)
{
    pjmedia_transport_info pj_mti;
    MediaTransportInfo mti;
    
    PJSUA2_CHECK_EXPR( pjsua_call_get_med_transport_info(id, med_idx,
                                                         &pj_mti) );
    mti.fromPj(pj_mti);
    return mti;
}

void Call::processMediaUpdate(OnCallMediaStateParam &prm)
{
    pjsua_call_info pj_ci;
    unsigned mi;
    
    if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS) {
        for (mi = 0; mi < pj_ci.media_cnt; mi++) {
            if (mi >= medias.size()) {
                if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) {
                    medias.push_back(new CallAudioMedia);
                } else {
                    medias.push_back(NULL);
                }
            }
            
            if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) {
                CallAudioMedia *aud_med = (CallAudioMedia *)medias[mi];
                
                aud_med->setPortId(pj_ci.media[mi].stream.aud.conf_slot);
                /* Add media if the conference slot ID is valid. */
                if (pj_ci.media[mi].stream.aud.conf_slot != PJSUA_INVALID_ID)
                {
                    Endpoint::instance().mediaAdd((AudioMedia &)*aud_med);
                } else {
                    Endpoint::instance().mediaRemove((AudioMedia &)*aud_med);
                }
            }
        }
    }
    
    /* Call media state callback. */
    onCallMediaState(prm);
}

void Call::processStateChange(OnCallStateParam &prm)
{
    pjsua_call_info pj_ci;
    unsigned mi;
    
    if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS &&
        pj_ci.state == PJSIP_INV_STATE_DISCONNECTED)
    {
        /* Clear medias. */
        for (mi = 0; mi < medias.size(); mi++) {
            if (medias[mi])
                delete medias[mi];
        }
        medias.clear();
    }
    
    onCallState(prm);
    /* If the state is DISCONNECTED, this call may have already been deleted
     * by the application in the callback, so do not access it anymore here.
     */
}
