/**
 * Copyright 2020 German Tischler-Höhle
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **/
#if ! defined(LIBSECRECY_GCMSTREAMHEADER_HPP)
#define LIBSECRECY_GCMSTREAMHEADER_HPP

#include <libsecrecy/GCMStreamBase.hpp>
#include <libsecrecy/GCMIV.hpp>
#include <map>
#include <vector>

namespace libsecrecy
{
	struct GCMStreamHeader : public GCMStreamBase
	{
		std::map<std::string,std::string> M;

		static char const * getMagic()
		{
			return "LIBSECRECY";
		}

		GCMStreamHeader()
		{}

		GCMStreamHeader(std::istream & in)
		{
			char const * magic = getMagic();
			while ( *magic )
			{
				char const c = getChecked(in);
				char const e = *(magic++);
				if ( c != e )
				{
					throw std::runtime_error("libsecrecy::GCMStreamHeader: mismatch in file magic");
				}
			}

			std::size_t const numfields = readNumber(in);

			for ( std::size_t i = 0; i < numfields; ++i )
			{
				std::string const key = readString(in);
				std::string const value = readString(in);
				M[key] = value;
			}
		}

		std::string toString() const
		{
			std::ostringstream ostr;
			for ( auto const it : M )
				ostr << "M[" << it.first << "]=" << it.second << "\n";
			return ostr.str();
		}

		std::vector<uint8_t> decodeHexField(std::string const & key) const
		{
			auto const it = M.find(key);

			if ( it == M.end() )
				throw std::runtime_error(std::string("libsecrecy::GCMStreamHeader::decodeHexField: key ") + key + " is not present");

			std::string const shex = it->second;

			if ( shex.size() % 2 != 0 )
			{
				std::ostringstream ostr;
				ostr << "libsecrecy::GCMStreamHeader::decodeHexField: value " << shex << " for key " << key << " has uneven length";
				throw std::runtime_error(ostr.str());
			}

			std::vector<uint8_t> V;
			std::string::const_iterator sit = shex.begin();
			while ( sit != shex.end() )
			{
				char const chigh = *(sit++);
				assert ( sit != shex.end() );
				char const clow = *(sit++);
				V.push_back((NumToHex::hexToNum(chigh) << 4) | NumToHex::hexToNum(clow));
			}

			return V;
		}

		std::pair < std::shared_ptr<uint8_t[]>, std::size_t > decodeHexFieldAsArray(std::string const & key) const
		{
			std::vector<uint8_t> const V(decodeHexField(key));
			std::shared_ptr<uint8_t[]> ptr(new uint8_t[V.size()]);
			std::copy(V.begin(),V.end(),ptr.get());
			return std::make_pair(ptr,V.size());
		}

		std::pair < std::shared_ptr<uint8_t[]>, std::size_t > getAuthData() const
		{
			return decodeHexFieldAsArray("authdata");
		}

		std::shared_ptr<GCMIV> getIV() const
		{
			std::vector<uint8_t> const V(decodeHexField("IV"));
			std::shared_ptr<GCMIV> IV(new GCMIV);
			IV->set(V.begin(),V.end());
			return IV;
		}

		std::size_t serialise(std::ostream & out) const
		{
			std::size_t r = 0;

			// write magic
			char const * magic = getMagic();
			std::size_t const l_magic = strlen(magic);
			out.write(magic,l_magic);
			r += l_magic;

			// number of key=value pairs
			r += writeNumber(out,M.size());

			for ( auto it : M )
			{
				r += writeString(out,it.first);
				r += writeString(out,it.second);
			}

			if ( ! out )
			{
				throw std::runtime_error("libsecrecy::GCMStreamHeader::serialise: failed to write data");
			}

			return r;
		}

		std::size_t size() const
		{
			std::ostringstream ostr;
			std::size_t const r = serialise(ostr);

			if ( r != ostr.str().size() )
			{
				std::ostringstream eostr;
				eostr << "libsecrecy::GCMStreamHeader::size: mismatch between computed r=" << r << " and actual " << ostr.str().size();
				throw std::runtime_error(eostr.str());
			}

			return r;
		}

		void setStringValue(std::string const & key, std::string const & value)
		{
			M[key] = value;
		}

		template<typename value_type>
		void setNumericalValue(std::string const & key, value_type const & v)
		{
			std::ostringstream ostr;
			ostr << v;
			setStringValue(key,ostr.str());
		}

		std::string getStringValue(std::string const & key) const
		{
			auto const it = M.find(key);

			if ( it == M.end() )
			{
				std::ostringstream ostr;
				ostr << "libsecrecy::GCMStreamHeader::getStringValue: field " << key << " is not present";
				throw std::runtime_error(ostr.str());
			}

			return it->second;
		}

		template<typename value_type>
		value_type getParsedValue(std::string const & key) const
		{
			auto const it = M.find(key);

			if ( it == M.end() )
			{
				std::ostringstream ostr;
				ostr << "libsecrecy::GCMStreamHeader::getParsedValue: field " << key << " is not present";
				throw std::runtime_error(ostr.str());
			}

			std::istringstream istr(it->second);
			value_type v;
			istr >> v;

			if ( istr && istr.peek() == std::istream::traits_type::eof() )
			{
				return v;
			}
			else
			{
				std::ostringstream ostr;
				ostr << "libsecrecy::GCMStreamHeader::getParsedValue: field " << key << " has unparsable value " << it->second;
				throw std::runtime_error(ostr.str());
			}
		}
	};
}
#endif
