/*
    libmaus2
    Copyright (C) 2009-2013 German Tischler
    Copyright (C) 2011-2013 Genome Research Limited

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#if ! defined(LIBMAUS2_AIO_LINESPLITTINGPOSIXFDOUTPUTBUFFER_HPP)
#define LIBMAUS2_AIO_LINESPLITTINGPOSIXFDOUTPUTBUFFER_HPP

#include <ostream>
#include <sstream>
#include <iomanip>

#include <libmaus2/LibMausConfig.hpp>
#include <libmaus2/autoarray/AutoArray.hpp>
#include <libmaus2/aio/FileRemoval.hpp>
#include <libmaus2/aio/OutputStreamInstance.hpp>

namespace libmaus2
{
	namespace aio
	{
		struct LineSplittingPosixFdOutputStreamBuffer : public ::std::streambuf
		{
			private:
			static uint64_t getDefaultBlockSize()
			{
				return 64*1024;
			}

			std::string fn;
			uint64_t linemod;
			uint64_t linecnt;
			uint64_t fileno;
			uint64_t written;
			uint64_t totalwritten;
			std::string openfilename;

			libmaus2::aio::OutputStreamInstance::unique_ptr_type pOSI;
			int64_t const optblocksize;
			uint64_t const buffersize;
			::libmaus2::autoarray::AutoArray<char> buffer;

			bool reopenpending;

			void doFlush()
			{
				if ( pOSI )
					pOSI->flush();
			}

			void doClose()
			{
				if ( pOSI )
				{
					pOSI->flush();
					pOSI.reset();
				}

				openfilename = std::string();
				written = 0;
			}

			libmaus2::aio::OutputStreamInstance::unique_ptr_type doOpen()
			{
				std::string const filename = getFileName(fileno++);

				libmaus2::aio::OutputStreamInstance::unique_ptr_type pOSI(new libmaus2::aio::OutputStreamInstance(filename));
				pOSI->exceptions ( std::ostream::failbit | std::ostream::badbit );

				written = 0;
				openfilename = filename;

				return pOSI;
			}

			void doSync(char * p, uint64_t n)
			{
				pOSI->write(p,n);
				totalwritten += n;
			}

			void doSync()
			{
				// number of bytes in buffer
				uint64_t n = pptr()-pbase();
				pbump(-n);
				char * p = pbase();

				while ( n )
				{
					if ( reopenpending )
					{
						doFlush();
						doClose();

						libmaus2::aio::OutputStreamInstance::unique_ptr_type tOSI(doOpen());
						pOSI = std::move(tOSI);

						reopenpending = false;
					}

					char * pe = p + n;
					char * pc = p;

					while ( pc != pe )
						if ( *(pc++) == '\n' && ((++linecnt) % linemod) == 0 )
						{
							reopenpending = true;
							break;
						}

					uint64_t const t = pc - p;

					doSync(p,t);

					p += t;
					n -= t;
				}
			}

			public:
			LineSplittingPosixFdOutputStreamBuffer(std::string const & rfn, uint64_t const rlinemod, int64_t const rbuffersize)
			:
			  fn(rfn),
			  linemod(rlinemod),
			  linecnt(0),
			  fileno(0),
			  written(0),
			  totalwritten(0),
			  pOSI(doOpen()),
			  optblocksize((rbuffersize < 0) ? getDefaultBlockSize() : rbuffersize),
			  buffersize(optblocksize),
			  buffer(buffersize,false),
			  reopenpending(false)
			{
				setp(buffer.begin(),buffer.end()-1);
			}

			~LineSplittingPosixFdOutputStreamBuffer()
			{
				sync();

				std::string deletefilename = openfilename;
				bool const deletefile = ( (totalwritten == 0) && fileno == 1 );

				doClose();

				// delete empty file if no data was written
				if ( deletefile )
					libmaus2::aio::FileRemoval::removeFile(deletefilename);
			}

			int_type overflow(int_type c = traits_type::eof())
			{
				if ( c != traits_type::eof() )
				{
					*pptr() = c;
					pbump(1);
					doSync();
				}

				return c;
			}

			int sync()
			{
				doSync();
				doFlush();
				return 0; // no error, -1 for error
			}

			std::string getFileName(uint64_t const id) const
			{
				std::ostringstream fnostr;

				fnostr << fn << "_" << std::setw(6) << std::setfill('0') << id << std::setw(0);

				return fnostr.str();
			}

			uint64_t getNumFiles() const
			{
				return fileno;
			}
		};
	}
}
#endif
