/*
 Author: Marcus Boerger <helly@users.sourceforge.net>
*/

/* $Id: stream_lc.h,v 1.10 2006/02/26 20:36:09 helly Exp $ */

#ifndef _stream_lc_h
#define _stream_lc_h

#include <iosfwd>
#include <fstream>
#include <assert.h>
#include <stdio.h>

namespace re2c
{

template<class _E, class _Tr = std::char_traits<_E> >
class basic_null_streambuf
	: public std::basic_streambuf<_E, _Tr>
{
public:
	basic_null_streambuf()
		: std::basic_streambuf<_E, _Tr>()
	{
	}	
};

typedef basic_null_streambuf<char> null_streambuf;

template<class _E, class _Tr = std::char_traits<_E> >
class basic_null_stream
	: public std::basic_ostream<_E, _Tr>
{
public:
	basic_null_stream()
		: std::basic_ostream<_E, _Tr>(null_buf = new basic_null_streambuf<_E, _Tr>())
	{
	}
	
	virtual ~basic_null_stream()
	{
		delete null_buf;
	}

	basic_null_stream& put(_E)
	{
		// nothing to do
		return *this;
	}
	
	basic_null_stream& write(const _E *, std::streamsize)
	{
		// nothing to do
		return *this;
	}

protected:
	basic_null_streambuf<_E, _Tr> * null_buf;
};

typedef basic_null_stream<char> null_stream;

class line_number
{
public:
	virtual ~line_number()
	{
	}

	virtual uint get_line() const = 0;
};

template<class _E, class _Tr = std::char_traits<_E> >
class basic_filebuf_lc
	: public std::basic_streambuf<_E, _Tr>
	, public line_number
{
public:
	typedef std::basic_streambuf<_E, _Tr> _Mybase;
	typedef basic_filebuf_lc<_E, _Tr> _Myt;
	typedef _E char_type;
	typedef _Tr traits_type;
	typedef typename _Tr::int_type int_type;
	typedef typename _Tr::pos_type pos_type;
	typedef typename _Tr::off_type off_type;

	basic_filebuf_lc(FILE *_fp = 0)
		: _Mybase()
		, fp(_fp)
		, must_close(false)
		, fline(1)
	{
	}

	virtual ~basic_filebuf_lc()
	{
		sync();
		if (must_close)
		{
			close();
		}
	}

	uint get_line() const
	{
		return fline + 1;
	}

	bool is_open() const
	{
		return fp != 0;
	}

	_Myt* open(const char *filename, std::ios_base::openmode mode = std::ios_base::out)
	{
		if (fp != 0)
		{
			return 0;
		}
		const char * fmode = (mode & std::ios_base::out)
		                   ? "wt"
		                   : "rt";
		if ((fp = fopen(filename, fmode)) == 0)
		{
			return 0;
		}

		must_close = true;
		return this;
	}

	_Myt* open(FILE * _fp)
	{
		if (fp != 0)
		{
			return 0;
		}
		fp = _fp;
		must_close = false;
		return this;
	}

	_Myt* close()
	{
		sync();

		if (fp == 0 || fclose(fp) != 0)
		{
			fp = 0;
			return 0;
		}
		else
		{
			fp = 0;
			return this;
		}
	}

protected:

	virtual int_type overflow(int_type c = _Tr::eof())
	{
		if (c == '\n')
		{
			++fline;
		}
		if (_Tr::eq_int_type(_Tr::eof(), c))
		{
			return _Tr::not_eof(c);
		}
		else
		{
			buffer += _Tr::to_char_type(c);
			return c;
		}
	}

	virtual int_type pbackfail(int_type c = _Tr::eof())
	{
		assert(0);
		c = 0;
		return _Tr::eof();
	}

	virtual int_type underflow() // don't point past it
	{
		int c;

		if (buffer.length())
		{
			return buffer[0];
		}
		if (fp == 0 || ((c = fgetc(fp)) == EOF))
		{
			return _Tr::eof();
		}
		buffer += (char)c;
		return c;
	}

	virtual int_type uflow() // point past it
	{
		int c;

		if (buffer.length())
		{
			c = buffer[0];
			buffer.erase(0, 1);
			return c;
		}
		if (fp == 0 || ((c = fgetc(fp)) == EOF))
		{
			return _Tr::eof();
		}
		else if (c == '\n')
		{
			++fline;
		}
		return c;
	}

#if 0
	virtual std::streamsize xsgetn(_E* buf, std::streamsize n)
	{
		std::streamsize r = 0;
		while(n--)
		{
			int_type c = underflow();
			if (_Tr::eq_int_type(_Tr::eof(), c))
			{
				break;
			}
			buf[r++] = c;
		}
		buf[r] = '\0';
		return r;
	}
#endif

	virtual pos_type seekoff(off_type off, std::ios_base::seekdir whence,
		std::ios_base::openmode = (std::ios_base::openmode)(std::ios_base::in | std::ios_base::out))
	{
		return fseek(fp, (long)off, whence);
	}

	virtual pos_type seekpos(pos_type fpos,
		std::ios_base::openmode = (std::ios_base::openmode)(std::ios_base::in | std::ios_base::out))
	{
		return fseek(fp, (long)fpos, SEEK_SET);
	}

	virtual _Mybase * setbuf(_E *, std::streamsize)
	{
		assert(0);
		return this;
	}

	virtual int sync()
	{
		fwrite(buffer.c_str(), sizeof(_E), buffer.length(), fp);
		buffer.clear();
		return fp == 0
			|| _Tr::eq_int_type(_Tr::eof(), overflow())
			|| 0 <= fflush(fp) ? 0 : -1;
	}

	virtual std::streamsize xsputn(const _E *buf, std::streamsize cnt)
	{
		fwrite(buffer.c_str(), sizeof(_E), buffer.length(), fp);
		buffer.clear();
		/*fline += std::count(buf, buf + cnt, '\n');*/
		for (std::streamsize pos = 0; pos < cnt; ++pos) 
		{
			if (buf[pos] == '\n')
			{
				++fline;
			}
		}
		return fwrite(buf, sizeof(_E), cnt, fp);
	}

private:

	FILE * fp;
	bool   must_close;
	uint   fline;
	std::basic_string<_E, _Tr> buffer;
};

typedef basic_filebuf_lc<char> filebuf_lc;

template<
	class _E, 
	class _BaseStream,
	std::ios_base::openmode  _DefOpenMode,
	class _Tr = std::char_traits<_E> >
class basic_fstream_lc
	: public _BaseStream
	, public line_number
{
public:
	typedef basic_fstream_lc<_E, _BaseStream, _DefOpenMode, _Tr> _Myt;
	typedef std::basic_ios<_E, _Tr> _Myios;
	typedef _BaseStream _Mybase;
	typedef basic_filebuf_lc<_E, _Tr> _Mybuf;

	basic_fstream_lc()
		: _Mybase(mybuf = new _Mybuf())
	{
	}

	virtual ~basic_fstream_lc()
	{
		delete mybuf;
	}

	bool is_open() const
	{
		return mybuf->is_open();
	}

	_Myt& open(const char * filename, std::ios_base::openmode mode = _DefOpenMode)
	{
		if ((mode & _DefOpenMode) == 0 || mybuf->open(filename, mode) == 0)
		{
			_Myios::setstate(std::ios_base::failbit);
		}
		return *this;
	}
	
	_Myt& open(FILE *fp)
	{
		if (mybuf->open(fp) == 0)
		{
			_Myios::setstate(std::ios_base::failbit);
		}
		return *this;
	}
	
	void close()
	{
		if (mybuf->close() == 0)
		{
			_Myios::setstate(std::ios_base::failbit);
		}
	}
	
	uint get_line() const
	{
		return mybuf->get_line();
	}

protected:
	mutable _Mybuf *mybuf;
};

template<class _E, class _Tr = std::char_traits<_E> >
class basic_ofstream_lc
	: public basic_fstream_lc<_E, std::basic_ostream<_E, _Tr>, std::ios_base::out, _Tr>
{
};

typedef basic_ofstream_lc<char> ofstream_lc;

template<class _E, class _Tr = std::char_traits<_E> >
class basic_ifstream_lc
	: public basic_fstream_lc<_E, std::basic_istream<_E, _Tr>, std::ios_base::in, _Tr>
{
};

typedef basic_ifstream_lc<char> ifstream_lc;

class file_info
{
public:

	static std::string escape(const std::string& _str)
	{
		std::string str(_str);
		size_t l = str.length();
		for (size_t p = 0; p < l; ++p)
		{
			if (str[p] == '\\')
			{
				str.insert(++p, "\\");
				++l;
			}
		}
		return str;
	}

	file_info()
		: ln(NULL)
	{
	}

	file_info(const std::string& _fname, const line_number* _ln, bool _escape = true)
		: fname(_escape ? escape(_fname) : _fname)
		, ln(_ln)
	{
	}

	file_info(const file_info& oth, const line_number* _ln = NULL)
		: fname(oth.fname)
		, ln(_ln)
	{
	}

	file_info& operator = (const file_info& oth)
	{
		*(const_cast<std::string*>(&this->fname)) = oth.fname;
		ln = oth.ln;
		return *this;
	}

	const std::string  fname;
	const line_number* ln;
};

std::ostream& operator << (std::ostream& o, const file_info& li);

} // end namespace re2c

#endif /* _stream_lc_h */
