/*
    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_GTF_GTFDATA_HPP)
#define LIBMAUS2_GTF_GTFDATA_HPP

#include <libmaus2/autoarray/AutoArray.hpp>
#include <libmaus2/gtf/CDS.hpp>
#include <libmaus2/gtf/Tag.hpp>
#include <libmaus2/gtf/Exon.hpp>
#include <libmaus2/gtf/Transcript.hpp>
#include <libmaus2/gtf/Gene.hpp>
#include <libmaus2/gtf/GeneChrComparator.hpp>
#include <libmaus2/lz/PlainOrGzipStream.hpp>
#include <libmaus2/trie/TrieState.hpp>
#include <libmaus2/util/GetFileSize.hpp>
#include <libmaus2/util/LineBuffer.hpp>
#include <libmaus2/util/StringSerialisation.hpp>
#include <libmaus2/util/TabEntry.hpp>
#include <libmaus2/util/stringFunctions.hpp>
#include <libmaus2/fastx/acgtnMap.hpp>

namespace libmaus2
{
	namespace gtf
	{
		struct GTFData
		{
			typedef GTFData this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;

			libmaus2::autoarray::AutoArray<char> Aid;

			libmaus2::autoarray::AutoArray<libmaus2::gtf::Tag> Atag;
			libmaus2::autoarray::AutoArray<libmaus2::gtf::CDS> ACDS;
			libmaus2::autoarray::AutoArray<libmaus2::gtf::Exon> Aexon;
			libmaus2::autoarray::AutoArray<libmaus2::gtf::Transcript> Atranscript;
			libmaus2::autoarray::AutoArray<libmaus2::gtf::Gene> Agene;

			std::vector<std::string> Vchr;

			uint64_t Aid_o;
			uint64_t next_gene_id;
			uint64_t next_transcript_id;
			uint64_t next_exon_id;
			uint64_t next_cds_id;
			uint64_t next_tag_id;

			bool operator==(GTFData const & O) const
			{
				if ( Aid_o != O.Aid_o )
				{
					std::cerr << "[E] Aid_o mismatch" << std::endl;
					return false;
				}
				if ( next_gene_id != O.next_gene_id )
				{
					std::cerr << "[E] next_gene_id mismatch" << std::endl;
					return false;
				}
				if ( next_transcript_id != O.next_transcript_id )
				{
					std::cerr << "[E] next_transcript_id mismatch" << std::endl;
					return false;
				}
				if ( next_exon_id != O.next_exon_id )
				{
					std::cerr << "[E] next_exon_id mismatch" << std::endl;
					return false;
				}
				if ( next_cds_id != O.next_cds_id )
				{
					std::cerr << "[E] next_cds_id mismatch" << std::endl;
					return false;
				}
				if ( next_tag_id != O.next_tag_id )
				{
					std::cerr << "[E] next_tag_id mismatch" << std::endl;
					return false;
				}
				if ( memcmp(Aid.begin(),O.Aid.begin(),Aid_o) != 0 )
				{
					std::cerr << "[E] Aid mismatch" << std::endl;
					return false;
				}
				for ( uint64_t i = 0; i < next_tag_id; ++i )
					if ( Atag[i] != O.Atag[i] )
					{
						std::cerr << "[E] Atag mismatch at index " << i << "/" << next_tag_id << std::endl;
						return false;
					}
				for ( uint64_t i = 0; i < next_cds_id; ++i )
					if ( ACDS[i] != O.ACDS[i] )
					{
						std::cerr << "[E] ACDS mismatch at index " << i << "/" << next_cds_id << std::endl;
						return false;
					}
				for ( uint64_t i = 0; i < next_exon_id; ++i )
					if ( Aexon[i] != O.Aexon[i] )
					{
						std::cerr << "[E] Aexon mismatch" << std::endl;
						return false;
					}
				for ( uint64_t i = 0; i < next_transcript_id; ++i )
					if ( Atranscript[i] != O.Atranscript[i] )
					{
						std::cerr << "[E] Atranscript mismatch" << std::endl;
						return false;
					}
				for ( uint64_t i = 0; i < next_gene_id; ++i )
					if ( Agene[i] != O.Agene[i] )
					{
						std::cerr << "[E] Agene mismatch" << std::endl;
						return false;
					}
				if ( Vchr != O.Vchr )
				{
					std::cerr << "[E] Vchr mismatch" << std::endl;
					return false;
				}
				return true;
			}

			std::ostream & print(std::ostream & out) const
			{
				for ( uint64_t gi = 0; gi < next_gene_id; ++gi )
				{
					assert ( gi < next_gene_id );
					assert ( gi < Agene.size() );

					Agene[gi].print(out,Aid);

					for ( uint64_t ti = Agene[gi].start; ti < Agene[gi].end; ++ti )
					{
						assert ( ti < next_transcript_id );
						assert ( ti < Atranscript.size() );

						Atranscript[ti].print(
							out,
							Aid,
							Aid.begin() + Agene[gi].chr_offset,
							Aid.begin() + Agene[gi].id_offset,
							Aid.begin() + Agene[gi].name_offset
						);

						for ( uint64_t ei = Atranscript[ti].start; ei < Atranscript[ti].end; ++ei )
						{
							assert ( ei < next_exon_id );
							assert ( ei < Aexon.size() );

							Aexon[ei].print(
								out,
								Aid,
								Aid.begin() + Agene[gi].chr_offset,
								Aid.begin() + Agene[gi].id_offset,
								Aid.begin() + Agene[gi].name_offset,
								Aid.begin() + Atranscript[ti].id_offset,
								(ei - Atranscript[ti].start)+1
							);
						}
					}
				}

				return out;
			}

			int64_t getChrId(std::string const & s) const
			{
				std::vector<std::string>::const_iterator it = std::lower_bound(Vchr.begin(),Vchr.end(),s);

				if ( it != Vchr.end() && *it == s )
					return it - Vchr.begin();
				else
					return -1;
			}

			static std::string getCacheFileName(std::string const & annofn)
			{
				return annofn + ".feature_cache";
			}

			static bool validCacheExists(std::string const & annofn)
			{
				std::string const cachefn = getCacheFileName(annofn);

				if (
					(! libmaus2::util::GetFileSize::fileExists(cachefn))
					||
					libmaus2::util::GetFileSize::isOlder(cachefn,annofn)
				)
					return false;
				else
					return true;
			}

			void createCacheIf(std::string const & annofn, int const verbose) const
			{
				if ( (! validCacheExists(annofn)) || (! readEOF(getCacheFileName(annofn))) )
				{
					std::string const cachefn = getCacheFileName(annofn);

					if ( verbose )
						std::cerr << "[V] cache fn " << cachefn << std::endl;

					{
					libmaus2::aio::OutputStreamInstance OSI(cachefn);
					serialise(OSI);
					OSI.flush();
					}

					{
					libmaus2::aio::InputStreamInstance ISI(cachefn);
					GTFData GTFback;
					GTFback.deserialise(ISI);
					assert ( GTFback == *this );
					}
				}
			}

			static std::string getEOF()
			{
				std::ostringstream ostr;
				for ( uint64_t i = 0; i < 8; ++i )
					ostr.put(i);
				ostr << "EOF";
				for ( uint64_t i = 0; i < 8; ++i )
					ostr.put(static_cast<uint8_t>(i^0xff));

				return ostr.str();
			}

			static bool readEOF(std::istream & in)
			{
				std::string const data = getEOF();
				libmaus2::autoarray::AutoArray<char> A(data.size());
				in.read(A.begin(),data.size());

				if ( in.gcount() != static_cast<int64_t>(data.size()) )
					return false;

				std::string const cdata(A.begin(),A.end());

				return data == cdata;
			}

			static bool readEOF(std::string const & cachefn)
			{
				libmaus2::aio::InputStreamInstance ISI(cachefn);
				std::string const data = getEOF();

				ISI.seekg(-static_cast<int64_t>(data.size()),std::ios::end);

				return readEOF(ISI);
			}

			std::ostream & serialise(std::ostream & out) const
			{
				libmaus2::util::NumberSerialisation::serialiseNumber(out,Aid_o);
				libmaus2::util::NumberSerialisation::serialiseNumber(out,next_gene_id);
				libmaus2::util::NumberSerialisation::serialiseNumber(out,next_transcript_id);
				libmaus2::util::NumberSerialisation::serialiseNumber(out,next_exon_id);
				libmaus2::util::NumberSerialisation::serialiseNumber(out,next_cds_id);
				libmaus2::util::NumberSerialisation::serialiseNumber(out,next_tag_id);

				out.write(Aid.begin(),Aid_o);
				for ( uint64_t i = 0; i < next_tag_id; ++i )
					Atag[i].serialise(out);
				for ( uint64_t i = 0; i < next_cds_id; ++i )
					ACDS[i].serialise(out);
				for ( uint64_t i = 0; i < next_exon_id; ++i )
					Aexon[i].serialise(out);
				for ( uint64_t i = 0; i < next_transcript_id; ++i )
					Atranscript[i].serialise(out);
				for ( uint64_t i = 0; i < next_gene_id; ++i )
					Agene[i].serialise(out);

				libmaus2::util::StringSerialisation::serialiseStringVector(out,Vchr);

				std::string const eof = getEOF();
				out.write(eof.c_str(),eof.size());

				if ( ! out )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] failed to serialise GTFData object" << std::endl;
					lme.finish();
					throw lme;
				}

				return out;
			}

			std::istream & deserialise(std::istream & in)
			{
				Aid_o = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
				next_gene_id = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
				next_transcript_id = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
				next_exon_id = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
				next_cds_id = libmaus2::util::NumberSerialisation::deserialiseNumber(in);
				next_tag_id = libmaus2::util::NumberSerialisation::deserialiseNumber(in);

				Aid.ensureSize(Aid_o);
				in.read(Aid.begin(),Aid_o);

				Atag.ensureSize(next_tag_id);
				for ( uint64_t i = 0; i < next_tag_id; ++i )
					Atag[i].deserialise(in);
				ACDS.ensureSize(next_cds_id);
				for ( uint64_t i = 0; i < next_cds_id; ++i )
					ACDS[i].deserialise(in);
				Aexon.ensureSize(next_exon_id);
				for ( uint64_t i = 0; i < next_exon_id; ++i )
					Aexon[i].deserialise(in);
				Atranscript.ensureSize(next_transcript_id);
				for ( uint64_t i = 0; i < next_transcript_id; ++i )
					Atranscript[i].deserialise(in);
				Agene.ensureSize(next_gene_id);
				for ( uint64_t i = 0; i < next_gene_id; ++i )
					Agene[i].deserialise(in);

				Vchr = libmaus2::util::StringSerialisation::deserialiseStringVector(in);

				bool const eofok = readEOF(in);

				if ( !eofok )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] failed to read eof block in GTFData::deserialise" << std::endl;
					lme.finish();
					throw lme;
				}

				return in;
			}

			void reset()
			{
				Aid_o = 0;
				next_gene_id = 0;
				next_transcript_id = 0;
				next_exon_id = 0;
				next_cds_id = 0;
				next_tag_id = 0;
			}

			GTFData()
			{
				reset();
			}

			uint64_t pushGene(Gene const & OG, libmaus2::autoarray::AutoArray<char> const & OAid)
			{
				Gene G;

				uint64_t const this_gene_id = next_gene_id;

				G.chr_offset  = pushId(Aid,Aid_o,OAid.begin()+OG.chr_offset);
				G.id_offset   = pushId(Aid,Aid_o,OAid.begin()+OG.id_offset);
				G.name_offset = pushId(Aid,Aid_o,OAid.begin()+OG.name_offset);
				G.chr_id      = OG.chr_id;
				G.start       = 0;
				G.end         = 0;
				G.cstart      = OG.cstart;
				G.cend        = OG.cend;
				G.strand      = OG.strand;
				G.id          = this_gene_id;

				Agene.push(next_gene_id,G);

				return this_gene_id;
			}

			uint64_t pushTranscript(
				Transcript const & OT,
				libmaus2::autoarray::AutoArray<char> const & OAid,
				uint64_t const gene_id
			)
			{
				Transcript T;

				uint64_t const this_transcript_id = next_transcript_id;

				T.id_offset = pushId(Aid,Aid_o,OAid.begin()+OT.id_offset);
				T.start = 0;
				T.end = 0;
				T.strand = OT.strand;
				T.gene_id = gene_id;
				T.c_start = OT.c_start;
				T.c_end = OT.c_end;
				T.id = this_transcript_id;
				T.tag_start = 0;
				T.tag_end = 0;
				T.support_level = OT.support_level;

				Atranscript.push(next_transcript_id,T);

				return this_transcript_id;
			}

			uint64_t pushExon(
				Exon const & OE,
				libmaus2::autoarray::AutoArray<char> const & OAid,
				uint64_t const transcript_id
			)
			{
				Exon E;

				uint64_t const this_exon_id = next_exon_id;

				E.id = this_exon_id;
				E.id_offset = pushId(Aid,Aid_o,OAid.begin()+OE.id_offset);
				E.start = OE.start;
				E.end = OE.end;
				E.strand = OE.strand;
				E.transcript_id = transcript_id;
				E.CDS_start = 0;
				E.CDS_end = 0;

				Aexon.push(next_exon_id,E);

				return this_exon_id;
			}

			uint64_t pushCDS(
				CDS const & OC,
				libmaus2::autoarray::AutoArray<char> const & /* OAid */,
				uint64_t const exonid
			)
			{
				CDS C;

				uint64_t const this_cds_id = next_cds_id;

				C.id = this_cds_id;
				C.start = OC.start;
				C.end = OC.end;
				C.strand = OC.strand;
				C.frame = OC.frame;
				C.exonid = exonid;

				ACDS.push(next_cds_id,C);

				return this_cds_id;
			}

			unique_ptr_type filter(std::vector<std::string> const & Vgene) const
			{
				unique_ptr_type ptr(new this_type());

				for ( uint64_t i = 1; i < Vgene.size(); ++i )
				{
					bool const ok = Vgene[i-1] < Vgene[i];

					if ( ! ok )
					{
						std::cerr << Vgene[i-1] << " " << Vgene[i] << std::endl;
					}

					assert ( ok );
				}

				for ( uint64_t i_gene = 0; i_gene < next_gene_id; ++i_gene )
				{
					char const * name = Aid.begin() + Agene[i_gene].name_offset;

					std::vector<std::string>::const_iterator it = std::lower_bound(Vgene.begin(),Vgene.end(),std::string(name));

					bool const found = (it != Vgene.end()) && (*it == name);

					// std::cerr << "looking for name ::" << name << ":: found=" << found << " " << Vgene.size() << std::endl;

					if ( found )
					{
						Gene const & G = Agene[i_gene];

						uint64_t const gene_id = ptr->pushGene(G,Aid);
						ptr->Agene[gene_id].start = ptr->next_transcript_id;

						for ( uint64_t i_transcript = G.start; i_transcript < G.end; ++i_transcript )
						{
							Transcript const & T = Atranscript[i_transcript];
							uint64_t const transcript_id = ptr->pushTranscript(T,Aid,gene_id);
							ptr->Atranscript[transcript_id].start = ptr->next_exon_id;

							for ( uint64_t i_exon = T.start; i_exon < T.end; ++i_exon )
							{
								Exon const & E = Aexon[i_exon];
								uint64_t const exon_id = ptr->pushExon(E,Aid,transcript_id);
								ptr->Aexon[exon_id].CDS_start = ptr->next_cds_id;

								for ( uint64_t i_CDS = E.CDS_start; i_CDS < E.CDS_end; ++i_CDS )
								{
									CDS const & C = ACDS[i_CDS];
									ptr->pushCDS(C,Aid,exon_id);
								}

								ptr->Aexon[exon_id].CDS_end = ptr->next_cds_id;
							}

							ptr->Atranscript[transcript_id].end = ptr->next_exon_id;
						}

						ptr->Agene[gene_id].end = ptr->next_transcript_id;
					}
					else
					{
						#if 0
						if ( it != Vgene.begin() )
						{
							std::cerr << "::" << it[-1] << "::" << std::endl;
						}
						if ( it != Vgene.end() )
						{
							std::cerr << "::" << *it << "::" << std::endl;
						}
						#endif
					}
				}

				ptr->Vchr = Vchr;

				return ptr;
			}

			std::vector<std::string> getTags(libmaus2::gtf::Transcript const & T) const
			{
				std::vector<std::string> V;
				for ( uint64_t i = T.tag_start; i < T.tag_end; ++i )
					V.push_back(Aid.begin() + Atag[i].text_offset);
				return V;
			}

			GTFData(std::string const & annofn, int const verbose)
			{
				reset();

				libmaus2::aio::InputStreamInstance ISI(annofn);
				libmaus2::lz::PlainOrGzipStream POG(ISI);
				libmaus2::util::LineBuffer LB(POG);
				char const * a;
				char const * e;
				libmaus2::util::TabEntry<> TE;
				libmaus2::util::TabEntry<';'> TE8;
				libmaus2::util::TabEntry<' '> TES;

				static char const * c_gene = "gene";
				std::ptrdiff_t const l_gene = strlen(c_gene);
				static char const * c_transcript = "transcript";
				std::ptrdiff_t const l_transcript = strlen(c_transcript);
				static char const * c_exon = "exon";
				std::ptrdiff_t const l_exon = strlen(c_exon);
				static char const * c_CDS = "CDS";
				std::ptrdiff_t const l_CDS = strlen(c_CDS);

				static char const * c_gene_id = "gene_id";
				std::ptrdiff_t const l_gene_id = strlen(c_gene_id);
				static char const * c_transcript_id = "transcript_id";
				std::ptrdiff_t const l_transcript_id = strlen(c_transcript_id);
				static char const * c_gene_name = "gene_name";
				std::ptrdiff_t const l_gene_name = strlen(c_gene_name);

				static char const * c_tag = "tag";
				std::ptrdiff_t const l_tag = strlen(c_tag);

				static char const * c_transcript_support_level = "transcript_support_level";
				std::ptrdiff_t const l_transcript_support_level = strlen(c_transcript_support_level);

				static char const * c_exon_id = "exon_id";
				std::ptrdiff_t const l_exon_id = strlen(c_exon_id);
				static char const * c_exon_number = "exon_number";
				std::ptrdiff_t const l_exon_number = strlen(c_exon_number);

				libmaus2::autoarray::AutoArray<char> A_gene_id;
				std::ptrdiff_t A_gene_id_o = 0;
				libmaus2::autoarray::AutoArray<char> A_gene_name;
				std::ptrdiff_t A_gene_name_o = 0;
				libmaus2::autoarray::AutoArray<char> A_gene_chr;
				std::ptrdiff_t A_gene_chr_o = 0;

				libmaus2::autoarray::AutoArray<char> A_transcript_id;
				std::ptrdiff_t A_transcript_id_o = 0;
				libmaus2::autoarray::AutoArray<char> A_transcript_chr;
				std::ptrdiff_t A_transcript_chr_o = 0;

				libmaus2::autoarray::AutoArray<char> A_exon_id;
				std::ptrdiff_t A_exon_id_o = 0;
				libmaus2::autoarray::AutoArray<char> A_exon_chr;
				std::ptrdiff_t A_exon_chr_o = 0;
				libmaus2::autoarray::AutoArray<char> A_exon_number;
				std::ptrdiff_t A_exon_number_o = 0;

				bool have_gene = false;
				bool have_transcript = false;
				bool have_exon = false;
				// bool have_CDS = false;
				uint64_t exonidnext = std::numeric_limits<uint64_t>::max();
				uint64_t lineid = 0;

				for ( ; LB.getline(&a,&e); ++lineid )
				{
					if ( a && e-a && a[0] != '#' )
					{
						TE.parse(a,a,e);

						if ( TE.size() >= 8 )
						{
							std::pair<char const *,char const *> const P2 = TE.get(2,a);

							if ( P2.second-P2.first == l_gene && strncmp(P2.first,c_gene,l_gene) == 0 )
							{
								std::pair<char const *,char const *> const P8 = TE.get(8,a);
								TE8.parse(P8.first,P8.first,P8.second);

								bool havegeneid = false;
								bool havegenename = false;

								for ( uint64_t i = 0; i < TE8.size(); ++i )
								{
									std::pair<char const *,char const *> P8i = TE8.get(i,P8.first);
									if ( P8i.first != P8i.second && ::isspace(*P8i.first) )
										++P8i.first;

									TES.parse(P8i.first,P8i.first,P8i.second);

									if ( TES.size() == 2 )
									{
										std::pair<char const *,char const *> P80 = TES.get(0,P8i.first);
										std::pair<char const *,char const *> P81 = TES.get(1,P8i.first);

										if ( P80.second-P80.first == l_gene_id && strncmp(P80.first,c_gene_id,l_gene_id) == 0 )
										{
											clipQuotes(P81);
											A_gene_id_o = copyString(A_gene_id,P81);
											havegeneid = true;
										}
										else if ( P80.second-P80.first == l_gene_name && strncmp(P80.first,c_gene_name,l_gene_name) == 0 )
										{
											clipQuotes(P81);
											A_gene_name_o = copyString(A_gene_name,P81);
											havegenename = true;
										}
									}
								}

								if ( havegeneid && havegenename )
								{
									A_gene_chr_o = copyString(A_gene_chr,TE.get(0,a));

									#if 0
									std::cerr << "gene"
										<< "\t" << std::string(A_gene_chr.begin(),A_gene_chr.begin()+A_gene_chr_o)
										<< "\t" << std::string(A_gene_id.begin(),A_gene_id.begin()+A_gene_id_o)
										<< "\t" << std::string(A_gene_name.begin(),A_gene_name.begin()+A_gene_name_o)
										<< "\n"
										;
									#endif

									uint64_t const off_gene_id = pushId(Aid,Aid_o,A_gene_id,A_gene_id_o);
									uint64_t const off_chr_id = pushId(Aid,Aid_o,A_gene_chr,A_gene_chr_o);
									uint64_t const off_gene_name = pushId(Aid,Aid_o,A_gene_name,A_gene_name_o);

									uint64_t const cstart = parseNumber(TE.get(3,a));
									uint64_t const cend = parseNumber(TE.get(4,a));
									bool const strand = parseStrand(TE.get(6,a));

									uint64_t const this_gene_id = next_gene_id;

									Agene.push(
										next_gene_id,
										libmaus2::gtf::Gene(
											off_chr_id,
											off_gene_id,
											off_gene_name,
											next_transcript_id,
											next_transcript_id,
											cstart,
											cend,
											this_gene_id,
											strand
										)
									);

									have_gene = true;
								}
								else
								{
									libmaus2::exception::LibMausException lme;
									lme.getStream() << "[E] broken gene line: " << std::string(a,e) << std::endl;
									lme.getStream() << "[E] tag field: " << TE.getString(8,a) << std::endl;
									lme.getStream() << "[E] havegeneid=" << havegeneid << std::endl;
									lme.getStream() << "[E] havegenename=" << havegenename << std::endl;
									lme.finish();
									throw lme;
								}
							}
							else if ( P2.second-P2.first == l_transcript && strncmp(P2.first,c_transcript,l_transcript) == 0 )
							{
								if ( ! have_gene )
								{
									libmaus2::exception::LibMausException lme;
									lme.getStream() << "[E] transcript line not matching gene meta data " << std::string(a,e) << std::endl;
									lme.finish();
									throw lme;
								}
								// std::cerr << "transcript " << std::string(a,e) << std::endl;

								bool havegeneid = false;
								bool havegenename = false;
								bool havetranscriptid = false;

								std::pair<char const *,char const *> const P8 = TE.get(8,a);
								TE8.parse(P8.first,P8.first,P8.second);

								std::vector < std::string > Vtag;
								int64_t support_level = -1;

								for ( uint64_t i = 0; i < TE8.size(); ++i )
								{
									std::pair<char const *,char const *> P8i = TE8.get(i,P8.first);
									if ( P8i.first != P8i.second && ::isspace(*P8i.first) )
										++P8i.first;

									TES.parse(P8i.first,P8i.first,P8i.second);

									if ( TES.size() == 2 )
									{
										std::pair<char const *,char const *> P80 = TES.get(0,P8i.first);
										std::pair<char const *,char const *> P81 = TES.get(1,P8i.first);

										if ( P80.second-P80.first == l_gene_id && strncmp(P80.first,c_gene_id,l_gene_id) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_gene_id_o
												&&
												strncmp(A_gene_id.begin(),P81.first,P81.second-P81.first) == 0
											)
											{
												havegeneid = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] transcript not matching gene meta data id " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
										else if ( P80.second-P80.first == l_gene_name && strncmp(P80.first,c_gene_name,l_gene_name) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_gene_name_o
												&&
												strncmp(A_gene_name.begin(),P81.first,P81.second-P81.first) == 0
											)
											{
												havegenename = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] transcript not matching gene meta data name " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
										else if ( P80.second-P80.first == l_transcript_id && strncmp(P80.first,c_transcript_id,l_transcript_id) == 0 )
										{
											clipQuotes(P81);
											A_transcript_id_o = copyString(A_transcript_id,P81);
											havetranscriptid = true;
										}
										else if ( P80.second-P80.first == l_tag && strncmp(P80.first,c_tag,l_tag) == 0 )
										{
											clipQuotes(P81);
											// std::cerr << "tag " << std::string(P81.first,P81.second) << std::endl;
											Vtag.push_back(std::string(P81.first,P81.second));
										}
										else if ( P80.second-P80.first == l_transcript_support_level && strncmp(P80.first,c_transcript_support_level,l_transcript_support_level) == 0 )
										{
											clipQuotes(P81);

											std::istringstream istr(std::string(P81.first,P81.second));
											uint64_t usupport_level;
											istr >> usupport_level;

											if ( istr && istr.peek() == std::istream::traits_type::eof() )
											{
												support_level = usupport_level;
											}
										}
									}
								}

								if ( havegeneid && havegenename && havetranscriptid )
								{
									A_transcript_chr_o = copyString(A_transcript_chr,TE.get(0,a));

									if (
										A_transcript_chr_o != A_gene_chr_o
										||
										strncmp(A_transcript_chr.begin(),A_gene_chr.begin(),A_transcript_chr_o) != 0
									)
									{
										libmaus2::exception::LibMausException lme;
										lme.getStream() << "[E] broken transcript line (refseq mismatch with gene): " << std::string(a,e) << std::endl;
										lme.finish();
										throw lme;
									}

									#if 0
									std::cerr << "transcript"
										<< "\t" << std::string(A_transcript_chr.begin(),A_transcript_chr.begin()+A_transcript_chr_o)
										<< "\t" << std::string(A_gene_id.begin(),A_gene_id.begin()+A_gene_id_o)
										<< "\t" << std::string(A_gene_name.begin(),A_gene_name.begin()+A_gene_name_o)
										<< "\t" << std::string(A_transcript_id.begin(),A_transcript_id.begin()+A_transcript_id_o)
										<< "\n"
										;
									#endif

									bool const strand = parseStrand(TE.get(6,a));
									uint64_t const cstart = parseNumber(TE.get(3,a));
									uint64_t const cend = parseNumber(TE.get(4,a));

									uint64_t const off_transcript_id =
										pushId(Aid,Aid_o,A_transcript_id,A_transcript_id_o);
									uint64_t const this_transcript_id =
										next_transcript_id;

									uint64_t tag_start = next_tag_id;

									for ( uint64_t i = 0; i < Vtag.size(); ++i )
									{
										uint64_t const off_tag_text =
											pushId(
												Aid,
												Aid_o,
												Vtag[i].c_str()
											);

										Atag.push(next_tag_id,libmaus2::gtf::Tag(off_tag_text));
									}

									uint64_t tag_end = next_tag_id;

									Atranscript.push(
										next_transcript_id,
										libmaus2::gtf::Transcript(
											off_transcript_id,
											next_exon_id,
											next_exon_id,
											strand,
											next_gene_id-1,
											cstart,
											cend,
											this_transcript_id,
											tag_start,
											tag_end,
											support_level
										)
									);
									Agene[next_gene_id-1].end += 1;

									have_transcript = true;
									exonidnext = 1;
								}
								else
								{
									libmaus2::exception::LibMausException lme;
									lme.getStream() << "[E] broken transcript line: " << std::string(a,e) << std::endl;
									lme.finish();
									throw lme;
								}
							}
							else if ( P2.second-P2.first == l_exon && strncmp(P2.first,c_exon,l_exon) == 0 )
							{
								if ( ! have_transcript )
								{
									libmaus2::exception::LibMausException lme;
									lme.getStream() << "[E] exon line not matching transcript meta data " << std::string(a,e) << std::endl;
									lme.finish();
									throw lme;
								}
								// std::cerr << "transcript " << std::string(a,e) << std::endl;

								bool havegeneid = false;
								bool havegenename = false;
								bool havetranscriptid = false;
								bool haveexonid = false;
								bool haveexonnumber = false;

								std::pair<char const *,char const *> const P8 = TE.get(8,a);
								TE8.parse(P8.first,P8.first,P8.second);

								for ( uint64_t i = 0; i < TE8.size(); ++i )
								{
									std::pair<char const *,char const *> P8i = TE8.get(i,P8.first);
									if ( P8i.first != P8i.second && ::isspace(*P8i.first) )
										++P8i.first;

									TES.parse(P8i.first,P8i.first,P8i.second);

									if ( TES.size() == 2 )
									{
										std::pair<char const *,char const *> P80 = TES.get(0,P8i.first);
										std::pair<char const *,char const *> P81 = TES.get(1,P8i.first);

										if ( P80.second-P80.first == l_gene_id && strncmp(P80.first,c_gene_id,l_gene_id) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_gene_id_o
												&&
												strncmp(A_gene_id.begin(),P81.first,P81.second-P81.first) == 0
											)
											{
												havegeneid = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] exon not matching transcript meta data id " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
										else if ( P80.second-P80.first == l_gene_name && strncmp(P80.first,c_gene_name,l_gene_name) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_gene_name_o
												&&
												strncmp(A_gene_name.begin(),P81.first,P81.second-P81.first) == 0
											)
											{
												havegenename = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] exon not matching transcript meta data name " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
										else if ( P80.second-P80.first == l_transcript_id && strncmp(P80.first,c_transcript_id,l_transcript_id) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_transcript_id_o
												&&
												strncmp(A_transcript_id.begin(),P81.first,P81.second-P81.first) == 0
											)
											{
												havetranscriptid = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] exon not matching transcript meta data name " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
										else if ( P80.second-P80.first == l_exon_id && strncmp(P80.first,c_exon_id,l_exon_id) == 0 )
										{
											clipQuotes(P81);
											A_exon_id_o = copyString(A_exon_id,P81);
											haveexonid = true;
										}
										else if ( P80.second-P80.first == l_exon_number && strncmp(P80.first,c_exon_number,l_exon_number) == 0 )
										{
											clipQuotes(P81);
											A_exon_number_o = copyString(A_exon_number,P81);
											haveexonnumber = true;
										}
									}
								}

								if (
									havegeneid
									&&
									havegenename
									&&
									havetranscriptid
									&&
									haveexonid
									&&
									haveexonnumber
								)
								{
									A_exon_chr_o = copyString(A_exon_chr,TE.get(0,a));

									uint64_t uexonnumber = 0;
									uint64_t uexonnumber_c = 0;
									char const * cexonnumbera = A_exon_number.begin();
									char const * cexonnumbere = A_exon_number.begin() + A_exon_number_o;
									while ( cexonnumbera != cexonnumbere && *cexonnumbera >= '0' && *cexonnumbera <= '9' )
									{
										uexonnumber *= 10;
										uexonnumber += (*(cexonnumbera++)) - '0';
										uexonnumber_c += 1;
									}

									if ( cexonnumbera != cexonnumbere || !uexonnumber_c )
									{
										libmaus2::exception::LibMausException lme;
										lme.getStream() << "[E] broken exon line (unparsable exon_number): " << std::string(a,e) << std::endl;
										lme.finish();
										throw lme;
									}
									if ( uexonnumber != exonidnext )
									{
										libmaus2::exception::LibMausException lme;
										lme.getStream() << "[E] broken exon line (exon number out of sequence) " << std::string(a,e) << std::endl;
										lme.finish();
										throw lme;
									}

									uint64_t const start = parseNumber(TE.get(3,a));
									uint64_t const end = parseNumber(TE.get(4,a));
									bool const strand = parseStrand(TE.get(6,a));

									if (
										A_exon_chr_o != A_gene_chr_o
										||
										strncmp(A_exon_chr.begin(),A_gene_chr.begin(),A_exon_chr_o) != 0
									)
									{
										libmaus2::exception::LibMausException lme;
										lme.getStream() << "[E] broken exon line (refseq mismatch with transcript): " << std::string(a,e) << std::endl;
										lme.finish();
										throw lme;
									}

									#if 0
									std::cerr << "exon"
										<< "\t" << std::string(A_exon_chr.begin(),A_exon_chr.begin()+A_exon_chr_o)
										<< "\t" << std::string(A_exon_id.begin(),A_exon_id.begin()+A_exon_id_o)
										<< "\t" << uexonnumber
										<< "\t" << start
										<< "\t" << end
										<< "\t" << std::string(A_gene_id.begin(),A_gene_id.begin()+A_gene_id_o)
										<< "\t" << std::string(A_gene_name.begin(),A_gene_name.begin()+A_gene_name_o)
										<< "\t" << std::string(A_transcript_id.begin(),A_transcript_id.begin()+A_transcript_id_o)
										<< "\n"
										;
									#endif

									uint64_t const off_exon_id = pushId(Aid,Aid_o,A_exon_id,A_exon_id_o);
									uint64_t const this_exon_id = next_exon_id;

									have_exon = true;

									Aexon.push(next_exon_id,libmaus2::gtf::Exon(this_exon_id,off_exon_id,start,end,strand,next_transcript_id-1,next_cds_id,next_cds_id));
									Atranscript[next_transcript_id-1].end += 1;

									exonidnext += 1;
								}
								else
								{
									libmaus2::exception::LibMausException lme;
									lme.getStream() << "[E] broken exon line: " << std::string(a,e) << std::endl;
									lme.getStream() << "[E] havegeneid=" << havegeneid << std::endl;
									lme.getStream() << "[E] havegenename=" << havegenename << std::endl;
									lme.getStream() << "[E] havetranscriptid=" << havetranscriptid << std::endl;
									lme.getStream() << "[E] haveexonid=" << haveexonid << std::endl;
									lme.getStream() << "[E] haveexonnumber=" << haveexonnumber << std::endl;
									lme.finish();
									throw lme;
								}
							}
							else if ( P2.second-P2.first == l_CDS && strncmp(P2.first,c_CDS,l_CDS) == 0 )
							{
								if ( ! have_exon )
								{
									libmaus2::exception::LibMausException lme;
									lme.getStream() << "[E] CDS line not matching exon meta data " << std::string(a,e) << std::endl;
									lme.finish();
									throw lme;
								}
								// std::cerr << "transcript " << std::string(a,e) << std::endl;

								bool havegeneid = false;
								bool havegenename = false;
								bool havetranscriptid = false;
								bool haveexonid = false;
								bool haveexonnumber = false;

								std::pair<char const *,char const *> const P8 = TE.get(8,a);
								TE8.parse(P8.first,P8.first,P8.second);

								for ( uint64_t i = 0; i < TE8.size(); ++i )
								{
									std::pair<char const *,char const *> P8i = TE8.get(i,P8.first);
									if ( P8i.first != P8i.second && ::isspace(*P8i.first) )
										++P8i.first;

									TES.parse(P8i.first,P8i.first,P8i.second);

									if ( TES.size() == 2 )
									{
										std::pair<char const *,char const *> P80 = TES.get(0,P8i.first);
										std::pair<char const *,char const *> P81 = TES.get(1,P8i.first);

										if ( P80.second-P80.first == l_gene_id && strncmp(P80.first,c_gene_id,l_gene_id) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_gene_id_o
												&&
												strncmp(A_gene_id.begin(),P81.first,P81.second-P81.first) == 0
											)
											{
												havegeneid = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] CDS not matching exon meta data id " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
										else if ( P80.second-P80.first == l_gene_name && strncmp(P80.first,c_gene_name,l_gene_name) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_gene_name_o
												&&
												strncmp(A_gene_name.begin(),P81.first,P81.second-P81.first) == 0
											)
											{
												havegenename = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] CDS not matching exon meta data name " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
										else if ( P80.second-P80.first == l_transcript_id && strncmp(P80.first,c_transcript_id,l_transcript_id) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_transcript_id_o
												&&
												strncmp(A_transcript_id.begin(),P81.first,P81.second-P81.first) == 0
											)
											{
												havetranscriptid = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] CDS not matching exon meta data name " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
										else if ( P80.second-P80.first == l_exon_id && strncmp(P80.first,c_exon_id,l_exon_id) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_exon_id_o
												&&
												strncmp(A_exon_id.begin(),P81.first,P81.second-P81.first) == 0
											)
											{
												haveexonid = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] CDS not matching exon meta data name " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
										else if ( P80.second-P80.first == l_exon_number && strncmp(P80.first,c_exon_number,l_exon_number) == 0 )
										{
											clipQuotes(P81);

											if (
												P81.second-P81.first == A_exon_number_o
												&&
												strncmp(A_exon_number.begin(),P81.first,P81.second-P81.first) == 0
											)
											{

												haveexonnumber = true;
											}
											else
											{
												libmaus2::exception::LibMausException lme;
												lme.getStream() << "[E] CDS not matching exon meta data name " << std::string(a,e) << std::endl;
												lme.finish();
												throw lme;
											}
										}
									}
								}

								#if 0
								if ( ! haveexonid )
								{
									std::clog << "[W] warning: CDS line without exon_id " << std::string(a,e) << std::endl;
								}
								#endif

								if ( havegeneid && havegenename && havetranscriptid && haveexonnumber )
								{
									uint64_t const start = parseNumber(TE.get(3,a));
									uint64_t const end = parseNumber(TE.get(4,a));
									bool const strand = parseStrand(TE.get(6,a));
									uint64_t const frame = parseNumber(TE.get(7,a));

									uint64_t const this_CDS_id = next_cds_id;

									// have_CDS = true;

									ACDS.push(next_cds_id,
										libmaus2::gtf::CDS(
											this_CDS_id,
											start,
											end,
											strand,
											frame,
											next_exon_id-1
										)
									);
									Aexon[next_exon_id-1].CDS_end += 1;

									// std::cerr << "found " << ACDS[next_cds_id-1] << " for " << Aexon[next_exon_id-1] << std::endl;
								}
								else
								{
									libmaus2::exception::LibMausException lme;
									lme.getStream() << "[E] broken CDS line: " << std::string(a,e) << std::endl;
									lme.getStream() << "[E] havegeneid=" << havegeneid << std::endl;
									lme.getStream() << "[E] havegenename=" << havegenename << std::endl;
									lme.getStream() << "[E] havetranscriptid=" << havetranscriptid << std::endl;
									lme.getStream() << "[E] haveexonid=" << haveexonid << std::endl;
									lme.getStream() << "[E] haveexonnumber=" << haveexonnumber << std::endl;
									lme.finish();
									throw lme;
								}
							}
						}
					}

					if ( verbose && (lineid % (1024*1024) == 0) && (lineid != 0) )
					{
						std::cerr << "[V] processed " << lineid << " annotation lines" << std::endl;
					}
				}

				if ( verbose )
				{
					std::cerr << "[V] processed " << lineid << " annotation lines" << std::endl;
					std::cerr << "[V] number of genes " << next_gene_id << std::endl;
					std::cerr << "[V] number of transcripts " << next_transcript_id << std::endl;
					std::cerr << "[V] number of exons " << next_exon_id << std::endl;
				}

				libmaus2::autoarray::AutoArray<libmaus2::gtf::Gene> Agenecopy(next_gene_id);
				std::copy(Agene.begin(),Agene.begin()+next_gene_id,Agenecopy.begin());
				libmaus2::gtf::GeneChrComparator const GIC(Aid.begin());
				std::sort(Agenecopy.begin(),Agenecopy.begin()+next_gene_id,GIC);

				uint64_t low = 0;
				while ( low < next_gene_id )
				{
					uint64_t high = low+1;

					while ( high < next_gene_id && ! GIC(Agenecopy[low],Agenecopy[high]) )
						++high;

					#if 0
					std::cerr << GIC.get(Agenecopy[low]) << "\t" << high-low << std::endl;
					#endif

					Vchr.push_back(GIC.get(Agenecopy[low]));

					low = high;
				}

				// std::sort(Vchr.begin(),Vchr.end());
				for ( uint64_t i = 1; i < Vchr.size(); ++i )
					assert ( Vchr[i-1] < Vchr[i] );

				::libmaus2::trie::Trie<char> trienofailure;
				trienofailure.insertContainer(Vchr);
				::libmaus2::trie::LinearHashTrie<char,uint32_t>::unique_ptr_type LHTnofailure(trienofailure.toLinearHashTrie<uint32_t>());

				for ( uint64_t i = 0; i < next_gene_id; ++i )
				{
					char const * chr = Aid.begin() + Agene[i].chr_offset;
					int64_t const id = LHTnofailure->searchCompleteNoFailureZ(chr);
					assert ( id >= 0 );
					Agene[i].chr_id = id;
				}

				for ( uint64_t i = 0; i < next_gene_id; ++i )
				{
					libmaus2::gtf::Gene const & gene = Agene[i];
					char const * id = Aid.begin() + gene.chr_offset;
					assert ( Vchr[gene.chr_id] == id );
				}
			}

			static void clipQuotes(std::pair<char const *,char const *> & P)
			{
				if (
					P.second - P.first >= 2
					&&
					P.first[0] == '"'
					&&
					P.second[-1] == '"'
				)
				{
					++P.first;
					--P.second;
				}
			}

			static uint64_t copyString(libmaus2::autoarray::AutoArray<char> & A, std::pair<char const *,char const *> const & P)
			{
				std::ptrdiff_t const l = P.second-P.first;
				A.ensureSize(l);
				std::copy(P.first,P.second,A.begin());
				return l;
			}

			static uint64_t pushId(libmaus2::autoarray::AutoArray<char> & Aid, uint64_t & o, char const * a)
			{
				uint64_t const o_in = o;

				std::size_t const l = strlen(a);

				Aid.ensureSize(o+l+1);

				std::copy(a,a+l,Aid.begin()+o);
				Aid[o + l] = 0;

				o += (l+1);

				return o_in;
			}

			static uint64_t pushId(
				libmaus2::autoarray::AutoArray<char> & Aid,
				uint64_t & o_in,
				libmaus2::autoarray::AutoArray<char> const & A,
				uint64_t const A_o
			)
			{
				uint64_t const o_r = o_in;

				Aid.ensureSize(o_in+A_o+1);
				std::copy(A.begin(),A.begin()+A_o,Aid.begin() + o_in);
				Aid[o_in + A_o] = 0;
				o_in += (A_o + 1);
				return o_r;
			}

			static uint64_t parseNumber(std::pair<char const *,char const *> P)
			{
				uint64_t n = 0, nc = 0;
				char const * a = P.first;
				char const * e = P.second;

				while ( a != e && a[0] >= '0' && a[0] <= '9' )
				{
					n *= 10;
					n += (*(a++)) - '0';
					nc += 1;
				}

				if ( nc && a == e )
					return n;
				else
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] unable to parse number " << std::string(P.first,P.second) << std::endl;
					lme.finish();
					throw lme;
				}
			}

			static bool parseStrand(std::pair<char const *,char const *> P)
			{
				if ( P.second-P.first == 1 && P.first[0] == '+' )
					return true;
				else if ( P.second-P.first == 1 && P.first[0] == '-' )
					return false;
				else
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] unable to parse strand " << std::string(P.first,P.second) << std::endl;
					lme.finish();
					throw lme;
				}
			}

			static libmaus2::gtf::GTFData::unique_ptr_type obtain(std::string const & annofn, int const verbose)
			{
				libmaus2::gtf::GTFData::unique_ptr_type pGTF;

				if ( GTFData::validCacheExists(annofn) )
				{
					libmaus2::gtf::GTFData::unique_ptr_type tGTF(new GTFData);
					libmaus2::aio::InputStreamInstance ISI(libmaus2::gtf::GTFData::getCacheFileName(annofn));
					tGTF->deserialise(ISI);
					pGTF = std::move(tGTF);
					if ( verbose )
						std::cerr << "[V] obtained GTF data from cache" << std::endl;
				}
				else
				{
					GTFData::unique_ptr_type tGTF(new GTFData(annofn,verbose));
					pGTF = std::move(tGTF);
					pGTF->createCacheIf(annofn,verbose);
				}

				return pGTF;
			}

			static uint64_t parseInteger(std::string const & s)
			{
				std::istringstream istr(s);
				uint64_t u;
				istr >> u;

				if ( istr && istr.peek() == std::istream::traits_type::eof() )
				{
					return u;
				}
				else
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] unable to parse " << s << " as number" << std::endl;
					lme.finish();
					throw lme;
				}
			}

			/**
			 * get whether a transcript is coding
			 **/
			bool isCoding(libmaus2::gtf::Transcript const & T) const
			{
				// scan for CDS
				for ( uint64_t i = T.start; i < T.end; ++i )
				{
					libmaus2::gtf::Exon const & exon = Aexon[i];

					if ( exon.CDS_end - exon.CDS_start )
						return true;
				}

				return false;
			}

			/**
			 * get whether a transcript is on the forward (true) or rc strand (false)
			 **/
			bool getStrand(libmaus2::gtf::Transcript const & T) const
			{
				// scan for CDS
				for ( uint64_t i = T.start; i < T.end; ++i )
					return Aexon[i].strand;
				return true;
			}

			struct ExonPosSorterIncreasing
			{
				bool operator()(libmaus2::gtf::Exon const * A, libmaus2::gtf::Exon const * B) const
				{
					return A->getFrom() < B->getFrom();
				}
			};

			struct ExonPosSorterDecreasing
			{
				bool operator()(libmaus2::gtf::Exon const * A, libmaus2::gtf::Exon const * B) const
				{
					return A->getFrom() > B->getFrom();
				}
			};

			/**
			 * get coding sequence for a transcript
			 **/
			template<typename iterator>
			uint64_t getCDS(
				/* transcript reference */
				libmaus2::gtf::Transcript const & T,
				/* reference sequence, forward strand */
				iterator R,
				/* temp array for storing exons */
				libmaus2::autoarray::AutoArray<libmaus2::gtf::Exon const *> PE,
				/* output array for storing CDS */
				libmaus2::autoarray::AutoArray<char> & A,
				/* position vector */
				std::vector<std::size_t> * Vpos = nullptr
			) const
			{
				// scan for CDS
				uint64_t PE_o = 0;
				uint64_t CDS_p = 0;
				for ( uint64_t i = T.start; i < T.end; ++i )
				{
					libmaus2::gtf::Exon const & exon = Aexon[i];
					uint64_t const l_CDS = (exon.CDS_end - exon.CDS_start);

					if ( l_CDS )
					{
						PE.push(PE_o,&exon);
						CDS_p += l_CDS;
					}
				}
				bool const strand = PE_o ? PE[0]->strand : true;

				assert ( CDS_p );

				// sort by increasing or descreasing position depending on strand
				if ( strand )
					std::sort(
						PE.begin(),
						PE.begin()+PE_o,
						ExonPosSorterIncreasing()
					);
				else
					std::sort(
						PE.begin(),
						PE.begin()+PE_o,
						ExonPosSorterDecreasing()
					);

				// symbol vector
				uint64_t A_o = 0;

				// construct coding nucleotide sequence
				for ( uint64_t i = 0; i < PE_o; ++i )
				{
					libmaus2::gtf::Exon const & E = *(PE[i]);
					assert ( E.CDS_end - E.CDS_start > 0 );
					libmaus2::gtf::CDS const & C = ACDS[E.CDS_start];
					assert ( C.exonid == E.id );
					uint64_t const from = C.getFrom();
					uint64_t const to   = C.getTo();

					// plus strand
					if ( strand )
					{
						uint64_t const frame_add_front = (i == 0) ? C.frame : 0;
						uint64_t p = from + frame_add_front;
						char const * c_a = R + p;
						char const * c_e = R + to;

						for ( char const * c_c = c_a; c_c < c_e; ++c_c, ++p )
						{
							// normalise
							A.push(A_o,libmaus2::fastx::remapChar(libmaus2::fastx::mapChar(*c_c)));

							if ( Vpos )
								Vpos->push_back(c_c-R);
						}
					}
					// minus strand
					else
					{
						uint64_t const frame_sub_back  = (i == 0) ? C.frame : 0;
						uint64_t p = to - frame_sub_back;

						char const * c_a = R + from;
						char const * c_e = R + p;

						for ( char const * c_c = c_e; c_c > c_a; )
						{
							// normalise
							A.push(A_o,libmaus2::fastx::invertUnmapped(libmaus2::fastx::remapChar(libmaus2::fastx::mapChar(*(--c_c)))));

							if ( Vpos )
								Vpos->push_back(c_c-R);
						}
					}
				}

				return A_o;
			}

			static libmaus2::gtf::GTFData::unique_ptr_type bedPseudo(std::string const & bedfn)
			{
				libmaus2::gtf::GTFData::unique_ptr_type pGTF(new libmaus2::gtf::GTFData);

				struct BedLine
				{
					std::string chr;
					uint64_t start;
					uint64_t end;
					std::string id;

					BedLine() {}
					BedLine(
						std::string const & rchr,
						uint64_t const rstart,
						uint64_t const rend,
						std::string const & rid
					) : chr(rchr), start(rstart), end(rend), id(rid) {}

					bool operator<(BedLine const & B) const
					{
						if ( chr != B.chr )
							return chr < B.chr;
						else if ( start != B.start )
							return start < B.start;
						else
							return end > B.end;
					}
				};

				libmaus2::aio::InputStreamInstance::unique_ptr_type pISI(new libmaus2::aio::InputStreamInstance(bedfn));
				std::string line;
				std::vector<BedLine> Vbed;

				while ( std::getline(*pISI,line) )
					if ( line.size() )
					{
						std::deque<std::string> Vtok =
							libmaus2::util::stringFunctions::tokenize(line,std::string("\t"));
						if ( Vtok.size() >= 4 )
						{
							std::string const chr = Vtok[0];
							uint64_t const start = parseInteger(Vtok[1]);
							uint64_t const end = parseInteger(Vtok[2]);
							std::string const id = Vtok[3];

							Vbed.push_back(
								BedLine(chr,start,end,id)
							);
						}
					}

				std::sort(Vbed.begin(),Vbed.end());

				uint64_t low = 0;
				for ( uint64_t chr_id = 0; low < Vbed.size(); ++chr_id )
				{
					std::string const & chr = Vbed[low].chr;

					uint64_t high = low+1;
					while ( high < Vbed.size() && Vbed[high].chr == chr )
						++high;

					pGTF->Vchr.push_back(chr);

					Gene G;

					uint64_t const this_gene_id = pGTF->next_gene_id;

					G.chr_offset  = pushId(pGTF->Aid,pGTF->Aid_o,chr.c_str());
					G.id_offset   = pushId(pGTF->Aid,pGTF->Aid_o,chr.c_str());
					G.name_offset = pushId(pGTF->Aid,pGTF->Aid_o,chr.c_str());
					G.chr_id      = this_gene_id;
					G.start       = pGTF->next_transcript_id;
					G.end         = pGTF->next_transcript_id+1;
					G.cstart      = Vbed[low].start;
					G.cend        = Vbed[high-1].end;
					G.id          = this_gene_id;

					pGTF->Agene.push(pGTF->next_gene_id,G);

					Transcript T;

					uint64_t const this_transcript_id = pGTF->next_transcript_id;

					T.id_offset = pushId(pGTF->Aid,pGTF->Aid_o,chr.c_str());
					T.start = pGTF->next_exon_id;
					T.end = pGTF->next_exon_id + (high-low);
					T.strand = true;
					T.gene_id = this_gene_id;
					T.c_start = G.cstart;
					T.c_end = G.cend;
					T.id = this_transcript_id;

					pGTF->Atranscript.push(pGTF->next_transcript_id,T);

					for ( uint64_t i = low; i < high; ++i )
					{
						Exon E;

						uint64_t const this_exon_id = pGTF->next_exon_id;

						std::ostringstream idstr;
						idstr << chr << "_" << (i-low);
						std::string const sid = idstr.str();

						E.id = this_exon_id;
						E.id_offset = pushId(pGTF->Aid,pGTF->Aid_o,sid.c_str());
						E.start = Vbed[i].start;
						E.end = Vbed[i].end;
						E.strand = true;
						E.transcript_id = this_transcript_id;
						E.CDS_start = 0;
						E.CDS_end = 0;

						pGTF->Aexon.push(pGTF->next_exon_id,E);
					}


					low = high;
				}

				return pGTF;
			}
		};
	}
}
#endif
