/*
 * pakstream.h - buffer and stream classes for writing to and reading from 
 * files through the Cipher pak file system.
 */
#ifndef pakstream_h
#define pakstream_h

#include <streambuf>
#include <iostream>
// Plus, include whatever you need to expose the cipher engine funcs

//////////////////////////////////////////////////////////////////////////

/*
 * basic_pakstreambuf - A stream buffer for writing to and reading from 
 * cipher's pak filesystem. This class is generally not used directly
 * by the user, but is used in the implementation of i/o streams (see
 * below).
 */
template <class Ch, class Tr = std::char_traits<Ch> >
class basic_pakstreambuf : public std::basic_streambuf<Ch, Tr>
{
	// HACK - There is a bug in Microsoft's STL string, where basic_string's
	// notion of eof does not equal char_traits's eof. If you are using a
	// different STL, try switching the comments on the following two lines.
	//static const int_type eof = Tr::eof();
	static const int_type eof = -1;

	bool open_flag;
	char_type *buf;
	std::streamsize bufsz;
	
	filehandle_t hfile;
	int mode;
	int filesz;
	int filepos;

public:
	basic_pakstreambuf()
	{
		open_flag = false;
		buf = 0;
		bufsz = 0;
		hfile = 0;
		mode = 0;
		filesz = 0;
		filepos = 0;
	}

	virtual ~basic_pakstreambuf()
	{
		close();
	}

	bool is_open()
	{
		return open_flag;
	}

	// File paths are always wide, regardless of template specialization.
	// mode must be one of Cipher's FS_* constants.
	bool open(const wchar_t *p, int mode)
	{
		filesz = cipher_fs_Open(p, mode, &hfile);
		this->mode = mode;
		open_flag =  (hfile != 0);
		return open_flag;
	}

	void close()
	{
		if (open_flag)
			cipher_fs_Close(hfile);
		hfile = 0;
		filesz = 0;
		filepos = 0;
		mode = 0;
		open_flag = false;
	}

protected:
	/*
	 * The protected interface determines this streambuf's policy, and is only
	 * used internally. For an understanding of each function's role, see
	 * Stroustrup's TC++PL, Sec. 21.6, as well as 
	 * http://www.cplusplus.com/ref/iostream/ .
	 *
	 * Any un-overridden virtual functions are thus because the base class'
	 * default implementation is sufficient.
	 */


	virtual std::streambuf *setbuf(char_type *p, std::streamsize n)
	{
		if (!open_flag)
			return this; // some sort of warning here would be nice

		buf = p;
		bufsz = n;

		// We don't support simultaneous read-write modes - this would imply
		// seeking, which is difficult for compressed files
		if (mode == FS_READ) {
			setp(buf, buf);
		}
		else if (mode == FS_WRITE || mode == FS_APPEND) {
			setg(buf, buf, buf);
			setp(buf, buf+bufsz);
		}

		return this;
	}

	virtual int showmanyc()
	{
		return egptr() - gptr();
	}

	virtual int_type underflow()
	{
		if (mode != FS_READ || !open_flag)
			return eof;
		
		
		if (filepos < filesz) {
			cipher_fs_ReadBytes(buf, min(bufsz, filesz-filepos), hfile);
			setg(buf, buf, (filesz-filepos < bufsz) ? buf+filesz-filepos : buf+bufsz);
			filepos += filepos+bufsz < filesz ? bufsz : filesz-filepos;
		}
		
		if (filepos <= filesz) {
			if (filepos == filesz) ++filepos;
			return *gptr();
		}
		else return eof;
	}

	virtual int_type uflow()
	{
		if (mode != FS_READ || !open_flag)
			return eof;

		if (filepos < filesz) {
			cipher_fs_ReadBytes(buf, min(bufsz, filesz-filepos), hfile);
			setg(buf, buf, (filesz-filepos < bufsz) ? buf+filesz-filepos : buf+bufsz);
			filepos += filepos+bufsz < filesz ? bufsz : filesz-filepos;
		}
		
		if (filepos <= filesz) {
			gbump(1);		
			if (filepos == filesz) ++filepos;
			return buf[0];
		}
		else return eof;
	}
		
	virtual int_type overflow(int_type c = eof)
	{
		if (!(mode == FS_WRITE || mode == FS_APPEND) || !open_flag)
			return eof;

		cipher_fs_WriteBytes(buf, bufsz, hfile);
		if (c != eof)
			cipher_fs_WriteBytes(&c, 1, hfile);
		return 1; // any non-eof value indicates success
	}

	virtual int sync()
	{
		if (mode == FS_READ || !open_flag)
			return 0;

		cipher_fs_WriteBytes(buf, pptr()-buf, hfile);
		setp(buf, buf+bufsz);
		return 0;
	}
};

//////////////////////////////////////////////////////////////////////////

/*
 * The I/O streams. Provided here are two templated classes for I/O to/from
 * Cipher's pak filesytem. Note however, that there is no stream class taht
 * handles both input and output simultaneously.
 */

 
 // NOTE: All of my error-handling mechanisms have been commented out.
 // You will probably want to replace them with whatever logging/exception
 // handling system you have set up.

//////////////////////////////////////////////////////////////////////////

/*
 * basic_opakstream - A stream for sending output to a file in Cipher's
 * pak filesystem. Use it essentially the same as std's basic_ofstream.
 * There are convenient typedefs for narrow and wide specializations 
 * at the bottom of this file.
 */

template <class Ch, class Tr = std::char_traits<Ch> >
class basic_opakstream : public std::basic_ostream<Ch, Tr>
{
	static const int BUF_SIZE = 512;
	basic_pakstreambuf<Ch, Tr> sb;
	Ch buffer[BUF_SIZE];

public:
	basic_opakstream(const tchar_t *filename, int mode) 
		: basic_ostream<Ch,Tr>(&sb), sb()
	{		
		if (!(mode == FS_WRITE || mode == FS_APPEND)) {
			// TODO: an exception or somesuch
			//LOG(ERROR, "Attempted to open opakstream with mode FS_READ!");
			return;
		}
		if (!sb.open(filename, mode)) {
			//LOG(ERROR, "Unable to open file for output:");
			//cphrout << "\t\t\"" << filename << endl;
		}
		sb.pubsetbuf(buffer, BUF_SIZE);
	}

	virtual ~basic_opakstream() { }
};

/*
 * basic_ipakstream - A stream for reading input from a file in Cipher's
 * pak filesystem. Use it essentially the same as std's basic_ifstream.
 * There are convenient typedefs for narrow and wide specializations 
 * at the bottom of this file.
 */
template <class Ch, class Tr = std::char_traits<Ch> >
class basic_ipakstream : public std::basic_istream<Ch, Tr>
{
	static const int BUF_SIZE = 512;
	basic_pakstreambuf<Ch, Tr> sb;
	Ch buffer[BUF_SIZE];

public:
	basic_ipakstream(const tchar_t *filename) 
		: basic_istream<Ch, Tr>(&sb), sb()
	{
		if (!sb.open(filename, FS_READ)) {
			//LOG(ERROR, "Unable to open file for input:");
			//cphrout << "\t\t\"" << filename << endl;
			return;
		}
		sb.pubsetbuf(buffer, BUF_SIZE);
	}

	virtual ~basic_ipakstream() { }
};


/*
 * Convenient typedefs
 */
typedef basic_opakstream<char> opakstream;
typedef basic_opakstream<wchar_t> wopakstream;
typedef basic_ipakstream<char> ipakstream;
typedef basic_ipakstream<wchar_t> wipakstream;


#endif
