/*
 *   MediaMVP Server Library
 *
 *   (C) 2003 Dominic Morris
 *
 *   $Id: stream.c,v 1.38 2004/10/13 10:47:02 dom Exp $
 *   $Date: 2004/10/13 10:47:02 $
 *
 *
 *   Handles the media stream socket..all a bit of mess at the moment..
 *
 */

#include "libmvp_internal.h"
#include "ringbuffy.h"

typedef struct {
    int           id;
    char          filename[FILENAME_MAX+1];
    off_t         filelength;
    ringbuffy     buffy;
    int           start;
    uint32_t      offset;              /* Should be 64 bit - check protocol */
    uint32_t      blocklen;
    fops_t       *fops;
    void         *urlptr;
} media_t;

struct _stream {
    dongle_t      *dongle;
    int            fd;
    struct event   event;
    char           inbuf[2048];
    int            inbufstart;
    media_t        media;
    int            mediatype;
    int            last_mediatype;
    bool_t         pending_data;
    int            min_msg_length;
};




/* Definitions for the commands the MVP gives us */
#define MEDIA_REQUEST    2
#define MEDIA_STOP       3
#define MEDIA_BLOCK      4
#define MEDIA_STEP       5
#define MEDIA_SEEK       7
#define MEDIA_8          8

static void       stream_accept(int fd, short event, void *arg);
static void       stream_read(int fd, short event, void *arg);
static int        media_request(stream_t *stream, unsigned char *buf, int len);
static int        media_stop(stream_t *stream, unsigned char *buf, int len);
static int        media_block(stream_t *stream, unsigned char *buf, int len);
static int        media_seek(stream_t *stream, unsigned char *buf, int len);
static int        media_step(stream_t *stream, unsigned char *buf, int len);
static int        media_8(stream_t *stream, unsigned char *buf, int len);

static void       buffer_read(void *cb_ptr);
static void       stream_write_data(stream_t *stream, bool_t flush);


struct event      listen_event;
static int        listen_sock;

#define DEBUG_TIMESTAMP debug_timestamp()

static char buf[30];
static const char * debug_timestamp()
{
   struct timeval tv;
   gettimeofday(&tv, NULL);
   snprintf(buf,30,"[TS: %ld.%03ld]", tv.tv_sec, (tv.tv_usec+500)/1000);
   return buf;
}


int stream_init()
{

    if ( (listen_sock = tcp_listen(NULL,initparams->stream_port,0) ) == -1 ) {
        Dprintf(ERROR,"Cannot listen to port %d\n",initparams->stream_port);
        return -1;
    }

    event_set(&listen_event,listen_sock,EV_READ,stream_accept,&listen_event);

    event_add(&listen_event,NULL);

    Dprintf(INFO,"Listening on port %d for stream connection\n",initparams->stream_port);

    return 0;

}

void stream_exit()
{
    event_del(&listen_event);

    close(listen_sock);
}



static stream_t *new_stream(dongle_t *dongle)
{
    stream_t   *stream = (stream_t*)calloc(1,sizeof(*stream));

    stream->dongle     = dongle;
    stream->inbufstart = 0;
    stream->mediatype  = 0;
    stream->media.urlptr = NULL;

    if ( initparams->dongle_version < 21365 ) {
        stream->min_msg_length = 24;
    } else {
        stream->min_msg_length = 40;
    }

    return stream;
}

static void delete_media(media_t *media)
{
    ring_destroy(&media->buffy);

    if ( media->urlptr ) {
        media->fops->close(media->urlptr);
    }

    media->urlptr = NULL;
}

void delete_stream(stream_t *stream)
{
    /* Cleanup the media file stuff */
    if ( stream->mediatype ) {
        delete_media(&stream->media);    
        stream->mediatype = 0;
    }

    /* Now clean up the stream input channel */
    event_del(&stream->event);
    close(stream->fd);
    shutdown(stream->fd,SHUT_RDWR);
    free(stream);
}

static void stream_accept(int fd, short event, void *arg)
{
    struct event       *ev = (struct event*)arg;
    dongle_t           *dongle;
    stream_t           *stream;
    char               *hostname;
    int                 cfd;
    int                 WRITEBUFSIZE = 2000;

    /* Reschedule event */
    event_add(ev,NULL);

    if ( ( cfd = tcp_accept(fd,&hostname) ) == -1 ) {
        return;
    }
    Dprintf(INFO,"Accepted stream connection from %s\n",hostname);
    dongle = dongle_return(hostname);

    if ( dongle->stream ) {
        dongle_close(dongle);
        dongle = dongle_return(hostname);
    }


    dongle->stream = stream = new_stream(dongle);
    stream->dongle = dongle;

    setsockopt(cfd, SOL_SOCKET, SO_SNDBUF, &WRITEBUFSIZE, sizeof(WRITEBUFSIZE));

    event_set(&stream->event,cfd,EV_READ,stream_read,stream);
    stream->fd = cfd;
    event_add(&stream->event,NULL);
}



static void stream_read(int fd, short event, void *arg)
{
    stream_t    *stream = (stream_t *)arg;
    dongle_t    *dongle = stream->dongle;
    int          r,diff;

    /* Reschedule event */
    event_add(&stream->event,NULL);


    /* We should do some buffering here - we occasionally don't get the
       filename with the initial request packet
    */
    r = read(fd,stream->inbuf + stream->inbufstart,sizeof(stream->inbuf)-stream->inbufstart);

    /* Socket closed/error */
    if ( r == 0 || r == -1 ) {
        dongle_close(dongle);
        return;
    }

    /* We only expect to get one message a time */

    stream->inbufstart += r;

    while ( 1 ) {
        Dprintf(DEBUG, "%s try processing stream msg %d ...\n", DEBUG_TIMESTAMP, stream->inbuf[0]);

        switch ( stream->inbuf[0] ) {
        case MEDIA_REQUEST:
            diff = media_request(stream,(unsigned char*)stream->inbuf,stream->inbufstart);
            break;
        case MEDIA_STOP:
            diff = media_stop(stream,(unsigned char*)stream->inbuf,stream->inbufstart);
            break;
        case MEDIA_BLOCK:
            diff = media_block(stream,(unsigned char*)stream->inbuf,stream->inbufstart);
            break;
        case MEDIA_SEEK:
            diff = media_seek(stream,(unsigned char*)stream->inbuf,stream->inbufstart);
            break;
        case MEDIA_STEP:
            diff = media_step(stream,(unsigned char*)stream->inbuf,stream->inbufstart);
            break;
        case MEDIA_8:
            diff = media_8(stream,(unsigned char*)stream->inbuf,stream->inbufstart);
            break;
        default:
            Dprintf(INFO,"Unknown Stream Message\n");
            dump_hex(INFO,(unsigned char*)stream->inbuf,r);
            diff = stream->inbufstart;

        }
        if ( diff == 0 ) {
            break;
        } else if ( diff > 0 ) {
            int  rem = stream->inbufstart - diff;
            Dprintf(DEBUG, "%s processed stream msg %d\n", DEBUG_TIMESTAMP, stream->inbuf[0]);
            if ( rem > 0 ) {
                memmove(stream->inbuf,stream->inbuf+diff,rem);
                stream->inbufstart = rem;
            } else {
                stream->inbufstart = 0;
                break;
            }
        }
    }
}

static int media_request(stream_t *stream, unsigned char *buf, int len)
{
    int            filename_len;
    unsigned char *ptr;
    media_t       *media = &stream->media;

    if ( len < stream->min_msg_length ) {
        return 0;
    }


    ptr = buf + stream->min_msg_length - 4 ;

    BUF_TO_INT16(filename_len,ptr);
    ptr += 2;

    if ( len < filename_len + stream->min_msg_length  ) {
        return 0; /* Wait fo more data */
    }

    strcpy(media->filename,(char*)ptr);    /* Filename is NUL terminated */

    Dprintf(DEBUG,"Client has requested %s\n",media->filename);

    /* Now set up the reply string */
    /* Do a random stream id */
    ++media->id;

    /* Setup a media id thing */
    ptr = buf + stream->min_msg_length - 6;
    INT16_TO_BUF(media->id,ptr);

    if ( stream->mediatype ) {
        delete_media(&stream->media);
        stream->last_mediatype = stream->mediatype;
    }
    stream->mediatype = 0;

    /* Open the filename now - need to handle no file existing.. */
    media->urlptr = urlhandler_open(media->filename,&stream->mediatype,&media->fops,buffer_read,stream);

    /* If we write back exactly what we've got then the MVP will abort trying to read
       the stream, neat! Suspect the trick is buf[4] being 0x00
    */
    if ( media->urlptr != NULL ) {
        if ( (stream->mediatype & MEDIA_MASK) == MEDIA_MPEG ) {
            off_t	length;
            ring_init(&media->buffy, initparams->mpeg_bufsize * 12);

            buf[4] = 0x01;
            buf[6] = 0xFF;

            /* These parameters don't actually seem to matter... */
            /* [Ingo Schneider] Actually, they DO. Bitrate is needed for skip buttons. */
            /*                  and size is needed for 0-9 buttons */
            buf[8] =  0x02;    /* FIXME: 720 */
            buf[9] =  0xd0;
            if ( stream->dongle->tvmode == 0 ) { /* If NTSC mode */
                buf[10] = 0x01;    /* FIXME: 480 */
                buf[11] = 0xe0;
            } else {
                buf[10] = 0x02;    /* FIXME: 576 */
                buf[11] = 0x40;
            }
            buf[14] = 0x3a;    /* Bitrate 15000, needed for skip buttons. Shouldn't be hardcoded, DVDs have 24500, some small mpeg1s only 1000. */
            buf[15] = 0x98;

            /* We fill the file length up with a fop call, streams return
             * 0 since we can't seek in them
             */
            length = 0;
            media->fops->info(media->urlptr,URLFILE_SIZE,&length);
            buf[16] = (length >> 24 ) & 0xFF;
            buf[17] = (length >> 16 ) & 0xFF;
            buf[18] = (length >> 8 ) & 0xFF;
            buf[19] = (length >> 0 ) & 0xFF;

            media->blocklen = initparams->mpeg_bufsize;
        } else {
            off_t length;

            ring_init(&media->buffy, initparams->mp3_bufsize * 12);
            media->blocklen = initparams->mp3_bufsize;
            buf[4] = 0x02;
            /* We fill the file length up with a fop call, streams return
             * 0 since we can't seek in them
             */
            length = 0;
            media->fops->info(media->urlptr,URLFILE_SIZE,&length);
            buf[16] = (length >> 24 ) & 0xFF;
            buf[17] = (length >> 16 ) & 0xFF;
            buf[18] = (length >> 8 ) & 0xFF;
            buf[19] = (length >> 0 ) & 0xFF;
        }
        media->start = 1;       

    } else {
        Dprintf(ERROR,"No media file found (%s)...ack!\n",media->filename);
        stream->mediatype = 0;
    }

    write(stream->fd,buf,stream->min_msg_length);

    return stream->min_msg_length + filename_len;
}

static int media_stop(stream_t *stream, unsigned char *buf, int len)
{
    unsigned char *ptr;
    media_t       *media = &stream->media;

    if ( len < stream->min_msg_length ) {
        return 0;
    }
    Dprintf(DEBUG,"Stop\n");

    if ( stream->mediatype ) {
        delete_media(media);
        stream->last_mediatype = stream->mediatype;
    }
    stream->mediatype = 0;

    ptr = buf + 18;
    INT16_TO_BUF(media->id,ptr);
    write(stream->fd,buf,len);

    return stream->min_msg_length;
}

/* We've had a request for a block of data */
static int media_block(stream_t *stream, unsigned char *buf, int len)
{
    unsigned char *ptr = buf + 8;
    media_t       *media = &stream->media;
    int            mlen;

    if ( len < stream->min_msg_length ) {
        return 0;
    }


    BUF_TO_INT32(media->blocklen,ptr);

    /* Move this somewhere.. */

    if ( stream->mediatype & MEDIA_LIVE ) {
        if ( media->urlptr == NULL ) {
            /* The file has come to an end */
            stream_write_data(stream,TRUE);
            stream->pending_data = FALSE;
        } else if ( !media->start && ring_rest(&media->buffy) >= media->blocklen ) {
            stream_write_data(stream,FALSE);
            stream->pending_data = FALSE;
        } else {
            Dprintf(DEBUG, "%s Not enough data ...\n", DEBUG_TIMESTAMP);
            stream->pending_data = TRUE;

        }

        return stream->min_msg_length;
    }


    /* It's just a file on disc, no buffering needed */
    mlen = ring_write_file(&media->buffy,media->fops,media->urlptr,media->blocklen);

    if ( mlen < 0 ) {
        mlen = 0;
    }

    Dprintf(DEBUG, "%s sending block at %u ... %d\n", DEBUG_TIMESTAMP, media->offset,mlen);

    ptr = buf + 8;
    INT32_TO_BUF(mlen,ptr);
    INT32_TO_BUF(media->offset,ptr);

    media->offset += mlen;

    write(stream->fd,buf,stream->min_msg_length);
    ring_read_file(&media->buffy,stream->fd,mlen);


    return stream->min_msg_length;
}

static int media_seek(stream_t *stream, unsigned char *buf, int len)
{
    unsigned char *ptr;
    media_t       *media = &stream->media;
    uint32_t       offset;


    /* Needs extending slightly... */
    if ( len < stream->min_msg_length ) {
        return 0;
    }
    Dprintf(INFO,"Seek received\n");
    dump_hex(INFO,buf,stream->min_msg_length);

    if ( (stream->mediatype & MEDIA_LIVE ) == 0 ) {
        ptr = buf + 8;
        BUF_TO_INT32(offset,ptr);
        Dprintf(INFO,"Current offset is %d need %d\n",media->offset,offset);

        Dprintf(DEBUG, "%s seeking to %u ...\n", DEBUG_TIMESTAMP, offset);
        if ( media->fops->seek(media->urlptr,offset,SEEK_SET) > 0 ) {
            media->offset = offset;
        }
        ptr = buf + 8;
        BUF_TO_INT32(media->offset,ptr);
    }

    /* Just acknowledge it back */
    write(stream->fd,buf,stream->min_msg_length);

    return stream->min_msg_length;
}

static int media_8(stream_t *stream, unsigned char *buf, int len)
{
    /* Needs extending slightly... */
    if ( len < stream->min_msg_length ) {
        return 0;
    }
    Dprintf(INFO,"Media 8\n");
    dump_hex(INFO,buf,stream->min_msg_length);
    write(stream->fd,buf,stream->min_msg_length);

    return stream->min_msg_length;
}

/** \internal
 *
 *  \brief Handle a step message
 *
 *  \param stream Stream this message is associate dwith
 *  \param buf The message
 *  \param len Length of the message
 *
 *  This should only be called for non-streaming mpeg files
 */
static int media_step(stream_t *stream, unsigned char *buf, int len)
{
    unsigned char *ptr;
    media_t       *media = &stream->media;
    off_t          offset;
    int            mlen;
    gopseek_t      gop;

    /* Needs extending slightly... */
    if ( len < stream->min_msg_length ) {
        return 0;
    }


    if ( buf[9] == 0x00 ) {
        gop.count = -2;
    } else {
        gop.count = 1;
    }

    offset = media->fops->info(media->urlptr,URLFILE_IFRAME,&gop);
   

    if ( offset != -1 ) {
        media->offset = gop.start;
        media->fops->seek(media->urlptr,media->offset,SEEK_SET);
        ring_clear(&media->buffy);
        ptr = buf + 12;
        INT32_TO_BUF(gop.length,ptr);
        ptr = buf + 16;
        INT32_TO_BUF(media->offset,ptr);
        
        Dprintf(DEBUG, "%s Step sending block at %u ... length %u\n", DEBUG_TIMESTAMP, media->offset,gop.length);
        write(stream->fd,buf,stream->min_msg_length);
            
        /* It's just a file on disc, no buffering needed */
        mlen = ring_write_file(&media->buffy,media->fops,media->urlptr,gop.length);
        
        
        ring_read_file(&media->buffy,stream->fd,mlen);
    } else {       
        media->fops->seek(media->urlptr,media->offset,SEEK_SET); 
        ptr = buf + 12;
        INT32_TO_BUF(0,ptr);
        ptr = buf + 16;
        INT32_TO_BUF(media->offset,ptr);
        write(stream->fd,buf,stream->min_msg_length);
    }

    return stream->min_msg_length;
}



/* This event is used for reading into the event buffer - called by the url
 handler
*/
static void buffer_read(void *cb_ptr)
{
    stream_t   *stream = (stream_t *)cb_ptr;
    media_t    *media  = &stream->media;
    int         r;

    r = ring_write_file(&media->buffy,media->fops,media->urlptr,media->buffy.size);

    /* Check for socket shutdown */
    if (  r == -1 || r == 0 ) {
        Dprintf(INFO,"Remote side shutdown on us <%s>\n", r == -1 ? strerror(errno) : "Closed");
        delete_media(media);
        stream->last_mediatype = stream->mediatype;
        stream->mediatype = 0;
        return;
    }


    if (media->start && ring_rest(&media->buffy) < media->blocklen * 4) {
        return; // small delay, but switching is smoother because mvp is always buffering a little
    }


    if ( stream->pending_data && ring_rest(&media->buffy) >= media->blocklen ) {
            media->start = 0;

        stream->pending_data = FALSE;
        stream_write_data(stream,FALSE);
    }


}


static void stream_write_data(stream_t *stream, bool_t flush)
{
    unsigned char      msg[50];
    unsigned char     *ptr;
    int                len;
    media_t           *media = &stream->media;

    if ( flush ) {
        if ( ring_rest(&media->buffy) >= media->blocklen ) {
            len = media->blocklen;
        } else {
            len = ring_rest(&media->buffy);
        }
    } else {
        if ( ring_rest(&media->buffy) >= media->blocklen ) {
            len = media->blocklen;
        } else {
            len = ring_rest(&media->buffy);
        }
    }

    /* Set up the media message */
    memset(msg,0,stream->min_msg_length);
    msg[0] = 0x04;
    ptr = msg+6;
    INT16_TO_BUF(media->id,ptr);

    INT32_TO_BUF(len,ptr);
    media->offset += len;
    INT32_TO_BUF(media->offset,ptr);



    write(stream->fd,msg,stream->min_msg_length);
    if ( len ) {
        ring_read_file(&media->buffy,stream->fd,len);
    }


    return;
}



/** \internal
 *  \brief Return the streamtype
 *
 *  \param stream Handle to the stream channel
 *
 *  \return The current streamtype
 */
int stream_get_type(stream_t *stream)
{
    return ( stream->mediatype );
}

/** \internal
 *  \brief Return the last streamtype
 *
 *  \param stream Handle to the stream channel
 *
 *  \return The current streamtype
 */
int stream_get_lasttype(stream_t *stream)
{
    return ( stream->last_mediatype );
}

/** \internal
 *  \brief Return the name of the current streaming file
 *
 *  \param stream Handle to the stream channel 
 *
 *  \return The current name/NULL
 */
char *stream_get_name(stream_t *stream)
{
    char  *name = NULL;
    media_t *media = &stream->media;

    if ( media->urlptr ) {
        media->fops->info(media->urlptr,URLFILE_NAME,&name);
    }
    return name;
}




/*
 * Local Variables:
 *  indent-tabs-mode:nil
 *  require-final-newline:t
 *  c-basic-offset: 4
 * End:
 */
