/*
    libmaus2
    Copyright (C) 2019 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_VCF_VCFPARSER_HPP)
#define LIBMAUS2_VCF_VCFPARSER_HPP

#include <libmaus2/trie/TrieState.hpp>
#include <libmaus2/bambam/Chromosome.hpp>
#include <libmaus2/util/TabEntry.hpp>
#include <libmaus2/util/LineBuffer.hpp>
#include <libmaus2/util/GetFileSize.hpp>
#include <libmaus2/lz/PlainOrGzipStream.hpp>

namespace libmaus2
{
	namespace vcf
	{
		struct VCFParser
		{
			typedef VCFParser this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;
			typedef std::shared_ptr<this_type> shared_ptr_type;

			typedef libmaus2::util::TabEntry<','> header_tab_entry;

			libmaus2::aio::InputStreamInstance::unique_ptr_type PISI;
			int64_t const fs;
			libmaus2::lz::PlainOrGzipStream POGS;
			libmaus2::util::LineBuffer LB;

			libmaus2::autoarray::AutoArray<char> Acontig;
			uint64_t Acontigo;
			libmaus2::autoarray::AutoArray<header_tab_entry::P> AcontigP;
			uint64_t AcontigPo;
			libmaus2::autoarray::AutoArray<header_tab_entry> AcontigT;
			uint64_t AcontigTo;
			libmaus2::autoarray::AutoArray<char> AINFO;
			uint64_t AINFOo;
			libmaus2::autoarray::AutoArray<header_tab_entry::P> AINFOP;
			uint64_t AINFOPo;
			libmaus2::autoarray::AutoArray<header_tab_entry> AINFOT;
			uint64_t AINFOTo;

			libmaus2::autoarray::AutoArray<char> AFORMAT;
			uint64_t AFORMATo;
			libmaus2::autoarray::AutoArray<header_tab_entry::P> AFORMATP;
			uint64_t AFORMATPo;
			libmaus2::autoarray::AutoArray<header_tab_entry> AFORMATT;
			uint64_t AFORMATTo;

			libmaus2::autoarray::AutoArray<char> AFILTER;
			uint64_t AFILTERo;
			libmaus2::autoarray::AutoArray<header_tab_entry::P> AFILTERP;
			uint64_t AFILTERPo;
			libmaus2::autoarray::AutoArray<header_tab_entry> AFILTERT;
			uint64_t AFILTERTo;

			libmaus2::autoarray::AutoArray<char> Asource;
			uint64_t Asourceo;

			libmaus2::autoarray::AutoArray<char> AREST;
			uint64_t ARESTo;

			uint64_t numheader;
			std::string header;
			libmaus2::util::TabEntry<'\t'> headerTE;

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


			std::ostream & printTextFilteredInfo(std::ostream & out, ::libmaus2::trie::LinearHashTrie<char,uint32_t> const & LHT) const
			{
				printRESTText(out);
				printSourceText(out);
				printContigText(out);
				// printINFOText(out);
				filterInfo(out,LHT);
				printFORMATText(out);
				printFILTERText(out);
				printHeaderText(out);
				return out;
			}

			std::ostream & printTextContigPrepend(std::ostream & out, std::string const & prepend) const
			{
				printRESTText(out);
				printSourceText(out);
				// printContigText(out);
				patchContigPrepend(out,prepend);
				printINFOText(out);
				printFORMATText(out);
				printFILTERText(out);
				printHeaderText(out);
				return out;
			}

			std::ostream & filterInfo(std::ostream & out, ::libmaus2::trie::LinearHashTrie<char,uint32_t> const & LHT) const
			{
				for ( uint64_t i = 0; i < AINFOTo; ++i )
				{
					header_tab_entry const & T = AINFOT[i];
					std::pair<char const *,char const * > P(nullptr,nullptr);

					for ( uint64_t j = 0; j < T.size(); ++j )
					{
						std::pair<char const *,char const * > P = T.get(j,AINFO.begin());

						if ( P.second - P.first >= 3 &&
							P.first[0] == 'I' &&
							P.first[1] == 'D' &&
							P.first[2] == '='
						)
						{
							char const * IDstart = P.first + 3;
							char const * IDend = P.second;

							int64_t const id = LHT.searchCompleteNoFailure(IDstart,IDend);

							if ( id >= 0 )
							{
								out << "##INFO=<";

								for ( uint64_t i = 0; i < T.size(); ++i )
								{
									if ( i )
										out << ",";

									std::pair<char const *,char const * > P = T.get(i,AINFO.begin());

									out.write(P.first,P.second-P.first);
								}

								out << ">\n";
							}

							break;
						}
					}
				}

				return out;
			}

			uint64_t getContigNames(
				libmaus2::autoarray::AutoArray< char > & A,
				libmaus2::autoarray::AutoArray< std::pair<uint64_t,uint64_t> > & O
			) const
			{
				uint64_t Ao = 0;
				uint64_t Oo = 0;

				for ( uint64_t i = 0; i < AcontigTo; ++i )
				{
					header_tab_entry const & T = AcontigT[i];
					std::pair<char const *,char const * > IDP(nullptr,nullptr);

					for ( uint64_t j = 0; j < T.size(); ++j )
					{
						std::pair<char const *,char const * > P = T.get(j,Acontig.begin());

						if (
							P.second-P.first >= 3
							&&
							P.first[0] == 'I'
							&&
							P.first[1] == 'D'
							&&
							P.first[2] == '='
						)
						{
							IDP.first = P.first+3;
							IDP.second = P.second;
						}
					}

					if ( IDP.first )
					{
						uint64_t const Ao_start = Ao;
						A.push(
							Ao,
							IDP.first,
							IDP.second
						);
						uint64_t const Ao_end = Ao;
						A.push(Ao,0);

						O.push(Oo,std::pair<uint64_t,uint64_t>(Ao_start,Ao_end));
					}
					else
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] VCFParser::getContigNames: contig has no id" << std::endl;
						lme.finish();
						throw lme;
					}
				}

				return Oo;
			}

			static std::string getContigLine(std::string const & id, uint64_t const l)
			{
				std::ostringstream ostr;
				ostr << "##contig=<ID=" << id << ",length=" << l << ">";
				return ostr.str();
			}

			std::vector < std::map<std::string,std::string> > getContigInfo() const
			{
				std::vector < std::map<std::string,std::string> > info;

				for ( uint64_t i = 0; i < AcontigTo; ++i )
				{
					header_tab_entry const & T = AcontigT[i];
					std::map<std::string,std::string> M;

					for ( uint64_t j = 0; j < T.size(); ++j )
					{
						std::string const s = T.getString(j,Acontig.begin());

						if ( s.find('=') != std::string::npos )
						{
							std::string const key = s.substr(0,s.find('='));
							std::string const val = s.substr(s.find('=')+1);
							M[key] = val;
						}
					}

					info.push_back(M);
				}

				return info;
			}

			std::vector < std::map<std::string,std::string> > getInfoInfo() const
			{
				std::vector < std::map<std::string,std::string> > info;

				for ( uint64_t i = 0; i < AINFOTo; ++i )
				{
					header_tab_entry const & T = AINFOT[i];
					std::map<std::string,std::string> M;

					for ( uint64_t j = 0; j < T.size(); ++j )
					{
						std::string const s = T.getString(j,AINFO.begin());

						if ( s.find('=') != std::string::npos )
						{
							std::string const key = s.substr(0,s.find('='));
							std::string const val = s.substr(s.find('=')+1);
							M[key] = val;
						}
					}

					info.push_back(M);
				}

				return info;
			}

			std::vector < std::map<std::string,std::string> > getFormatInfo() const
			{
				std::vector < std::map<std::string,std::string> > info;

				for ( uint64_t i = 0; i < AFORMATTo; ++i )
				{
					header_tab_entry const & T = AFORMATT[i];
					std::map<std::string,std::string> M;

					for ( uint64_t j = 0; j < T.size(); ++j )
					{
						std::string const s = T.getString(j,AFORMAT.begin());

						if ( s.find('=') != std::string::npos )
						{
							std::string const key = s.substr(0,s.find('='));
							std::string const val = s.substr(s.find('=')+1);
							M[key] = val;
						}
					}

					info.push_back(M);
				}

				return info;
			}

			std::vector < std::map<std::string,std::string> > getFilterInfo() const
			{
				std::vector < std::map<std::string,std::string> > info;

				for ( uint64_t i = 0; i < AFILTERTo; ++i )
				{
					header_tab_entry const & T = AFILTERT[i];
					std::map<std::string,std::string> M;

					for ( uint64_t j = 0; j < T.size(); ++j )
					{
						std::string const s = T.getString(j,AFILTER.begin());

						if ( s.find('=') != std::string::npos )
						{
							std::string const key = s.substr(0,s.find('='));
							std::string const val = s.substr(s.find('=')+1);
							M[key] = val;
						}
					}

					info.push_back(M);
				}

				return info;
			}

			std::vector<std::string> getContigNames() const
			{
				libmaus2::autoarray::AutoArray< char > A;
				libmaus2::autoarray::AutoArray< std::pair<uint64_t,uint64_t> > O;
				uint64_t const o = getContigNames(A,O);
				std::vector<std::string> V(o);
				for ( uint64_t i = 0; i < o; ++i )
					V[i] = std::string(A.begin()+O[i].first,A.begin()+O[i].second);
				return V;
			}


			std::pair<
				std::vector<std::string>,
				::libmaus2::trie::LinearHashTrie<char,uint32_t>::shared_ptr_type
			> getContigNamesAndTrie() const
			{
				std::vector<std::string> const Vcontig = getContigNames();
			        ::libmaus2::trie::Trie<char> trienofailure;
			        trienofailure.insertContainer(Vcontig);
				::libmaus2::trie::LinearHashTrie<char,uint32_t>::unique_ptr_type LHTnofailure(trienofailure.toLinearHashTrie<uint32_t>());
				::libmaus2::trie::LinearHashTrie<char,uint32_t>::shared_ptr_type sLHT(LHTnofailure.release());

				return std::pair<
					std::vector<std::string>,
					::libmaus2::trie::LinearHashTrie<char,uint32_t>::shared_ptr_type
				>(Vcontig,sLHT);
			}

			static std::pair<
				std::vector<std::string>,
				::libmaus2::trie::LinearHashTrie<char,uint32_t>::shared_ptr_type
			> getContigNamesAndTrie(std::string const & vcffn)
			{
				libmaus2::aio::InputStreamInstance ISI(vcffn);
				this_type parser(ISI);
				return parser.getContigNamesAndTrie();
			}

			std::ostream & patchContigPrepend(std::ostream & out, std::string const & prepend) const
			{
				for ( uint64_t i = 0; i < AcontigTo; ++i )
				{
					header_tab_entry const & T = AcontigT[i];
					std::pair<char const *,char const * > P(nullptr,nullptr);

					out << "##contig=<";

					for ( uint64_t j = 0; j < T.size(); ++j )
					{

						if ( j )
							out << ",";

						std::pair<char const *,char const * > P = T.get(j,Acontig.begin());

						if (
							P.second-P.first >= 3
							&&
							P.first[0] == 'I'
							&&
							P.first[1] == 'D'
							&&
							P.first[2] == '='
						)
						{
							out.write("ID=",3);
							out.write(prepend.c_str(),prepend.size());
							out.write(P.first+3,(P.second-P.first)-3);
						}
						else
						{
							out.write(P.first,P.second-P.first);
						}
					}

					out << ">\n";
				}

				return out;
			}

			std::ostream & printText(std::ostream & out) const
			{
				printRESTText(out);
				printSourceText(out);
				printContigText(out);
				printINFOText(out);
				printFORMATText(out);
				printFILTERText(out);
				printHeaderText(out);
				return out;
			}

			std::ostream & printHeader(std::ostream & out) const
			{
				headerTE.print(out,header.c_str());
				return out;
			}

			std::ostream & printHeaderText(std::ostream & out) const
			{
				out << header;
				return out;
			}

			std::ostream & printSourceText(std::ostream & out) const
			{
				out.write(Asource.begin(),Asourceo);
				return out;
			}

			std::ostream & printRESTText(std::ostream & out) const
			{
				out.write(AREST.begin(),ARESTo);
				return out;
			}

			std::ostream & printContigText(std::ostream & out) const
			{
				out.write(Acontig.begin(),Acontigo);
				return out;
			}

			std::ostream & printINFOText(std::ostream & out) const
			{
				out.write(AINFO.begin(),AINFOo);
				return out;
			}

			std::ostream & printFILTERText(std::ostream & out) const
			{
				out.write(AFILTER.begin(),AFILTERo);
				return out;
			}

			std::ostream & printFORMATText(std::ostream & out) const
			{
				out.write(AFORMAT.begin(),AFORMATo);
				return out;
			}

			std::ostream & printContig(std::ostream & out) const
			{
				for ( uint64_t i = 0; i < AcontigTo; ++i )
				{
					out << "contig[" << i << "]\n";
					AcontigT[i].print(out,Acontig.begin());
				}
				return out;
			}

			std::ostream & printINFO(std::ostream & out) const
			{
				for ( uint64_t i = 0; i < AINFOTo; ++i )
				{
					out << "INFO[" << i << "]\n";
					AINFOT[i].print(out,AINFO.begin());
				}
				return out;
			}

			std::ostream & printFORMAT(std::ostream & out) const
			{
				for ( uint64_t i = 0; i < AFORMATTo; ++i )
				{
					out << "FORMAT[" << i << "]\n";
					AFORMATT[i].print(out,AFORMAT.begin());
				}
				return out;
			}

			std::ostream & printFILTER(std::ostream & out) const
			{
				for ( uint64_t i = 0; i < AFILTERTo; ++i )
				{
					out << "FILTER[" << i << "]\n";
					AFILTERT[i].print(out,AFILTER.begin());
				}
				return out;
			}

			void pushContig(
				char const * const a, char const * const e,
				char const * const datastart, char const * const dataend
			)
			{
				uint64_t const from = Acontigo;
				Acontig.push(Acontigo,a,e);
				//uint64_t const to = Acontigo;
				Acontig.push(Acontigo,'\n');

				libmaus2::util::TabEntry<','> TE(&AcontigP,AcontigPo);
				TE.parse(
					Acontig.begin(),
					Acontig.begin()+from+(datastart-a),
					Acontig.begin()+from+(dataend-a),
					false
				);
				AcontigPo = TE.n;

				AcontigT.push(AcontigTo,TE);
			}

			void pushContigLine(std::string const & id, uint64_t const l)
			{
				std::string const contigline = getContigLine(id,l);

				std::string::size_type const p0 = contigline.find('<');
				assert ( p0 != std::string::npos );
				std::string::size_type const p1 = contigline.find_last_of('>');
				assert ( p1 != std::string::npos );

				char const * a = contigline.c_str();
				char const * e = a+contigline.size();
				char const * datastart = a + p0 + 1; // position after <
				char const * dataend = a + p1; // position of >

				pushContig(a,e,datastart,dataend);
			}

			void pushChromosome(libmaus2::bambam::Chromosome const & C)
			{
				pushContigLine(C.getNameString(),C.getLength());
			}

			void pushChromosomeVector(std::vector<libmaus2::bambam::Chromosome> const & V)
			{
				for ( uint64_t i = 0; i < V.size(); ++i )
					pushChromosome(V[i]);
			}

			void setChromosomeVector(std::vector<libmaus2::bambam::Chromosome> const & V)
			{
				resetContig();
				pushChromosomeVector(V);
			}

			void resetContig()
			{
				Acontig.resize(0);
				Acontigo = 0;
				AcontigP.resize(0);
				AcontigPo = 0;
				AcontigT.resize(0);
				AcontigTo = 0;
			}

			void setup(libmaus2::util::LineBuffer & LB)
			{
				stallSlotFilled = false;
				stallSlotA = nullptr;

				Acontigo = 0;
				AcontigPo = 0;
				AcontigTo = 0;
				AINFOo = 0;
				AINFOPo = 0;
				AINFOTo = 0;
				AFORMATo = 0;
				AFORMATPo = 0;
				AFORMATTo = 0;
				AFILTERo = 0;
				AFILTERPo = 0;
				AFILTERTo = 0;

				Asourceo = 0;
				ARESTo = 0;

				numheader = 0;

				char const * a = nullptr;
				char const * e = nullptr;
				while ( LB.getline(&a,&e) )
				{
					if ( e-a >= 2 && a[0] == '#' && a[1] == '#' )
					{
						char const * typestart = a+2;
						char const * typeend = typestart;
						while ( typeend < e && *typeend != '=' )
							++typeend;

						std::string const stype(typestart,typeend);

						if ( stype == "contig" || stype == "INFO" || stype == "FORMAT" || stype == "FILTER" )
						{
							if (
								typeend == e
								||
								typeend+1 == e || typeend[1] != '<' || e[-1] != '>'
							)
							{
								libmaus2::exception::LibMausException lme;
								lme.getStream() << "[E] malformed " << stype << " line" << std::endl;
								lme.finish();
								throw lme;
							}
							assert ( *typeend == '=' );

							/* part inside < > */
							char const * datastart = typeend+2;
							char const * dataend = e-1;

							if ( stype == "contig" )
							{
								uint64_t const from = Acontigo;
								Acontig.push(Acontigo,a,e);
								//uint64_t const to = Acontigo;
								Acontig.push(Acontigo,'\n');

								libmaus2::util::TabEntry<','> TE(&AcontigP,AcontigPo);
								TE.parse(Acontig.begin(),Acontig.begin()+from+(datastart-a),Acontig.begin()+from+(dataend-a),false);
								AcontigPo = TE.n;

								AcontigT.push(AcontigTo,TE);
							}
							else if ( stype == "INFO" )
							{
								uint64_t const from = AINFOo;
								AINFO.push(AINFOo,a,e);
								//uint64_t const to = AINFOo;
								AINFO.push(AINFOo,'\n');

								libmaus2::util::TabEntry<','> TE(&AINFOP,AINFOPo);
								TE.parse(AINFO.begin(),AINFO.begin()+from+(datastart-a),AINFO.begin()+from+(dataend-a),false);
								AINFOPo = TE.n;

								AINFOT.push(AINFOTo,TE);
							}
							else if ( stype == "FORMAT" )
							{
								uint64_t const from = AFORMATo;
								AFORMAT.push(AFORMATo,a,e);
								//uint64_t const to = AFORMATo;
								AFORMAT.push(AFORMATo,'\n');

								libmaus2::util::TabEntry<','> TE(&AFORMATP,AFORMATPo);
								TE.parse(AFORMAT.begin(),AFORMAT.begin()+from+(datastart-a),AFORMAT.begin()+from+(dataend-a),false);
								AFORMATPo = TE.n;

								AFORMATT.push(AFORMATTo,TE);
							}
							else // if ( stype == "FILTER" )
							{
								assert ( stype == "FILTER" );

								uint64_t const from = AFILTERo;
								AFILTER.push(AFILTERo,a,e);
								//uint64_t const to = AFILTERo;
								AFILTER.push(AFILTERo,'\n');

								libmaus2::util::TabEntry<','> TE(&AFILTERP,AFILTERPo);
								TE.parse(AFILTER.begin(),AFILTER.begin()+from+(datastart-a),AFILTER.begin()+from+(dataend-a),false);
								AFILTERPo = TE.n;

								AFILTERT.push(AFILTERTo,TE);
							}
						}
						else if ( stype == "source" )
						{
							Asource.push(Asourceo,a,e);
							Asource.push(Asourceo,'\n');
						}
						else
						{
							AREST.push(ARESTo,a,e);
							AREST.push(ARESTo,'\n');
							// std::cerr << "type " << std::string(typestart,typeend) << std::endl;
						}
					}
					else
					{
						LB.putback(a);
						break;
					}
				}

				while ( LB.getline(&a,&e) )
				{
					if ( e-a >= 1 && a[0] == '#' )
					{
						if ( e-a == 1 || a[1] == '#' )
						{
							libmaus2::exception::LibMausException lme;
							lme.getStream() << "[E] malformed header line " << std::string(a,e) << std::endl;
							lme.finish();
							throw lme;
						}
						else if ( numheader )
						{
							libmaus2::exception::LibMausException lme;
							lme.getStream() << "[E] extraneous header line " << std::string(a,e) << std::endl;
							lme.finish();
							throw lme;
						}
						else
						{
							header = std::string(a,e) + '\n';
							headerTE.parse(
								header.c_str(),
								header.c_str()+1,
								header.c_str()+(e-a),
								false
							);
						}
					}
					else
					{
						LB.putback(a);
						break;
					}
				}
			}

			std::vector<std::string> getSources() const
			{
				std::string const s(Asource.begin(),Asource.begin()+Asourceo);
				std::istringstream istr(s);

				std::string line;
				std::string const prefix = "##source=";
				std::vector<std::string> V;
				while ( std::getline(istr,line) )
					if ( line.size() >= prefix.size() && line.substr(0,prefix.size()) == prefix )
						V.push_back(line.substr(prefix.size()));
				return V;
			}

			static std::vector<std::string> getSources(std::string const & fn)
			{
				return VCFParser(fn).getSources();
			}

			VCFParser(std::istream & in)
			: PISI(), fs(-1), POGS(in), LB(POGS)
			{
				setup(LB);
			}

			VCFParser(std::string const & fn)
			: PISI(new libmaus2::aio::InputStreamInstance(fn)), fs(libmaus2::util::GetFileSize::getFileSize(*PISI)), POGS(*PISI), LB(POGS)
			{
				setup(LB);
			}

			double getProcessedFraction()
			{
				if ( PISI && fs > 0 )
					return PISI->tellg() / static_cast<double>(fs);
				else
					return 0.0;
			}

			void unparsedCopy(std::ostream & out)
			{
				char const * a = nullptr;
				char const * e = nullptr;

				while ( LB.getline(&a,&e) )
				{
					if ( e-a == 0 )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] malformed empty line " << std::string(a,e) << std::endl;
						lme.finish();
						throw lme;
					}
					else if ( a[0] == '#' )
					{

						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] malformed line in data part: " << std::string(a,e) << std::endl;
						lme.finish();
						throw lme;
					}
					else
					{
						out.write(a,e-a);
						out.put('\n');
					}
				}
			}

			std::pair<
				libmaus2::util::TabEntry<> const *,
				char const *
			>
				peekEntry()
			{
				if ( ! stallSlotFilled )
				{
					std::pair<bool, char const *> const P = readEntry(stallSlotTE);
					stallSlotFilled = P.first;
					stallSlotA = P.second;
				}

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

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

				stallSlotFilled = false;

				return P;
			}

			std::pair<bool, char const *> readEntry(libmaus2::util::TabEntry<> & T)
			{
				char const * a = nullptr;
				char const * e = nullptr;

				if ( LB.getline(&a,&e) )
				{
					if ( e-a == 0 )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] malformed empty line " << std::string(a,e) << std::endl;
						lme.finish();
						throw lme;
					}
					else if ( a[0] == '#' )
					{

						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] malformed line in data part: " << std::string(a,e) << std::endl;
						lme.finish();
						throw lme;
					}
					else
					{
						T.parse(a,a,e);

						if ( T.n - T.n0 < 8 )
						{
							libmaus2::exception::LibMausException lme;
							lme.getStream() << "[E] malformed short data line: " << std::string(a,e) << std::endl;
							lme.finish();
							throw lme;
						}

						return std::pair<bool,char const *>(true,a);
					}
				}
				else
				{
					return std::pair<bool,char const *>(false,nullptr);
				}
			}

			static std::ostream & runPatchContigPrepend(std::istream & in, std::ostream & out, std::string const & prepend)
			{
				VCFParser VCF(in);
				VCF.printTextContigPrepend(out,prepend);

				std::pair<bool, char const *> E;
				libmaus2::util::TabEntry<> T;

				while ( (E=VCF.readEntry(T)).first )
				{
					{
						out.write(prepend.c_str(),prepend.size());
						std::pair<char const *,char const *> IP = T.get(0,E.second);
						out.write(IP.first,IP.second-IP.first);
					}

					for ( uint64_t i = 1; i < T.size(); ++i )
					{
						out.put('\t');
						std::pair<char const *,char const *> IP = T.get(i,E.second);
						out.write(IP.first,IP.second-IP.first);

					}

					out.put('\n');
				}

				return out;
			}

			static std::ostream & runFilterInfo(std::istream & in, std::ostream & out, std::vector<std::string> const & Vfilter)
			{
				libmaus2::trie::Trie<char> TR;
				TR.insertContainer(Vfilter);
				::libmaus2::trie::LinearHashTrie<char,uint32_t>::unique_ptr_type LHT(TR.toLinearHashTrie<uint32_t>());

				VCFParser VCF(in);
				VCF.printTextFilteredInfo(out,*LHT);

				std::pair<bool, char const *> E;
				libmaus2::util::TabEntry<> T;
				libmaus2::util::TabEntry<';'> TI;

				while ( (E=VCF.readEntry(T)).first )
				{
					for ( uint64_t i = 0; i < 7; ++i )
					{
						if ( i )
							out.put('\t');
						std::pair<char const *,char const *> IP = T.get(i,E.second);
						out.write(IP.first,IP.second-IP.first);

					}

					std::pair<char const *,char const *> IP = T.get(7,E.second);

					out.put('\t');

					TI.parse(IP.first,IP.first,IP.second);
					uint64_t TIp = 0;
					for ( uint64_t i = 0; i < TI.size(); ++i )
					{
						std::pair<char const *,char const *> subIP = TI.get(i,IP.first);

						char const * c = std::find(subIP.first,subIP.second,'=');

						if ( c != subIP.second )
						{
							assert ( *c == '=' );

							int64_t const id = LHT->searchCompleteNoFailure(subIP.first,c);

							if ( id >= 0 )
							{
								if ( TIp )
									out.put(';');

								out.write(subIP.first,subIP.second-subIP.first);

								TIp += 1;
							}
						}
						else
						{
							int64_t const id = LHT->searchCompleteNoFailure(subIP.first,subIP.second);

							if ( id >= 0 )
							{
								if ( TIp )
									out.put(';');

								out.write(subIP.first,subIP.second-subIP.first);

								TIp += 1;
							}
						}
					}

					for ( uint64_t i = 8; i < T.size(); ++i )
					{
						out.put('\t');
						std::pair<char const *,char const *> IP = T.get(i,E.second);
						out.write(IP.first,IP.second-IP.first);

					}

					out.put('\n');
				}

				return out;
			}

			static std::ostream & runFilterFilterFlags(std::istream & in, std::ostream & out, std::vector<std::string> const & Vfilter)
			{
				libmaus2::trie::Trie<char> TR;
				TR.insertContainer(Vfilter);
				::libmaus2::trie::LinearHashTrie<char,uint32_t>::unique_ptr_type LHT(TR.toLinearHashTrie<uint32_t>());

				VCFParser VCF(in);
				VCF.printText(out);

				std::pair<bool, char const *> E;
				libmaus2::util::TabEntry<> T;
				libmaus2::util::TabEntry<';'> TI;

				while ( (E=VCF.readEntry(T)).first )
				{
					if ( 6 < T.size() )
					{
						std::pair<char const *,char const *> IP = T.get(6,E.second);
						TI.parse(IP.first,IP.first,IP.second);

						bool ok = false;
						for ( uint64_t i = 0; i < TI.size(); ++i )
						{
							std::pair<char const *,char const *> P = TI.get(i,IP.first);

							int64_t const id = LHT->searchCompleteNoFailure(P.first,P.second);
							if ( id >= 0 )
								ok = true;
						}

						if ( ok )
						{
							char const * a = E.second;
							char const * e = T.get(T.size()-1,a).second;
							out.write(a,e-a);
							out.put('\n');
						}
					}
				}

				return out;
			}


			static std::ostream & runFilterInfo(std::istream & in, std::ostream & out, std::string const & filter)
			{
				libmaus2::util::TabEntry<';'> TE;
				TE.parse(filter.c_str(),filter.c_str(),filter.c_str()+filter.size());

				std::vector<std::string> Vfilter;
				for ( uint64_t i = 0; i < TE.size(); ++i )
					Vfilter.push_back(TE.getString(i,filter.c_str()));

				return runFilterInfo(in,out,Vfilter);
			}

			static std::ostream & runFilterFilterFlags(std::istream & in, std::ostream & out, std::string const & filter)
			{
				libmaus2::util::TabEntry<';'> TE;
				TE.parse(filter.c_str(),filter.c_str(),filter.c_str()+filter.size());

				std::vector<std::string> Vfilter;
				for ( uint64_t i = 0; i < TE.size(); ++i )
					Vfilter.push_back(TE.getString(i,filter.c_str()));

				return runFilterFilterFlags(in,out,Vfilter);
			}

			static std::ostream & runFilterSamples(std::istream & in, std::ostream & out, std::vector<std::string> const & Vfilter)
			{
				libmaus2::trie::Trie<char> TR;
				TR.insertContainer(Vfilter);
				::libmaus2::trie::LinearHashTrie<char,uint32_t>::unique_ptr_type LHT(TR.toLinearHashTrie<uint32_t>());

				VCFParser VCF(in);
				VCF.printText(out); // FilteredInfo(out,*LHT);

				std::pair<bool, char const *> E;
				libmaus2::util::TabEntry<> T;
				libmaus2::util::TabEntry<':'> TI;
				libmaus2::autoarray::AutoArray<uint64_t> U;

				while ( (E=VCF.readEntry(T)).first )
				{
					for ( uint64_t i = 0; i < T.size() && i < 8; ++i )
					{
						if ( i )
							out.put('\t');
						std::pair<char const *,char const *> IP = T.get(i,E.second);
						out.write(IP.first,IP.second-IP.first);
					}

					if ( 8 < T.size() )
					{
						std::pair<char const *,char const *> IP = T.get(8,E.second);
						TI.parse(IP.first,IP.first,IP.second);
						uint64_t U_o = 0;

						out.put('\t');

						uint64_t TIp = 0;
						for ( uint64_t i = 0; i < TI.size(); ++i )
						{
							std::pair<char const *,char const *> subIP = TI.get(i,IP.first);
							int64_t const id = LHT->searchCompleteNoFailure(subIP.first,subIP.second);
							if ( id >= 0 )
							{
								U.push(U_o,i);

								if ( TIp )
									out.put(':');

								out.write(subIP.first,subIP.second-subIP.first);

								TIp += 1;
							}
						}

						for ( uint64_t i = 9; i < T.size(); ++i )
						{
							out.put('\t');

							std::pair<char const *,char const *> IP = T.get(i,E.second);
							TI.parse(IP.first,IP.first,IP.second);

							uint64_t k = 0;
							for ( uint64_t j = 0; j < TI.size(); ++j )
								if ( k < U_o && U[k] == j )
								{
									if ( k )
										out << ":";

									std::pair<char const *,char const *> subIP = TI.get(j,IP.first);
									out.write(subIP.first,subIP.second-subIP.first);

									++k;
								}
						}
					}

					out.put('\n');
				}

				return out;
			}


			static std::ostream & runFilterSamples(std::istream & in, std::ostream & out, std::string const & filter)
			{
				libmaus2::util::TabEntry<';'> TE;
				TE.parse(filter.c_str(),filter.c_str(),filter.c_str()+filter.size());

				std::vector<std::string> Vfilter;
				for ( uint64_t i = 0; i < TE.size(); ++i )
					Vfilter.push_back(TE.getString(i,filter.c_str()));

				return runFilterSamples(in,out,Vfilter);
			}

			static int64_t parseUInt(char const * a, char const * e)
			{
				uint64_t v = 0;

				while ( a != e )
				{
					char const c = *(a++);

					if ( c >= '0' && c <= '9' )
					{
						v *= 10;
						v += c-'0';
					}
					else
					{
						return -1;
					}
				}

				return v;
			}

			static std::pair<int64_t,int64_t> getCoordinate(
				libmaus2::util::TabEntry<> const & T,
				char const * a,
				::libmaus2::trie::LinearHashTrie<char,uint32_t> const & LHT
			)
			{
				if ( T.size() >= 2 )
				{
					std::pair<char const *,char const *> P0(T.get(0,a));
					std::pair<char const *,char const *> P1(T.get(1,a));

					int64_t const contid = LHT.searchCompleteNoFailure(P0.first,P0.second);
					int64_t const pos = parseUInt(P1.first,P1.second);

					return std::pair<int64_t,int64_t>(contid,pos);
				}
				else
				{
					return std::pair<int64_t,int64_t>(-1,-1);
				}
			}

			static bool getInfoKey(
				libmaus2::util::TabEntry<> const & T,
				char const * a,
				std::string const & key,
				libmaus2::util::TabEntry<';'> & TS,
				std::string & value
			)
			{
				if ( 7 < T.size() )
				{
					std::pair<char const *,char const *> P(T.get(7,a));
					TS.parse(P.first,P.first,P.second);

					for ( uint64_t i = 0; i < TS.size(); ++i )
					{
						std::pair<char const *,char const *> PK(TS.get(i,P.first));

						char const * pp = std::find(PK.first,PK.second,'=');

						if ( pp != PK.second )
						{
							uint64_t const ks = pp - PK.first;
							if ( ks == key.size() && (strncmp(PK.first,key.c_str(),ks) == 0) )
							{
								value = std::string(pp+1,PK.second);
								return true;
							}
						}
					}
				}

				return false;
			}

			static uint64_t getSampleValues(
				libmaus2::util::TabEntry<> const & T,
				char const * a,
				libmaus2::util::TabEntry<':'> & TSC0,
				libmaus2::util::TabEntry<':'> & TSC1,
				uint64_t const samplenum,
				libmaus2::autoarray::AutoArray<
					std::pair<
						// key
						std::pair<char const *,char const *>,
						// value
						std::pair<char const *,char const *>
					> > & A
			)
			{
				if ( 9+samplenum < T.size() )
				{
					char const * format_a = T.get(8,a).first;
					char const * format_e = T.get(8,a).second;
					char const * sample_a = T.get(9+samplenum,a).first;
					char const * sample_e = T.get(9+samplenum,a).second;

					TSC0.parse(format_a,format_a,format_e);
					TSC1.parse(sample_a,sample_a,sample_e);

					if ( TSC0.size() != TSC1.size() )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] malformed VCF line " <<
							std::string(a,T.get(T.size()-1,a).second) << std::endl;
						lme.finish();
						throw lme;
					}

					A.ensureSize(TSC0.size());

					for ( uint64_t i = 0; i < TSC0.size(); ++i )
					{
						A[i].first = TSC0.get(i,format_a);
						A[i].second = TSC1.get(i,sample_a);
					}

					return TSC0.size();
				}
				else
				{
					return 0;
				}
			}

			static std::pair<char const *,char const *> getSampleValueForKey(
				std::string const & key,
				libmaus2::util::TabEntry<> const & T,
				char const * a,
				libmaus2::util::TabEntry<':'> & TSC0,
				libmaus2::util::TabEntry<':'> & TSC1,
				uint64_t const samplenum,
				libmaus2::autoarray::AutoArray<
					std::pair<
						std::pair<char const *,char const *>,
						std::pair<char const *,char const *>
					> > & A
			)
			{
				uint64_t const n = getSampleValues(T,a,TSC0,TSC1,samplenum,A);

				for ( uint64_t i = 0; i < n; ++i )
					if (
						static_cast<int64_t>(A[i].first.second-A[i].first.first) == static_cast<int64_t>(key.size())
						&&
						strncmp(A[i].first.first,key.c_str(),key.size()) == 0
					)
						return A[i].second;

				return std::pair<char const *,char const *>(nullptr,nullptr);
			}

			static std::string getSampleValueForKey(
				std::string const & key,
				libmaus2::util::TabEntry<> const & T,
				char const * a,
				uint64_t const samplenum
			)
			{
				libmaus2::util::TabEntry<':'> TSC0;
				libmaus2::util::TabEntry<':'> TSC1;
				libmaus2::autoarray::AutoArray<
					std::pair<
						std::pair<char const *,char const *>,
						std::pair<char const *,char const *>
					> > A;

				std::pair<char const *,char const *> const P =
					getSampleValueForKey(key,T,a,TSC0,TSC1,samplenum,A);

				return std::string(P.first,P.second);
			}

			template<char separator>
			static uint64_t getSampleValueVectorForKey(
				std::string const & key,
				libmaus2::util::TabEntry<> const & T,
				char const * a,
				libmaus2::util::TabEntry<':'> & TSC0,
				libmaus2::util::TabEntry<':'> & TSC1,
				uint64_t const samplenum,
				libmaus2::autoarray::AutoArray<
					std::pair<
						std::pair<char const *,char const *>,
						std::pair<char const *,char const *>
					> > & A,
				libmaus2::util::TabEntry<separator> & TC,
				libmaus2::autoarray::AutoArray< std::pair<char const *,char const *> > & O
			)
			{
				std::pair<char const *,char const *> const P = getSampleValueForKey(key,T,a,TSC0,TSC1,samplenum,A);

				if ( ! P.first )
					return 0;

				TC.parse(P.first,P.first,P.second);

				O.ensureSize(TC.size());

				for ( uint64_t i = 0; i < TC.size(); ++i )
					O[i] = TC.get(i,P.first);

				return TC.size();
			}

			template<typename type, char separator = ','>
			static uint64_t getSampleValueVectorForKeyParsed(
				std::string const & key,
				libmaus2::util::TabEntry<> const & T,
				char const * a,
				libmaus2::util::TabEntry<':'> & TSC0,
				libmaus2::util::TabEntry<':'> & TSC1,
				uint64_t const samplenum,
				libmaus2::autoarray::AutoArray<
					std::pair<
						std::pair<char const *,char const *>,
						std::pair<char const *,char const *>
					> > & A,
				libmaus2::util::TabEntry<separator> & TC,
				libmaus2::autoarray::AutoArray< type > & O
			)
			{
				std::pair<char const *,char const *> const P = getSampleValueForKey(key,T,a,TSC0,TSC1,samplenum,A);

				if ( ! P.first )
					return 0;

				TC.parse(P.first,P.first,P.second);

				O.ensureSize(TC.size());

				for ( uint64_t i = 0; i < TC.size(); ++i )
				{
					std::istringstream istr(TC.getString(i,P.first));
					type t;
					istr >> t;

					if ( ! istr || istr.peek() != std::istream::traits_type::eof() )
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] getSampleValueVectorForKeyParsed: unable to parse " << TC.getString(i,P.first) << std::endl;
						lme.finish();
						throw lme;
					}

					O[i] = t;
				}

				return TC.size();
			}

			template<typename type, char separator = ','>
			static std::vector<type> getSampleValueVectorForKeyParsed(
				std::string const & key,
				libmaus2::util::TabEntry<> const & T,
				char const * a,
				uint64_t const samplenum
			)
			{
				libmaus2::util::TabEntry<':'> TSC0;
				libmaus2::util::TabEntry<':'> TSC1;
				libmaus2::autoarray::AutoArray<
					std::pair<
						std::pair<char const *,char const *>,
						std::pair<char const *,char const *>
					> > A;
				libmaus2::util::TabEntry<separator> TC;
				libmaus2::autoarray::AutoArray< type > O;

				uint64_t const o = getSampleValueVectorForKeyParsed(key,T,a,TSC0,TSC1,samplenum,A,TC,O);

				return std::vector<type>(O.begin(),O.begin()+o);
			}
		};
	}
}
#endif
