/*
    libmaus2
    Copyright (C) 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_VEP_VEPPARSER_HPP)
#define LIBMAUS2_VEP_VEPPARSER_HPP

#include <libmaus2/vep/VEPCoordinate.hpp>
#include <libmaus2/lz/PlainOrGzipStream.hpp>
#include <libmaus2/util/LineBuffer.hpp>
#include <libmaus2/util/TabEntry.hpp>
#include <libmaus2/trie/TrieState.hpp>
#include <libmaus2/vcf/VCFParser.hpp>

namespace libmaus2
{
	namespace vep
	{
		struct VEPParser
		{
			typedef VEPParser this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;
			typedef std::shared_ptr<this_type> shared_ptr_type;

			libmaus2::aio::InputStreamInstance::unique_ptr_type ISI;
			libmaus2::lz::PlainOrGzipStream POGS;
			libmaus2::util::LineBuffer LB;
			std::string header;

			bool stallSlotFilled;
			char const * stallSlotA;
			char const * stallSlotE;
			libmaus2::util::TabEntry<> stallSlotTE;

			bool fillStallSlot()
			{
				stallSlotFilled = false;

				char const * a = nullptr;
				char const * e = nullptr;
				if ( LB.getline(&a,&e) )
				{
					if ( e-a && a[0] == '#' )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] VEPParser: cannot parse " << std::string(a,e) << std::endl;
						lme.finish();
						throw lme;
					}

					stallSlotTE.parse(a,a,e);

					if ( stallSlotTE.size() < 14 )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] VEPParser: cannot parse [" << stallSlotTE.size() << "] " << std::string(a,e) << std::endl;
						lme.finish();
						throw lme;
					}

					stallSlotA = a;
					stallSlotE = e;

					return true;
				}
				else
				{
					return false;
				}
			}

			std::pair<
				libmaus2::util::TabEntry<> const *,
				char const *
			> peek()
			{
				if ( !stallSlotFilled )
					stallSlotFilled = fillStallSlot();
				if ( !stallSlotFilled )
				{
					return std::pair<libmaus2::util::TabEntry<> const *,char const *>();
				}

				return
					std::pair<
						libmaus2::util::TabEntry<> const *,
						char const *
				>(
					&stallSlotTE,
					stallSlotA
				);
			}

			std::pair<
				libmaus2::util::TabEntry<> const *,
				char const *
			> read()
			{
				std::pair<
					libmaus2::util::TabEntry<> const *,
					char const *
				> const P = peek();

				stallSlotFilled = false;

				return P;
			}


			static libmaus2::vep::VEPCoordinate getCoordinate(
				libmaus2::util::TabEntry<> const & TE,
				char const * a,
				::libmaus2::trie::LinearHashTrie<char,uint32_t> const & LHT
			)
			{
				assert ( TE.size() >= 2 );

				std::pair<char const *,char const *> const P1 = TE.get(1,a);

				char const * c = P1.second;
				bool cfound = false;

				while ( !cfound )
				{
					if ( c == a )
						break;

					char const s = *(--c);

					if ( s == ':' )
					{
						cfound = true;
					}
				}

				if ( ! cfound )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] cannot parse " << std::string(P1.first,P1.second) << std::endl;
					lme.finish();
					throw lme;
				}

				int64_t const contid = LHT.searchCompleteNoFailure(P1.first,c);

				if ( contid < 0 )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] cannot parse " << std::string(P1.first,P1.second) << std::endl;
					lme.finish();
					throw lme;
				}

				char const * m = c+1;
				while ( m != P1.second && *m != '-' )
					++m;

				if ( m == P1.second )
				{
					int64_t const pos = libmaus2::vcf::VCFParser::parseUInt(c+1,m);

					if ( pos < 0 )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] cannot parse " << std::string(P1.first,P1.second) << std::endl;
						lme.finish();
						throw lme;
					}

					return libmaus2::vep::VEPCoordinate(contid,pos,pos);
				}
				else
				{
					int64_t const pos_0 = libmaus2::vcf::VCFParser::parseUInt(c+1,m);
					int64_t const pos_1 = libmaus2::vcf::VCFParser::parseUInt(m+1,P1.second);

					return libmaus2::vep::VEPCoordinate(contid,pos_0,pos_1);
				}
			}

			void init()
			{
				std::ostringstream headerostr;

				char const * a = nullptr;
				char const * e = nullptr;
				while ( LB.getline(&a,&e) )
				{
					if ( e-a >= 1 && a[0] == '#' )
					{
						headerostr.write(a,e-a);
						headerostr.put('\n');
					}
					else
					{
						LB.putback(a);
						break;
					}
				}

				header = headerostr.str();
			}

			VEPParser(std::istream & in)
			: ISI(), POGS(in), LB(POGS), stallSlotFilled(false), stallSlotA(nullptr), stallSlotE(nullptr)
			{
				init();
			}

			VEPParser(std::string const & fn)
			: ISI(new libmaus2::aio::InputStreamInstance(fn)), POGS(*ISI), LB(POGS), stallSlotFilled(false), stallSlotA(nullptr), stallSlotE(nullptr)
			{
				init();
			}
		};
	}
}
#endif
