/*
  these classes define behaviour of bob_dom_o*filestream and 
  bob_dom_i*filestream on windows filesystem
  
  I also wanted to write streams for x2 filesystem (x2fd library) but they
  don't exist know
*/

#ifndef BOB_REALFILE_STREAM_INCLUDED
#define BOB_REALFILE_STREAM_INCLUDED 

#include <io.h> 
#include <fcntl.h>
#include <sys/stat.h>

/*
	bob_dom_obinaryrealfile - output binary stream (real filesystem files)
	bob_dom_otextrealfile - output text stream (real filesystem files)
*/

template <class Ty>
class bob_dom_owinfilestream : public Ty
{
	private:
		static const size_t buffer_size=5000;
		
		int m_fd;
		unsigned char m_buffer[buffer_size];
		unsigned char *m_bufferPos;
		unsigned char *m_bufferEnd;
		
		size_t availBuffer() const { return m_bufferEnd - m_bufferPos; }
		
	public:
		typedef Ty base;
		typedef typename base::state state;
		typedef typename base::size_type size_type;
		typedef typename base::const_value_type_ptr const_value_type_ptr;
		typedef typename const base::value_type const_value_type;
		
		bob_dom_owinfilestream() { m_fd=-1; m_bufferPos=m_buffer; m_bufferEnd=m_buffer + buffer_size; clear(); }
		~bob_dom_owinfilestream() { close(); }
		
		static size_type bufferSize() { return buffer_size; }
		
		void clear(state s=goodbit) { base::clear(s); setstate(m_fd!=-1 ? goodbit : badbit); }
		
		virtual bool open(const char *fileName, bob_filestream::mode m)
		{
			int f=0, cf=0;
			
			name(fileName);
			
			if((m & bob_filestream::rdonly && m & bob_filestream::create) || (m & bob_filestream::rdonly && m & bob_filestream::rdwr)){
				clear(failbit);
				return false;
			}
			if(m & bob_filestream::rdonly)
				f|=_O_RDONLY;
			else if(m & bob_filestream::rdwr)
				f|=_O_RDWR;
			
			if(m & bob_filestream::create)
				f|=_O_CREAT | _O_RDWR;
			
			m_fd=::open(fileName, _O_BINARY | f, _S_IREAD | _S_IWRITE);
			if(m & bob_filestream::seekeof) 
				lseek(m_fd, 0, SEEK_END);
			
			clear();
			return m_fd!=-1;
		}
		
		virtual void close() 
		{ 
			flush();
			chsize(m_fd, ::tell(m_fd));
			::close(m_fd); 
			m_fd=-1; 
			setstate(badbit);
		}
		
		void flush() 
		{
			if(!good()) return;
			size_t s=m_bufferPos - m_buffer;
			if(s==0) return;
			if(::write(m_fd, m_buffer, (unsigned int)s)!=(unsigned int)s)	
				setstate(failbit);
			else
				m_bufferPos=m_buffer;
		}
		
		virtual size_type write(const_value_type_ptr data, size_t size)
		{
			if(!good()) return -1;
			
			size_type res=0;
			if(size > availBuffer())
				flush();
			
			if(size > availBuffer()){
				res=::write(m_fd, data, (unsigned int)size);
				if(res!=size)
					setstate(failbit);
			}
			else{
				memcpy(m_bufferPos, data, size);
				m_bufferPos+=size;
				res=size;
			}
			return res;
		}
		
		virtual bool put(const_value_type &ch)
		{
			return write(&ch, 1) > 0;
		}
		
		virtual size_t tell()
		{
			size_t pos=::tell(m_fd);
			if(pos>=0) pos+=m_bufferPos - m_buffer;
			return pos;
		}
		
		virtual size_t seek(int offset, bob_filestream::seek_type origin) 
		{ 
			int o;
			size_t res;
			flush();
			
			switch(origin){
				case bob_filestream::seek_begin:
					o=SEEK_SET;
					break;
				case bob_filestream::seek_end:
					o=SEEK_END;
					break;
				case bob_filestream::seek_current:
					o=SEEK_CUR;
					break;
				default:	
					o=-1;
			}
			res=lseek(m_fd, offset, o);
			
			return res;
		}
};

typedef bob_dom_owinfilestream<bob_dom_obinaryfilestream> bob_dom_obinaryrealfile;
typedef bob_dom_owinfilestream<bob_dom_otextfilestream> bob_dom_otextrealfile;

class bob_iwinfilestream : public bob_dom_ibinaryfilestream
{	
	public:
		typedef bob_dom_ibinaryfilestream base;
		typedef base::value_type_ptr value_type_ptr;
		typedef base::value_type value_type;
		typedef base::size_type size_type;
		typedef ext::stream_base::state state;
		typedef base::offset_type offset_type;
		
	private:
		static const size_t m_bufferSize=5000;
		//static const size_t m_bufferSize=8;

		int m_fd;
		
		value_type_ptr m_buffer;
		const_value_type_ptr m_bufferEnd;
		const_value_type_ptr m_bufferPos;
		
		offset_type m_lineEnd;
		
		bool m_bBufferEOF;
		
	public:
		static size_type bufferSize() { return m_bufferSize; }
		
	private:
		size_type bufferAvail() const { return m_bufferEnd - m_bufferPos; }
		
		size_t fillBuffer()
		{
			size_t r=(size_t)::read(m_fd, m_buffer, m_bufferSize);
			m_bufferEnd=m_buffer + r; // set buffer end
			m_bufferPos=m_buffer; // buffer pos to beginning
			m_bBufferEOF=::eof(m_fd)!=0; // is this the last buffer?
			return r;
		}
		
	public:
		bob_iwinfilestream() 
		{ 
			m_fd=-1; clear(badbit); 
			m_buffer=new value_type[m_bufferSize]; 
		}
		
		~bob_iwinfilestream() { close(); delete m_buffer; }
		
		void clear(state s=goodbit) { base::clear(s); setstate(m_fd!=-1 ? goodbit : badbit); }
		
		bool open(const char *fileName, bob_filestream::mode m)
		{
			int f=0, cf=0;
			
			name(fileName);
			
			if((m & bob_filestream::rdonly && m & bob_filestream::create) || (m & bob_filestream::rdonly && m & bob_filestream::rdwr)){
				clear(failbit);
				return false;
			}
			if(m & bob_filestream::rdonly)
				f|=_O_RDONLY;
			else if(m & bob_filestream::rdwr)
				f|=_O_RDWR;
			
			if(m & bob_filestream::create)
				f|=_O_CREAT | _O_RDWR;
			
			m_fd=::open(fileName, _O_BINARY | f, _S_IREAD | _S_IWRITE);
			clear();
			
			if(m & bob_filestream::seekeof) 
				lseek(m_fd, 0, SEEK_END);

			m_bufferPos = m_bufferEnd = m_buffer + m_bufferSize; 
			m_bBufferEOF=false;
			
			m_lineEnd=0;
			
			return m_fd!=-1;
		}
		
		void close() 
		{ 
			::close(m_fd);
			m_fd=-1; 
			setstate(badbit);
		}
		
		bool advance(offset_type off) 
		{ 
			// offset is in our buffer, just shift the buffer pos
			if((m_bufferPos + off >= m_buffer) && (m_bufferPos + off <= m_bufferEnd)){
				m_bufferPos+=off;
				// our buffer is the last one, if we are at its end we are at eof
				if(m_bBufferEOF && m_bufferPos==m_bufferEnd)
					setstate(eofbit);
				else
					clear((state)(rdstate() & ~eofbit));
				return true;
			}
			// offset is outside our buffer, do a file seek
			else{
				// file offset is always at end of our buffer but buffer offset is different
				// we must adjust the offset to lseek by difference between end of buffer and buffer pos
				int res=::lseek(m_fd, off - (long)(m_bufferEnd - m_bufferPos), SEEK_CUR); 
				if(res==-1) 
					setstate(failbit); 
				else
					m_bufferPos=m_bufferEnd; // "erase" the buffer
				
				if(::eof(m_fd)) 
					setstate(eofbit);
				else
					clear((state)(rdstate() & ~eofbit));
				
				return res!=-1; 
			}
		}
		
		size_t tell() const 
		{ 
			return ::tell(m_fd) - (m_bufferEnd - m_bufferPos);
		}
		
		size_type read(value_type_ptr buffer, size_type size)
		{
			if(eof() && size > 0) setstate(failbit);
			if(!good()) return -1;
			
			// copy everything from our buffer to the out buffer
			size_t readed=0;
			size_t s=bufferAvail();
			if(size < s) s=size;
			
			memcpy(buffer, m_bufferPos, s);
			advance((offset_type)s);
			size-=s;
			readed=s;
			
			// our buffer did't have all the required data - we must read more
			if(size){
				// our buffer is not large enough - read it directly
				if(size > m_bufferSize){
					readed+=(size_t)::read(m_fd, buffer + s, (unsigned int)size);
					m_bufferPos=m_bufferEnd; // this means that our buffer is empty
					if(::eof(m_fd)) 
						setstate(eofbit);
					else
						clear((state)(rdstate() & ~eofbit));
				}
				// read the data to our buffer and copy the required size to out buffer
				else{
					size_t r=fillBuffer();
					if(size < r) r=size; // copy the missing data to out buffer
					memcpy(buffer + readed, m_bufferPos, r);
					readed+=r;
					advance((offset_type) r);
				}
			}
			return readed;
		}
		
		value_type_ptr readln()
		{
			value_type_ptr buff=0;
			value_type ch, old;
			int size;
			
			if(!good()) return 0;
			
			while(!ext::stream_base::eof()){
				if(bufferAvail()==0)
					fillBuffer();
					
				// 0xD might be followed by 0xA
				ch=old=*m_bufferPos++;
				if(ch==0xD || ch==0xA){
					size=(offset_type)tell() - m_lineEnd;
					advance(-size);
					buff=new value_type[size];
					read(buff, size);
					buff[size-1]=0; // to overwrite line end (0xD or 0xA)

					advance(1);
					m_lineEnd=(offset_type)tell();
					
					if(ch==0xD){
						// we are not at eof
						if(!eof()){
							if(bufferAvail()==0) fillBuffer();
							if(*m_bufferPos==0xA){
								advance(1);
								m_lineEnd++;
							}
						}
					}
					break;
				}
			}
			if(buff==0){
				size=(offset_type)tell() - m_lineEnd;
				advance(-size);
				buff=new value_type[size + 1];
				read(buff, size);
				buff[size]=0;
			}
			
			return buff;
		}
		
		value_type_ptr readln2()
		{
			if(!good()) return 0;
			
			if(bufferAvail()==0) fillBuffer();
			/*
			value_type *pos;
			for(pos=m_bufferPos; pos < m_bufferEnd; ++pos){
				if(*pos==0xD || 0xA){
					newLineEnd=tell() + pos;
					
					lseek(m_fd, m_lineEnd, SEEK_SET);
					::read
				}
			}*/
		}
};

typedef bob_iwinfilestream bob_dom_ibinaryrealfile;

#endif // !defined(BOB_REALFILE_STREAM_INCLUDED)