/*
 *   MediaMVP Plugin
 *
 *   (C) 2003 Dominic Morris
 *
 *   $Id: vdrrecurl.c,v 1.14 2005/03/31 11:25:47 dom Exp $
 *   $Date: 2005/03/31 11:25:47 $
 *
 *
 *   Handle the vdrrecurl:// URL type
 */

#include "libmvp.h"

#include "vdrrecurl.h"
#include <vdr/recording.h>

static fops_t vdrrecurl_fops = {
    NULL,
    cMediamvpVdrRecURL::sOpen,
    cMediamvpVdrRecURL::sClose,
    cMediamvpVdrRecURL::sSeek,
    cMediamvpVdrRecURL::sRead,
    cMediamvpVdrRecURL::sInfo
};


static int mystat(const char *fname, int fnum, struct stat *sb)
{
    char    buf[FILENAME_MAX+1];
    
    snprintf(buf,sizeof(buf),"%s/%03d.vdr",fname,fnum);

    if ( stat(buf,sb) != 0 ) {
        snprintf(buf,sizeof(buf),"%s/%03d.mpg",fname,fnum);

        return stat(buf,sb);
    }
    return 0;
}

cMediamvpVdrRecURL::cMediamvpVdrRecURL()
{
    fd = -1;
    strip.tmplen = 0;
    strip.offset = 0;
    strip.pid = 0xC1;
    name = NULL;
}

cMediamvpVdrRecURL::~cMediamvpVdrRecURL()
{
    if ( fd != -1 ) {
        close(fd);
        fd = -1;
    }

    if ( name != NULL ) {
        free(name);
        name = NULL;
    }
}

void cMediamvpVdrRecURL::Init()
{
    urlhandler_add("vdrrec://",&vdrrecurl_fops);
}



void *cMediamvpVdrRecURL::sOpen(char *name, int *type, fops_t **fops, readready_cb cb, void *cb_ptr)
{

    cMediamvpVdrRecURL *vdrrecurl = new cMediamvpVdrRecURL;
    char               *realname;
    cRecordings         Recordings;  

 

    if ( strncmp(name,"vdrrec://",strlen("vdrrec://") ) ) {
        return NULL;
    }

    if ( !Recordings.Load() ) {
        return NULL;
    }

    realname = name + strlen("vdrrec://");


    cRecording *rec = Recordings.First();
    while ( rec != NULL && strcmp(rec->Title(' ',true), realname ) ) {
        rec = Recordings.Next(rec);
    }


    if ( rec == NULL ) {
        Dprintf(INFO,"Cannot find recording - %s\n",realname);
        return NULL;
    }

    /* So rec is our recording.. */
    snprintf(vdrrecurl->filename,sizeof(vdrrecurl->filename),"%s/",rec->FileName());
    vdrrecurl->curfile = 0;
    vdrrecurl->name = strdup(realname);

    vdrrecurl->OpenFile();

    *type = MEDIA_MPEG;

    if ( vdrrecurl->fd == -1 ) {
        Dprintf(INFO,"Couldn't open recording - %s\n",vdrrecurl->filename);
        delete vdrrecurl;
        return NULL;
    }

    return vdrrecurl;
}


void cMediamvpVdrRecURL::OpenFile()
{
    char      buf[FILENAME_MAX+1];

    if ( fd != -1 ) {
        close(fd);
    }

    snprintf(buf,sizeof(buf),"%s%03d.vdr",filename,++curfile);

    fd = open(buf,O_RDONLY);

    if ( fd == -1 ) {
        snprintf(buf,sizeof(buf),"%s%03d.mpg",filename,curfile);
        
        fd = open(buf,O_RDONLY);
    }
}

/* We need to go through the files looking for the appropriate position
   within the files
*/
off_t cMediamvpVdrRecURL::sSeek(void *ptr, off_t offset, int whence)
{
    cMediamvpVdrRecURL *vdrrecurl = ( cMediamvpVdrRecURL *) ptr;

    return vdrrecurl->Seek(offset,whence);
}

off_t cMediamvpVdrRecURL::Seek(off_t offset, int whence)
{
    struct stat sb;
    off_t      oldlength;
    off_t      length;
    int         number;
    
    for ( oldlength = 0, length = 0, number = 1; length < offset; number++ ) {
        if ( mystat(filename,number,&sb) == -1 ) {
            return (off_t)-1;
        }
        oldlength = length;
        length += sb.st_size;
    }

    curfile = number - 2;  /* We incremented at top of loop
                                         and ::Open() increments */

    OpenFile();

    lseek(fd,offset - oldlength,SEEK_SET);

    return offset;;
}

void cMediamvpVdrRecURL::sClose(void *ptr)
{
    cMediamvpVdrRecURL *vdrrecurl = ( cMediamvpVdrRecURL *) ptr;

    if ( vdrrecurl != NULL ) {       
        delete vdrrecurl;
    }
    return;
}

size_t cMediamvpVdrRecURL::Read(unsigned char *buf, size_t buflen)
{
    size_t    red;
    size_t    ret = 0;

    red = read(fd,buf + ret,buflen - ret);

    if ( red >= 0 ) {
        ret += red;
    }

    while ( ret != buflen ) {
        OpenFile();
        if ( fd != -1 ) {
            red = read(fd,buf + ret, buflen - ret);
            if ( red > 0 ) {
                ret += red;
                break;     /* Out of retrying file loop */
            }
        } else {
            break;
        }
    }
    return ret;
}

int cMediamvpVdrRecURL::sRead(void *ptr, unsigned char *buf, size_t buflen)
{
    cMediamvpVdrRecURL *vdrrecurl = ( cMediamvpVdrRecURL *)ptr;
    int     ret = 0;


    /* Read data into the buffer */
    ret = vdrrecurl->Read(buf,buflen);

    audio_strip(&vdrrecurl->strip,buf,ret);

    return ret;
}

int cMediamvpVdrRecURL::sInfo(void *ptr, int cmd, void *args)
{
    cMediamvpVdrRecURL *vdrrecurl = ( cMediamvpVdrRecURL *) ptr;

    if ( cmd == URLFILE_SIZE ) {
        struct stat sb;
        off_t	    length;
        int         number;
    
        for ( length = 0, number = 1; ; number++ ) {
            if ( mystat(vdrrecurl->filename,number,&sb) == -1 ) {
                break;
            }
            length += sb.st_size;
        }
       
        *((off_t *)args) = length;

        return 0;
    } else if ( cmd == URLFILE_NAME ) {
        *(char **)args = vdrrecurl->name;
    } else if ( cmd == URLFILE_IFRAME ) {
        gopseek_t   *gop = (gopseek_t *)args;
        /* Here we have to determine where the next iframe is */
        return vdrrecurl->GopSeek(gop);
#if 0
    } else if ( cmd == URLFILE_WRITERESUME ) {
        char        buf[FILENAME_MAX+1];
        snprintf(buf,sizeof(buf),"%sresume.mediamvp",vdrrecurl->filename);

        if ( ( fp = fopen(buf,"wb") ) != NULL ) {
            fwrite(&vdrrecrecurl->offset, sizeof(off_t), 1,fp);
            fclose(fp);
        }
        return 0;
#endif
    }
    return -1;
}

off_t cMediamvpVdrRecURL::CurrentOffset()
{
    struct stat sb;
    off_t	    length;
    int         number;

    length = lseek(fd,0,SEEK_CUR);
    
    for ( number = 1; number < curfile ; number++ ) {
        if ( mystat(filename,number,&sb) == -1 ) {
            break;
        }
        length += sb.st_size;
    }
    return length;
}






int cMediamvpVdrRecURL::GopSeek(gopseek_t *gop)
{
    unsigned char    buf[131072]; // should be enough space for 2 GOPs --> less I/O
    int     count =  1;
    int     offset = 0;
    int     i,r;
    int     seekmode;
    bool_t  backward;

   /*
     It should work the following way:

     When seeking backwards, first seek back two pictures. This avoids hanging at one picture,
     which may happen when identifing data as a GOP/sequence header, which sometimes happens (seekmode == -1).

     Then, seek given count of GOP headers forward / backward (seekmode == 0).
     
     Then, seek backwards for sequence start code (seekmode == 1),
     then backwards to system start code (seekmode == 2), which is gop->start
     
     Then, seek start of picture after I-frame picture (seekmode == 3) for gop->length
    */


    offset = CurrentOffset();

    Dprintf(DEBUG,"Seeking %d GOPs @ 0x%08x\n", gop->count, offset);

    if ( gop->count < 0 ) {
        /* Search backward two pictures, avoids hanging */
        backward = TRUE;
        count = 2;
        seekmode = -1;
    } else {
    	/* search forward N GOP headers */
        backward = FALSE;
        count = gop->count;
        seekmode = 0;
    }

    if (backward) {
        offset -= sizeof(buf);
        if (offset < 0) {
	    offset = 0;
        }
    }

    while ( count > 0 && offset >= 0 ) {
        Seek(offset,SEEK_SET);
        r = Read(buf,sizeof(buf));
        if ( r <= 0 ) {
            break;
        }
            
        for ( i = (backward ? r - 4 : 0); count && (backward ? i >= 0 : i < r - 3) ; (backward ? i-- : i++) ) {
            if ( seekmode == -1 ) {
                if ( buf[i] == 0x00 && buf[i+1] == 0x00 && buf[i+2] == 0x01 && buf[i+3] == 0x00 ) {
	            /* Search backward two pictures, avoids hanging */
                    if ( --count == 0 ) {
                    	/* search backward N GOP headers */
		        backward = TRUE;
		        count = -gop->count;
                    	seekmode = 0;
                    }                            
                }
            } else if ( seekmode == 0 ) {
                /* Seek GOP code - this will contain an iframe */
                if ( buf[i] == 0x00 && buf[i+1] == 0x00 && buf[i+2] == 0x01 && buf[i+3] == 0xb8 ) {
                    if ( --count == 0 ) {
                    	/* search backward one sequence header */
		        backward = TRUE;
                    	count = 1;
                    	seekmode = 1;
                    }            
                }
            } else if ( seekmode == 1 ) {
                /* Seek sequence start code */
                if ( buf[i] == 0x00 && buf[i+1] == 0x00 && buf[i+2] == 0x01 && buf[i+3] == 0xb3 ) {
                    if ( --count == 0 ) {
                    	/* search backward elementary stream header */
		        backward = TRUE;
		        count = 1;
		        seekmode = 2;
                    }                            
                }
            } else if (seekmode == 2) {
                /* System code for video elementary stream */
                if ( buf[i] == 0x00 && buf[i+1] == 0x00 && buf[i+2] == 0x01 && buf[i+3] == 0xe0 ) {
                    if ( --count == 0 ) {
                    	/* search picture after i-frame picture for end of I-frame */
                        gop->start = offset + i;
		        backward = FALSE;
		        count = 2;
		        seekmode = 3;
                    }                            
                }
            } else if (seekmode == 3) {
                /* Seek a picture header */
                if ( buf[i] == 0x00 && buf[i+1] == 0x00 && buf[i+2] == 0x01 && buf[i+3] == 0x00 ) {
                    if ( --count == 0 ) {
                        gop->length = offset + i - gop->start;
                    }
                }
            }
        }    
        if ( count != 0 ) {
            if ( backward ) {
                offset -= sizeof(buf) - 3; // we may have missed header at start of buf !
            } else {
                offset += r - 3; // we may have missed header at end of buf !
            }
            /* If we go back to far, GOP is at start of file */
            if ( offset < 0 ) {
               gop->start = 0;
	       backward = FALSE;
	       count = 2;
	       seekmode = 3;
            }
        }
    }
    
    Dprintf(DEBUG,"Found GOP %d @ 0x%08x\n", gop->length, gop->start);

    if ( count != 0 ) {
        return -1;
    } 
    return 0;
}
        

