/*
reader.c - compact version of famous library mpg123
Copyright (C) 2017 Uncle Mike

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
*/

#include "mpg123.h"
#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

#define READER_STREAM	0
#define READER_FEED		1
#define READER_BUF_STREAM	2

static int default_init( mpg123_handle_t *fr );
static mpg_off_t get_fileinfo( mpg123_handle_t *fr );

// methods for the buffer chain, mainly used for feed reader, but not just that.
static buffy_t* buffy_new( size_t size, size_t minsize )
{
	buffy_t	*newbuf = malloc( sizeof( buffy_t ));

	if( newbuf == NULL )
		return NULL;

	newbuf->realsize = size > minsize ? size : minsize;
	newbuf->data = malloc( newbuf->realsize );

	if( newbuf->data == NULL )
	{
		free( newbuf );
		return NULL;
	}

	newbuf->size = 0;
	newbuf->next = NULL;

	return newbuf;
}

static void buffy_del( buffy_t *buf )
{
	if( buf )
	{
		free( buf->data );
		free( buf );
	}
}

// delete this buffy and all following buffies.
static void buffy_del_chain( buffy_t *buf )
{
	while( buf )
	{
		buffy_t *next = buf->next;
		buffy_del( buf );
		buf = next;
	}
}

// fetch a buffer from the pool (if possible) or create one.
static buffy_t* bc_alloc( bufferchain_t *bc, size_t size )
{
	// Easy route: Just try the first available buffer.
	// size does not matter, it's only a hint for creation of new buffers.
	if( bc->pool )
	{
		buffy_t *buf = bc->pool;

		bc->pool = buf->next;
		buf->next = NULL; // that shall be set to a sensible value later. 
		buf->size = 0;
		bc->pool_fill--;

		return buf;
	}

	return buffy_new( size, bc->bufblock );
}

// either stuff the buffer back into the pool or free it for good.
static void bc_free( bufferchain_t *bc, buffy_t* buf )
{
	if( !buf ) return;

	if( bc->pool_fill < bc->pool_size )
	{
		buf->next = bc->pool;
		bc->pool = buf;
		bc->pool_fill++;
	}
	else buffy_del( buf );
}

// make the buffer count in the pool match the pool size.
static int bc_fill_pool( bufferchain_t *bc )
{
	// remove superfluous ones.
	while( bc->pool_fill > bc->pool_size )
	{
		// lazyness: Just work on the front.
		buffy_t *buf = bc->pool;
		bc->pool = buf->next;
		buffy_del( buf );
		bc->pool_fill--;
	}

	// add missing ones.
	while( bc->pool_fill < bc->pool_size )
	{
		// again, just work on the front.
		buffy_t *buf = buffy_new( 0, bc->bufblock ); // use default block size.
		if( !buf ) return -1;

		buf->next = bc->pool;
		bc->pool = buf;
		bc->pool_fill++;
	}

	return 0;
}

static void bc_init( bufferchain_t *bc )
{
	bc->first = NULL;
	bc->last = bc->first;
	bc->size = 0;
	bc->pos = 0;
	bc->firstpos = 0;
	bc->fileoff = 0;
}

static void bc_reset( bufferchain_t *bc )
{
	// free current chain, possibly stuffing back into the pool.
	while( bc->first )
	{
		buffy_t *buf = bc->first;
		bc->first = buf->next;
		bc_free( bc, buf );
	}

	bc_fill_pool( bc );	// ignoring an error here...
	bc_init( bc );
}

// create a new buffy at the end to be filled.
static int bc_append( bufferchain_t *bc, mpg_ssize_t size )
{
	buffy_t	*newbuf;

	if( size < 1 )
		return -1;

	newbuf = bc_alloc( bc, size );
	if( newbuf == NULL ) return -2;

	if( bc->last != NULL )
		bc->last->next = newbuf;
	else if( bc->first == NULL )
		bc->first = newbuf;

	bc->last  = newbuf;

	return 0;
}

void bc_prepare( bufferchain_t *bc, size_t pool_size, size_t bufblock )
{
	bc_poolsize( bc, pool_size, bufblock );
	bc->pool = NULL;
	bc->pool_fill = 0;
	bc_init( bc );	// ensure that members are zeroed for read-only use.
}

size_t bc_fill( bufferchain_t *bc )
{
	return (size_t)(bc->size - bc->pos);
}

void bc_poolsize( bufferchain_t *bc, size_t pool_size, size_t bufblock )
{
	bc->pool_size = pool_size;
	bc->bufblock = bufblock;
}

void bc_cleanup( bufferchain_t *bc )
{
	buffy_del_chain( bc->pool );
	bc->pool_fill = 0;
	bc->pool = NULL;
}

// append a new buffer and copy content to it.
static int bc_add( bufferchain_t *bc, const byte *data, mpg_ssize_t size )
{
	int	ret = 0;
	mpg_ssize_t	part = 0;

	while( size > 0 )
	{
		// try to fill up the last buffer block.
		if( bc->last != NULL && bc->last->size < bc->last->realsize )
		{
			part = bc->last->realsize - bc->last->size;
			if( part > size ) part = size;

			memcpy( bc->last->data + bc->last->size, data, part );
			bc->last->size += part;
			size -= part;
			bc->size += part;
			data += part;
		}

		// if there is still data left, put it into a new buffer block.
		if( size > 0 && ( ret = bc_append( bc, size )) != 0 )
			break;
	}

	return ret;
}

// common handler for "You want more than I can give." situation.
static mpg_ssize_t bc_need_more( bufferchain_t *bc )
{
	// go back to firstpos, undo the previous reads
	bc->pos = bc->firstpos;

	return MPG123_NEED_MORE;
}

// give some data, advancing position but not forgetting yet.
static mpg_ssize_t bc_give( bufferchain_t *bc, byte *out, mpg_ssize_t size )
{
	buffy_t	*b = bc->first;
	mpg_ssize_t	gotcount = 0;
	mpg_ssize_t	offset = 0;

	if( bc->size - bc->pos < size )
		return bc_need_more( bc );

	// find the current buffer
	while( b != NULL && ( offset + b->size ) <= bc->pos )
	{
		offset += b->size;
		b = b->next;
	}

	// now start copying from there
	while( gotcount < size && ( b != NULL ))
	{
		mpg_ssize_t	loff = bc->pos - offset;
		mpg_ssize_t	chunk = size - gotcount; // amount of bytes to get from here...

		if( chunk > b->size - loff )
			chunk = b->size - loff;

		memcpy( out + gotcount, b->data + loff, chunk );
		gotcount += chunk;
		bc->pos += chunk;
		offset += b->size;
		b = b->next;
	}

	return gotcount;
}

// skip some bytes and return the new position.
// the buffers are still there, just the read pointer is moved!
static mpg_ssize_t bc_skip( bufferchain_t *bc, mpg_ssize_t count )
{
	if( count >= 0 )
	{
		if( bc->size - bc->pos < count )
			return bc_need_more( bc );
		return bc->pos += count;
	}

	return MPG123_ERR;
}

static mpg_ssize_t bc_seekback( bufferchain_t *bc, mpg_ssize_t count )
{
	if( count >= 0 && count <= bc->pos )
		return bc->pos -= count;
	return MPG123_ERR;
}

// throw away buffies that we passed.
static void bc_forget( bufferchain_t *bc )
{
	buffy_t	*b = bc->first;

	// free all buffers that are def'n'tly outdated
	// we have buffers until filepos... delete all buffers fully below it
	while( b != NULL && bc->pos >= b->size )
	{
		buffy_t	*n = b->next;	// != NULL or this is indeed the end and the last cycle anyway

		if( n == NULL )
			bc->last = NULL;	// Going to delete the last buffy...
		bc->fileoff += b->size;
		bc->pos  -= b->size;
		bc->size -= b->size;
		bc_free( bc, b );
		b = n;
	}

	bc->first = b;
	bc->firstpos = bc->pos;
}

// reader for input via manually provided buffers
static int feed_init( mpg123_handle_t *fr )
{
	bc_init( &fr->rdat.buffer );
	bc_fill_pool( &fr->rdat.buffer );
	fr->rdat.filelen = 0;
	fr->rdat.filepos = 0;
	fr->rdat.flags |= READER_BUFFERED;

	return 0;
}

// externally called function, returns 0 on success, -1 on error
int feed_more( mpg123_handle_t *fr, const byte *in, long count )
{
	if( bc_add( &fr->rdat.buffer, in, count ) != 0 )
		return MPG123_ERR;

	return MPG123_OK;
}

static mpg_ssize_t feed_read( mpg123_handle_t *fr, byte *out, mpg_ssize_t count )
{
	mpg_ssize_t	gotcount = bc_give( &fr->rdat.buffer, out, count );

	if( gotcount >= 0 && gotcount != count )
		return MPG123_ERR;

	return gotcount;
}

// returns reached position... negative ones are bad...
static mpg_off_t feed_skip_bytes( mpg123_handle_t *fr, mpg_off_t len )
{
	// this is either the new buffer offset or some negative error value.
	mpg_off_t res = bc_skip( &fr->rdat.buffer, (mpg_ssize_t)len );
	if( res < 0 ) return res;

	return fr->rdat.buffer.fileoff + res;
}

static int feed_back_bytes( mpg123_handle_t *fr, mpg_off_t bytes )
{
	if( bytes >= 0 )
		return bc_seekback(&fr->rdat.buffer, (mpg_ssize_t)bytes) >= 0 ? 0 : MPG123_ERR;
	return feed_skip_bytes( fr, -bytes ) >= 0 ? 0 : MPG123_ERR;
}

static int feed_seek_frame( mpg123_handle_t *fr, mpg_off_t num )
{
	return MPG123_ERR;
}

// not just for feed reader, also for self-feeding buffered reader.
static void buffered_forget( mpg123_handle_t *fr )
{
	bc_forget( &fr->rdat.buffer );
	fr->rdat.filepos = fr->rdat.buffer.fileoff + fr->rdat.buffer.pos;
}

mpg_off_t feed_set_pos( mpg123_handle_t *fr, mpg_off_t pos )
{
	bufferchain_t	*bc = &fr->rdat.buffer;

	if( pos >= bc->fileoff && pos - bc->fileoff < bc->size )
	{
		// we have the position!
		bc->pos = (mpg_ssize_t)(pos - bc->fileoff);

		// next input after end of buffer...
		return bc->fileoff + bc->size;
	}
	else
	{
		// i expect to get the specific position on next feed. Forget what I have now.
		bc_reset( bc );
		bc->fileoff = pos;

		// next input from exactly that position.
		return pos;
	}
}

// the specific stuff for buffered stream reader.
static mpg_ssize_t buffered_fullread( mpg123_handle_t *fr, byte *out, mpg_ssize_t count )
{
	bufferchain_t	*bc = &fr->rdat.buffer;
	mpg_ssize_t		gotcount;

	if( bc->size - bc->pos < count )
	{
		// add more stuff to buffer. If hitting end of file, adjust count.
		byte	readbuf[4096];

		mpg_ssize_t need = count - (bc->size - bc->pos);

		while( need > 0 )
		{
			mpg_ssize_t	got = fr->rdat.fullread( fr, readbuf, sizeof( readbuf ));
			int	ret;

			if( got < 0 )
				return MPG123_ERR;

			if( got > 0 && ( ret = bc_add( bc, readbuf, got )) != 0 )
				return MPG123_ERR;

			need -= got; // may underflow here...

			if( got < sizeof( readbuf )) // that naturally catches got == 0, too.
				break; // end.
		}

		if( bc->size - bc->pos < count )
			count = bc->size - bc->pos; // we want only what we got.
	}

	gotcount = bc_give( bc, out, count );

	if( gotcount != count )
		return MPG123_ERR;
	return gotcount;
}

// stream based operation
static mpg_ssize_t plain_fullread( mpg123_handle_t *fr, byte *buf, mpg_ssize_t count )
{
	mpg_ssize_t	ret, cnt=0;

	// there used to be a check for expected file end here (length value or ID3 flag).
	// this is not needed:
	// 1. EOF is indicated by fdread returning zero bytes anyway.
	// 2. We get false positives of EOF for either files that grew or
	// 3. ... files that have ID3v1 tags in between (stream with intro).
	while( cnt < count )
	{
		ret = fr->rdat.fdread( fr, buf + cnt, count - cnt );

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

		if(!( fr->rdat.flags & READER_BUFFERED ))
			fr->rdat.filepos += ret;
		cnt += ret;
	}

	return cnt;
}

// wrappers for actual reading/seeking... I'm full of wrappers here.
static mpg_off_t io_seek( reader_data_t *rdat, mpg_off_t offset, int whence )
{
	if( rdat->flags & READER_HANDLEIO )
	{
		if( rdat->r_lseek_handle != NULL )
			return rdat->r_lseek_handle( rdat->iohandle, offset, whence );
		return -1;
	}

	return rdat->lseek( rdat->filept, offset, whence );
}

static mpg_ssize_t io_read( reader_data_t *rdat, void *buf, size_t count )
{
	if( rdat->flags & READER_HANDLEIO )
	{
		if( rdat->r_read_handle != NULL )
			return rdat->r_read_handle( rdat->iohandle, buf, count );
		return -1;
	}

	return rdat->read( rdat->filept, buf, count );
}

// A normal read and a read with timeout.
static mpg_ssize_t plain_read( mpg123_handle_t *fr, void *buf, size_t count )
{
	return io_read( &fr->rdat, buf, count );
}

static mpg_off_t stream_lseek( mpg123_handle_t *fr, mpg_off_t pos, int whence )
{
	mpg_off_t	ret;

	ret = io_seek( &fr->rdat, pos, whence );

	if( ret >= 0 )
	{
		fr->rdat.filepos = ret;
	}
	else
	{
		fr->err = MPG123_LSEEK_FAILED;
		ret = MPG123_ERR;
	}

	return ret;
}

static void stream_close( mpg123_handle_t *fr )
{
	if( fr->rdat.flags & READER_FD_OPENED )
		close( fr->rdat.filept );

	fr->rdat.filept = 0;

	if( fr->rdat.flags & READER_BUFFERED )
		bc_reset( &fr->rdat.buffer );

	if( fr->rdat.flags & READER_HANDLEIO )
	{
		if( fr->rdat.cleanup_handle != NULL )
			fr->rdat.cleanup_handle( fr->rdat.iohandle );
		fr->rdat.iohandle = NULL;
	}
}

static int stream_seek_frame( mpg123_handle_t *fr, mpg_off_t newframe )
{
	// seekable streams can go backwards and jump forwards.
	// non-seekable streams still can go forward, just not jump.
	if(( fr->rdat.flags & READER_SEEKABLE ) || ( newframe >= fr->num ))
	{
		mpg_off_t	preframe;	// a leading frame we jump to
		mpg_off_t	seek_to;	// the byte offset we want to reach
		mpg_off_t	to_skip;	// bytes to skip to get there (can be negative)

		// now seek to nearest leading index position and read from there until newframe is reached.
		// we use skip_bytes, which handles seekable and non-seekable streams
		// (the latter only for positive offset, which we ensured before entering here).
		seek_to = frame_index_find( fr, newframe, &preframe );

		// no need to seek to index position if we are closer already.
		// but I am picky about fr->num == newframe, play safe by reading the frame again.
		// if you think that's stupid, don't call a seek to the current frame.
		if( fr->num >= newframe || fr->num < preframe )
		{
			to_skip = seek_to - fr->rd->tell( fr );
			if( fr->rd->skip_bytes( fr, to_skip ) != seek_to )
				return MPG123_ERR;

			fr->num = preframe - 1; // watch out! I am going to read preframe... fr->num should indicate the frame before!
		}

		while( fr->num < newframe )
		{
			// try to be non-fatal now... frameNum only gets advanced on success anyway
			if( !read_frame( fr )) break;
		}

		// now the wanted frame should be ready for decoding.
		return MPG123_OK;
	}
	else
	{
		fr->err = MPG123_NO_SEEK;
		return MPG123_ERR; // invalid, no seek happened
	}
}

// return FALSE on error, TRUE on success, READER_MORE on occasion
static int generic_head_read( mpg123_handle_t *fr, ulong *newhead )
{
	byte	hbuf[4];
	int	ret = fr->rd->fullread( fr, hbuf, 4 );

	if( ret == MPG123_NEED_MORE )
		return ret;

	if( ret != 4 ) return FALSE;

	*newhead = ((ulong) hbuf[0] << 24) | ((ulong) hbuf[1] << 16) | ((ulong) hbuf[2] << 8) | (ulong) hbuf[3];

	return TRUE;
}

// return FALSE on error, TRUE on success, READER_MORE on occasion
static int generic_head_shift( mpg123_handle_t *fr, ulong *head )
{
	byte	hbuf;
	int	ret = fr->rd->fullread( fr, &hbuf, 1 );

	if( ret == MPG123_NEED_MORE )
		return ret;

	if( ret != 1 ) return FALSE;

	*head <<= 8;
	*head |= hbuf;
	*head &= 0xffffffff;

	return TRUE;
}

// returns reached position... negative ones are bad...
static mpg_off_t stream_skip_bytes( mpg123_handle_t *fr, mpg_off_t len )
{
	if( fr->rdat.flags & READER_SEEKABLE )
	{
		mpg_off_t ret = stream_lseek( fr, len, SEEK_CUR );
		return (ret < 0) ? MPG123_ERR : ret;
	}
	else if( len >= 0 )
	{
		byte	buf[1024]; // ThOr: Compaq cxx complained and it makes sense to me... or should one do a cast? What for?
		mpg_ssize_t	ret;

		while( len > 0 )
		{
			mpg_ssize_t num = len < (mpg_off_t)sizeof( buf ) ? (mpg_ssize_t)len : (mpg_ssize_t)sizeof( buf );
			ret = fr->rd->fullread( fr, buf, num );
			if( ret < 0 ) return ret;
			else if( ret == 0 ) break; // EOF... an error? interface defined to tell the actual position...
			len -= ret;
		}

		return fr->rd->tell( fr );
	}
	else if( fr->rdat.flags & READER_BUFFERED )
	{
		// perhaps we _can_ go a bit back.
		if( fr->rdat.buffer.pos >= -len )
		{
			fr->rdat.buffer.pos += len;
			return fr->rd->tell( fr );
		}
		else
		{
			fr->err = MPG123_NO_SEEK;
			return MPG123_ERR;
		}
	}
	else
	{
		fr->err = MPG123_NO_SEEK;
		return MPG123_ERR;
	}
}

// return 0 on success...
static int stream_back_bytes( mpg123_handle_t *fr, mpg_off_t bytes )
{
	mpg_off_t	want = fr->rd->tell( fr ) - bytes;

	if( want < 0 ) return MPG123_ERR;

	if( stream_skip_bytes( fr, -bytes ) != want )
		return MPG123_ERR;

	return 0;
}


// returns size on success...
static int generic_read_frame_body( mpg123_handle_t *fr, byte *buf, int size )
{
	long	l;

	if(( l = fr->rd->fullread( fr, buf, size )) != size )
		return MPG123_ERR;

	return l;
}

static mpg_off_t generic_tell( mpg123_handle_t *fr )
{
	if( fr->rdat.flags & READER_BUFFERED )
		fr->rdat.filepos = fr->rdat.buffer.fileoff + fr->rdat.buffer.pos;

	return fr->rdat.filepos;
}

// this does not (fully) work for non-seekable streams... You have to check for that flag, pal!
static void stream_rewind( mpg123_handle_t *fr )
{
	if( fr->rdat.flags & READER_SEEKABLE )
	{
		fr->rdat.filepos = stream_lseek( fr, 0, SEEK_SET );
		fr->rdat.buffer.fileoff = fr->rdat.filepos;
	}

	if( fr->rdat.flags & READER_BUFFERED )
	{
		fr->rdat.buffer.pos = 0;
		fr->rdat.buffer.firstpos = 0;
		fr->rdat.filepos = fr->rdat.buffer.fileoff;
	}
}

// returns length of a file (if filept points to a file)
// reads the last 128 bytes information into buffer
// ... that is not totally safe...
static mpg_off_t get_fileinfo( mpg123_handle_t *fr )
{
	mpg_off_t	len;

	if(( len = io_seek( &fr->rdat, 0, SEEK_END )) < 0 )
		return -1;

	if( io_seek( &fr->rdat, -128, SEEK_END ) < 0 )
		return -1;

	if( fr->rd->fullread( fr, (byte *)fr->id3buf, 128 ) != 128 )
		return -1;

	if( !strncmp((char *)fr->id3buf, "TAG", 3 ))
		len -= 128;

	if( io_seek( &fr->rdat, 0, SEEK_SET ) < 0 )
		return -1;

	if( len <= 0 )
		return -1;

	return len;
}

static int bad_init( mpg123_handle_t *mh ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
static mpg_ssize_t bad_fullread( mpg123_handle_t *mh, byte *data, mpg_ssize_t count ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
static int bad_head_read( mpg123_handle_t *mh, ulong *newhead ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
static int bad_head_shift( mpg123_handle_t *mh, ulong *head ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
static mpg_off_t bad_skip_bytes( mpg123_handle_t *mh, mpg_off_t len ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
static int bad_read_frame_body( mpg123_handle_t *mh, byte *data, int size ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
static int bad_back_bytes( mpg123_handle_t *mh, mpg_off_t bytes ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
static int bad_seek_frame( mpg123_handle_t *mh, mpg_off_t num ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
static mpg_off_t bad_tell( mpg123_handle_t *mh ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
static void bad_rewind( mpg123_handle_t *mh ) { }
static void bad_close( mpg123_handle_t *mh ) { } 

static reader_t bad_reader =
{
	bad_init,
	bad_close,
	bad_fullread,
	bad_head_read,
	bad_head_shift,
	bad_skip_bytes,
	bad_read_frame_body,
	bad_back_bytes,
	bad_seek_frame,
	bad_tell,
	bad_rewind,
	NULL
};

void open_bad( mpg123_handle_t *mh )
{
	mh->rd = &bad_reader;
	mh->rdat.flags = 0;
	bc_init( &mh->rdat.buffer );
	mh->rdat.filelen = -1;
}

static reader_t readers[] =
{
	{	// READER_STREAM
		default_init,
		stream_close,
		plain_fullread,
		generic_head_read,
		generic_head_shift,
		stream_skip_bytes,
		generic_read_frame_body,
		stream_back_bytes,
		stream_seek_frame,
		generic_tell,
		stream_rewind,
		NULL
	},
	{	// READER_FEED
		feed_init,
		stream_close,
		feed_read,
		generic_head_read,
		generic_head_shift,
		feed_skip_bytes,
		generic_read_frame_body,
		feed_back_bytes,
		feed_seek_frame,
		generic_tell,
		stream_rewind,
		buffered_forget
	},
	{	// READER_BUF_STREAM
		default_init,
		stream_close,
		buffered_fullread,
		generic_head_read,
		generic_head_shift,
		stream_skip_bytes,
		generic_read_frame_body,
		stream_back_bytes,
		stream_seek_frame,
		generic_tell,
		stream_rewind,
		buffered_forget
	}
};

// final code common to open_stream and open_stream_handle.
static int open_finish( mpg123_handle_t *fr )
{
	fr->rd = &readers[READER_STREAM];
	if( fr->rd->init( fr ) < 0 )
		return -1;

	return MPG123_OK;
}

int open_stream_handle( mpg123_handle_t *fr, void *iohandle )
{
	fr->rdat.filelen = -1;
	fr->rdat.filept  = -1;
	fr->rdat.iohandle = iohandle;
	fr->rdat.flags = 0;
	fr->rdat.flags |= READER_HANDLEIO;

	return open_finish( fr );
}

int open_feed( mpg123_handle_t *fr )
{
	fr->rd = &readers[READER_FEED];
	fr->rdat.flags = 0;

	if( fr->rd->init( fr ) < 0 )
		return -1;

	return 0;
}

static int default_init( mpg123_handle_t *fr )
{
	fr->rdat.fdread = plain_read;
	fr->rdat.read = fr->rdat.r_read  != NULL ? fr->rdat.r_read  : read;
	fr->rdat.lseek = fr->rdat.r_lseek != NULL ? fr->rdat.r_lseek : lseek;
	fr->rdat.filelen = get_fileinfo( fr );
	fr->rdat.filepos = 0;

	// don't enable seeking on ICY streams, just plain normal files.
	// this check is necessary since the client can enforce ICY parsing on files that would otherwise be seekable.
	// it is a task for the future to make the ICY parsing safe with seeks ... or not.
	if( fr->rdat.filelen >= 0 )
	{
		fr->rdat.flags |= READER_SEEKABLE;
		if( !strncmp((char *)fr->id3buf,"TAG", 3 ))
		{
			fr->rdat.flags |= READER_ID3TAG;
			fr->metaflags  |= MPG123_NEW_ID3;
		}
	}
	else if( fr->p.flags & MPG123_SEEKBUFFER )
	{
		// switch reader to a buffered one, if allowed.
		if( fr->rd == &readers[READER_STREAM] )
		{
			fr->rd = &readers[READER_BUF_STREAM];
			fr->rdat.fullread = plain_fullread;
		}
		else
		{
			return -1;
		}

		bc_init( &fr->rdat.buffer );
		fr->rdat.filelen = 0; // we carry the offset, but never know how big the stream is.
		fr->rdat.flags |= READER_BUFFERED;
	}

	return 0;
}
