/*****************************************************************************
 * ram.c : RAM playlist format import
 *****************************************************************************
 * Copyright (C) 2009 VLC authors and VideoLAN
 *
 * Authors: Srikanth Raju <srikiraju@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*
An example:
rtsp://helixserver.example.com/video1.rm?rpcontextheight=250
&rpcontextwidth=280&rpcontexturl="http://www.example.com/relatedinfo1.html"
rtsp://helixserver.example.com/video2.rm?rpurl="http://www.example.com/index.html"
rtsp://helixserver.example.com/sample1.smil?screensize=full
rtsp://helixserver.example.com/audio1.rm?start=55&end=1:25
rtsp://helixserver.example.com/introvid.rm?title="Introduction to Streaming Media
Production"&author="RealNetworks, Inc."&copyright="&#169;2001, RealNetworks, Inc."
rtsp://helixserver.example.com/song1.rm?clipinfo="title=Artist of the Year|artist name=Your Name
Here|album name=My Debut|genre=Rock|copyright=2001|year=2001|comments=This one really
knows how to rock!"

See also:
http://service.real.com/help/library/guides/realone/IntroGuide/HTML/htmfiles/ramsum.htm
http://service.real.com/help/library/guides/realone/IntroGuide/HTML/htmfiles/ramfile.htm
*/


/*****************************************************************************
 * Preamble
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <ctype.h>

#include <vlc_common.h>
#include <vlc_arrays.h>
#include <vlc_access.h>
#include <vlc_url.h>
#include <vlc_charset.h>

#include "playlist.h"

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int ReadDir( stream_t *, input_item_node_t * );
static void ParseClipInfo( const char * psz_clipinfo, char **ppsz_artist, char **ppsz_title,
                           char **ppsz_album, char **ppsz_genre, char **ppsz_year,
                           char **ppsz_cdnum, char **ppsz_comments );

/**
 * Import_RAM: main import function
 * @param p_this: this demux object
 * @return VLC_SUCCESS if everything is okay
 */
int Import_RAM( vlc_object_t *p_this )
{
    stream_t *p_demux = (stream_t *)p_this;
    const uint8_t *p_peek;

    if( !stream_HasExtension( p_demux, ".ram" )
     && !stream_HasExtension( p_demux, ".rm" ) )
        return VLC_EGENERIC;

    /* Many Real Media Files are misdetected */
    if( vlc_stream_Peek( p_demux->s, &p_peek, 4 ) < 4 )
        return VLC_EGENERIC;
    if( !memcmp( p_peek, ".ra", 3 ) || !memcmp( p_peek, ".RMF", 4 ) )
    {
        return VLC_EGENERIC;
    }

    msg_Dbg( p_demux, "found valid RAM playlist" );
    p_demux->pf_readdir = ReadDir;
    p_demux->pf_control = PlaylistControl;

    return VLC_SUCCESS;
}

/**
 * Skips blanks in a given buffer
 * @param s: input string
 * @param i_strlen: length of the buffer
 */
static const char *SkipBlanks( const char *s, size_t i_strlen )
{
    while( i_strlen > 0 ) {
        switch( *s )
        {
            case ' ':
            case '\t':
            case '\r':
            case '\n':
                --i_strlen;
                ++s;
                break;
            default:
                i_strlen = 0;
        }
    }
    return s;
}

/**
 * Converts a time of format hour:minutes:sec.fraction to seconds
 * @param s: input string
 * @param i_strlen: length of the buffer
 * @return time in seconds
 */
static int ParseTime( const char *s, size_t i_strlen)
{
    // need to parse hour:minutes:sec.fraction string
    int result = 0;
    int val;
    const char *end = s + i_strlen;
    // skip leading spaces if any
    s = SkipBlanks(s, i_strlen);

    val = 0;
    while( (s < end) && isdigit((unsigned char)*s) )
    {
        int newval = val*10 + (*s - '0');
        if( newval < val )
        {
            // overflow
            val = 0;
            break;
        }
        val = newval;
        ++s;
    }
    result = val;
    s = SkipBlanks(s, end-s);
    if( *s == ':' )
    {
        ++s;
        s = SkipBlanks(s, end-s);
        result = result * 60;
        val = 0;
        while( (s < end) && isdigit((unsigned char)*s) )
        {
            int newval = val*10 + (*s - '0');
            if( newval < val )
            {
                // overflow
                val = 0;
                break;
            }
            val = newval;
            ++s;
        }
        result += val;
        s = SkipBlanks(s, end-s);
        if( *s == ':' )
        {
            ++s;
            s = SkipBlanks(s, end-s);
            result = result * 60;
            val = 0;
            while( (s < end) && isdigit((unsigned char)*s) )
            {
                int newval = val*10 + (*s - '0');
                if( newval < val )
                {
                    // overflow
                    val = 0;
                    break;
                }
                val = newval;
                ++s;
            }
            result += val;
            // TODO: one day, we may need to parse fraction for sub-second resolution
        }
    }
    return result;
}

static int ReadDir( stream_t *p_demux, input_item_node_t *p_subitems )
{
    const char *psz_prefix = p_demux->psz_url;
    if( unlikely(psz_prefix == NULL) )
        return VLC_SUCCESS;

    char       *psz_line;
    char       *psz_artist = NULL, *psz_album = NULL, *psz_genre = NULL, *psz_year = NULL;
    char       *psz_author = NULL, *psz_title = NULL, *psz_copyright = NULL, *psz_cdnum = NULL, *psz_comments = NULL;
    const char **ppsz_options = NULL;
    int        i_options = 0, i_start = 0, i_stop = 0;
    bool b_cleanup = false;
    input_item_t *p_input;

    psz_line = vlc_stream_ReadLine( p_demux->s );
    while( psz_line )
    {
        char *psz_parse = psz_line;

        /* Skip leading tabs and spaces */
        while( *psz_parse == ' ' || *psz_parse == '\t' ||
               *psz_parse == '\n' || *psz_parse == '\r' ) psz_parse++;

        if( *psz_parse == '#' )
        {
            /* Ignore comments */
        }
        else if( *psz_parse )
        {
            char *psz_mrl, *psz_option_next, *psz_option;
            char *psz_param, *psz_value;

            /* Get the MRL from the file. Note that this might contain parameters of form ?param1=value1&param2=value2 in a RAM file */
            psz_mrl = ProcessMRL( psz_parse, psz_prefix );

            b_cleanup = true;
            if ( !psz_mrl ) goto error;

            /* We have the MRL, now we have to check for options and parse them from MRL */
            psz_option = strchr( psz_mrl, '?' ); /* Look for start of options */
            if( psz_option )
            {
                /* Remove options from MRL
                   because VLC can't get the file otherwise */
                *psz_option = '\0';
                psz_option++;
                psz_option_next = psz_option;
                while( 1 ) /* Process each option */
                {
                    /* Look for end of first option which maybe a & or \0 */
                    psz_option = psz_option_next;
                    psz_option_next = strchr( psz_option, '&' );
                    if( psz_option_next )
                    {
                        *psz_option_next = '\0';
                        psz_option_next++;
                    }
                    else
                        psz_option_next = strchr( psz_option, '\0' );
                    /* Quit if options are over */
                    if( psz_option_next == psz_option )
                        break;

                    /* Parse out param and value */
                    psz_param = psz_option;
                    psz_value = strchr( psz_option, '=' );
                    if( psz_value == NULL )
                        break;
                    *psz_value = '\0';
                    psz_value++;

                    /* Take action based on parameter value in the below if else structure */
                    /* TODO: Remove any quotes surrounding values if required */
                    if( !strcmp( psz_param, "clipinfo" ) )
                    {
                        ParseClipInfo( psz_value, &psz_artist, &psz_title,
                           &psz_album, &psz_genre, &psz_year,
                           &psz_cdnum, &psz_comments ); /* clipinfo has various sub parameters, which is parsed by this function */
                    }
                    else if( !strcmp( psz_param, "author" ) )
                    {
                        psz_author = vlc_uri_decode_duplicate(psz_value);
                        EnsureUTF8( psz_author );
                    }
                    else if( !strcmp( psz_param, "start" )
                            && strncmp( psz_mrl, "rtsp", 4 ) /* Our rtsp-real or our real demuxer is wrong */  )
                    {
                        i_start = ParseTime( psz_value, strlen( psz_value ) );
                        char *temp;
                        if( i_start )
                        {
                            if( asprintf( &temp, ":start-time=%d", i_start ) != -1 )
                                TAB_APPEND( i_options, ppsz_options, temp );
                        }
                    }
                    else if( !strcmp( psz_param, "end" ) )
                    {
                        i_stop = ParseTime( psz_value, strlen( psz_value ) );
                        char *temp;
                        if( i_stop )
                        {
                            if( asprintf( &temp, ":stop-time=%d", i_stop ) != -1 )
                                TAB_APPEND( i_options, ppsz_options, temp );
                        }
                    }
                    else if( !strcmp( psz_param, "title" ) )
                    {
                        free( psz_title );
                        psz_title = vlc_uri_decode_duplicate(psz_value);
                        EnsureUTF8( psz_title );
                    }
                    else if( !strcmp( psz_param, "copyright" ) )
                    {
                        psz_copyright = vlc_uri_decode_duplicate(psz_value);
                        EnsureUTF8( psz_copyright );
                    }
                    else
                    {   /* TODO: insert option anyway? Currently ignores*/
                        //TAB_APPEND( i_options, ppsz_options, psz_option );
                    }
                }
            }

            /* Create the input item and pump in all the options into playlist item */
            p_input = input_item_New( psz_mrl, psz_title );
            if( !p_input )
            {
                free( psz_mrl );
                goto error;
            }
            if( ppsz_options )
                input_item_AddOptions( p_input, i_options, ppsz_options, 0 );

            if( !EMPTY_STR( psz_artist ) ) input_item_SetArtist( p_input, psz_artist );
            if( !EMPTY_STR( psz_author ) ) input_item_SetPublisher( p_input, psz_author );
            if( !EMPTY_STR( psz_title ) ) input_item_SetTitle( p_input, psz_title );
            if( !EMPTY_STR( psz_copyright ) ) input_item_SetCopyright( p_input, psz_copyright );
            if( !EMPTY_STR( psz_album ) ) input_item_SetAlbum( p_input, psz_album );
            if( !EMPTY_STR( psz_genre ) ) input_item_SetGenre( p_input, psz_genre );
            if( !EMPTY_STR( psz_year ) ) input_item_SetDate( p_input, psz_year );
            if( !EMPTY_STR( psz_cdnum ) ) input_item_SetTrackNum( p_input, psz_cdnum );
            if( !EMPTY_STR( psz_comments ) ) input_item_SetDescription( p_input, psz_comments );

            input_item_node_AppendItem( p_subitems, p_input );
            input_item_Release( p_input );
            free( psz_mrl );
        }

 error:
        /* Fetch another line */
        free( psz_line );
        psz_line = vlc_stream_ReadLine( p_demux->s );
        if( !psz_line ) b_cleanup = true;

        if( b_cleanup )
        {
            /* Cleanup state */
            while( i_options-- ) free( (char*)ppsz_options[i_options] );
            FREENULL( ppsz_options );
            FREENULL( psz_artist );
            FREENULL( psz_title );
            FREENULL( psz_author );
            FREENULL( psz_copyright );
            FREENULL( psz_album );
            FREENULL( psz_genre );
            FREENULL( psz_year );
            FREENULL( psz_cdnum );
            FREENULL( psz_comments );
            i_options = 0;
            i_start = 0;
            i_stop = 0;
            b_cleanup = false;
        }
    }
    return VLC_SUCCESS;
}

/**
 * Parses clipinfo parameter
 * @param psz_clipinfo: string containing the clipinfo parameter along with quotes
 * @param ppsz_artist: Buffer to store artist name
 * @param ppsz_title: Buffer to store title
 * @param ppsz_album: Buffer to store album
 * @param ppsz_genre: Buffer to store genre
 * @param ppsz_year: Buffer to store year
 * @param ppsz_cdnum: Buffer to store cdnum
 * @param ppsz_comments: Buffer to store comments
 */
static void ParseClipInfo( const char *psz_clipinfo, char **ppsz_artist, char **ppsz_title,
                           char **ppsz_album, char **ppsz_genre, char **ppsz_year,
                           char **ppsz_cdnum, char **ppsz_comments )
{
    char *psz_option_next, *psz_option_start, *psz_param, *psz_value, *psz_suboption;
    char *psz_temp_clipinfo = strdup( psz_clipinfo );
    psz_option_start = strchr( psz_temp_clipinfo, '"' );
    if( !psz_option_start )
    {
        free( psz_temp_clipinfo );
        return;
    }

    psz_option_start++;
    psz_option_next = psz_option_start;
    while( 1 ) /* Process each sub option */
    {
        /* Get the sub option */
        psz_option_start = psz_option_next;
        psz_option_next = strchr( psz_option_start, '|' );
        if( psz_option_next )
            *psz_option_next = '\0';
        else
            psz_option_next = strchr( psz_option_start, '"' );
        if( psz_option_next )
            *psz_option_next = '\0';
        else
            psz_option_next = strchr( psz_option_start, '\0' );
        if( psz_option_next == psz_option_start )
            break;

        psz_suboption = strdup( psz_option_start );
        if( !psz_suboption )
            break;

        /* Parse out param and value */
        psz_param = psz_suboption;
        if( strchr( psz_suboption, '=' ) )
        {
            psz_value = strchr( psz_suboption, '=' ) + 1;
            *( strchr( psz_suboption, '=' ) ) = '\0';
        }
        else
        {
            free( psz_suboption );
            break;
        }
        /* Put into args */
        if( !strcmp( psz_param, "artist name" ) )
            *ppsz_artist = vlc_uri_decode_duplicate( psz_value );
        else if( !strcmp( psz_param, "title" ) )
            *ppsz_title = vlc_uri_decode_duplicate( psz_value );
        else if( !strcmp( psz_param, "album name" ) )
            *ppsz_album = vlc_uri_decode_duplicate( psz_value );
        else if( !strcmp( psz_param, "genre" ) )
            *ppsz_genre = vlc_uri_decode_duplicate( psz_value );
        else if( !strcmp( psz_param, "year" ) )
            *ppsz_year = vlc_uri_decode_duplicate( psz_value );
        else if( !strcmp( psz_param, "cdnum" ) )
            *ppsz_cdnum = vlc_uri_decode_duplicate( psz_value );
        else if( !strcmp( psz_param, "comments" ) )
            *ppsz_comments = vlc_uri_decode_duplicate( psz_value );

        free( psz_suboption );
        psz_option_next++;
    }

    free( psz_temp_clipinfo );
}
