/*
 *   MediaMVP Server Library
 *
 *   (C) 2003 Dominic Morris
 *
 *   $Id: url_file.c,v 1.12 2004/11/13 10:56:52 dom Exp $
 *   $Date: 2004/11/13 10:56:52 $
 *
 *
 *   Deals with opening a file and fobbing off playlist detection
 *   to a lower level
 */

#include "libmvp_internal.h"

typedef struct {
    int            fd;
    char          *name;
#ifdef HAVE_LIBID3TAG
    mp3info_t     *info;
#endif
    astrip_t       strip;

} file_t;


static void           *file_open(char *name, int *type, fops_t **fops_ptr, readready_cb cb, void *cb_ptr );
static void            file_close(void *ptr);
static off_t           file_seek(void *ptr, off_t offset, int whence);
static int             file_read(void *ptr, unsigned char *buf, size_t buflen);
static int             file_info(void *ptr, int cmd, void *dest);
static int             file_gop_seek(file_t *file, gopseek_t *gop);


static fops_t          file_fops = {
    NULL,
    file_open,
    file_close,
    file_seek,
    file_read,
    file_info,
};

void urlfile_init()
{
    urlhandler_add("file://",&file_fops);
}


static void *file_open(char *name, int *type, fops_t **fops_ptr, readready_cb cb, void *cb_ptr )
{
    file_t  *file;
    char    *filename;
    char    *ptr;
    int      fd;


    /* This shouldn't happen... */
    if ( strncmp(name,"file://",7) ) {
        return NULL;
    }

    filename = name + 7;

    if ( ( ptr = strrchr(filename,'.') ) != NULL ) {
        if ( strcasecmp(ptr,".mp3") == 0 ) {
            *type = MEDIA_MP3;
        } else if ( strcasecmp(ptr,".mpeg") == 0 || strcasecmp(ptr,".vdr") == 0 ||
                    strcasecmp(ptr,".mpg") == 0 || strcasecmp(ptr,".nvu") == 0 ) {
            *type = MEDIA_MPEG;
        }
    } else {
        /* Try and guess the filetype */
        *type = file_get_type(filename);
    }

    if ( *type > 0 ) {
        if ( (fd = open(filename,O_RDONLY) ) < 0 ) {
            return NULL;
        }
        file = (file_t*)calloc(1,sizeof(*file));
        file->fd = fd;
        file->strip.tmplen = 0;
        file->strip.offset = 0;
        file->strip.pid = 0xC1;

#ifdef HAVE_LIBID3TAG
        file->info = NULL;

        if ( *type == MEDIA_MP3 ) {
            file->info = mp3_get_info_fd(file->fd);
        }

        if ( mp3_get_title(file->info) == NULL ) {
            file->name = strdup(filename);
        } else {
            file->name = strdup(mp3_get_title(file->info));
        }
#else            
        file->name = strdup(filename);
#endif

        return file;
    }

    return NULL;
}

static void file_close(void *ptr)
{
    file_t   *file = (file_t*)ptr;
    close(file->fd);
    if ( file->name ) {
        free(file->name);
        file->name = NULL;
    }
#ifdef HAVE_LIBID3TAG
    mp3_info_delete(file->info);
    file->info = NULL;
#endif
    file->fd = -1;
    free(file);
}

static off_t file_seek(void *ptr, off_t off, int whence)
{
    file_t *file = (file_t*)ptr;

    return lseek(file->fd,off,whence);
}

static int file_read(void *ptr, unsigned char *buf, size_t buflen)
{
    file_t  *file = (file_t*)ptr;
    int      ret = read(file->fd,buf,buflen);

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

    return ret;
}

static int file_info(void *ptr, int cmd, void *dest)
{
    file_t      *file = (file_t*)ptr;
    mpeginfo_t  *info = NULL;
    gopseek_t   *gop = (gopseek_t *)dest;

    struct stat  sb;

    switch ( cmd ) {
    case URLFILE_SIZE:
        *((off_t *)dest) = 0;
        if ( fstat(file->fd,&sb) == 0 ) {
            if ( S_ISREG(sb.st_mode) ) {
                *((off_t *)dest) = sb.st_size;
                return 0;
            }
        }
        return -1;
    case URLFILE_NAME:
        *(char **)dest = file->name;
        break;
#ifdef HAVE_LIBID3TAG
    case URLFILE_MP3TAG:
        dest = file->info;
        break;
#endif     
    case URLFILE_MPEG_FRAME_RATE:
        info = mpeg_get_info_fd(file->fd);
        *((int *)dest) = mpeg_get_rate_index(info);
        delete_mpeg_info(info);
        break;
    case URLFILE_IFRAME:
        /* Here we have to determine where the next iframe is */
        return file_gop_seek(file,gop);
    default:
        return -1;
    }
    return 0;
}

static int file_gop_seek(file_t *file, 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 = lseek(file->fd,0,SEEK_CUR);

    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 ) {
        lseek(file->fd,offset,SEEK_SET);
        r = read(file->fd,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;
}
        
        

    





