/*
    libmaus2
    Copyright (C) 2020 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_UTIL_SERIALISEHELPER_HPP)
#define LIBMAUS2_UTIL_SERIALISEHELPER_HPP

#include <libmaus2/exception/LibMausException.hpp>

namespace libmaus2
{
	namespace util
	{
		struct SerialiseHelper
		{
			template<typename number_type>
			static void writeNumber(std::ostream & out, number_type const v)
			{
				out.write(reinterpret_cast<char const *>(&v),sizeof(v));
				if ( ! out )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] SerialiseHelper::writeNumber: failed to write number" << std::endl;
					lme.finish();
					throw lme;
				}
			}

			static void writeString(std::ostream & out, std::string const & s)
			{
				uint64_t const n = s.size();
				writeNumber(out,n);
				out.write(s.c_str(),n+1);
			}

			static void writeStringVector(std::ostream & out, std::vector<std::string> const & V)
			{
				uint64_t const n = V.size();
				writeNumber(out,n);
				for ( uint64_t i = 0; i < n; ++i )
					writeString(out,V[i]);
			}

			template<typename number_type>
			static void readNumber(std::istream & in, number_type & v)
			{
				in.read(reinterpret_cast<char *>(&v),sizeof(v));
				if ( ! in || in.gcount() != sizeof(v) )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] SerialiseHelper::readNumber: failed to read number" << std::endl;
					lme.finish();
					throw lme;
				}
			}

			static void readString(std::istream & in, std::string & s)
			{
				uint64_t n;
				readNumber(in,n);
				s.resize(n);
				for ( uint64_t i = 0; i < n; ++i )
				{
					int c = in.get();
					if ( in && c != std::istream::traits_type::eof() )
						s[i] = c;
					else
					{
						libmaus2::exception::LibMausException lme;
						lme.getStream() << "[E] SerialiseHelper::readString: failed to read string" << std::endl;
						lme.finish();
						throw lme;
					}
				}

				int c = in.get();
				if ( ! in || c == std::istream::traits_type::eof() || c != 0 )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] SerialiseHelper::readString: failed to read string" << std::endl;
					lme.finish();
					throw lme;
				}
			}

			static void readStringVector(std::istream & in, std::vector<std::string> & V)
			{
				uint64_t n;
				readNumber(in,n);
				V.resize(n);
				for ( uint64_t i = 0; i < n; ++i )
					readString(in,V[i]);
			}

			template<typename number_type>
			static void getNumber(char const * & p, number_type & v)
			{
				v = *reinterpret_cast<number_type const *>(p);
				p += sizeof(number_type);
			}

			template<typename number_type>
			static number_type getNumber(char const * & p)
			{
				number_type v;
				getNumber(p,v);
				return v;
			}

			static void getStringPair(char const * & p, std::pair<char const *, char const *> & P)
			{
				uint64_t n;
				getNumber(p,n);
				P.first = p;
				p += n;
				P.second = p;
				p += 1;
			}

			static std::pair<char const *, char const *> getStringPair(char const * & p)
			{
				std::pair<char const *, char const *> P;
				getStringPair(p,P);
				return P;
			}

			static std::string getString(char const * & p)
			{
				std::pair<char const *, char const *> P;
				getStringPair(p,P);
				return std::string(P.first,P.second);
			}

			static std::vector<std::string> getStringVector(char const * & p)
			{
				uint64_t const n = getNumber<uint64_t>(p);
				std::vector<std::string> V(n);
				for ( uint64_t i = 0; i < n; ++i )
					V[i] = getString(p);
				return V;
			}

			static void test()
			{
				uint64_t n0 = 4;
				std::string s = "hello world";
				uint64_t n1 = 13;
				std::vector<std::string> VS({"hello","world"});

				std::ostringstream ostr;
				writeNumber(ostr,n0);
				writeString(ostr,s);
				writeNumber(ostr,n1);
				writeStringVector(ostr,VS);
				ostr.flush();

				std::istringstream istr(ostr.str());

				uint64_t in0, in1;
				std::string ins;
				std::vector<std::string> inVS;

				readNumber(istr,in0);
				readString(istr,ins);
				readNumber(istr,in1);
				readStringVector(istr,inVS);

				if ( in0 != n0 || ins != s || inVS != VS || in1 != n1 )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] SerialiseHelper::test: failed" << std::endl;
					lme.finish();
					throw lme;
				}

				std::string const sdata = ostr.str();
				char const * cdata = sdata.c_str();

				uint64_t cn0 = getNumber<uint64_t>(cdata);
				std::string cs = getString(cdata);
				uint64_t cn1 = getNumber<uint64_t>(cdata);
				std::vector<std::string> cVS = getStringVector(cdata);

				if ( cn0 != n0 || cs != s || cn1 != n1 || cVS != VS )
				{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "[E] SerialiseHelper::test: failed" << std::endl;
					lme.finish();
					throw lme;
				}
			}
		};
	}
}
#endif
