#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "mpg123.h"
#include "buffer.h"
#include "common.h"

#ifdef READ_MMAP
#include <sys/mman.h>
#ifndef MAP_FAILED
#define MAP_FAILED ( (void *) -1 )
#endif
#endif

static int get_fileinfo(struct reader *,char *buf);
static void readers_add_data(struct reader *rds,unsigned char *buf,int len);

/* can hold 4096-1 = 4095 bytes! */
#define BACKBUF_SIZE (8192)

/*******************************************************************
 * stream based operation
 */
static int bufdiff(struct reader *rds,int start, int end) 
{
  return (end >= start) ? end - start : rds->bufsize + end - start;
}

static int fullread(struct reader *rds,int fd,unsigned char *buf,int count)
{
    int ret,cnt=0;

    while(cnt < count) {
        int toread = count-cnt;
        int num = bufdiff(rds,rds->bufpos,rds->bufend);
  
        /* if we have some data in the backbuffer .. use it first */
        if(num > 0) {
           int part1,part2;

           if(toread > num)
              toread = num;

           part1 = rds->bufsize - rds->bufpos;
           if(part1 > toread)
               part1 = toread;
           part2 = toread - part1;
           memcpy(buf+cnt,&rds->backbuf[rds->bufpos],part1);
           if(part2 > 0)
             memcpy(buf+cnt+part1,&rds->backbuf[0],part2);
           rds->bufpos += toread;
           if(rds->bufpos >= rds->bufsize)
               rds->bufpos -= rds->bufsize;
           ret = toread;

           if(!rds->mark)
             rds->bufstart = rds->bufpos;
        }
        else {
	  ret = read(fd,buf+cnt,toread);

	  if(ret < 0)
	    return ret;
	  if(ret == 0)
	    break;

          if(rds->mark) {
            readers_add_data(rds,buf+cnt,ret);
            rds->bufpos = rds->bufend;
          }

        }
	cnt += ret;
    } 


if(0)
{
  int i;
  fprintf(stderr,"Fullread2 %d\n",cnt);
  for(i=0;i<cnt;i++) {
    fprintf(stderr,"%02x ",buf[i]);
    if(i % 16 == 15)
         fprintf(stderr,"\n");
  }
}


    return cnt;
}

static void readers_add_data(struct reader *rds,unsigned char *buf,int len) 
{
  int diff,part1,part2,store = len;

            if(store >= rds->bufsize)
               store = rds->bufsize - 1;

            /* check whether the new bytes would overwrite the buffer front */
            diff = bufdiff(rds,rds->bufstart,rds->bufend);
            if(diff+store >= rds->bufsize) {
              fprintf(stderr,"Warning: backbuffer overfull %d %d\n",diff+store,rds->bufsize);
              /* +1 because end should never be the same as start if the is data in the buffer */
              rds->bufstart += diff + store + 1 - rds->bufsize;
              if(rds->bufstart >= rds->bufsize)
                   rds->bufstart -= rds->bufsize;
            }

            part1 = rds->bufsize - rds->bufend;
            if(part1 > store)
              part1 = store;
            part2 = store - part1;

            memcpy(rds->backbuf+rds->bufend,&buf[len-part1+part2],part1);
            if(part2 > 0)
               memcpy(rds->backbuf,&buf[len-part2],part2);

            rds->bufend += store;
            if(rds->bufend >= rds->bufsize)
               rds->bufend -= rds->bufsize;


}

void readers_pushback_header(struct reader *rds,unsigned long aLong) 
{
  unsigned char buf[4];

  if(rds->mark || (rds->bufpos != rds->bufend) ) {
    rds->bufpos -= 4;
    if(rds->bufpos < 0)
        rds->bufpos += rds->bufsize;
  }
  else {
    buf[0] = (aLong>>24) & 0xff;
    buf[1] = (aLong>>16) & 0xff;
    buf[2] = (aLong>>8)  & 0xff;
    buf[3] = (aLong>>0)  & 0xff;
  }
  
  readers_add_data(rds,buf,4);
}

void readers_mark_pos(struct reader *rds) {
 /* fprintf(stderr,"M%d ",rds->bufpos); */
  rds->bufstart = rds->bufpos;
  rds->mark     = 1;
}

void readers_goto_mark(struct reader *rds) {
 /*  fprintf(stderr,"G%d ",rds->bufstart); */
  rds->mark = 0;
  rds->bufpos = rds->bufstart;
}


static int default_init(struct reader *rds)
{
    char buf[128];

    rds->mark = 0;
    rds->bufend   = 0;
    rds->bufstart = 0;
    rds->bufpos   = 0;
    rds->bufsize = BACKBUF_SIZE;

    rds->backbuf = (unsigned char *) malloc(rds->bufsize);

    rds->filepos = 0;
    rds->filelen = get_fileinfo(rds,buf);
  
    if(rds->filelen > 0) {
	if(!strncmp(buf,"TAG",3)) {
	    rds->flags |= READER_ID3TAG;
	    memcpy(rds->id3buf,buf,128);
	}
    }
    return 0;
}

void stream_close(struct reader *rds)
{
    if (rds->flags & READER_FD_OPENED)
        close(rds->filept);
}

/**************************************** 
 * HACK,HACK,HACK: step back <num> frames 
 * can only work if the 'stream' isn't a real stream but a file
 */
static int stream_back_bytes(struct reader *rds,int bytes)
{
    if(lseek(rds->filept,-bytes,SEEK_CUR) < 0)
	return -1;
    if(param.usebuffer)
	buffer_resync();
    return 0;
}

static int stream_back_frame(struct reader *rds,struct frame *fr,int num)
{
    long bytes;
    int skipped;

    if(!fr->firsthead)
	return 0;

    bytes = (fr->framesize+8)*(num+2);

    /* Skipping back/forth requires a bit more work in buffered mode. 
     * See mapped_back_frame(). 
     */
#ifndef NOXFERMEM
    if(param.usebuffer)
	bytes += (long)(xfermem_get_usedspace(buffermem) /
			(buffermem->buf[0] * buffermem->buf[1]
			 * (buffermem->buf[2] & AUDIO_FORMAT_MASK ?
			    16.0 : 8.0 ))
			* (tabsel_123[fr->lsf][fr->lay-1][fr->bitrate_index] << 10));
#endif
    /*
      bytes += (long)(compute_buffer_offset(fr)*compute_bpf(fr));
    */	
    if(lseek(rds->filept,-bytes,SEEK_CUR) < 0)
	return -1;

    sync_stream(rds,fr,0xffff,&skipped);

    read_frame(rds,fr);
    read_frame(rds,fr);

    if(fr->lay == 3) {
	set_pointer(fr->sideInfoSize,512);
    }

    if(param.usebuffer)
	buffer_resync();
	
    return 0;
}

static int stream_head_read(struct reader *rds,unsigned long *newhead)
{
    unsigned char hbuf[4];

    if(fullread(rds,rds->filept,hbuf,4) != 4)
	return FALSE;
  
    *newhead = ((unsigned long) hbuf[0] << 24) |
	((unsigned long) hbuf[1] << 16) |
	((unsigned long) hbuf[2] << 8)  |
	(unsigned long) hbuf[3];
  
    return TRUE;
}

static int stream_head_shift(struct reader *rds,unsigned long *head)
{
    unsigned char hbuf;

    if(fullread(rds,rds->filept,&hbuf,1) != 1)
	return 0;
    *head <<= 8;
    *head |= hbuf;
    *head &= 0xffffffff;
    return 1;
}

static int stream_skip_bytes(struct reader *rds,int len)
{
    if (!param.usebuffer)
  	return lseek(rds->filept,len,SEEK_CUR);

    else {
	int ret = lseek(rds->filept,len,SEEK_CUR);
	buffer_resync();
	return ret;
    }
}

static int stream_read_frame_body(struct reader *rds,unsigned char *buf,
				  int size)
{
    long l;

    if( (l=fullread(rds,rds->filept,buf,size)) != size)
	{
	    if(l <= 0)
		return 0;
	    memset(buf+l,0,size-l);
	}

    return 1;
}

static long stream_tell(struct reader *rds)
{
    return lseek(rds->filept,0,SEEK_CUR);
}

static void stream_rewind(struct reader *rds)
{
    lseek(rds->filept,0,SEEK_SET);
    if(param.usebuffer) 
	buffer_resync();
}

/*
 * returns length of a file (if filept points to a file)
 * reads the last 128 bytes information into buffer
 */
static int get_fileinfo(struct reader *rds,char *buf)
{
    int len;

    if((len=lseek(rds->filept,0,SEEK_END)) < 0) {
	return -1;
    }
    if(lseek(rds->filept,-128,SEEK_END) < 0)
	return -1;
    if(fullread(rds,rds->filept,(unsigned char *)buf,128) != 128) {
	return -1;
    }
    if(!strncmp(buf,"TAG",3)) {
	len -= 128;
    }
    if(lseek(rds->filept,0,SEEK_SET) < 0)
	return -1;
    if(len <= 0)
	return -1;
    return len;
}


#ifdef READ_MMAP
/*********************************************************+
 * memory mapped operation 
 *
 */
static unsigned char *mapbuf;
static unsigned char *mappnt;
static unsigned char *mapend;

static int mapped_init(struct reader *rds) 
{
    long len;
    char buf[128];

    len = get_fileinfo(rds,buf);
    if(len < 0)
	return -1;

    if(!strncmp(buf,"TAG",3)) {
	rds->flags |= READER_ID3TAG;
	memcpy(rds->id3buf,buf,128);
    }

    mappnt = mapbuf = (unsigned char *)
	mmap(NULL, len, PROT_READ, MAP_SHARED , rds->filept, 0);
    if(!mapbuf || mapbuf == MAP_FAILED)
	return -1;

    mapend = mapbuf + len;
	
    if(param.verbose > 1)
	fprintf(stderr,"Using memory mapped IO for this stream.\n");

    rds->filelen = len;
    return 0;
}

static void mapped_rewind(struct reader *rds)
{
    mappnt = mapbuf;
    if (param.usebuffer) 
	buffer_resync();	
}

static void mapped_close(struct reader *rds)
{
    munmap((void *)mapbuf,mapend-mapbuf);
    if (rds->flags & READER_FD_OPENED)
	close(rds->filept);
}

static int mapped_head_read(struct reader *rds,unsigned long *newhead) 
{
    unsigned long nh;

    if(mappnt + 4 > mapend)
	return FALSE;

    nh = (*mappnt++)  << 24;
    nh |= (*mappnt++) << 16;
    nh |= (*mappnt++) << 8;
    nh |= (*mappnt++) ;

    *newhead = nh;
    return TRUE;
}

static int mapped_head_shift(struct reader *rds,unsigned long *head)
{
    if(mappnt + 1 > mapend)
	return FALSE;
    *head <<= 8;
    *head |= *mappnt++;
    *head &= 0xffffffff;
    return TRUE;
}

static int mapped_skip_bytes(struct reader *rds,int len)
{
    if(mappnt + len > mapend)
	return FALSE;
    mappnt += len;
    if (param.usebuffer)
	buffer_resync();
    return TRUE;
}

static int mapped_read_frame_body(struct reader *rds,unsigned char *buf,
				  int size)
{
    if(size <= 0) {
	fprintf(stderr,"Ouch. Read_frame called with size <= 0\n");
	return FALSE;
    }
    if(mappnt + size > mapend)
	return FALSE;
    memcpy(buf,mappnt,size);
    mappnt += size;

    return TRUE;
}

static int mapped_back_bytes(struct reader *rds,int bytes)
{
    if( (mappnt - bytes) < mapbuf || (mappnt - bytes + 4) > mapend)
        return -1;
    mappnt -= bytes;
    if(param.usebuffer)
	buffer_resync();
    return 0;
}

static int mapped_back_frame(struct reader *rds,struct frame *fr,int num)
{
    long bytes;
    unsigned long newhead;


    if(!firsthead)
        return 0;

    bytes = (fr->framesize+8)*(num+2);

    /* Buffered mode is a bit trickier. From the size of the buffered
     * output audio stream we have to make a guess at the number of frames
     * this corresponds to.
     */
#ifndef NOXFERMEM
    if(param.usebuffer) 
	bytes += (long)(xfermem_get_usedspace(buffermem) /
			(buffermem->buf[0] * buffermem->buf[1] 
			 * (buffermem->buf[2] & AUDIO_FORMAT_MASK ?
			    16.0 : 8.0 )) 
			* (tabsel_123[fr->lsf][fr->lay-1][fr->bitrate_index] << 10));
#endif
    /*
      bytes += (long)(compute_buffer_offset(fr)*compute_bpf(fr));  
    */

    if( (mappnt - bytes) < mapbuf || (mappnt - bytes + 4) > mapend)
        return -1;
    mappnt -= bytes;

    newhead = (mappnt[0]<<24) + (mappnt[1]<<16) + (mappnt[2]<<8) + mappnt[3];
    mappnt += 4;

    while( (newhead & HDRCMPMASK) != (firsthead & HDRCMPMASK) ) {
        if(mappnt + 1 > mapend)
            return -1;
        newhead <<= 8;
        newhead |= *mappnt++;
        newhead &= 0xffffffff;
    }
    mappnt -= 4;

    read_frame(fr);
    read_frame(fr);

    if(fr->lay == 3)
        set_pointer(fr->sideInfoSize,512);

    if(param.usebuffer)
	buffer_resync();
    
    return 0;
}

static long mapped_tell(struct reader *rds)
{
    return mappnt - mapbuf;
}

#endif

/*****************************************************************
 * read frame helper
 */

struct reader *rd;
struct reader readers[] = {
#ifdef READ_SYSTEM
    { system_init,
      NULL,	/* filled in by system_init() */
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL } ,
#endif
#ifdef READ_MMAP
    { mapped_init,
      mapped_close,
      mapped_head_read,
      mapped_head_shift,
      mapped_skip_bytes,
      mapped_read_frame_body,
      mapped_back_bytes,
      mapped_back_frame,
      mapped_tell,
      mapped_rewind } , 
#endif 
    { default_init,
      stream_close,
      stream_head_read,
      stream_head_shift,
      stream_skip_bytes,
      stream_read_frame_body,
      stream_back_bytes,
      stream_back_frame,
      stream_tell,
      stream_rewind } ,
    { NULL, }
};


/* open the device to read the bit stream from it */

struct reader *open_stream(const char *bs_filenam,int fd)
{
    int i;
    int filept_opened = 1;
    int filept;

    if (!bs_filenam) {
	if(fd < 0) {
	    filept = 0;
	    filept_opened = 0;
	}
	else
	    filept = fd;
    }
    else if (!strncasecmp(bs_filenam, "http://", 7)) 
	filept = http_open(bs_filenam);
    else if (!strncasecmp(bs_filenam, "ftp://", 6))
        filept = http_open(bs_filenam);

#ifndef O_BINARY
#define O_BINARY (0)
#endif
    else if ( (filept = open(bs_filenam, O_RDONLY|O_BINARY)) < 0) {
	perror (bs_filenam);
	return NULL;
    }

    rd = NULL;
    for(i=0;;i++) {
	readers[i].filelen = -1;
	readers[i].filept  = filept;
	readers[i].flags = 0;
	if(filept_opened)
	    readers[i].flags |= READER_FD_OPENED;
	if(!readers[i].init) {
	    fprintf(stderr,"Fatal error!\n");
	    exit(1);
	}
	if(readers[i].init(readers+i) >= 0) {
	    rd = &readers[i];
	    break;
	}
    }

    if(rd && rd->flags & READER_ID3TAG) {
	print_id3_tag(rd->id3buf);
    }

    return rd;
}












