/*
 *   MediaMVP Server Library
 *
 *   (C) 2003 Dominic Morris
 *
 *   $Id: url_http.c,v 1.13 2004/10/24 11:37:37 dom Exp $
 *   $Date: 2004/10/24 11:37:37 $
 *
 *
 *   Deals with opening a http connection and fobbing off playlist detection
 *   to a lower level
 */

#include "libmvp_internal.h"


static void           *http_open(char *name, int *type, fops_t **fops_ptr, readready_cb cb, void *cb_ptr );
static void            http_close(void *ptr);
static off_t           http_seek(void *ptr, off_t offset, int whence);
static int             http_read(void *ptr, unsigned char *buf, size_t buflen);

static void            http_read_event(int fd, short event, void *arg);
static int             http_open_direct(char *url, char **name, int *content_type);
static int             parse_content_type(char *string);
static int             http_info(void *ptr, int cmd, void *dest);

typedef struct {
    struct  event *ev;
    readready_cb   cb;
    void          *cb_ptr;
    char          *location;
    char          *name;
    int            fd;
    astrip_t       strip;
} httpfile_t;

static fops_t          http_fops = {
    NULL,
    http_open,
    http_close,
    http_seek,
    http_read,
    http_info
};


#define ACCEPT "Accept: video/mpeg, video/x-mpegurl, */*\r\n"

void urlhttp_init()
{
    urlhandler_add("mpeg;http://",&http_fops);
    urlhandler_add("mp3;http://",&http_fops);
    urlhandler_add("http://",&http_fops);
}

static void *http_open(char *url, int *type,fops_t **fops_ptr,readready_cb cb, void *cb_ptr )
{
    httpfile_t   *httpfile;
    void         *ptr;
    char         *icy_name;
    int           content_type;
    int           fd;
    int           ret;
    bool_t        playlist = FALSE;

    /* This is a bit of splendiferous hack <grin> */
    if ( strncmp(url,"mpeg;",5) == 0 ) {
        *type = MEDIA_MPEG | MEDIA_LIVE;
        url += 5;
    } else if ( strncmp(url,"mp3;",3) == 0 ) {
        *type = MEDIA_MP3 | MEDIA_LIVE;
        url += 4;
    } else {       /* Unknown type, try to guess from the file extension */
        char    *ptr;

        playlist = TRUE;
        if ( (ptr = strrchr(url,'.') ) != NULL ) {
            if ( strcasecmp(ptr,".mp3") == 0 ) {
                *type = MEDIA_MP3 | MEDIA_LIVE;
                playlist = FALSE;
            } else if ( strcasecmp(ptr,".mpeg") == 0 || strcasecmp(ptr,".vdr") == 0 || strcasecmp(ptr,".mpg") == 0 ) {
                *type = MEDIA_MPEG | MEDIA_LIVE;
                playlist = FALSE;
            }
        } 
    } 


    if ( (fd = http_open_direct(url,&icy_name,&content_type) ) == -1 ) {
        return NULL;
    }

    if ( icy_name == NULL ) 
        icy_name = strdup(url);

    /* Createa a http structure */
    httpfile = (httpfile_t*)calloc(1,sizeof(*httpfile));
    httpfile->fd = fd;
    httpfile->cb_ptr = cb_ptr;
    httpfile->cb = cb;
    httpfile->name = icy_name;
    httpfile->strip.tmplen = 0;
    httpfile->strip.offset = 0;
    httpfile->strip.pid = 0xC1;

    /* Obey the content type from the server if we got one */
    if ( content_type != -1 ) {
        *type = content_type;
        playlist = FALSE;
    }
    

    if ( playlist == TRUE && (ret = playlist_check("",fd,httpfile,type,&ptr,fops_ptr,cb,cb_ptr) ) != 0 ) {
        /* This is where the playlist has done something, be it a bad filetype or a playlist, we needn't
           care about it, we know that it has closed up our file for us, so return whatever ptr was set           
        */       
        return ptr;
    }

    /* Force it to be live - it's coming over http afterall */
    *type |= MEDIA_LIVE;

   
    httpfile->ev = (struct event*)calloc(1,sizeof(*httpfile->ev));
    event_set(httpfile->ev,fd,EV_READ,http_read_event,httpfile);
    event_add(httpfile->ev,NULL);
  
    return httpfile;
}

static void http_read_event(int fd, short event, void *arg)
{
    httpfile_t  *file = (httpfile_t*)arg;


    /* Read the event here */
    event_add(file->ev,NULL);

    file->cb(file->cb_ptr);
}

static void http_close(void *ptr)
{
    httpfile_t   *file = (httpfile_t*)ptr;

    if ( file->ev ) {
        event_del(file->ev);
        file->ev = NULL;
    }

    if ( file->name ) {
        free(file->name);
        file->name = NULL;
    }

    close(file->fd);
    shutdown(file->fd,SHUT_RDWR);

    free(file);
}

static off_t http_seek(void *ptr,off_t offset, int whence)
{
    return (off_t) -1;
}

static int http_read(void *ptr, unsigned char *buf, size_t bufsize)
{
    httpfile_t  *file = (httpfile_t*)ptr;
    int          ret = read(file->fd,buf,bufsize);

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

    return ret;
}







/* HTTP stuff is stolen from mpegtools/ctools.c */
static void write_all (int fd, uint8_t *data, int length)
{
        int r;

        while (length) {
                if ((r = write(fd, data, length)) > 0) {
                        data += r;
                        length -= r;
                }
        }
}

static int read_all (int fd, uint8_t *data, int length)
{
        int c = 0;

        while(c<length) {
                if( read(fd, data+c, 1) == 1) {
                        c++;
                        if(data[c-1] == '\n') {
                                data[c] = 0;
                                break;
                        }
                }
                else {
                        return -1;
                }
        }
        return 0;
}

static char *url2host (uint8_t *url, char **name, uint32_t *ip, uint32_t *port)
{
        uint8_t *murl;
        struct hostent *hoste;
        struct in_addr haddr;
        int found_ip = 1;

        if (!(strncmp((char*)url, "http://", 7)))
                url += 7;

        *name = strdup((char*)url);
        if (!(*name)) {
                *name = NULL;
                return (NULL);
        }

        murl = url;
        while (*murl && *murl != ':' && *murl != '/') {
                if ((*murl < '0' || *murl > '9') && *murl != '.')
                        found_ip = 0;
                murl++;
        }
        (*name)[murl - url] = 0;
        if (found_ip) {
                if ((*ip = inet_addr(*name)) == INADDR_NONE)
                        return (NULL);
        } else {
                if (!(hoste = gethostbyname(*name)))
                        return (NULL);
                memcpy (&haddr, hoste->h_addr, sizeof(haddr));
                *ip = haddr.s_addr;
        }

        if (!*murl || *murl == '/') {
                *port = 80;
                return ((char*)murl);
        }
        *port = atoi((char*)++murl);

        while (*murl && *murl != '/')
                murl++;
        return ((char*)murl);
}



static int http_open_direct(char *url, char **name, int *content_type)
{
    char purl[1024], *host, req[1024], *sptr;
    uint32_t ip;
    uint32_t port;
    int sock;
    int reloc, relocnum = 0;
    struct sockaddr_in server;
    int mfd = -1;

    *content_type = -1;
    *name = NULL;


    strncpy (purl, url, 1023);
    purl[1023] = '\0';

    do {
        Dprintf(INFO,"Requesting from %s\n",purl);
        host = NULL;
        strcpy (req, "GET ");
        if (!(sptr = url2host((uint8_t*)purl, &host, &ip, &port))) {
            Dprintf(ERROR,"Unknown host %s\n",purl);
            return -1;
        }
        strcat (req, sptr);
        sprintf (req + strlen(req),
                 " HTTP/1.0\r\nUser-Agent: %s/%s\r\n"
                 "Icy-MetaData:1\r\n",
                 SERVER_NAME, SERVER_VERSION);
        if (host) {
            sprintf(req + strlen(req),
                    "Host: %s:%u\r\n", host, port);
            free (host);
        }

        strcat (req, ACCEPT);
        strcat (req, "\r\n");

        server.sin_port = htons(port);
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = ip;

        if ((sock = socket(PF_INET, SOCK_STREAM, 6)) < 0) {
            return -1;
        }

        if (connect(sock, (struct sockaddr *)&server,
                    sizeof(server))) {
            if ( mfd != -1 )
                close(mfd);
            Dprintf(ERROR,"Couldn't connect to %s\n",purl);
            return -1;
        }

        write_all (sock, (uint8_t*)req, strlen(req));
        if (!(mfd = fileno(fdopen(sock, "rb")))) {
            close(mfd);
            shutdown(mfd,SHUT_RDWR);
            return -1;
        }
        reloc = 0;
        purl[0] = '\0';
        if ( read_all (mfd, (uint8_t*)req, 1023) == -1 ) {
            return -1;
        }
        if ((sptr = strchr(req, ' '))) {
            switch (sptr[1]) {
            case '2':
                /* Check for coming from a streamcast server */
                if ( strncmp(req,"ICY",3) == 0 ) {
                    *content_type = MEDIA_MP3;
                }
                break;
            case '3':
                reloc = 1;
            default:
                Dprintf(ERROR, "HTTP req failed:%s",sptr+1);
                close(mfd);
                shutdown(mfd,SHUT_RDWR);
                return -1;
            }
        }
        do {
            if ( read_all (mfd,(uint8_t*)req, 1023) == -1 ) {
                return -1;
            }
            if ( strncmp(req, "Location:", 9) == 0) {
                strncpy (purl, req+10, 1023);
            } else if ( strncmp(req, "Content-Type:",13) == 0 ) {
                int type;
                sptr = req + 13;
                while ( *sptr && isspace(*sptr) )
                    ++sptr;
                /* Got the content type..shall we grok it? */
                type = parse_content_type(sptr);
                if ( type != -1 ) {
                    *content_type = type;
                }
            } else if ( strncmp(req, "icy-name:",9) == 0 ) {
                sptr = req + 9;
                while ( *sptr && isspace(*sptr) )
                    ++sptr;
                if ( *sptr ) {
                    char *tmp = sptr;

                    while ( *tmp ) {
                        if ( *tmp == '\n' || *tmp == '\r' )
                            *tmp = 0;
                        ++tmp;
                    }
                    *name = strdup(sptr);                    
                }
            }
        } while (req[0] != '\r' && req[0] != '\n');
    } while (reloc && purl[0] && relocnum++ < 3);

    if (reloc) {
        Dprintf(ERROR,"Too many HTTP relocations.\n");
        close(mfd);
        shutdown(mfd,SHUT_RDWR);
        return -1;
    } 

    return sock;
}


static int  http_info(void *ptr, int cmd, void *dest)
{    
    httpfile_t      *httpfile = (httpfile_t*)ptr;

    switch ( cmd ) {
    case URLFILE_SIZE:
        *(off_t *)dest = 0;
        break;
    case URLFILE_NAME:
        *(char **)dest = httpfile->name;
        break;
    default:
        return -1;
    }
  
    return 0;
}


static int parse_content_type(char *string)
{
    int   ret = -1;

    if ( strncmp(string,"audio/mpeg",10) == 0 )
        ret = MEDIA_MP3;
    else if ( strncmp(string,"video/mpeg",10) == 0 )
        ret = MEDIA_MPEG;

    return ret;
}
