// Data reader interface for uniform access

// File_Extractor 0.4.0
#ifndef DATA_READER_H
#define DATA_READER_H

#include "blargg_common.h"

#ifdef HAVE_ZLIB_H
#include <zlib.h>
#endif

// Supports reading and finding out how many bytes are remaining
class Data_Reader {
public:
	virtual ~Data_Reader() { }
	
	static const char eof_error []; // returned by read() when request goes beyond end
	
	// Read at most count bytes and return number actually read, or <= 0 if error
	virtual long read_avail( void*, long n ) = 0;
	
	// Read exactly count bytes and return error if they couldn't be read
	virtual blargg_err_t read( void*, long count );
	
	// Number of bytes remaining until end of file
	virtual long remain() const = 0;
	
	// Read and discard count bytes
	virtual blargg_err_t skip( long count );
	
public:
	Data_Reader() { }
	typedef blargg_err_t error_t; // deprecated
private:
	// noncopyable
	Data_Reader( const Data_Reader& );
	Data_Reader& operator = ( const Data_Reader& );
};

// Supports seeking in addition to Data_Reader operations
class File_Reader : public Data_Reader {
public:
	// Size of file
	virtual long size() const = 0;
	
	// Current position in file
	virtual long tell() const = 0;
	
	// Go to new position
	virtual blargg_err_t seek( long ) = 0;
	
	long remain() const;
	blargg_err_t skip( long n );
};

// Disk file reader
class Std_File_Reader : public File_Reader {
public:
	blargg_err_t open( const char* path );
	void close();
	
public:
	Std_File_Reader();
	~Std_File_Reader();
	long size() const;
	blargg_err_t read( void*, long );
	long read_avail( void*, long );
	long tell() const;
	blargg_err_t seek( long );
private:
	void* file_; // Either FILE* or zlib's gzFile
#ifdef HAVE_ZLIB_H
	long size_; // TODO: Fix ABI compat
#endif /* HAVE_ZLIB_H */
};

// Treats range of memory as a file
class Mem_File_Reader : public File_Reader {
public:
	Mem_File_Reader( const void*, long size );
#ifdef HAVE_ZLIB_H
	~Mem_File_Reader( );
#endif /* HAVE_ZLIB_H */

public:
	long size() const;
	long read_avail( void*, long );
	long tell() const;
	blargg_err_t seek( long );
private:
#ifdef HAVE_ZLIB_H
	bool gz_decompress();
#endif /* HAVE_ZLIB_H */

	const char* m_begin;
	long m_size;
	long m_pos;
#ifdef HAVE_ZLIB_H
	bool m_ownedPtr = false; // set if we must free m_begin
#endif /* HAVE_ZLIB_H */
};


// Makes it look like there are only count bytes remaining
class Subset_Reader : public Data_Reader {
public:
	Subset_Reader( Data_Reader*, long count );

public:
	long remain() const;
	long read_avail( void*, long );
private:
	Data_Reader* in;
	long remain_;
};

// Joins already-read header and remaining data into original file (to avoid seeking)
class Remaining_Reader : public Data_Reader {
public:
	Remaining_Reader( void const* header, long size, Data_Reader* );

public:
	long remain() const;
	long read_avail( void*, long );
	blargg_err_t read( void*, long );
private:
	char const* header;
	char const* header_end;
	Data_Reader* in;
	long read_first( void* out, long count );
};

// Invokes callback function to read data. Size of data must be specified in advance.
class Callback_Reader : public Data_Reader {
public:
	typedef const char* (*callback_t)( void* data, void* out, int count );
	Callback_Reader( callback_t, long size, void* data = 0 );
public:
	long read_avail( void*, long );
	blargg_err_t read( void*, long );
	long remain() const;
private:
	callback_t const callback;
	void* const data;
	long remain_;
};

#endif
