/*
    libmaus2
    Copyright (C) 2016-2019 German Tischler-Höhle

    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_FASTX_FASTAINDEXGENERATOR_HPP)
#define LIBMAUS2_FASTX_FASTAINDEXGENERATOR_HPP

#include <libmaus2/aio/InputStreamInstance.hpp>
#include <libmaus2/aio/OutputStreamInstance.hpp>
#include <libmaus2/aio/OutputStreamFactoryContainer.hpp>
#include <libmaus2/fastx/FastAIndex.hpp>
#include <libmaus2/fastx/SpaceTable.hpp>
#include <libmaus2/util/ArgInfo.hpp>
#include <libmaus2/util/GetFileSize.hpp>
#include <libmaus2/util/LineBuffer.hpp>
#include <libmaus2/util/TempFileRemovalContainer.hpp>

namespace libmaus2
{
	namespace fastx
	{
		struct FastAIndexGenerator
		{
			static bool needRun(
				std::string const & fastaname, std::string const & fainame, std::string const * plongnamefn
			)
			{
				if ( (! libmaus2::util::GetFileSize::fileExists(fainame)) || (libmaus2::util::GetFileSize::isOlder(fainame,fastaname) ) )
					return true;

				if ( plongnamefn )
				{
					std::string const & longnamefn = *plongnamefn;

					if ( (! libmaus2::util::GetFileSize::fileExists(longnamefn)) || (libmaus2::util::GetFileSize::isOlder(longnamefn,fastaname) ) )
						return true;
				}

				return false;
			}

			static void generate(
				std::string const & fastaname,
				std::string const & fainame,
				int const verbose,
				std::string const * plongnamefn = nullptr,
				std::string const * ptmpprefix = nullptr
			)
			{
				if ( needRun(fastaname,fainame,plongnamefn) )
				{
					if ( verbose > 0 )
						std::cerr << "[V] generating " << fainame << "...";

					std::string const tmpprefix = ptmpprefix ? (*ptmpprefix) : libmaus2::util::ArgInfo::getDefaultTmpFileName(fainame);

					std::string const fainametmp = tmpprefix + ".faitmp";
					libmaus2::util::TempFileRemovalContainer::addTempFile(fainametmp);
					std::string const longnametmp = tmpprefix + ".longnametmp";
					libmaus2::util::TempFileRemovalContainer::addTempFile(longnametmp);

					libmaus2::aio::InputStreamInstance ISI(fastaname);
					libmaus2::aio::OutputStreamInstance::unique_ptr_type OSI(new libmaus2::aio::OutputStreamInstance(fainametmp));
					libmaus2::util::LineBuffer LB(ISI, 8*1024);
					libmaus2::fastx::SpaceTable ST;

					libmaus2::aio::OutputStreamInstance::unique_ptr_type longOSI;
					if ( plongnamefn )
					{
						libmaus2::aio::OutputStreamInstance::unique_ptr_type tOSI(new libmaus2::aio::OutputStreamInstance(longnametmp));
						longOSI = std::move(tOSI);
					}

					char const * a = 0;
					char const * e = 0;

					std::string seqname;
					std::string longname;
					int64_t linewidth = -1;
					int64_t linelength = -1;
					uint64_t seqlength = 0;
					uint64_t seqoffset = 0;
					uint64_t o = 0;

					while ( LB.getline(&a,&e) )
					{
						if ( e-a && *a == '>' )
						{
							if ( seqname.size() )
							{
								(*OSI) << seqname << "\t" << seqlength << "\t" << seqoffset << "\t" << linewidth << "\t" << linelength << "\n";
								if ( longOSI )
									*longOSI << longname << "\n";
							}

							longname = std::string(a+1,e);
							seqname = libmaus2::fastx::FastAIndex::computeShortName(longname);
							linewidth = -1;
							linelength = -1;
							seqlength = 0;
						}
						else
						{
							uint64_t nonspace = 0;
							for ( char const * c = a; c != e && !ST.spacetable[static_cast<uint8_t>(static_cast<unsigned char>(*c))]; ++c )
								++nonspace;

							if ( linewidth < 0 )
							{

								linewidth = nonspace;
								linelength = e-a + 1 /* newline */;
								seqoffset = o;
							}
							else
							{
								// todo: check line width consistency
							}

							seqlength += nonspace;
						}

						o += (e-a)+1;
					}
					if ( seqname.size() )
					{
						(*OSI) << seqname << "\t" << seqlength << "\t" << seqoffset << "\t" << linewidth << "\t" << linelength << "\n";
						if ( longOSI )
							*longOSI << longname << "\n";
					}

					OSI->flush();

					if ( ! *OSI )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] FastAIndexGenerator::generate: unable to write output" << std::endl;
						lme.finish();
						throw lme;
					}

					OSI.reset();

					libmaus2::aio::OutputStreamFactoryContainer::rename(
						fainametmp,
						fainame
					);

					if ( longOSI )
					{
						longOSI->flush();

						if ( ! *longOSI )
						{
							libmaus2::exception::LibMausException lme;
							lme.getStream() << "[E] FastAIndexGenerator::generate: unable to write output" << std::endl;
							lme.finish();
							throw lme;
						}

						longOSI.reset();

						assert ( plongnamefn );
						libmaus2::aio::OutputStreamFactoryContainer::rename(
							longnametmp,
							*plongnamefn
						);
					}


					if ( verbose > 0 )
						std::cerr << "done." << std::endl;
				}
			}
		};
	}
}
#endif
