/*
    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_UTIL_TABENTRY_HPP)
#define LIBMAUS2_UTIL_TABENTRY_HPP

#include <libmaus2/autoarray/AutoArray.hpp>

namespace libmaus2
{
	namespace util
	{
		template<char split_sym = '\t', char quote_sym = '"', char escape_sym = '\\'>
		struct TabEntry
		{
			typedef TabEntry<split_sym,quote_sym,escape_sym> this_type;
			typedef std::unique_ptr<this_type> unique_ptr_type;
			typedef std::shared_ptr<this_type> shared_ptr_type;

			typedef std::pair<uint64_t,uint64_t> P;
			libmaus2::autoarray::AutoArray<P>::shared_ptr_type PA;
			libmaus2::autoarray::AutoArray<P> * A;
			uint64_t n0;
			uint64_t n;

			static char getSplitSym()
			{
				return split_sym;
			}

			static char getQuoteSym()
			{
				return quote_sym;
			}

			static char getEscapeSym()
			{
				return escape_sym;
			}

			enum token { token_symbol, token_quote, token_tab, token_eof };

			static std::pair<token,char> getNextToken(char const * & a, char const * const e)
			{
				if ( a == e )
					return std::pair<token,char>(token_eof,0);
				if ( *a == escape_sym )
				{
					if ( a+1 != e )
					{
						char const sym = a[1];
						a += 2;
						return std::pair<token,char>(token_symbol,sym);
					}
					else
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] found incomplete escape sequence" << std::endl;
						lme.finish();
						throw lme;
					}
				}
				else if ( *a == quote_sym )
				{
					a += 1;
					return std::pair<token,char>(token_quote,0);
				}
				else if ( *a == split_sym )
				{
					a += 1;
					return std::pair<token,char>(token_tab,0);
				}
				else
				{
					char const sym = a[0];
					a += 1;
					return std::pair<token,char>(token_symbol,sym);
				}
			}

			TabEntry(uint64_t const rn0 = 0) : PA(new libmaus2::autoarray::AutoArray<P>), A(PA.get()), n0(rn0), n(n0)
			{
			}

			TabEntry(libmaus2::autoarray::AutoArray<P> * rA, uint64_t const rn0 = 0) : PA(), A(rA), n0(rn0), n(n0)
			{
			}

			uint64_t size() const
			{
				return n-n0;
			}

			P operator[](uint64_t const i) const
			{
				if ( i < size() )
					return (*A)[i+n0];
				else
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] TabEntry<>::operator[" <<i << "]: argument is out of range [" << 0 << "," << size() << ")" << std::endl;
					lme.finish();
					throw lme;
				}
			}

			std::pair<char const *,char const *> get(uint64_t const i, char const * a) const
			{
				P const LP = (*this)[i];
				return std::pair<char const *,char const *>(a+LP.first,a+LP.second);
			}

			std::pair<char const *,char const *> getUnquoted(uint64_t const i, char const * a) const
			{
				P LP = (*this)[i];

				if (
					(LP.second - LP.first >= 2)
					&&
					a[LP.first] == quote_sym
					&&
					a[LP.second-1] == quote_sym
				)
				{
					LP.first += 1;
					LP.second -= 1;
				}

				return std::pair<char const *,char const *>(a+LP.first,a+LP.second);
			}

			std::string getString(uint64_t const i, char const * a) const
			{
				std::pair<char const *,char const *> const P = get(i,a);
				return std::string(P.first,P.second);
			}

			std::string getUnquotedString(uint64_t const i, char const * a) const
			{
				std::pair<char const *,char const *> const P = getUnquoted(i,a);
				return std::string(P.first,P.second);
			}

			std::vector<std::string> getStrings(char const * a) const
			{
				std::vector<std::string> V(size());
				for ( uint64_t i = 0; i < size(); ++i )
					V[i] = getString(i,a);
				return V;
			}

			std::vector<std::string> getUnquotedStrings(char const * a) const
			{
				std::vector<std::string> V(size());
				for ( uint64_t i = 0; i < size(); ++i )
					V[i] = getUnquotedString(i,a);
				return V;
			}

			template<typename value_type>
			value_type getParsed(uint64_t const i, char const * a) const
			{
				if ( i < size() )
				{
					std::istringstream istr(getString(i,a));

					value_type v;

					istr >> v;

					if ( istr && istr.peek() == std::istream::traits_type::eof() )
						return v;
					else
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] TabEntry<>::getParsed: unable to parse " << getString(i,a) << std::endl;
						lme.finish();
						throw lme;
					}
				}
				else
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] TabEntry<>::getParsed[" <<i << "]: argument is out of range [" << 0 << "," << size() << ")" << std::endl;
					if ( size() )
					{
						lme.getStream() << "[E] "
							<< std::string(
								get(0,a).first,
								get(size()-1,a).second
							)
							<< std::endl;
					}
					lme.finish();
					throw lme;
				}
			}

			void print(std::ostream & out, char const * const a) const
			{
				for ( uint64_t i = n0; i < n; ++i )
				{
					out << "token[" << i-n0 << "]=";
					out.write(a+(*A)[i].first,(*A)[i].second-(*A)[i].first);
					out << '\n';
				}
			}

			void parse(char const * o, char const * const a, char const * const e, bool const reset = true)
			{
				if ( reset )
					n = n0;

				std::pair<token,char> T;
				char const * c = a;
				char const * s = c;

				while ( (T = getNextToken(c,e)).first != token_eof )
				{
					switch ( T.first )
					{
						case token_symbol:
						case token_eof:
						{
							break;
						}
						case token_tab:
						{
							A->push(n,P(s-o,(c-1)-o));
							s = c;
							break;
						}
						case token_quote:
						{
							while ( (T = getNextToken(c,e)).first != token_quote )
							{
								switch ( T.first )
								{
									case token_eof:
									{
										libmaus2::exception::LibMausException lme;
										lme.getStream() << "[E] found end of line within string" << std::endl;
										lme.finish();
										throw lme;
										break;
									}
									default:
										break;
								}
							}

							break;
						}
					}
				}

				if ( s != e )
				{
					A->push(n,P(s-o,e-o));
				}
			}
		};
	}
}
#endif
