/*********************************************************************
 * This file is part of the propertyservice library.
 *
 * Copyright (C) 2003 Topi Mäenpää
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.2 $
 *********************************************************************/

#ifndef _LISTSTREAM_H
#define _LISTSTREAM_H

#include <iostream>
#include "../List.h"

namespace util { namespace io {
	/**
	 * An input/output buffer that stores and retrieves character data
	 * to and from a List object. The buffer is used with the ListStream
	 * I/O stream.
	 **/
	template <class E=char, class Tr=std::char_traits<char> > class ListBuffer :
		virtual public Object, public std::basic_streambuf<E,Tr>
	{
	public:
		/**
		 * Create a new ListBuffer that writes and reads data to and from
		 * the given List.
		 *
		 * @param buffer the List the data should be read from and written to.
		 **/
		ListBuffer(List<E>& buffer) : _plstBuffer(NULL),
																	_lstBuffer(buffer),
																	_clstBuffer(buffer)
		{ initPointers(); }

		/**
		 * Create a new ListBuffer that only reads data from the given
		 * List. A separate write buffer is created for writing.
		 *
		 * @param buffer the List the data should be read from.
		 *
		 * @param blockSize the block size of the write buffer. This value
		 * defaults to 1 to minimize memory consumption in cases where no
		 * output buffer is needed.
		 **/
		ListBuffer(const List<E>& buffer, int blockSize=1) : _plstBuffer(new List<E>(blockSize, blockSize)),
																												 _lstBuffer(*_plstBuffer),
																												 _clstBuffer(buffer)
		{ initPointers(); }
			
		/**
		 * Create a new ListBuffer with the given block size.
		 *
		 * @param blockSize the number of elements the buffer will grow
		 * each time it overflows. Use a reasonable value here to balance
		 * between speed and memory consumption.
		 **/
		ListBuffer(int blockSize=1024) : _plstBuffer(new List<E>(blockSize, blockSize)),
																		 _lstBuffer(*_plstBuffer),
																		 _clstBuffer(*_plstBuffer)
		{ initPointers(); }

		/**
		 * Get a reference to the List the buffer writes its data to. In
		 * most cases this will be the same as the read buffer. If
		 * modifications are made to this list, the behavior of the
		 * ListBuffer is undefined. If you need to alter the contents of
		 * the list, it is a good idea to top using the ListBuffer
		 * instance that uses the list as an output buffer.
		 *
		 * @return a reference to the write buffer
		 **/
		List<E>& getWriteBuffer() { return _lstBuffer; }

		/**
		 * Get a reference to the List the buffer writes its data to. In
		 * most cases this will be the same as the read buffer.
		 *
		 * @return a const reference to the write buffer
		 **/
		const List<E>& getWriteBuffer() const { return _lstBuffer; }

		/**
		 * Get a const reference to the List the buffer reads its data
		 * from. In most cases this will be the same as the read buffer.
		 *
		 * @return a const reference to the read buffer
		 **/
		const List<E>& getReadBuffer() const { return _clstBuffer; }

		/**
		 * Destroy the ListBuffer.
		 **/
		virtual ~ListBuffer() { delete _plstBuffer; }

	protected:
		virtual int underflow();
		virtual int overflow(int = traits_type::eof());
		virtual std::streamsize xsputn(const E *ptr, std::streamsize count);
		virtual std::streamsize xsgetn(E *ptr, std::streamsize count);
	private:
		List<E>* _plstBuffer;
    List<E>& _lstBuffer;
    const List<E>& _clstBuffer;

		void initPointers();
	};


	/**
	 * A stream that writes and retrieves data to and from a List
	 * object. ListStream works both as an input stream and as an output
	 * stream. An example:
	 *
	 * <pre>
	 * using namespace std;
	 * using namespace util;
	 *
	 * ListStream&lt;&gt; stream;
	 * stream &lt;&lt; "Here we have " &lt;&lt; 3 &lt;&lt; " different objects inserted.";
	 * string str;
	 * for(;;)
	 *   {
	 *     string &gt;&gt; str;
	 *     if (!stream) break;
	 *     cout &lt;&lt; str &lt;&lt; endl;
	 *   }
	 * </pre>
	 *
	 * The example should output:
	 *
	 * <pre>
	 * Here
	 * we
	 * have
	 * 3
	 * different
	 * objects
	 * inserted.
	 * </pre>
	 **/
	template <class E=char, class Tr=std::char_traits<char> > class ListStream :
		virtual public Object, public std::basic_iostream<E,Tr>
	{
	public:
		/**			 
		 * Create a new ListStream with the given List as an I/O buffer.
		 *
		 * @param buffer the List the stream should write its data to or
		 * from which it should read data
		 **/
		ListStream(List<E>& buffer) : std::basic_iostream<E,Tr>(&_bfr),
																	_bfr(buffer)
		{}

		/**
		 * Create a new ListStream with the given List as an input buffer. 
		 * A new output buffer will be allocated with the given block
		 * size.
		 *
		 * @param buffer the List the stream should read its data from
		 *
		 * @param blocSize the amount of bytes (or double-bytes) the I/O
		 * buffer grows whenever its size is exceeded
		 **/
		ListStream(const List<E>& buffer, int blockSize=1) : std::basic_iostream<E,Tr>(&_bfr),
																												 _bfr(buffer, blockSize)
		{}

		/**
		 * Create a new ListStream with the given block size.
		 *
		 * @param blocSize the amount of bytes (or double-bytes) the I/O
		 * buffer grows whenever its size is exceeded
		 **/
		ListStream(int blockSize=1024) : std::basic_iostream<E,Tr>(&_bfr), _bfr(blockSize) {}

		/**
		 * Destroy the ListStream object.
		 **/
		virtual ~ListStream() {}

		/**
		 * Get a reference to the list the input buffer uses as a data
		 * storage.
		 **/
		List<E>& getWriteBuffer() { return _bfr.getWriteBuffer(); }

		/**
		 * Get a const reference to the list the input buffer uses as a
		 * data storage.
		 **/
		const List<E>& getWriteBuffer() const { return _bfr.getWriteBuffer(); }
		
		/**
		 * Get a reference to the list the input buffer reads its data
		 * from.
		 **/
		const List<E>& getReadBuffer() const { return _bfr.getReadBuffer(); }

	private:
		ListBuffer<E,Tr> _bfr;
	};

	template <class E, class Tr> int ListBuffer<E,Tr>::underflow()
	{
		//Check that the input buffer is valid and that its size is not
		//exceeded. If it is OK, return the next item
		if (gptr() && gptr() < egptr())
			return traits_type::to_int_type(*gptr());
		return traits_type::eof();
	}

	
	template <class E, class Tr> int ListBuffer<E,Tr>::overflow(int c)
	{
		if(c == traits_type::eof())
			return traits_type::not_eof(c);

		//Append the given character to the output buffer
		_lstBuffer += traits_type::to_char_type(c);
		initPointers();
		
		return traits_type::not_eof(c);
	}

	template <class E, class Tr> std::streamsize ListBuffer<E,Tr>::xsputn(const E *ptr,
																																				std::streamsize count)
	{
		std::string tmp(ptr,count);
		int more = 0;
		//Make sure our list will be able to store all the data
		while (count + _lstBuffer.getLength() > _lstBuffer.getCapacity() + more)
			more += _lstBuffer.getBlockSize();
		if (more)
			{
				_lstBuffer.setCapacity(_lstBuffer.getCapacity() + more);
				initPointers();
			}
		//Copy the data
		memcpy(pptr(), ptr, count*sizeof(E));
		//Update list size
		_lstBuffer.setLength(_lstBuffer.getLength() + count);
		//Update stream buffer pointers
		initPointers();
		
		return count;
	}

	template <class E, class Tr> std::streamsize ListBuffer<E,Tr>::xsgetn(E *ptr,
																																				std::streamsize count)
	{
		int diff = egptr() - gptr();
		count = (count < diff) ? count : diff;
		memcpy(ptr, gptr(), count*sizeof(E));
		gbump(count);
		return count;
	}

	template <class E, class Tr> void ListBuffer<E,Tr>::initPointers()
	{
		//Store new get pointer positions (start, current, end)
		int diffg = gptr() - eback();
		E* ptr = const_cast<E*>(_clstBuffer.getData());
		setg(ptr, ptr+diffg, ptr+_clstBuffer.getLength());

		//Store new put pointer positions (start, end)
		int len = _lstBuffer.getLength();
		ptr = _lstBuffer.getData();
		setp(ptr+len, ptr+len);
	}
}}
#endif
