// File_Extractor 0.4.0. http://www.slack.net/~ant/

#include "Data_Reader.h"

#include "blargg_endian.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>

/* Copyright (C) 2005-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */

#include "blargg_source.h"

const char Data_Reader::eof_error [] = "Unexpected end of file";

blargg_err_t Data_Reader::read( void* p, long s )
{
	long result = read_avail( p, s );
	if ( result != s )
	{
		if ( result >= 0 && result < s )
			return eof_error;

		return "Read error";
	}

	return 0;
}

blargg_err_t Data_Reader::skip( long count )
{
	char buf [512];
	while ( count )
	{
		long n = sizeof buf;
		if ( n > count )
			n = count;
		count -= n;
		RETURN_ERR( read( buf, n ) );
	}
	return 0;
}

long File_Reader::remain() const { return size() - tell(); }

blargg_err_t File_Reader::skip( long n )
{
	assert( n >= 0 );
	if ( !n )
		return 0;
	return seek( tell() + n );
}

// Subset_Reader

Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
{
	in = dr;
	remain_ = dr->remain();
	if ( remain_ > size )
		remain_ = size;
}

long Subset_Reader::remain() const { return remain_; }

long Subset_Reader::read_avail( void* p, long s )
{
	if ( s > remain_ )
		s = remain_;
	remain_ -= s;
	return in->read_avail( p, s );
}

// Remaining_Reader

Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
{
	header = (char const*) h;
	header_end = header + size;
	in = r;
}

long Remaining_Reader::remain() const { return header_end - header + in->remain(); }

long Remaining_Reader::read_first( void* out, long count )
{
	long first = header_end - header;
	if ( first )
	{
		if ( first > count )
			first = count;
		void const* old = header;
		header += first;
		memcpy( out, old, first );
	}
	return first;
}

long Remaining_Reader::read_avail( void* out, long count )
{
	long first = read_first( out, count );
	long second = count - first;
	if ( second )
	{
		second = in->read_avail( (char*) out + first, second );
		if ( second <= 0 )
			return second;
	}
	return first + second;
}

blargg_err_t Remaining_Reader::read( void* out, long count )
{
	long first = read_first( out, count );
	long second = count - first;
	if ( !second )
		return 0;
	return in->read( (char*) out + first, second );
}

// Mem_File_Reader

Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
	begin( (const char*) p ),
	size_( s )
{
	pos = 0;
}

long Mem_File_Reader::size() const { return size_; }

long Mem_File_Reader::read_avail( void* p, long s )
{
	long r = remain();
	if ( s > r )
		s = r;
	memcpy( p, begin + pos, s );
	pos += s;
	return s;
}

long Mem_File_Reader::tell() const { return pos; }

blargg_err_t Mem_File_Reader::seek( long n )
{
	if ( n > size_ )
		return eof_error;
	pos = n;
	return 0;
}

// Callback_Reader

Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
	callback( c ),
	data( d )
{
	remain_ = size;
}

long Callback_Reader::remain() const { return remain_; }

long Callback_Reader::read_avail( void* out, long count )
{
	if ( count > remain_ )
		count = remain_;
	if ( Callback_Reader::read( out, count ) )
		count = -1;
	return count;
}

blargg_err_t Callback_Reader::read( void* out, long count )
{
	if ( count > remain_ )
		return eof_error;
	return callback( data, out, count );
}

// Std_File_Reader

Std_File_Reader::Std_File_Reader() : file_( 0 ) { }

Std_File_Reader::~Std_File_Reader() { close(); }

blargg_err_t Std_File_Reader::open( const char* path )
{
	file_ = fopen( path, "rb" );
	if ( !file_ )
		return "Couldn't open file";
	return 0;
}

long Std_File_Reader::size() const
{
	long pos = tell();
	fseek( (FILE*) file_, 0, SEEK_END );
	long result = tell();
	fseek( (FILE*) file_, pos, SEEK_SET );
	return result;
}

long Std_File_Reader::read_avail( void* p, long s )
{
	return fread( p, 1, s, (FILE*) file_ );
}

blargg_err_t Std_File_Reader::read( void* p, long s )
{
	if ( s == (long) fread( p, 1, s, (FILE*) file_ ) )
		return 0;
	if ( feof( (FILE*) file_ ) )
		return eof_error;
	return "Couldn't read from file";
}

long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); }

blargg_err_t Std_File_Reader::seek( long n )
{
	if ( !fseek( (FILE*) file_, n, SEEK_SET ) )
		return 0;
	if ( n > size() )
		return eof_error;
	return "Error seeking in file";
}

void Std_File_Reader::close()
{
	if ( file_ )
	{
		fclose( (FILE*) file_ );
		file_ = 0;
	}
}

// Gzip_File_Reader

static const char* get_gzip_eof( const char* path, long* eof )
{
	FILE* file = fopen( path, "rb" );
	if ( !file )
		return "Couldn't open file";

	unsigned char buf[4];
	if (fread (buf, 2, 1, file) == 1 && buf[0] == 0x1F && buf[1] == 0x8B)
	{
		if (fseek (file, -4, SEEK_END) != 0 || fread (buf, 4, 1, file) != 1)
			goto ERR_CLOSE;
		*eof = GET_LE32 (buf);
	}
	else
	{
		long pos;
		if (fseek (file, 0, SEEK_END) != 0 || (pos = ftell (file) < 0))
			goto ERR_CLOSE;
		*eof = pos;
	}

	fclose (file);
	return 0;

ERR_CLOSE:
	fclose (file);
	return "Couldn't get file size";
}

Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { }

Gzip_File_Reader::~Gzip_File_Reader() { close(); }

blargg_err_t Gzip_File_Reader::open( const char* path )
{
	close();

	RETURN_ERR( get_gzip_eof( path, &size_ ) );

	file_ = gzopen( path, "rb" );
	if ( !file_ )
		return "Couldn't open file";

	return 0;
}

long Gzip_File_Reader::size() const { return size_; }

long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); }

long Gzip_File_Reader::tell() const { return gztell( file_ ); }

blargg_err_t Gzip_File_Reader::seek( long n )
{
	if ( gzseek( file_, n, SEEK_SET ) >= 0 )
		return 0;
	if ( n > size_ )
		return eof_error;
	return "Error seeking in file";
}

void Gzip_File_Reader::close()
{
	if ( file_ )
	{
		gzclose( file_ );
		file_ = 0;
	}
}
