/*	Copyright (C) 2004 Garrett A. Kajmowicz

	This file is part of the uClibc++ Library.
	This library 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 library 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 library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifndef __STD_HEADER_FSTREAM 
#define __STD_HEADER_FSTREAM 1


#include<basic_definitions>

#include <cstdio>
#include <cstdlib>
#include <streambuf>
#include <istream>
#include <ostream>
#include <char_traits>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#ifdef __UCLIBCXX_HAS_WCHAR__
#include <cwchar>
#include <cwctype>
#endif //__UCLIBCXX_HAS_WCHAR__

#pragma GCC visibility push(default)

namespace std{

	template <class C, class T> class basic_filebuf;

	typedef basic_filebuf<char>    filebuf;
#ifdef __UCLIBCXX_HAS_WCHAR__
	typedef basic_filebuf<wchar_t> wfilebuf;
#endif


	template <class charT, class traits> class _UCXXEXPORT basic_filebuf
		: public basic_streambuf<charT,traits>
	{
#ifdef __UCLIBCXX_SUPPORT_CDIR__
		friend ios_base::Init::Init();	//Needed for cout/cin stuff
#endif
	public:
		// Types (inherited from basic_streambuf:
		typedef typename basic_streambuf<charT, traits>::char_type	char_type;
		typedef typename basic_streambuf<charT, traits>::int_type 	int_type;
		typedef typename basic_streambuf<charT, traits>::pos_type	pos_type;
		typedef typename basic_streambuf<charT, traits>::off_type	off_type;
		typedef traits							traits_type;

		//Constructors/destructor:

		_UCXXEXPORT basic_filebuf()	: basic_streambuf<charT, traits>(), fp(0), pbuffer(0), gbuffer(0)
		{
			append=false;
			pbuffer = new char_type[__UCLIBCXX_IOSTREAM_BUFSIZE__];
			gbuffer = new char_type[__UCLIBCXX_IOSTREAM_BUFSIZE__];

			this->setp(pbuffer, pbuffer + __UCLIBCXX_IOSTREAM_BUFSIZE__);
			//Position get buffer so that there is no data available
			this->setg(gbuffer, gbuffer + __UCLIBCXX_IOSTREAM_BUFSIZE__,
				gbuffer + __UCLIBCXX_IOSTREAM_BUFSIZE__);
		}


		_UCXXEXPORT virtual ~basic_filebuf(){
			sync();
			close();
			delete [] pbuffer;
			delete [] gbuffer;
			pbuffer = 0;
			gbuffer = 0;
		}


		//Members:

		_UCXXEXPORT bool is_open() const{
			if(fp == 0){
				return false;
			}
			return true;
		}

		_UCXXEXPORT basic_filebuf<charT,traits>* open(const char* s, ios_base::openmode mode){
			bool move_end = (mode & ios_base::ate) != 0;
			if(is_open() !=false){	//Must call close() first
				return 0;
			}
			basic_streambuf<charT,traits>::openedFor = mode;
			mode = mode & ~ios_base::ate;

			if(mode == ios_base::out || mode == (ios_base::out | ios_base::trunc)){
				fp = fopen(s, "w" );
			}else if((mode & ios_base::app) && ! (mode & ios_base::trunc)){
				if(mode & ios_base::binary){
					if(mode & ios_base::in){
						fp = fopen(s, "a+b");
					}else{
						fp = fopen(s, "ab");
					}
				}else{
					if(mode & ios_base::in){
						fp = fopen(s, "a+");
					}else{
						fp = fopen(s, "a");
					}
				}
			}else if(mode == ios_base::in){
				fp = fopen(s, "r");
			}else if(mode == (ios_base::in | ios_base::out)){
				fp = fopen(s, "r+");
			}else if(mode == (ios_base::in | ios_base::out | ios_base::trunc)){
				fp = fopen(s, "w+");
			}else if(mode == (ios_base::binary | ios_base::out)){
				fp = fopen(s, "wb");
			}else if(mode == (ios_base::in | ios_base::binary)){
				fp = fopen(s, "rb");
			}else if(mode == (ios_base::in | ios_base::binary | ios_base::out)){
				fp = fopen(s, "r+b");
			}else if(mode==(ios_base::binary | ios_base::out | ios_base::trunc)){
				fp = fopen(s, "w+b");
			}else if(mode == (ios_base::in | ios_base::binary | ios_base::out | ios_base::trunc)){
				fp = fopen(s, "w+b");
			}

			if(fp == 0){
				return 0;
			}
			if(ferror(fp)){
				fclose(fp);
				fp=0;
				return 0;
			}
			int retval = 0;

			//Check to make sure the stream is good
			if(move_end == true){
				retval = fseek(fp, 0, SEEK_END);
			}else{
				retval = fseek(fp, 0, SEEK_SET);
			}
			if(retval!=0){		//Seek error
				fclose(fp);
				fp=0;
				return 0;
			}

			basic_streambuf<charT,traits>::mgnext = basic_streambuf<charT,traits>::mgend;

			return this;
		}
		_UCXXEXPORT basic_filebuf<charT,traits>* close(){
			if(fp != 0 && fp != stdin && fp != stdout && fp !=stderr ){
				overflow();
				sync();
				int retval = fclose(fp);
				if(retval !=0){		//Error of some sort
					return 0;
				}
				fp=0;
			}
			return this;
		}
	protected:
		_UCXXEXPORT basic_filebuf(const basic_filebuf<charT,traits> &){ }
		_UCXXEXPORT basic_filebuf<charT,traits> & operator=(const basic_filebuf<charT,traits> &){ return *this; }

		//Overridden virtual functions:

		virtual _UCXXEXPORT int showmanyc(){
			return basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr();
		}
		virtual _UCXXEXPORT int_type underflow(){
			/* Some variables used internally:
			   Buffer pointers:
			   charT * mgbeg;
			   charT * mgnext;
			   charT * mgend;

			   eback() returns mgbeg
			   gptr()  returns mgnext
			   egptr() returns mgend

			   gbump(int n) mgnext+=n

			*/

			if(!is_open()){
				return traits::eof();
			}

			if(basic_streambuf<charT,traits>::eback() == 0){
				//No buffer, so...
				charT c;
				int retval;
				retval = fread(&c, sizeof(charT), 1, fp);

				if(retval == 0 || feof(fp) || ferror(fp) ){
					return traits::eof();
				}
				return traits::to_int_type(c);
			}

			if(basic_streambuf<charT,traits>::eback() == basic_streambuf<charT,traits>::gptr()){	//Buffer is full
				return traits::to_int_type(*basic_streambuf<charT,traits>::gptr());
			}
			//Shift entire buffer back to the begining
			size_t offset = basic_streambuf<charT,traits>::gptr() - basic_streambuf<charT,traits>::eback();
			size_t amountData = basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr();

			for(charT * i = basic_streambuf<charT,traits>::gptr(); i < basic_streambuf<charT,traits>::egptr(); ++i){
				*(i-offset) = *i;
			}

			size_t retval = 0;
			//Save operating flags from file descriptor
			int fcntl_flags = fcntl(fileno(fp), F_GETFL);
			retval = 0;

			//Set to non_blocking mode
			fcntl(fileno(fp), F_SETFL, fcntl_flags | O_NONBLOCK);

			//Fill rest of buffer
			retval = fread(basic_streambuf<charT,traits>::egptr() - 
				basic_streambuf<charT,traits>::gptr() + basic_streambuf<charT,traits>::eback(),
				sizeof(charT),
				offset,
				fp
			);

			//Clear problems where we didn't read in enough characters
			if(EAGAIN == errno){
				clearerr(fp);
			}

			//Restore file descriptor clase
			fcntl(fileno(fp), F_SETFL, fcntl_flags);

			//Now we are going to make sure that we read in at least one character.  The hard way.
			if(retval == 0){
				fcntl_flags = fcntl(fileno(fp), F_GETFL);
				//Set to blocking mode
				fcntl(fileno(fp), F_SETFL, fcntl_flags & ~O_NONBLOCK);

				retval = fread(basic_streambuf<charT,traits>::egptr() - 
					basic_streambuf<charT,traits>::gptr() + basic_streambuf<charT,traits>::eback(),
					sizeof(charT),
					1,
					fp
				);

				//Restore file descriptor clase
				fcntl(fileno(fp), F_SETFL, fcntl_flags);

			}

			if(retval !=offset){ //Slide buffer forward again
				for(size_t i = 0; i < amountData + retval; ++i){
					*(basic_streambuf<charT,traits>::egptr() - i - 1) =
						*(basic_streambuf<charT,traits>::eback() + amountData + retval - i - 1);
				}
			}

			basic_streambuf<charT,traits>::mgnext -= retval;

			if( (retval <= 0 && feof(fp)) || ferror(fp) ){
				return traits::eof();
			}

			return traits::to_int_type(*basic_streambuf<charT,traits>::gptr());
		}
		virtual _UCXXEXPORT int_type uflow(){
			bool dobump = (basic_streambuf<charT,traits>::gptr() != 0);
			int_type retval = underflow();
			if(dobump){
				basic_streambuf<charT,traits>::gbump(1);
			}
			return retval;
		}
		virtual _UCXXEXPORT int_type pbackfail(int_type c = traits::eof()){
			if(is_open() == false || 
				basic_streambuf<charT,traits>::gptr() == basic_streambuf<charT,traits>::eback())
			{
				return traits::eof();
			}
			if(traits::eq_int_type(c,traits::eof()) == false){
				if(traits::eq(traits::to_char_type(c), basic_streambuf<charT,traits>::gptr()[-1]) == true){
					basic_streambuf<charT,traits>::gbump(-1);
				}else{
					basic_streambuf<charT,traits>::gbump(-1);
					basic_streambuf<charT,traits>::gptr()[0] = c;
				}
				return c;
			}else{
				basic_streambuf<charT,traits>::gbump(-1);
				return traits::not_eof(c);
			}
		}

		virtual _UCXXEXPORT int_type overflow(int_type c = traits::eof()){
			if(is_open() == false){
				//Can't do much
				return traits::eof();
			}
			if(basic_streambuf<charT,traits>::pbase() == 0){		//Unbuffered - elliminate dupe code below
				if(fputc(c, fp) == EOF){
					return traits::eof();
				}
				return c;
			}
			if(basic_streambuf<charT,traits>::pbase() == 0 && traits::eq_int_type(c,traits::eof()) ){
				//Nothing to flush
				return traits::not_eof(c);
			}
			size_t r = basic_streambuf<charT,traits>::pptr() - basic_streambuf<charT,traits>::pbase();

			if( r == 0 && traits::eq_int_type(c,traits::eof()) ){
				return traits::not_eof(c);
			}else if (r == 0 ){
				if(fputc(c, fp) == EOF){
					return traits::eof();
				}
				return c;
			}

			size_t totalChars = r;

			char_type *buffer = 0;
			if(traits::eq_int_type(c,traits::eof())){
				buffer = new char_type[r];
			}else{
				buffer = new char_type[r+1];
				buffer[r] = c;
				totalChars++;
			}

			traits::copy(buffer, basic_streambuf<charT,traits>::pbase(), r);
//			memcpy(buffer, basic_streambuf<charT,traits>::pbase(), r);

			size_t retval = fwrite(buffer, sizeof(charT), totalChars, fp);
			if(retval !=totalChars){
				if(retval == 0){
					delete [] buffer;
					return traits::eof();
				}
				basic_streambuf<charT,traits>::pbump(-retval);
				fprintf(stderr, "***** Did not write the full buffer out.  Should be: %d, actually: %d\n",
					 totalChars, retval);
			}else{
				basic_streambuf<charT,traits>::pbump(-r);
			}

			delete [] buffer;
			return traits::not_eof(c);
		}

		virtual _UCXXEXPORT basic_streambuf<charT,traits>* setbuf(char_type* s, streamsize n){
			if(s == 0 && n == 0){	//Unbuffered
				if(pbuffer !=0){
					delete [] pbuffer;
				}
				if(gbuffer !=0){
					delete [] gbuffer;
				}
				pbuffer = 0;
				gbuffer = 0;
			}else if(basic_streambuf<charT,traits>::gptr() !=0 && 
				basic_streambuf<charT,traits>::gptr()==basic_streambuf<charT,traits>::egptr())
			{
				delete [] pbuffer;
				pbuffer = s;
			}
			return this;
		}
		virtual _UCXXEXPORT pos_type seekoff(off_type off, ios_base::seekdir way, 
			ios_base::openmode = ios_base::in | ios_base::out)
		{
			if(is_open() == false){
				return -1;
			}
			int whence = SEEK_SET;	// if(way == basic_ios<charT>::beg)
			off_type position = off;

			if(way == basic_ios<charT>::cur){
				whence = SEEK_CUR;
				position -= (basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr());
			}else if(way == basic_ios<charT>::end){
				whence = SEEK_END;
			}

			sync();

			int retval = fseek(
				fp,
				sizeof(charT)*(position),
				whence
			);

			//Invalidate read buffer
			basic_streambuf<charT,traits>::gbump(
				basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr()
			);

			if(-1 != retval){
				retval = ftell(fp);
			}

			return retval;
		}
		virtual _UCXXEXPORT pos_type seekpos(pos_type sp, ios_base::openmode = ios_base::in | ios_base::out){
			if(is_open() == false){
				return -1;
			}
			sync();

			int retval = fseek(fp,sizeof(charT)* sp, SEEK_SET);

			//Invalidate read buffer
			basic_streambuf<charT,traits>::gbump(basic_streambuf<charT,traits>::egptr() - basic_streambuf<charT,traits>::gptr());
			if(retval > -1){
				return sp;
			}
			return -1;
		}
		virtual _UCXXEXPORT int sync(){
			if(pbuffer !=0){
				if(overflow() == traits::eof()){
					return -1;
				}
			}
			if(0 != fp && 0 != fflush(fp)){
				return -1;
			}
			return 0;
		}
		virtual _UCXXEXPORT void imbue(const locale&){
			return;
		}


		virtual _UCXXEXPORT streamsize xsputn(const char_type* s, streamsize n){
			if(is_open() == false){
				return 0;
			}
			//Check to see if buffered

			//Check to see if we can buffer the data
			streamsize buffer_avail = basic_streambuf<charT,traits>::epptr() - basic_streambuf<charT,traits>::pptr();

			if(n > buffer_avail){		//Flush buffer and write directly
				overflow();	//Flush the buffer
				return fwrite(s, sizeof(charT), n, fp);
			}

			//Add to buffer to be written later

			traits::copy(basic_streambuf<charT,traits>::pptr(), s, n);
			basic_streambuf<charT,traits>::pbump(n);

			return n;
		}

		FILE * fp;
		char_type * pbuffer;
		char_type * gbuffer;
		bool append;
	};


#ifdef __UCLIBCXX_HAS_WCHAR__

template <> _UCXXEXPORT basic_filebuf<wchar_t, char_traits<wchar_t> >::int_type
	basic_filebuf<wchar_t, char_traits<wchar_t> >::overflow(int_type c);

template <> _UCXXEXPORT basic_filebuf<wchar_t, char_traits<wchar_t> >::int_type
	basic_filebuf<wchar_t, char_traits<wchar_t> >::underflow();

#endif //__UCLIBCXX_HAS_WCHAR__



#ifdef __UCLIBCXX_EXPAND_FSTREAM_CHAR__
#ifndef __UCLIBCXX_COMPILE_FSTREAM__

#ifdef __UCLIBCXX_EXPAND_CONSTRUCTORS_DESTRUCTORS__

	template <> _UCXXEXPORT filebuf::basic_filebuf();
	template <> _UCXXEXPORT filebuf::~basic_filebuf();

#endif // __UCLIBCXX_EXPAND_CONSTRUCTORS_DESTRUCTORS__

	template <> _UCXXEXPORT filebuf::int_type filebuf::pbackfail(filebuf::int_type c);
	template <> _UCXXEXPORT filebuf * filebuf::open(const char* s, ios_base::openmode mode);
	template <> _UCXXEXPORT filebuf * filebuf::close();
	template <> _UCXXEXPORT filebuf::int_type filebuf::overflow(filebuf::int_type c);
	template <> _UCXXEXPORT filebuf::int_type filebuf::underflow ();

	template <> _UCXXEXPORT basic_streambuf<char, char_traits<char> > * filebuf::setbuf(char * s, streamsize n);
	template <> _UCXXEXPORT streamsize filebuf::xsputn(const char* s, streamsize n);

#endif
#endif


	template <class charT, class traits> class _UCXXEXPORT basic_ifstream
		: public basic_istream<charT,traits>
	{
	public:
		typedef charT char_type;
		typedef typename traits::int_type int_type;
		typedef typename traits::pos_type pos_type;
		typedef typename traits::off_type off_type;

		_UCXXEXPORT basic_ifstream(): basic_ios<charT, traits>(&sb), basic_istream<charT,traits>(&sb){
			//Passing the address of sb
		}
		explicit _UCXXEXPORT basic_ifstream(const char* s, ios_base::openmode mode = ios_base::in)
			: basic_ios<charT, traits>(&sb), basic_istream<charT,traits>(&sb)
		{
			if(sb.open(s, mode) == 0){
				basic_ios<charT,traits>::setstate(ios_base::failbit);
			}
		}

		virtual _UCXXEXPORT ~basic_ifstream(){

		}

		_UCXXEXPORT basic_filebuf<charT,traits>* rdbuf() const{
			return (basic_filebuf<charT,traits>*)&sb;
		}
		_UCXXEXPORT bool is_open() const{
			return sb.is_open();
		}
		_UCXXEXPORT void open(const char* s, ios_base::openmode mode = ios_base::in){
			if(sb.open(s, mode) == 0){
				basic_ios<charT,traits>::setstate(ios_base::failbit);
			}
		}
		_UCXXEXPORT void close(){
			sb.close();
		}
	private:
		basic_filebuf<charT,traits> sb;
	};


	template <class charT, class traits> class _UCXXEXPORT basic_ofstream
		: public basic_ostream<charT,traits>
	{
	public:
		typedef charT char_type;
		typedef typename traits::int_type int_type;
		typedef typename traits::pos_type pos_type;
		typedef typename traits::off_type off_type;

		_UCXXEXPORT basic_ofstream() : basic_ios<charT, traits>(&sb), basic_ostream<charT,traits>(&sb){

		}

		virtual _UCXXEXPORT ~basic_ofstream();

		explicit _UCXXEXPORT basic_ofstream(const char* s, ios_base::openmode mode = ios_base::out | ios_base::trunc) :
			basic_ios<charT, traits>(&sb), basic_ostream<charT,traits>(&sb)
		{
			if(sb.open(s, mode | ios_base::out ) == 0){
				basic_ios<charT,traits>::setstate(ios_base::failbit);
			}
		}

		_UCXXEXPORT basic_filebuf<charT,traits>* rdbuf() const{
			return (basic_filebuf<charT,traits>*)&sb;
		}

		_UCXXEXPORT bool is_open() const{
			return sb.is_open();
		}
		_UCXXEXPORT void open(const char* s, ios_base::openmode mode = ios_base::out | ios_base::trunc){
			if(sb.open(s, mode) == 0){
				basic_ios<charT,traits>::setstate(ios_base::failbit);
			}
		}
		_UCXXEXPORT void close(){
			sb.close();
		}
	private:
		basic_filebuf<charT,traits> sb;
	};

	template <class charT, class traits> _UCXXEXPORT basic_ofstream<charT, traits>::~basic_ofstream(){
		basic_ostream<charT, traits>::flush();
	}


	template <class charT, class traits> class _UCXXEXPORT basic_fstream
		: public basic_iostream<charT,traits>
	{
	public:
		typedef charT char_type;
		typedef typename traits::int_type ins_type;
		typedef typename traits::pos_type pos_type;
		typedef typename traits::off_type off_type;

		_UCXXEXPORT basic_fstream(): basic_ios<charT, traits>(&sb), basic_iostream<charT,traits>(&sb){ }

		explicit _UCXXEXPORT basic_fstream(const char* s, ios_base::openmode mode = ios_base::in|ios_base::out):
			basic_ios<charT, traits>(&sb), basic_iostream<charT,traits>(&sb)
		{
			if(sb.open(s, mode) == 0){
				basic_ios<charT,traits>::setstate(ios_base::failbit);
			}
		}

		_UCXXEXPORT basic_filebuf<charT,traits>* rdbuf() const{
			return (basic_filebuf<charT,traits>*)&sb;
		}
		_UCXXEXPORT bool is_open() const{
			return sb.is_open();
		}
		_UCXXEXPORT void open(const char* s, ios_base::openmode mode = ios_base::in|ios_base::out){
			if(sb.open(s, mode) == 0){
				basic_ios<charT,traits>::setstate(ios_base::failbit);
			}
		}
		_UCXXEXPORT void close(){
			sb.close();
		}
	private:
		basic_filebuf<charT,traits> sb;
	};



#ifdef __UCLIBCXX_EXPAND_FSTREAM_CHAR__
#ifndef __UCLIBCXX_COMPILE_FSTREAM__

#ifdef __UCLIBCXX_EXPAND_CONSTRUCTORS_DESTRUCTORS__

	template <> _UCXXEXPORT basic_ofstream<char, char_traits<char> >::basic_ofstream();
	template <> _UCXXEXPORT basic_ofstream<char, char_traits<char> >::basic_ofstream(const char* s, ios_base::openmode mode);
	template <> _UCXXEXPORT basic_ofstream<char, char_traits<char> >::~basic_ofstream();

	template <> _UCXXEXPORT basic_ifstream<char, char_traits<char> >::basic_ifstream();
	template <> _UCXXEXPORT basic_ifstream<char, char_traits<char> >::basic_ifstream(const char* s, ios_base::openmode mode);
	template <> _UCXXEXPORT basic_ifstream<char, char_traits<char> >::~basic_ifstream();

#endif // __UCLIBCXX_EXPAND_CONSTRUCTORS_DESTRUCTORS__

#endif
#endif



}

#pragma GCC visibility pop

#endif

