//
//  PJClient.m
//  NG911
//
//  Created by Rachid Jeitani on 10/17/11.
//  Copyright (c) 2011 Columbia University. All rights reserved.
//

#import "PJClient.h"

#define SIP_PORT 5080
#define RTP_START_PORT 4000

#define kUsernameKey    @"username"
#define kDomainKey      @"domain"
#define kLostDomainKey  @"lostdomain"
#define kRTPAddressKey   @"rtpaddress"
#define kMode   @"messageType"

#define TX_PT   98 // RTT payload type
#define RTP_INTERVAL 300 // interval in ms between realtime text packets

pjsua_acc_id acc_id; // account ID
pjsua_buddy_id b_id; // buddy ID

pjsua_transport_id transport_id = -1;
pjsua_transport_config udp_cfg; // pjsip udp configuration - set to the library default

id _self; // reference to singleton instance of this class

struct media_stream {
    
    pjmedia_rtp_session in_session;
    pjmedia_rtp_session out_session;
    
    pjmedia_rtcp_session rtcp;
    
    pjmedia_transport *transport;
    pjmedia_stream_info si;
    
    unsigned clock_rate;
        
    // threads - use carefully!
    
    pj_bool_t thread_quit_flag;
    pj_thread_t *thread;
    
};


// global variables for RTP (media) stream

static struct app {
    
    int sip_port;
    int rtp_start_port;
    pj_str_t local_addr;
    pj_str_t local_uri;
    pj_str_t local_contact;
    
    pj_caching_pool cp;
    pj_pool_t *pool;
    
    pjsip_endpoint *sip_endpt;
    pj_bool_t thread_quit;
    pj_thread_t *sip_thread[1];
    
    pjmedia_endpt *med_endpt;
    
    struct media_stream rtp_stream;
    
    pjsip_inv_session *inv; // SIP INVITE session
    pjmedia_sdp_session *sdp; // SDP Session
    
    
} app;


// rtt buffer

static struct rtt_buffer {
    
    char buffer[256]; // stores 256 characters to be send over rtt
    int length; // location of the last character in the buffer
    int lastSent; // location of the last sent character in the buffer
    
    // uses the difference between the length and lastSent to 
    // determine if new characters are present

    
} rtt_buffer;


@implementation PJClient


@synthesize delegate = _delegate;
@synthesize currentLocation = _currentLocation;
@synthesize pidfString = _pidfString;


#pragma mark - Shared Instance

// returns the shared instance of the PJClient
// this prevents initializing everything twice and wasting resources

+(PJClient *) sharedPJClient{
    
    static PJClient *sharedPJClient;
    
    @synchronized( self ) {
        if( !sharedPJClient ) {
            sharedPJClient = [[PJClient alloc] init];
            _self = sharedPJClient;
        }
    }

    return sharedPJClient;
}

#pragma mark - IP Address Functions

// returns the current IP address of the device for a specific interface
// use the method localAddress since it tries Wifi then 3G
+(NSString *) addressForInterface:(NSString *) interface {

    //http://mattbsoftware.blogspot.com/2009/04/how-to-get-ip-address-of-iphone-os-v221.html
    
    BOOL success;
    struct ifaddrs * addrs;
    const struct ifaddrs * cursor;
    
    success = getifaddrs(&addrs) == 0;
    if (success) {
        cursor = addrs;
        while (cursor != NULL) {
            // the second test keeps from picking up the loopback address
            if (cursor->ifa_addr->sa_family == AF_INET && (cursor->ifa_flags & IFF_LOOPBACK) == 0) 
            {
                NSString *name = [NSString stringWithUTF8String:cursor->ifa_name];
                if ([name isEqualToString:interface])  // interface
                    return [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)cursor->ifa_addr)->sin_addr)];
            }
            cursor = cursor->ifa_next;
        }
        freeifaddrs(addrs);
    }
    return nil; 
    
}


+(NSString *) localWifiAddress {
    return [self addressForInterface:@"en0"];
}

+(NSString *) localCellAddress {
    return [self addressForInterface:@"pdp_ip0"];
}

+(NSString *) localAddress {
    if ([PJClient localWifiAddress] != nil)
    {
        return [PJClient localWifiAddress];
    } else {
        return [PJClient localCellAddress];
    }
}


// C functions

#pragma mark - IM Message Functions

void sendMsg( const char* chat_dest_addr, const char* msg, char* type, int tcp ) {
    
    // Get settings
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *userdomain = [defaults objectForKey:kDomainKey];
    NSString *lostDomain = [defaults objectForKey:kLostDomainKey];
    
    if ( userdomain == nil ) {
        userdomain = @"127.0.0.1"; // if nothing is set, default to localhost so it won't crash
    }
    
    NSString *domain = nil;
    
    if (lostDomain == nil) {
        domain = userdomain;
    } else {
        domain = lostDomain;
    }
    
    NSLog(@"Lost Domain: %@", lostDomain);
    
    const char * c_domain = [userdomain UTF8String]; // TODO: change to [domain UTF8String] when using actual PSAP (instead of test server)

    pj_status_t status;
    pj_str_t mime_type = pj_str(type);
    
    char tmp_id[PJSIP_MAX_URL_SIZE];

        
    pj_ansi_sprintf( tmp_id, "sip:%s@%s:%d", chat_dest_addr, c_domain, SIP_PORT);    
    
    
    pj_str_t dst = pj_str( tmp_id );
    
    // create the contents of a multipart message
    
    pjsua_msg_data msg_data;
    pjsua_msg_data_init(&msg_data);
    
    pjsip_multipart_part *pidfPart;
    pj_str_t pidfType;
    pj_str_t pidfSubtype;
    pj_str_t pidfContent;
    
    pidfPart = pjsip_multipart_create_part(app.pool);
    pidfType = pj_str("application");
    pidfSubtype = pj_str("pidf+xml");
    const char *pidf = [[_self pidfString] UTF8String];
    pidfContent = pj_str((char *) pidf);
    pidfPart->body = pjsip_msg_body_create(app.pool, &pidfType, &pidfSubtype, &pidfContent);
    
    msg_data.multipart_ctype.type = pj_str("multipart");
    msg_data.multipart_ctype.subtype = pj_str("mixed");
    pj_list_push_back(&msg_data.multipart_parts, pidfPart);

    pj_str_t content = pj_str( ( char* ) msg ); // message content
    
    
    // send IM using PJSIP
    status = pjsua_im_send( acc_id, &dst, &mime_type, &content, &msg_data, NULL );
     
    printf("\n\nSend Message Status = %d\n\n", status);
    
}

// callback function when a SIP MESSAGE is received
void receiveMsg( 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 ){
    
    
    PJ_UNUSED_ARG( call_id );
    PJ_UNUSED_ARG( to );
    PJ_UNUSED_ARG( contact );
    PJ_UNUSED_ARG( mime_type );
    
    printf("\n\nMessage from %.*s: %.*s\n\n\n", (int) from->slen , from->ptr, (int)text->slen, text->ptr);
    
    const char* c_text = pj_strbuf(text);
    const char* c_from = pj_strbuf(from);
    
    NSString* ns_text = [NSString stringWithUTF8String:c_text];
    NSString* ns_from = [NSString stringWithUTF8String:c_from];
    
    
    // send message to delegate
    
    if (([_self delegate] != nil) && [[_self delegate] respondsToSelector:@selector(messageReceivedFrom:WithContent:)]) {
        
        [[_self delegate] messageReceivedFrom:ns_from WithContent:ns_text];
         
    }
    
    
}


// this is the objective-c method that is called by the delegate
// it will format the message appropriately and call the C function
// that will actually send the message
-(int) sendMsg:(Message *)msg to:(NSString *)dest {
        
    // register the thread
    
    pj_thread_desc desc;
    pj_thread_t *this_thread;
    pj_status_t status;
    
    pj_bzero( desc, sizeof( desc ) );
    
    status = pj_thread_register("thread", desc, &this_thread);
    
    if( status != PJ_SUCCESS ) {
        printf("Error Registering Thread!\n");
        return -1;
    }
    const char *text;
    
    if(![msg containsText] || [[msg message] isEqualToString:@""])
    {
        text = [msg dataString];
    }
    else
    {
        text = strdup([[msg message] UTF8String]);
    }
    
    NSLog(@"%s",text);
    
    const char *recepient = strdup([dest UTF8String]);
    
    char * type;
    
    // call the sendMsg function appropriately
    
    if ([msg containsImage]) {
        type = "image/jpeg";
        sendMsg(recepient, text, type, 1); 
    } else {
        type = "text/plain";
        sendMsg(recepient, text, type, 0);
    }
    
    
    free((void*)text);
    free((void*)recepient);
    
    return 0;
}


#pragma mark - Main Initializer

-(void) initClientWithLocation:(CLLocation *)location {
    
    // create Autorelease pool
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // Set location data
    _currentLocation = location;
    PIDF_LO_Client *pidf = [[PIDF_LO_Client alloc] initWithLocation:location];
    [_self setPidfString:[pidf constructXML]];
    [pidf release];
    // Get local IP Address
    
    NSString * address = [PJClient localAddress];
    
    if ( address == nil ) {
        address = @"127.0.0.1"; // if no IP, set to localhost
    }
    
    // Get settings
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *username = [defaults objectForKey:kUsernameKey];
    NSString *domain = [defaults objectForKey:kDomainKey];
    
    NSString *mode = [defaults stringForKey:kMode];
    BOOL isRealtime;
    
    // check to see if in RTT or IM mode
    // the boolean value 'isRealtime' is used to RTT specific 
    if([mode isEqualToString:@"RTT"]){
        isRealtime = YES;
        printf("Realtime Mode\n");
    } else if ([mode isEqualToString:@"IM"]){
        printf("IM Mode\n");
        isRealtime = NO;
    } else {
        isRealtime = YES; // default to RTT -- loads more modules
    }
    
    
    if ( username == nil ) { username = @"ng911"; }
    if ( domain == nil ) { domain = @"127.0.0.1"; }
    
    NSString *rtpAddress = [domain copy];
    NSString *portString = [NSString stringWithFormat:@":%d", SIP_PORT];
    domain = [domain stringByAppendingString:portString]; // add the SIP port
    
	NSString *sip_id = [@"sip:" stringByAppendingFormat:@"%@@%@", username, address];
    NSString *sip_uriWithPort = [sip_id stringByAppendingString:portString];
    NSString *reg_uri = [@"sip:" stringByAppendingString:domain];
    
    
    // create C-string copies of everything for use in the pjsip functions
    const char *c_local_uri = [sip_id UTF8String];
    const char *c_remote_uri = [reg_uri UTF8String];
    const char *c_rtp_addr = [rtpAddress UTF8String];
    const char *c_local_uriWithPort = [sip_uriWithPort UTF8String];
    
    [rtpAddress release];

    printf("ID: %s\nURI: %s\n", c_local_uri, c_remote_uri );
    
    app.local_uri =  pj_str( (char*) c_local_uri ); // set the local URI
    app.local_contact = app.local_uri;
    
	pj_status_t status;
	
	status = pjsua_create();
	if( status != PJ_SUCCESS ) {
		printf("pjsua_create error!\n");
	}
    
    // create memory pool
    
    app.pool = pjsua_pool_create("pjclient", 1000, 1000);

	// Init pjsua
	
    pjsua_config cfg;
    pjsua_logging_config log_cfg;
    pjsua_media_config media_cfg;
    
    pjsua_config_default( &cfg ); // initialize cfg with default values
    pjsua_logging_config_default( &log_cfg );
    pjsua_media_config_default( &media_cfg );
    log_cfg.console_level = 4;
    
    
    // assign callback to receive messages & typing indication
    cfg.cb.on_pager = (void*) &receiveMsg;
//    cfg.cb.on_typing = (void*) &on_typing;

    status = pjsua_init( &cfg, &log_cfg, &media_cfg );
    if( status != PJ_SUCCESS ){
        printf("pjsua_init error!\n");
        return;
    }
    
    // create TCP transport
    
    const char* c_loc_addr = [address UTF8String];
    app.local_addr = pj_str((char*) c_loc_addr);
	
    // get endpoint name
    app.sip_endpt = pjsua_get_pjsip_endpt();
    app.med_endpt = pjsua_get_pjmedia_endpt();
    
    const pj_str_t *endpt_name = pjsip_endpt_name(app.sip_endpt);
    const char* name = pj_strbuf(endpt_name);
    printf("\nEndpoint name: %s\n", name);
    
    app.sip_port = SIP_PORT;
    app.rtp_start_port = RTP_START_PORT;
    

    /* UDP Transport with pjmedia for RTP */
    
    pjsip_transport *tp;
    
    {
        pj_sockaddr_in addr;
        pjsip_host_port addrname;
        //pjsip_transport *tp;
        
        pj_bzero(&addr, sizeof(addr));
        addr.sin_family = pj_AF_INET();
        addr.sin_addr.s_addr = 0;
        addr.sin_port = pj_htons((pj_uint16_t) app.sip_port);
        
        if( app.local_addr.slen ) {
            addrname.host = app.local_addr;
            addrname.port = app.sip_port;
        
            status = pj_sockaddr_in_init(&addr, &app.local_addr, (pj_uint16_t) app.sip_port);
            if( status != PJ_SUCCESS ) {
                printf("Unable to resolve IP interface\n");
            }
        }
        
        
        // start the udp transport to be used for RTP/RTT
        status = pjsip_udp_transport_start(app.sip_endpt, &addr, (app.local_addr.slen ? &addrname:NULL), 1, &tp);
        if( status != PJ_SUCCESS ){
            printf("Error starting UDP Transport\n");
            return;
        }
        
        printf("SIP UDP listening on %.*s:%d\n", (int)tp->local_name.host.slen, tp->local_name.host.ptr, tp->local_name.port);
        
        pjsip_module * mod_inv = pjsip_inv_usage_instance();
        
        // unregister the module, then create it with custom configuration
        pjsip_endpt_unregister_module(app.sip_endpt, mod_inv);

        {
            pjsip_inv_callback inv_cb;
            pj_bzero(&inv_cb, sizeof(inv_cb));
            inv_cb.on_state_changed = &call_on_state_changed;
            inv_cb.on_media_update = &call_on_media_update;
            inv_cb.on_new_session = &call_on_forked;
    
            status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
            if (status != PJ_SUCCESS) {
                printf("Error creating INVITE callbacks\n");
            }
        }
                
        // register the udp transport we just started
        
        status = pjsua_transport_register(tp, &transport_id);
        if (status != PJ_SUCCESS){
            printf("Error registering UDP transport\n");
        } 
        
    }
    
    pj_uint16_t rtp_port;
    rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
    
    struct media_stream *rtp_stream = &app.rtp_stream;
    
    // initialize the rtp sessions we will need for RTT
    pjmedia_rtp_session_init(&rtp_stream->in_session, TX_PT, pj_rand());
    pjmedia_rtp_session_init(&rtp_stream->out_session, TX_PT, pj_rand());
    
    
    status = pjmedia_transport_udp_create2(app.med_endpt, "siprtp", &app.local_addr, rtp_port, 0, &rtp_stream->transport);
    if (status != PJ_SUCCESS){
        printf("Error creating PJMEDIA UDP Transport!\n");
    } 
    

	// now start pjsua
	
	status = pjsua_start();
	if( status != PJ_SUCCESS ) {
		printf("Error starting pjsua!\n");
		return;
	}
    
    
    // send the INVITE
    pj_str_t dst_uri = pj_str("");
    pj_cstr(&dst_uri, c_remote_uri);
    
    
    if (isRealtime) { // send invite only for realtime text
        status = sendInvite(&dst_uri);
        if (status != PJ_SUCCESS) {
            printf("Error sending Invite\n");
        }
    }
    // Get the remote RTP address 
    
    pj_sockaddr_in rtp_addr;
    pj_bzero(&rtp_addr, sizeof(rtp_addr));
    pj_str_t s;
    
    rtp_addr.sin_family = pj_AF_INET();
    rtp_addr.sin_port   = rtp_port;
    rtp_addr.sin_addr   = pj_inet_addr(pj_cstr(&s, c_rtp_addr)); // TODO: replace with actual RTP address
    
    printf("Address: %s\n", pj_inet_ntoa(rtp_addr.sin_addr));
    
    // set the remote RTP address into the media stream struct
    
    rtp_stream->si.rem_addr = (pj_sockaddr)rtp_addr;
    rtp_stream->si.tx_pt = TX_PT;
    
    // attach media to transport
    pjmedia_transport_attach(rtp_stream->transport, rtp_stream, &rtp_stream->si.rem_addr , NULL, sizeof(pj_sockaddr_in), &on_rx_rtp, &on_rx_rtcp);
    

    // start the RTT Thread
    if (isRealtime) {
        pj_thread_create(app.pool, "rtpthread", &rtp_thread, rtp_stream , 0, 0, &rtp_stream->thread);
    }
    status = pjsua_acc_add_local(transport_id, PJ_TRUE, &acc_id);
    
    // update config with new SIP URI

    pjsua_acc_info before_info;
    pjsua_acc_get_info(acc_id, &before_info);
    
    pjsua_acc_config acc_cfg;
    pjsua_acc_config_default(&acc_cfg); 
    
    acc_cfg.id = pj_str((char *)c_local_uriWithPort);
    pjsua_acc_modify(acc_id, &acc_cfg);

    pjsua_acc_info acc_info;
    pjsua_acc_get_info(acc_id, &acc_info);
    
    
    
    if (status != PJ_SUCCESS) {
        printf("Error creating local SIP account\n");
    }
	 
    
    // call our delegate
    
    [[_self delegate] didFinishLoadingWithStatus:0];
    
    [pool release];
    
}

#pragma mark - RTP & RTT Functions

// called when an RTP packet is received (process RTT)
void on_rx_rtp (void *user_data, void *pkt, pj_ssize_t size) {
    
    printf("Received RTP Packet.\n");

    struct media_stream *strm;
    pj_status_t status;
    const pjmedia_rtp_hdr *hdr;
    const void *payload;
    unsigned payloadLength;
    
    BOOL isBackspace = NO;
    BOOL isNewLine = NO;
    
    // get the incoming media stream
    strm = user_data;
    
    if(size < 0) {
        printf("RTP Receive Error!\n");
        return;
    }
    
    // decode the RTP packet
    status = pjmedia_rtp_decode_rtp(&strm->in_session, pkt, size, &hdr, &payload, &payloadLength);
    if (status != PJ_SUCCESS) {
        printf("RTP Decode Error.\n");
        return;
    }
    
    
    // for testing
    printf("Marker: %d\n", hdr->m); //pj_ntohs(hdr->m));
    printf("Payload Type (PT): %d\n", hdr->pt);// pj_ntohs(hdr->pt));
    printf("Sequence Number: %d\n", hdr->seq);// pj_tolower(hdr->seq));
    printf("Payload Length: %d\n", payloadLength);
    char packet[1500];
    
   // pjsip_msg_print(pkt, packet, sizeof(packet));
    
    pj_memcpy(packet, payload, payloadLength);

    printf("PACKET: %s\n", packet);
    pj_uint8_t *pload = (pj_uint8_t*) payload;
    
    printf("Payload: %s\n", pload);
    
    // detect a backspace
    if (*pload == '\b') isBackspace = YES;
    
    // detect a newline
    if (*pload == 0xe2 && *(pload + 1) == 0x80 && *(pload + 2) == 0xa8)
        isNewLine = YES;
    
    // parse the incoming payload
    char parsedChar[payloadLength];
    strncpy(parsedChar, (const char *)pload, payloadLength);
    parsedChar[payloadLength] = 0;
    printf("Parsed Payload: %s\n", parsedChar);

    // update the RTP session
    pjmedia_rtp_session_update(&strm->in_session, hdr, NULL);
    
    // notify the delegate
    
    if (hdr->pt == 98) { // check for correct payload type
        
        // create the string
        
        NSString *parsedString = [NSString stringWithCString:parsedChar encoding:NSUTF8StringEncoding];
        
        if (isBackspace) {
            [[_self delegate] performSelectorOnMainThread:@selector(realtimeBackspaceReceived) withObject:nil waitUntilDone:NO];
        }
        
        if (isNewLine) {
            [[_self delegate] performSelectorOnMainThread:@selector(realtimeReturnReceived) withObject:nil waitUntilDone:NO];
        }

        // check we have a valid string
        if (parsedString && !isBackspace && !isNewLine) {
            
            if( ([_self delegate] != nil) && ([[_self delegate] respondsToSelector:@selector(realtimeMessageReceivedwithContent:)]) && parsedString != nil) {
                
                [[_self delegate] performSelectorOnMainThread:@selector(realtimeMessageReceivedwithContent:) withObject:parsedString waitUntilDone:NO];
                       
            }
        }
        
    }
    
  
    
}

void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size) {
    
//    printf("Received RTCP packet\n");
    
}

void call_on_media_update (pjsip_inv_session *inv, pj_status_t status) {
    
    printf("Received RTP Message with status: %d\n", status);
    
    
    
}

static pj_bool_t on_rx_request( pjsip_rx_data *rdata ) {
    
    return PJ_TRUE;
}

void call_on_state_changed( pjsip_inv_session *inv, pjsip_event *e){
//    printf("State Changed\n");
    
}
void call_on_forked(pjsip_inv_session *inv, pjsip_event *e){
    
}



-(void) charTyped:(const char *)str {
    
    // adds the new character(s) to the RTT buffer & sets the available flag
    
    rtt_buffer.buffer[rtt_buffer.length] = str[0];
    rtt_buffer.length++;
    
}


static int rtp_thread(void *arg){
    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    struct media_stream *strm = arg;
    char packet[1500]; // RTP packet
    int marker = 0;
    int consecutiveSentPackets = 0;
    
    pj_timestamp next_rtp, freq;
    unsigned interval = RTP_INTERVAL; // in msec
    
    pj_thread_sleep(100); 
    
    pj_get_timestamp_freq(&freq);
    pj_get_timestamp(&next_rtp);

    next_rtp.u64 += (freq.u64 * interval / 1000);
    
    // for testing only
    bool t_flag = true;
    
    while (!strm->thread_quit_flag && t_flag) {
        
        pj_timestamp now, lesser;
        pj_time_val timeout;
//        pj_bool_t send_rtp;
        
//        send_rtp = PJ_FALSE;
        lesser = next_rtp;
//        send_rtp = PJ_TRUE;
        
        pj_get_timestamp(&now);
        
        if (lesser.u64 <= now.u64) {
            timeout.sec = timeout.msec = 0;
        } else {
            pj_uint64_t tick_delay;
            tick_delay = lesser.u64 - now.u64;
            timeout.sec = 0;
            timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
            pj_time_val_normalize(&timeout);
        }
        
        // wait for next send interval
        
        pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
        if (strm->thread_quit_flag) break;
        
        pj_get_timestamp(&now);
        
        // sending rtp packet
            
        pj_status_t status;
        const void *p_hdr; // packet header
        const pjmedia_rtp_hdr *hdr;
        pj_ssize_t size;
        int hdrlen;
        char payload[16];
        char *buf; 
        
        marker = (consecutiveSentPackets < 1) ? 1 : 0;
        
        int numNewChars = rtt_buffer.length - rtt_buffer.lastSent;
        
        if (numNewChars > 0){
            // more data has been added
        
            NSLog(@"New data, sending...\n");
        
            buf = rtt_buffer.buffer + rtt_buffer.lastSent;
            pj_memcpy(payload, buf, numNewChars);
            rtt_buffer.lastSent = rtt_buffer.length; // update lastSent marker
            int payloadLength = strlen(payload);
            
            // format the RTP header
            
            status = pjmedia_rtp_encode_rtp(&strm->out_session, strm->si.tx_pt, marker, payloadLength, sizeof(next_rtp), &p_hdr, &hdrlen);
            
            if(status == PJ_SUCCESS){
                printf("Created RTP Header\n");
            }
            
            hdr = (const pjmedia_rtp_hdr*) p_hdr;
            
            // copy rtp header into packet
            
            pj_memcpy(packet, hdr, hdrlen);
            pj_bzero(packet + hdrlen, payloadLength);
            size = hdrlen + payloadLength;
            pj_memcpy(packet+hdrlen, payload, payloadLength);
            status = pjmedia_transport_send_rtp(strm->transport, packet, size); // send the packet
            
            if(status == PJ_SUCCESS){
                consecutiveSentPackets++;
                printf("Sent RTP Packet!\n");
            }
            
            // update RTCP info
            pjmedia_rtcp_tx_rtp(&strm->rtcp, payloadLength);
            
            // schedule next send
            
            next_rtp.u64 += (interval * freq.u64 / 1000);
            
            
            
        } else {
            // no new data so decrement consecutiveSentPackets
            if (consecutiveSentPackets > 0) consecutiveSentPackets--;
            
        } // end length check
        
                   
    } // end while
    
    [pool release];
    return 0;
}


#pragma mark - SIP Functions

pj_status_t createSDP(pjmedia_sdp_session **p_sdp) {
    
    pjmedia_sdp_session *sdp;
    
    pj_time_val tv;
    pjmedia_sdp_media *m; // m= line
    pjmedia_transport_info tpinfo;
    
    struct media_stream *strm = &app.rtp_stream; // get the rtp media stream
    
    // get transport info
    pjmedia_transport_info_init(&tpinfo);
    pjmedia_transport_get_info(strm->transport, &tpinfo);
    
    // create & initialize SDP session
    sdp = pj_pool_zalloc(app.pool, sizeof(pjmedia_sdp_session));
    
    pj_gettimeofday(&tv);
    
    sdp->origin.user = pj_str("ng911-sdp");
    sdp->origin.version = 0;
    sdp->origin.id = tv.sec + 2208988800UL;
    sdp->origin.net_type = pj_str("IN");
    sdp->origin.addr_type = pj_str("IP4");
    sdp->origin.addr = *pj_gethostname();
    sdp->name = pj_str("NG911");
    
    sdp->conn = pj_pool_zalloc(app.pool, sizeof(pjmedia_sdp_conn));
    sdp->conn->net_type = pj_str("IN");
    sdp->conn->addr_type = pj_str("IP4");
    sdp->conn->addr = app.local_addr;
    
    sdp->time.start = sdp->time.stop = 0;
    sdp->attr_count = 0;
    
    // create sdp media stream
    sdp->media_count = 1;
    m = pj_pool_zalloc(app.pool, sizeof(pjmedia_sdp_media)); // allocate sdp media struct
    sdp->media[0] = m; 

    // set sdp media descriptions    
    m->desc.media = pj_str("text");
    m->desc.port = pj_ntohs(tpinfo.sock_info.rtp_addr_name.ipv4.sin_port);
    m->desc.port_count = 1;
    m->desc.transport = pj_str("RTP/AVP");
    m->desc.fmt_count = 1;
    m->attr_count = 0;
    
    {
        pjmedia_sdp_rtpmap rtpmap;
        pjmedia_sdp_attr *attr;
        char ptstr[10];
        
        sprintf(ptstr, "%d", 98);
        pj_strdup2(app.pool, &m->desc.fmt[0], ptstr);
        rtpmap.pt = m->desc.fmt[0];
        rtpmap.clock_rate = 1000;
        rtpmap.enc_name = pj_str("t140");
        rtpmap.param.slen = 0;
        
        pjmedia_sdp_rtpmap_to_attr(app.pool, &rtpmap, &attr);
        m->attr[m->attr_count++] = attr;
        
        // TODO: need to add two more attributes for t140 with redundancy
        
    }
    
    *p_sdp = sdp; 

    return PJ_SUCCESS;
    
} // end create_sdp
 

pj_status_t sendInvite(const pj_str_t *dst_uri) {
    
    pjsip_dialog *dlg;
    pjsip_tx_data *tdata;
    pj_status_t status;
    pjsip_msg_body *msg;
    
    
    // need to create UAC dialog
    
    status = pjsip_dlg_create_uac(pjsip_ua_instance(), &app.local_uri, &app.local_contact, dst_uri, dst_uri, &dlg);
    if (status != PJ_SUCCESS){
        printf("Error creating UAC Dialog\n");
        return status;
    }
    
    // create SDP
    
    status = createSDP(&app.sdp);
    if (status != PJ_SUCCESS){
        printf("Error creating SDP in sendInvite()\n");
        return status;
    }
 

    // create INVITE session
    
    status = pjsip_inv_create_uac(dlg, app.sdp, 0, &app.inv);
    if (status != PJ_SUCCESS) {
        printf("Error creating INVITE session.\n");
        return status;
    }
    
    // Create INVITE request
    
    status = pjsip_inv_invite(app.inv, &tdata);
    if (status != PJ_SUCCESS) {
        printf("Error creating INVITE request\n");
        return status;
    }
    
    // get the multipart message
    
    status = createMultipartMessageBody(&msg);
    if (status != PJ_SUCCESS) {
        printf("Error creating Multipart Message\n");
        return status;
    }
    
    
    // only enable for clients that support multipart messages
    
    tdata->msg->body = msg;
    
    
    // Send initial INVITE request
    
    status = pjsip_inv_send_msg(app.inv, tdata);
    if (status != PJ_SUCCESS) {
        printf("Error sending INVITE Request\n");
        return status;
    }
    
    return PJ_SUCCESS;
    
}



pj_status_t createMultipartMessageBody(pjsip_msg_body **msg) {
    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    pjsip_msg_body *messageBody;
    pjsip_multipart_part *sdpPart;
    pjsip_multipart_part *pidfPart;
//    pj_status_t status;
    
    // Create the message body
    messageBody = pjsip_multipart_create(app.pool, NULL, NULL);
    
    // Create each part of the message (For SDP & PIDF)
    
    sdpPart = pjsip_multipart_create_part(app.pool);
    pidfPart = pjsip_multipart_create_part(app.pool);

    char buf[500];
    int length = pjmedia_sdp_print(app.sdp, buf, sizeof(*app.sdp));
    
    printf("Length printed: %d\n", length);
    
    buf[length - 1] = '\0';

    printf("***** SDP BUFFER *****\n%s\n**************************\n", buf);

    // create the message body
    
    // for sdp
    pjsip_msg_body *sdpBody;
    pj_str_t mime_app = { "application", 11 };
    pj_str_t mime_sdp = { "sdp", 3 };
    pj_str_t sdp_contents = pj_str(buf);
    
    sdpBody = pjsip_msg_body_create(app.pool, &mime_app, &mime_sdp, &sdp_contents);
    sdpPart->body = sdpBody;
    
    // for pidf
    pjsip_msg_body *pidfBody;
    pj_str_t mime_pidf = pj_str("pidf+xml");
    
    // create PIDF Object
    
    const char *pidf = [[_self pidfString] UTF8String]; 
    pj_str_t pidf_contents = pj_str((char *)pidf);
    pidfBody = pjsip_msg_body_create(app.pool, &mime_app, &mime_pidf, &pidf_contents);    
    pidfPart->body = pidfBody;

    // Add each part to the message body
    
    pjsip_multipart_add_part(app.pool, messageBody, sdpPart);
    pjsip_multipart_add_part(app.pool, messageBody, pidfPart);
    
    *msg = messageBody;
    
    [pool release];
    
    return PJ_SUCCESS;
}



#pragma mark - Dealloc & Destroy


// method to destory the PJClient and release all it's resources
-(void) destroy {
    
    
    pj_thread_desc desc;
    pj_thread_t *this_thread;
    pj_status_t status;
    
    pj_bzero( desc, sizeof( desc ) );
    
    status = pj_thread_register("thread", desc, &this_thread);
    
    if( status != PJ_SUCCESS ) {
        printf("Error Registering Thread.\n");
        return;
    }
    
    pjsua_destroy();
    
}

- (void)dealloc {
    
    [super dealloc];
}



@end
