/*
 *   MediaMVP Server Library
 *
 *   (C) 2003 Dominic Morris
 *
 *   $Id: playlist.c,v 1.7 2004/05/07 22:50:13 dom Exp $
 *   $Date: 2004/05/07 22:50:13 $
 *
 *
 *   Deals with filelists
 */


#include "libmvp_internal.h"

typedef struct {
    fops_t     *fops;                /* Fops of the current playing file */
    readready_cb cb;
    void        *cb_ptr;
    int         *type_ptr;
    void      *current_media_ptr;
    char      *current_media;
    int        num_next_media;
    char     **next_media;
} playlist_t;

static int             playlist_readline(int fd, unsigned char *buf, int buflen);
static void            playlist_close(void *ptr);
static off_t           playlist_seek(void *ptr, off_t offset, int whence);
static int             playlist_read(void *ptr, unsigned char *buf, size_t buflen);
static int             playlist_info(void *ptr, int cmd, void *args);

static int             next_media_open(playlist_t *playlist);
static void            next_media_clear(playlist_t *playlist);
static void            next_media_pop(playlist_t *playlist);
static void            next_media_push(playlist_t *playlist, char *url);


static fops_t          playlist_fops = {
    NULL,
    NULL,
    playlist_close,
    playlist_seek,
    playlist_read,
    playlist_info
};


/** \brief Check whether a file is a playlist or not
 *
 * \param incoming_name Name of the file where this playlist came from
 * \param fd File to check
 * \param urlptr Void control pointer of the fileset that we are associated with
 * \param filetype Pointer to stream.c filetype
 * \param return_ptr Place to put whatever value we get from parsing urls etc
 * \param fops_ptr   Pointer to pointer for fops operation (contains the fd files fop values)
 * \param cb         Callback for read event
 * \param cb_ptr     Pointer to call the callback with
 *
 * \retval 1 - this was playlist
 * \retval 0 - this wasn't one
 * \retval -1 - unknown filetype
 *
 * We can do direct operations on this file
 * since we should only be called if we have a valid handle/socket (maybe it should be done via
 * the fops operation though?). We use the fops operation to seek back the start of the file
 * in case we spotted that the file is a media file
 *
 */
int playlist_check(char *incoming_name, int fd, void *urlptr, int *filetype, void **return_ptr,
                   fops_t **fops_ptr,readready_cb cb,void *cb_ptr)
{
    playlist_t   *playlist;
    char         *directory;
    char          buf[FILENAME_MAX+1];
    char          filename[2048];
    int           typ = file_get_type_fd(fd,(unsigned char*)filename);
    fops_t       *fops = *fops_ptr;
    char         *ptr;
    bool_t        firsttime = TRUE;
    bool_t        ispls = FALSE;
    bool_t        isfile = FALSE;
    int           ret;

    *return_ptr = NULL;

    /* If we detected a file type, return */
    if ( typ == MEDIA_MPEG || typ == MEDIA_MP3 ) {
        *filetype = typ;

        /* Rewind the file */
        fops->seek(urlptr,(off_t)0,SEEK_SET);

        return 0;
    }

    /* If we don't get a line within this many characters, something has gone wrong badly, so we'll assume
       it's an unknown filetype */
    if ( playlist_readline(fd,(unsigned char*)(filename+4),sizeof(filename)-4) ) {
        Dprintf(DEBUG,"Can't read a line - bad file\n");
        fops->close(urlptr);
        return -1;
    }

    ptr = filename;
    while ( *ptr ) {
        if ( !isprint(*ptr) ) {
            /* We'll take a stab and say it's mp3 */
            *filetype = MEDIA_MP3;
            /* Rewind the file */
            fops->seek(urlptr,(off_t)0,SEEK_SET);

            return 0;
        }
        ptr++;
    }

    /* Create a directory of some description, this already contains the url type */
    directory = strdup(incoming_name);
    if ( ( ptr = strrchr(directory,'/') ) != NULL ) {
         *ptr = 0;
    }
    Dprintf(DEBUG,"Directory is %s\n",directory);
    /* Create a playlist object */
    playlist = (playlist_t*)calloc(1,sizeof(*playlist));
    playlist->cb = cb;
    playlist->cb_ptr = cb_ptr;
    playlist->type_ptr = filetype;

    /* I'm too tired to think about an alternate way to structure this - this code was ripped
       out of one of the earlier betas where filelists weren't dealt with centrally
    */
    do {
        /* Kill any EOL characters */
        while ( ( ptr = strchr(filename,'\n') ) || (ptr = strchr(filename,'\r') ) ) {
            *ptr = 0;
        }

        if ( filename[0] == '[' ) {
            if ( firsttime && strncasecmp(filename+1,"playlist]",strlen("playlist]")) == 0 ) {
                ispls = TRUE;
            }
            goto cont;
        }

        if ( filename[0] == ';' ||
             filename[0] == '#' || strlen(filename) == 0 ) {
            goto cont;
        }

        /* Skip over .pls related things */
        if ( ispls ) {
            if ( strncasecmp(filename,"file",strlen("file")) ) {
                goto cont;
            } else {
                int      num;
                char    *endptr;
                char    *tempptr;

                num = strtol(filename+4,&endptr,10);

                if ( endptr != NULL && *endptr == '=' ) {
                    endptr++; /* Now points to filename, so get it */
                    while ( isspace(*endptr) )
                        endptr++;
                    /* Shove things along - can't use strcpy because
                       the strings overlap */
                    tempptr = filename;
                    while ( (*tempptr++ = *endptr++) != 0 )
                        ;
                    /* Additional sanity check */
                    if ( strlen(filename) == 0 ) {
                        goto cont;
                    }
                } else {        /* Not a number, ignore line? */
                    goto cont;
                }
            }
        }

        /* Try and be almost intelligent about adding the file */
        isfile = TRUE;
        if ( filename[0] != '/' ) {
            /* Add the line in the buffer onto the playlist */
            if ( strncmp(filename,"http://",strlen("http://")) == 0 ) {
                snprintf(buf,sizeof(buf),"%s",filename);
                isfile = FALSE;
            } else {
                snprintf(buf,sizeof(buf),"%s/%s",directory,filename);
            }
        } else {  /* We assume the complete path is there.. */
            snprintf(buf,sizeof(buf),"file://%s",filename);
        }

        /* Test to see if the file exists now */
        if ( isfile && file_get_type(buf + strlen("file://")) <= 0 ) {
            goto cont;
        }

        Dprintf(DEBUG,"Adding URL %s\n",buf);
        if ( firsttime ) {
            next_media_clear(playlist);
            firsttime = FALSE;
        }
        next_media_push(playlist,buf);
    cont:
		;
    } while ( playlist_readline(fd,(unsigned char*)filename,sizeof(filename)) == 0 );

    /* Free up our allocation */
    free(directory);



    /* Override the file operations with playlist ones */
    *fops_ptr = &playlist_fops;

    /* Now loop, trying to open a file */
    while ( 1 ) {
        ret = next_media_open(playlist);
        if ( ret == 0 ) {
            *return_ptr = playlist;
            return 1;
        } else if ( ret == -1 ) {   /* No files... */
            break;
        }
    }
    /* No valid files in playlist */
    playlist_close(playlist);
    return -1;
}



/** \brief Read in a line from a socket, skipping cr/nl at start
 *
 *  \param fd File to read from
 *  \param buf To place line in
 *  \param buflen Max length to read
 *
 *  \return 0 on succesful read, 1 on not so..
 */
static int playlist_readline(int fd, unsigned char *buf, int buflen)
{
    int           red;
    unsigned char c;
    unsigned char *ptr = buf;

    while ( buflen ) {
        *ptr = 0;
        red = read(fd,&c,1);
        /* Signal failure if necessary */
        if ( red == 0 || red == -1 ) {
            return 1;
        }
        /* Skip over white space */
        if ( (c == '\n' || c =='\r') && ptr == buf )
            continue;
        if ( c == '\n' || c == '\r' ) {
            *ptr = 0;
            return 0;
        } else {
            *ptr++ = c;
            buflen--;
        }
    }
    /* Was a long line, so probably not a playlist... */
    return 1;
}


/* Fops operations, these delegate to the lower level...ouch! */
static void playlist_close(void *ptr)
{
    playlist_t   *playlist = (playlist_t*)ptr;
    Dprintf(DEBUG,"Closing playlist at %p\n",ptr);
    if ( playlist->current_media ) {
        playlist->fops->close(playlist->current_media_ptr);
    }
    playlist->current_media = NULL;
    next_media_clear(playlist);
    free(playlist);
}


static off_t playlist_seek(void *ptr, off_t offset, int whence)
{
    playlist_t   *playlist = (playlist_t*)ptr;

    if ( playlist->current_media ) {
        return playlist->fops->seek(playlist->current_media_ptr,offset,whence);
    }
    return (off_t) -1;
}

static int playlist_read(void *ptr, unsigned char *buf, size_t buflen)
{
   playlist_t   *playlist = (playlist_t*)ptr;
   int           red;

    if ( playlist->current_media ) {
        red = playlist->fops->read(playlist->current_media_ptr,buf,buflen);
        if ( red == -1 || red == 0 ) {    /* File has closed, lets open the next one */
            /* Close the exhausted one */
            playlist->fops->close(playlist->current_media_ptr);
            /* Loop to try and find one that works */
            while ( next_media_open(playlist) == 0 ) {
                int  red2;

                red2 = playlist->fops->read(playlist->current_media_ptr,buf + red,buflen - red);

                if ( red2 > 0 ) {
                    red += red2;
                    break;
                }
            }
        }
        return red;
    }

    return -1;
}


static int playlist_info(void *ptr, int cmd, void *args)
{
    playlist_t   *playlist = (playlist_t*)ptr;
    if ( playlist->current_media ) {
        return playlist->fops->info(playlist->current_media_ptr,cmd,args);
    }
    return -1;
}



static int next_media_open(playlist_t *playlist)
{
    next_media_pop(playlist);

    if ( playlist->current_media == NULL ) {
        return -1;
    }

    Dprintf(DEBUG,"Trying to open <%s>\n",playlist->current_media);
    playlist->current_media_ptr = urlhandler_open(playlist->current_media,playlist->type_ptr,
                                                  &playlist->fops,playlist->cb,playlist->cb_ptr);

    if ( playlist->current_media_ptr != NULL ) {
        Dprintf(DEBUG,"Opened ok\n");
        return 0;
    }

    /* No more file left */
    return 1;
}

static void next_media_clear(playlist_t *playlist)
{
    int         i;

    if ( playlist->num_next_media ) {
        for ( i = 0; i > playlist->num_next_media; i++ ) {
            free(playlist->next_media[i]);
        }
        free(playlist->next_media);
        playlist->next_media = NULL;
        playlist->num_next_media = 0;
    }
}

static void next_media_pop(playlist_t *playlist)
{
    int       i;

    if ( playlist->num_next_media ) {     /* Which it already is... */
        if ( playlist->current_media ) {
            free(playlist->current_media);
        }

        playlist->current_media = playlist->next_media[0];

        for ( i = 1; i < playlist->num_next_media; i++ ) {
            playlist->next_media[i-1] = playlist->next_media[i];
        }
        if ( --playlist->num_next_media == 0 ) {
            free(playlist->next_media);
            playlist->next_media = NULL;
        }
    } else if ( playlist->current_media ) {
        free(playlist->current_media);
        playlist->current_media = NULL;
    }
}

static void next_media_push(playlist_t *playlist, char *url)
{
    int       i = playlist->num_next_media++;

    playlist->next_media = (char **)realloc(playlist->next_media,playlist->num_next_media * sizeof(char *));
    playlist->next_media[i] = strdup(url);
}
