/*
 * Copyright (C) 2002-2003 the xine project
 *
 * This file is part of xine, a free video player.
 *
 * xine 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.
 *
 * xine 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
 *
 * $Id: 34c49de566fd27b3571770e5bedb76399c704af5 $
 *
 * sdp/sdpplin parser.
 *
 */

#include "real.h"
#include <vlc_strings.h>
#define BUFLEN 32000

static inline char *nl(char *data) {
  char *nlptr = (data) ? strchr(data,'\n') : NULL;
  return (nlptr) ? nlptr + 1 : NULL;
}

static inline int line_length(char * data) {
  char const * p = nl(data);
  if (p) {
    return p - data - 1;
  }
  return strlen(data);
}

static int filter(stream_t *p_access, const char *in, const char *filter, char **out, size_t outlen) {

  int flen=strlen(filter);
  size_t len;

  if (!in) return 0;

  len = (strchr(in,'\n')) ? (size_t)(strchr(in,'\n')-in) : strlen(in);
  if (!strncmp(in,filter,flen)) {
    if(in[flen]=='"') flen++;
    if(in[len-1]==13) len--;
    if(in[len-1]=='"') len--;
    if( len-flen+1 > outlen )
    {
        msg_Warn(p_access, "Discarding end of string to avoid overflow");
        len=outlen+flen-1;
    }
    memcpy(*out, in+flen, len-flen+1);
    (*out)[len-flen]=0;
    return len-flen;
  }
  return 0;
}

static sdpplin_stream_t *sdpplin_parse_stream(stream_t *p_access, char **data) {

  sdpplin_stream_t *desc;
  char* buf = NULL;
  unsigned char* decoded = NULL;
  int handled;

  desc = calloc( 1, sizeof(sdpplin_stream_t) );
  if( !desc )
    return NULL;

  buf = malloc( BUFLEN );
  if( !buf )
    goto error;

  decoded = malloc( BUFLEN );
  if( !decoded )
    goto error;

  if (filter(p_access, *data, "m=", &buf, BUFLEN)) {
    desc->id = strdup(buf);
  } else {
    msg_Dbg(p_access, "sdpplin: no m= found.");
    goto error;
  }
  *data=nl(*data);

  while (*data && **data && *data[0]!='m') {
    handled=0;

    if(filter(p_access, *data,"a=control:streamid=",&buf, BUFLEN)) {
        /* This way negative values are mapped to unfeasibly high
         * values, and will be discarded afterward
         */
        unsigned long tmp = strtoul(buf, NULL, 10);
        if ( tmp > UINT16_MAX )
            msg_Warn(p_access, "stream id out of bound: %lu", tmp);
        else
            desc->stream_id=tmp;
        handled=1;
        *data=nl(*data);
    }
    if(filter(p_access, *data,"a=MaxBitRate:integer;",&buf, BUFLEN)) {
      desc->max_bit_rate=atoi(buf);
      if (!desc->avg_bit_rate)
        desc->avg_bit_rate=desc->max_bit_rate;
      handled=1;
      *data=nl(*data);
    }
    if(filter(p_access, *data,"a=MaxPacketSize:integer;",&buf, BUFLEN)) {
      desc->max_packet_size=atoi(buf);
      if (!desc->avg_packet_size)
        desc->avg_packet_size=desc->max_packet_size;
      handled=1;
      *data=nl(*data);
    }
    if(filter(p_access, *data,"a=StartTime:integer;",&buf, BUFLEN)) {
      desc->start_time=atoi(buf);
      handled=1;
      *data=nl(*data);
    }
    if(filter(p_access, *data,"a=Preroll:integer;",&buf, BUFLEN)) {
      desc->preroll=atoi(buf);
      handled=1;
      *data=nl(*data);
    }
    if(filter(p_access, *data,"a=length:npt=",&buf, BUFLEN)) {
      desc->duration=(uint32_t)(atof(buf)*1000);
      handled=1;
      *data=nl(*data);
    }
    if(filter(p_access, *data,"a=StreamName:string;",&buf, BUFLEN)) {
      desc->stream_name=strdup(buf);
      desc->stream_name_size=strlen(desc->stream_name);
      handled=1;
      *data=nl(*data);
    }
    if(filter(p_access, *data,"a=mimetype:string;",&buf, BUFLEN)) {
      desc->mime_type=strdup(buf);
      desc->mime_type_size=strlen(desc->mime_type);
      handled=1;
      *data=nl(*data);
    }
    if(filter(p_access, *data,"a=OpaqueData:buffer;",&buf, BUFLEN)) {
      desc->mlti_data_size =
          vlc_b64_decode_binary_to_buffer(decoded, BUFLEN, buf );
      if ( desc->mlti_data_size ) {
          desc->mlti_data = malloc(desc->mlti_data_size);
          memcpy(desc->mlti_data, decoded, desc->mlti_data_size);
          handled=1;
          *data=nl(*data);
          msg_Dbg(p_access, "mlti_data_size: %i", desc->mlti_data_size);
      }
    }
    if(filter(p_access, *data,"a=ASMRuleBook:string;",&buf, BUFLEN)) {
      desc->asm_rule_book=strdup(buf);
      handled=1;
      *data=nl(*data);
    }

    if(!handled) {
#ifdef LOG
      int len = line_length(*data);
      ;   len = len < BUFLEN ? len : BUFLEN-1;
      buf[len] = '\0';
      strncpy (buf, *data, len);
      msg_Warn(p_access, "libreal: sdpplin: not handled: '%s'", buf);
#endif
      *data=nl(*data); /* always move to next line */
    }
  }
  free( buf );
  free( decoded) ;
  return desc;

error:
  free( decoded );
  free( desc );
  free( buf );
  return NULL;
}


sdpplin_t *sdpplin_parse(stream_t *p_access, char *data)
{
  sdpplin_t*        desc;
  sdpplin_stream_t* stream;
  char*             buf;
  char*             decoded;
  int               handled;

  desc = calloc( 1, sizeof(sdpplin_t) );
  if( !desc )
    return NULL;

  buf = malloc( BUFLEN );
  if( !buf )
  {
    free( desc );
    return NULL;
  }

  decoded = malloc( BUFLEN );
  if( !decoded )
  {
    free( buf );
    free( desc );
    return NULL;
  }
  desc->stream = NULL;

  while (data && *data) {
    handled=0;

    if (filter(p_access, data, "m=", &buf, BUFLEN)) {
        if ( !desc->stream ) {
            msg_Warn(p_access, "sdpplin.c: stream identifier found before stream count, skipping.");
            data = nl(data);
            continue;
        }
        stream=sdpplin_parse_stream(p_access, &data);
        msg_Dbg(p_access, "got data for stream id %u", stream->stream_id);
        if ( stream->stream_id >= desc->stream_count )
            msg_Warn(p_access, "stream id %u is greater than stream count %u\n", stream->stream_id, desc->stream_count);
        else
            desc->stream[stream->stream_id]=stream;
        continue;
    }
    if(filter(p_access, data,"a=Title:buffer;",&buf, BUFLEN)) {
      desc->title=vlc_b64_decode(buf);
      if(desc->title) {
        handled=1;
        data=nl(data);
      }
    }
    if(filter(p_access, data,"a=Author:buffer;",&buf, BUFLEN)) {
      desc->author=vlc_b64_decode(buf);
      if(desc->author) {
        handled=1;
        data=nl(data);
      }
    }
    if(filter(p_access, data,"a=Copyright:buffer;",&buf, BUFLEN)) {
      desc->copyright=vlc_b64_decode(buf);
      if(desc->copyright) {
        handled=1;
        data=nl(data);
      }
    }
    if(filter(p_access, data,"a=Abstract:buffer;",&buf, BUFLEN)) {
      desc->abstract=vlc_b64_decode(buf);
      if(desc->abstract) {
        handled=1;
        data=nl(data);
      }
    }
    if(filter(p_access, data,"a=StreamCount:integer;",&buf, BUFLEN)) {
        /* This way negative values are mapped to unfeasibly high
         * values, and will be discarded afterward
         */
        unsigned long tmp = strtoul(buf, NULL, 10);
        if ( tmp > UINT16_MAX )
            msg_Warn(p_access, "stream count out of bound: %lu\n", tmp);
        else
            desc->stream_count = tmp;
        desc->stream = malloc(sizeof(sdpplin_stream_t*)*desc->stream_count);
        handled=1;
        data=nl(data);
    }
    if(filter(p_access, data,"a=Flags:integer;",&buf, BUFLEN)) {
      desc->flags=atoi(buf);
      handled=1;
      data=nl(data);
    }

    if(!handled) {
#ifdef LOG
      int len = line_length(data);
      ;   len = len < BUFLEN ? len : BUFLEN-1;
      buf[len] = '\0';
      strncpy (buf, data, len);
      msg_Warn(p_access, "libreal: sdpplin: not handled: '%s'", buf);
#endif
      data=nl(data);
    }
  }

  free( decoded );
  free( buf );
  return desc;
}

void sdpplin_free(sdpplin_t *description) {

  int i;

  if( !description ) return;

  for( i=0; i<description->stream_count; i++ ) {
    if( description->stream[i] ) {
      free( description->stream[i]->id );
      free( description->stream[i]->bandwidth );
      free( description->stream[i]->range );
      free( description->stream[i]->length );
      free( description->stream[i]->rtpmap );
      free( description->stream[i]->mimetype );
      free( description->stream[i]->stream_name );
      free( description->stream[i]->mime_type );
      free( description->stream[i]->mlti_data );
      free( description->stream[i]->rmff_flags );
      free( description->stream[i]->asm_rule_book );
      free( description->stream[i] );
    }
  }
  if( description->stream_count )
    free( description->stream );

  free( description->owner );
  free( description->session_name );
  free( description->session_info );
  free( description->uri );
  free( description->email );
  free( description->phone );
  free( description->connection );
  free( description->bandwidth );
  free( description->title );
  free( description->author );
  free( description->copyright );
  free( description->keywords );
  free( description->asm_rule_book );
  free( description->abstract );
  free( description->range );
  free( description );
}

