/*
 * Copyright (c) 2001, 2002 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rtpsession.h"
#include "rtppacket.h"
#include "rtpsourcedata.h"
#include "rtp_api.h"

// private function prototypes
int _find_next_session( void );
void _init_rtp_session_hash( void );

#define _RTP_MAX_STATIC_SESSIONS 64

static RTPSession* _rtp_session_hash[_RTP_MAX_STATIC_SESSIONS];
static bool        _rtp_session_hash_initialized = false;

int32
rtp_create_udp_p2p_session(struct rtp_session* s, uint32 peer_addr,
                           uint16 peer_rx_portbase,
                           uint16 local_rx_portbase)
{
    int session_id;
    int err;

    if (!s) // caller must allocate memory for C session struct
        return -1;
    
    // check to see if the hash is initialized.  if not, do it and set
    // the flag.
    if (!_rtp_session_hash_initialized)
        _init_rtp_session_hash();

    // grab the next available session
    session_id = _find_next_session();
    _rtp_session_hash[session_id] = new RTPSession;
    if (!_rtp_session_hash[session_id]) return -1;

    // create it, binding RTP to the local receive portbase, and RTCP
    // to the portbase + 1
    err = _rtp_session_hash[session_id]->Create(local_rx_portbase, 0);
    if (err < 0) return err;
    
    // add the peer as our only destination
    err = _rtp_session_hash[session_id]->AddDestination(peer_addr,
                                                        peer_rx_portbase);
    if (err < 0) return err;

    // fill out the C session struct
    s->id = session_id;
    err = _rtp_session_hash[session_id]->GetRTPSocket ( &(s->rx_rtp_socket) );
    if (err < 0) return err;

    err = _rtp_session_hash[session_id]->GetRTCPSocket( &(s->rx_rtcp_socket) );
    if (err < 0) return err;

    err = _rtp_session_hash[session_id]->GetSendSocket( &(s->tx_socket) );
    if (err < 0) return err;

    return 0;
}

int32
rtp_create_stream_pair_p2p_session(struct rtp_session* s,
                                   uint32 peer_addr,
                                   int peer_portbase,
                                   int session_type,
                                   RTPSOCKET rtpsock, 
                                   RTPSOCKET rtcpsock)
{
    int session_id;
    int err;

    if (!s) // caller must allocate memory for C session struct
        return -1;
    
    // check to see if the hash is initialized.  if not, do it and set
    // the flag.
    if (!_rtp_session_hash_initialized)
        _init_rtp_session_hash();

    // grab the next available session
    session_id = _find_next_session();
    _rtp_session_hash[session_id] = new RTPSession;
    if (!_rtp_session_hash[session_id]) return -1;

    // create the session, using our pre-established RTP&RTCP socket
    // pair
    err = _rtp_session_hash[session_id]->CreateFromStreamSocketPair(
        session_type, rtpsock, rtcpsock, 0);
    if (err < 0) return err;
    
    // add the peer as our only destination
    err = _rtp_session_hash[session_id]->AddDestination(peer_addr,
                                                        peer_portbase);
    if (err < 0) return err;

    // fill out the C session struct
    s->id = session_id;
    s->rx_rtp_socket = rtpsock;
    s->rx_rtcp_socket = rtcpsock;

    return 0;
}

int32
rtp_get_local_ssrc( struct rtp_session* s, uint32* ssrc )
{
    unsigned long int temp;

    if ( _rtp_session_hash[s->id] == 0 ) return -1;
    
    _rtp_session_hash[s->id]->GetLocalSSRC( &temp );
    *ssrc = temp;

    return 0;
}

int32
rtp_set_local_ssrc( struct rtp_session* s, uint32 ssrc )
{
    if ( _rtp_session_hash[s->id] == 0 ) return -1;
    
    _rtp_session_hash[s->id]->SetLocalSSRC( ssrc );

    return 0;
}

int32
rtp_get_peer_ssrc( struct rtp_session* s, uint32* ssrc )
{
    RTPSourceData* sd;

    if ( _rtp_session_hash[s->id] == 0 ) return -1;
    
    _rtp_session_hash[s->id]->GotoFirstSource( );
    sd = _rtp_session_hash[s->id]->GetCurrentSourceInfo( );
    *ssrc = sd->GetSSRC();

    return 0;
}

int32
rtp_poll(struct rtp_session* s)
{
    return 0;
}

int32
rtp_get_peer_rr(struct rtp_session* s)
{
    return 0;
}

int32
rtp_get_peer_sr(struct rtp_session* s)
{
    rtp_update_sd(s);

    return 0;
}

int32
rtp_bye(struct rtp_session* s)
{
    return 0;
}

/***************************************************************************
 * rtp_p2p_retrieve_packet_from_library
 *
 *  Pull a packet in from jrtplib's buffer.  This doesn't check the
 *  network buffers for waiting data. 
 *
 *  1. rtp_session struct pointer
 *  2. rtp_packet struct pointer.  WE WILL ALLOCATE SPACE FOR THE PAYLOAD.
 *
 * returns: error
 *
 ***************************************************************************/
int32
rtp_p2p_retrieve_packet_from_library(struct rtp_session* s,
                                     struct rtp_packet* packet) 
{
    RTPPacket* packet_obj;

    // this is p2p because we only look at one SSRC
    if ( !_rtp_session_hash[s->id]->GotoFirstSource() )
        return ERR_RTP_SOURCESNOTINITALIZED; /* sic */

    if ( ( packet_obj = _rtp_session_hash[s->id]->GetNextPacket() ) == 0 )
        return ERR_RTP_NORTPDATAAVAILABLE;
    
    // caller must allocate space for packet
    if (packet == 0) return ERR_RTP_NULLPTR;

    // stuff the packet structure and copy the payload
    packet->m = packet_obj->marked;
    packet->pt = packet_obj->payloadtype;
    packet->seq = packet_obj->seqnum;
    packet->ts = packet_obj->timestamp;
    packet->ssrc = packet_obj->syncsource;
    packet->payload_len = packet_obj->payloadlen;

    // We allocate space for the payload because we know how much we
    // need.
    if ( ( packet->payload = (char*)malloc( packet->payload_len ) ) == 0 )
        return ERR_RTP_OUTOFMEM;

    // how can we avoid this?
    // -- 7 APR 2003 rocko - I don't think we can.
    memcpy(packet->payload, packet_obj->payloaddata, packet->payload_len);
    
    delete packet_obj; // jrtplib demands this
    
    return 0;
}


/***************************************************************************
 * rtp_p2p_recv
 *
 *  1. pointer to rtp session 
 *  2. pointer to rtp packet allocated by caller.  WE WILL ALLOCATE PAYLOAD.
 *  3. boolean check_net_for_rtp
 *  4. boolean check_net_for_rtcp
 *
 *  This function retrieves a RTP packet.  If check_net_for_rtp or
 *  check_net_for_rtcp are set, the function pulls in packets waiting
 *  on the network socket-level buffers.
 * 
 * returns: 1 if packet read
 *          0 if packet not read
 *          error code
 *
 ***************************************************************************/
int32
rtp_p2p_recv( struct rtp_session* s, struct rtp_packet* packet, 
              int check_net_for_rtp, int check_net_for_rtcp)
{
    int32 err;
    int32 data_in_library;

    packet->payload = 0;
    
    /* pull stuff in from the socket */
    err = rtp_process_packets( s, check_net_for_rtp, check_net_for_rtcp, FALSE );
    if ( err < 0 ) return err;

    data_in_library = rtp_is_data_in_library_queue(s);
    
    if ( data_in_library < 0 ) 
        return data_in_library;

    if ( data_in_library == 0 ) return 0;

    /* retrieve the next packet from the library's buffers */
    err = rtp_p2p_retrieve_packet_from_library( s, packet );
    if ( err < 0 ) return err;
    
    return 1; /* we read a packet! */
}

/***************************************************************************
 * rtp_process_packets
 *
 *  1. pointer to rtp session 
 *  3. boolean rtp
 *  4. boolean rtcp
 *
 *  Check network-level buffers for more data and bring into jrtplib
 *  if available.
 * 
 * returns: 1 if packet read
 *          0 if packet not read
 *          error code
 *
 ***************************************************************************/
int32
rtp_process_packets(struct rtp_session* s, int rtp, int rtcp, int check_for_data)
{
    int err;
    bool process_rtp  = false;
    bool process_rtcp = false;
    bool cfd = false;
    
    if (rtp)  process_rtp  = true;
    if (rtcp) process_rtcp = true;
    if (check_for_data) cfd = true;

    // sucks packets from the socket buffer, placing them into the
    // library's buffer.
    err = _rtp_session_hash[s->id]->PollData(process_rtp, process_rtcp, cfd);

    if ( err < 0 )
    {
#ifdef DEBUG
        fprintf(stderr, "** jrtplib: %s\n", RTPGetErrorString(err));
#endif
        return err;
    }

    return 0;
}

int32
rtp_process_rtcp_if_any(struct rtp_session* s)
{
    int err;

    // process rtcp packets, checking for data
    err = _rtp_session_hash[s->id]->PollData(false, true, true);

    return 0;
}

int32
rtp_is_data_in_library_queue(struct rtp_session* s)
{
    RTPSourceData* sd;

    // you have to start the thing off at the beginning of the ssrc list
    if ( !_rtp_session_hash[s->id]->GotoFirstSource() )
        return ERR_RTP_SOURCESNOTINITALIZED; /* sic */

    sd = _rtp_session_hash[s->id]->GetCurrentSourceInfo();
    if (sd == 0) return ERR_RTP_SOURCESNOTINITALIZED; /* sic */

    if (sd->HasData()) return 1;

    return 0;
}

int32
rtp_p2p_send(struct rtp_session* s, char* data, int len, char pt,
             int mark, uint32 ts, uint32 seq) 
{
    return rtp_send(s, data, len, pt, mark, ts, seq);
}

int32
rtp_send(struct rtp_session* s, char* data, int len, char pt,
         int mark, uint32 ts, uint32 seq) 
{
    int err;
    bool mark_b = 0;
    
    // SendPacket apparently does RTCP reporting for us...

    if (mark) mark_b = 1;

    err = _rtp_session_hash[s->id]->SendPacket((void *)data, len, (unsigned char)pt, mark_b, (unsigned long)ts, (unsigned long)seq);
    // When SendData returns, the data will have been copied, so we
    // can reuse the packet or delete it.

    return err;
}

int32
rtp_p2p_send_simple(struct rtp_session* s, char* data, int len) 
{
    return rtp_send_simple(s, data, len);
}

int32
rtp_p2p_send_control(struct rtp_session* s, char* data, int len) 
{
    return rtp_send_control(s, data, len);
}

int32
rtp_send_simple(struct rtp_session* s, char* data, int len) 
{
    int err;
    // SendPacket apparently does RTCP reporting for us...

    err = _rtp_session_hash[s->id]->SendPacket((void *)data, len);
    // When SendData returns, the data will have been copied, so we
    // can reuse the packet or delete it.

    return err;
}

int32
rtp_send_control(struct rtp_session* s, char* data, int len) 
{
    int err;
    struct timeval tv;
    uint32 ntp32ts = 0;
    double ts;
    
    gettimeofday( &tv, NULL );
    ts = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0;
    ntp32ts = (uint32)(ts*65536.0);
    
    // SendPacket apparently does RTCP reporting for us...

    err = _rtp_session_hash[s->id]->SendPacketWithTS((void *)data, len, 42, false, ntp32ts);
    // When SendData returns, the data will have been copied, so we
    // can reuse the packet or delete it.

    return err;
}

int32
rtp_destroy_session( struct rtp_session* s )
{
    if ( s ) 
    {
        if (s->id < _RTP_MAX_STATIC_SESSIONS )
            if ( _rtp_session_hash[s->id] )
                delete _rtp_session_hash[s->id];
    }

    _rtp_session_hash[s->id] = 0;
    // caller must free rtp_session struct 

    return 0;
}

int32
rtp_update_sd( struct rtp_session* s )
{
    RTPSourceData* sd;
    struct rtp_source_data* ssd = &(s->sd); // our C-edible sd
    int i;
    
    // this is p2p because we only look at one SSRC
    if ( !_rtp_session_hash[s->id]->GotoFirstSource() )
        return ERR_RTP_SOURCESNOTINITALIZED; /* sic */
    
    sd = _rtp_session_hash[s->id]->GetCurrentSourceInfo();
    
    ssd->ssrc = sd->ssrc;
    ssd->hassentnewdata = (int)sd->hassentnewdata;
    ssd->isaCSRC = sd->isaCSRC;
    ssd->ip = sd->ip;
    ssd->rtpport = sd->rtpport;
    ssd->rtcpport = sd->rtcpport;
    ssd->tsunit = sd->tsunit;
    
    ssd->sr.srreceived = (int)sd->sr.srreceived;
    ssd->sr.srtime.tv_sec = sd->sr.srtime.tv_sec;
    ssd->sr.srtime.tv_usec = sd->sr.srtime.tv_usec;
    ssd->sr.ntp.secs = sd->sr.ntpmsw;
    ssd->sr.ntp.frac = sd->sr.ntplsw;
    ssd->sr.ts = sd->sr.rtptimestamp;
    ssd->sr.packetcount = sd->sr.packetcount;
    ssd->sr.bytecount = sd->sr.bytecount;

    ssd->rr.rrreceived = (int)sd->rr.rrreceived;
    ssd->rr.rrtime.tv_sec = sd->rr.rrtime.tv_sec;
    ssd->rr.rrtime.tv_usec = sd->rr.rrtime.tv_usec;
    ssd->rr.fractionlost = sd->rr.fractionlost;
    ssd->rr.packetslost = sd->rr.packetslost;
    ssd->rr.exthighseqnum = sd->rr.exthighseqnum;
    ssd->rr.jitter = sd->rr.jitter;
    ssd->rr.lsr = sd->rr.lsr;
    ssd->rr.dlsr = sd->rr.dlsr;

    ssd->stats.hassentdata = sd->stats.hassentdata;
    ssd->stats.numpacketsreceived =sd->stats.numpacketsreceived;
    ssd->stats.numnewpackets = sd->stats.numnewpackets;
    ssd->stats.numcycles = sd->stats.numcycles;
    ssd->stats.seqbase = sd->stats.seqbase;
    ssd->stats.maxseq = sd->stats.maxseq;
    ssd->stats.prevmaxseq = sd->stats.prevmaxseq;
    ssd->stats.prevts = sd->stats.prevts;
    ssd->stats.jitter = sd->stats.jitter;
    ssd->stats.djitter = sd->stats.djitter;
    ssd->stats.prevpacktime.tv_sec = sd->stats.prevpacktime.tv_sec;
    ssd->stats.prevpacktime.tv_usec = sd->stats.prevpacktime.tv_usec;
    ssd->stats.rtt.tv_sec = sd->stats.rtt.tv_sec;
    ssd->stats.rtt.tv_usec = sd->stats.rtt.tv_usec;
    ssd->stats.lastmsgtime = sd->stats.lastmsgtime;

    ssd->sdes.src = sd->sdes.src;
    for (i=0; i<RTP_NUM_SDES_INDICES; i++)
    {
        /* if there's info to be had at this index */
        if ( sd->sdes.sdesinfolen[i] > 0 )
        {
            if ( ssd->sdes.sdesinfolen[i] != sd->sdes.sdesinfolen[i] )
            {
                /* if there was memory allocated for the thing already, free it. */
                if ( ssd->sdes.sdesinfolen[i] > 0 )
                    free( ssd->sdes.sdesinfo[i] );
                /* then allocate new memory */
                ssd->sdes.sdesinfo[i] = (char*)malloc( sd->sdes.sdesinfolen[i] );
            }
            /* copy the info string */
            memcpy( ssd->sdes.sdesinfo[i], sd->sdes.sdesinfo[i], sd->sdes.sdesinfolen[i] );
            ssd->sdes.sdesinfolen[i] = sd->sdes.sdesinfolen[i];
        }
    }

                         
    return 0;
}

int32
rtp_set_tsu( struct rtp_session* s, double tsu )
{
    RTPSourceData* sd;

    // we have to do it in this session
    _rtp_session_hash[s->id]->SetTimestampUnit( tsu );
    
    ///// AND in the peer's RTPSourceData
    // this is p2p because we only look at one SSRC
    if ( !_rtp_session_hash[s->id]->GotoFirstSource() )
        return ERR_RTP_SOURCESNOTINITALIZED; /* sic */
    
    sd = _rtp_session_hash[s->id]->GetCurrentSourceInfo();
    sd->SetTimestampUnit( tsu );
    
    return 0;
}

int32
rtp_set_pt( struct rtp_session* s, uint8 pt )
{
    _rtp_session_hash[s->id]->SetDefaultPayloadType( pt );
    return 0;
}

int32
rtp_set_rtcp_bw( struct rtp_session* s, double bwfrac )
{
    _rtp_session_hash[s->id]->SetSessionBandwidth( bwfrac );
    return 0;
}


////////////////////////////////////////////////////////////////////////////
//
// PRIVATE FUNCTIONS
//
///////////////////////////////////////////////////////////////////////////

int
_find_next_session( void )
{
    int i = 0;
    
    while (_rtp_session_hash[i] != 0) i++;
    
    return i;
}

void
_init_rtp_session_hash( void )
{
    int i;
    for (i=0; i<_RTP_MAX_STATIC_SESSIONS; i++)
        _rtp_session_hash[i] = 0;
    _rtp_session_hash_initialized = true;
}
