/*
    libmaus2
    Copyright (C) 2021 German Tischler

    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_LZ_BGZFCHUNKINPUTSTREAMBUFFER_HPP)
#define LIBMAUS2_LZ_BGZFCHUNKINPUTSTREAMBUFFER_HPP

#include <libmaus2/lz/BgzfInflateStream.hpp>

namespace libmaus2
{
	namespace lz
	{
		struct BgzfChunkInputStreamBuffer : std::streambuf
		{
			struct Chunk
			{
				uint64_t cnk_beg;
				uint64_t cnk_end;

				Chunk() {}
				Chunk(uint64_t const rcnk_beg, uint64_t const rcnk_end) : cnk_beg(rcnk_beg), cnk_end(rcnk_end) {}

				bool operator<(Chunk const & C) const
				{
					if ( cnk_beg != C.cnk_beg )
						return cnk_beg < C.cnk_beg;
					else
						return cnk_end > C.cnk_end;
				}
			};


			std::shared_ptr<std::istream> sIn;
			std::istream & in;
			std::shared_ptr<libmaus2::lz::BgzfInflateStream> currentStream;
			std::vector<Chunk> Vchunks;
			std::vector<Chunk>::size_type nextChunk;
			std::size_t const b;
			std::shared_ptr<char[]> B;

			BgzfChunkInputStreamBuffer(
				std::istream & rIn,
				std::vector<Chunk> const & rVchunks
			) : sIn(), in(rIn), currentStream(), Vchunks(rVchunks), nextChunk(0), b(64*1024), B(new char[b])
			{

			}

			BgzfChunkInputStreamBuffer(
				std::shared_ptr<std::istream> rsIn,
				std::vector<Chunk> const & rVchunks
			) : sIn(rsIn), in(*sIn), currentStream(), Vchunks(rVchunks), nextChunk(0), b(64*1024), B(new char[b])
			{

			}

			// gptr as unsigned pointer
			uint8_t const * uptr() const
			{
				return reinterpret_cast<uint8_t const *>(gptr());
			}

			int_type underflow()
			{
				if ( gptr() < egptr() )
					return static_cast<int_type>(*uptr());

				assert ( gptr() == egptr() );

				std::size_t n = 0;
				bool done = false;

				while ( !done )
				{
					if ( currentStream )
					{
						currentStream->read(B.get(),b);

						n = currentStream->gcount();

						if ( n )
						{
							setg(B.get(),B.get(),B.get()+n);
							done = true;
						}
						else
						{
							currentStream.reset();
						}
					}
					else
					{
						if ( nextChunk < Vchunks.size() )
						{
							Chunk const & C = Vchunks[nextChunk++];

							in.clear();

							std::shared_ptr<libmaus2::lz::BgzfInflateStream> tptr(
								new libmaus2::lz::BgzfInflateStream(
									in,
									C.cnk_beg,
									C.cnk_end
								)
							);

							tptr->exceptions(std::ios::badbit);

							currentStream = tptr;
						}
						else
						{
							done = true;
						}
					}
				}

				if (!n)
					return traits_type::eof();

				return static_cast<int_type>(*uptr());
			}
		};
	}
}
#endif
