/**
 * 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_AES128ENCRYPTKEY_HPP)
#define LIBSECRECY_AES128ENCRYPTKEY_HPP

#include <libsecrecy/RawKey.hpp>
#include <nettle/aes.h>

namespace libsecrecy
{
	struct AES128Meta { static constexpr char const * getPrefix() { return "AES128"; } };
	struct AES192Meta { static constexpr char const * getPrefix() { return "AES192"; } };
	struct AES256Meta { static constexpr char const * getPrefix() { return "AES256"; } };

	struct EncryptKeyBase
	{
		virtual ~EncryptKeyBase() {}
	};

	template<
		typename _meta_type,
		unsigned int _keysize,
		unsigned int _blocksize,
		typename _cipher_context_type,
		void _set_encrypt_key_func(_cipher_context_type *, uint8_t const *),
		void _encrypt_func(_cipher_context_type const *, ::std::size_t, uint8_t *, uint8_t const *)
	>
	struct EncryptKey : public _meta_type, public EncryptKeyBase
	{
		typedef _meta_type meta_type;
		static unsigned int const keysize = _keysize;
		static std::size_t const blocksize = _blocksize;
		typedef _cipher_context_type cipher_context_type;
		static auto const constexpr set_encrypt_key_func = _set_encrypt_key_func;
		static auto const constexpr encrypt_func = _encrypt_func;

		typedef RawKey<_keysize> raw_key_type;
		typedef EncryptKey<meta_type,keysize,blocksize,cipher_context_type,set_encrypt_key_func,encrypt_func> this_type;

		private:
		std::shared_ptr<raw_key_type> kptr;
		LockedMemory<cipher_context_type> lctx;
		cipher_context_type * ctx;

		public:
		EncryptKey(Yarrow & Y) : kptr(new raw_key_type(Y)), lctx(1), ctx(lctx.get())
		{
			set_encrypt_key_func(ctx,kptr->get());
		}

		EncryptKey(std::shared_ptr<raw_key_type> rkptr) : kptr(rkptr), lctx(1), ctx(lctx.get())
		{
			set_encrypt_key_func(ctx,kptr->get());
		}

		EncryptKey(raw_key_type const & rawkey) : kptr(new raw_key_type(rawkey)), lctx(1), ctx(lctx.get())
		{
			set_encrypt_key_func(ctx,kptr->get());
		}

		EncryptKey(libsecrecy::GPGMEContext & context, std::istream & in) : kptr(new raw_key_type), lctx(1), ctx(lctx.get())
		{
			kptr->readKey(context,in);
			set_encrypt_key_func(ctx,kptr->get());
		}

		std::shared_ptr<raw_key_type> getRawKey() const
		{
			std::shared_ptr<raw_key_type> rk(new raw_key_type(*kptr));
			return rk;
		}

		std::shared_ptr<RawKeyBase> getRawKeyBase() const
		{
			std::shared_ptr<RawKeyBase> rk(new raw_key_type(*kptr));
			return rk;
		}

		bool operator==(this_type const & O) const
		{
			return *kptr == *(O.kptr);
		}

		bool operator!=(this_type const & O) const
		{
			return *kptr != *(O.kptr);
		}

		cipher_context_type * getContext()
		{
			return ctx;
		}

		static nettle_cipher_func * getCipherFunction()
		{
			return reinterpret_cast<nettle_cipher_func *>(encrypt_func);
		}

		void writeKey(libsecrecy::GPGMEContext & context, std::string const & keyname)
		{
			kptr->writeKey(context,getPrefix(),keyname);
		}

		/**
		 * construct hash key name using hex digest contained
		 **/
		std::string getKeyFileName() const
		{
			return kptr->getKeyHashFileName(getPrefix());
		}

		static std::string getKeyFileName(std::string const & hashhex)
		{
			return raw_key_type::getKeyFileNameCryptDigest(getPrefix(),hashhex);
		}

		std::string hashToHex() const
		{
			return kptr->hashToHex();
		}

		static std::string getPrefix()
		{
			return meta_type::getPrefix();
		}

		std::shared_ptr< LockedMemory<char> > keyToHex() const
		{
			return kptr->toHex();
		}
	};

	typedef EncryptKey<AES128Meta,AES128_KEY_SIZE,AES_BLOCK_SIZE,aes128_ctx,aes128_set_encrypt_key,aes128_encrypt> AES128EncryptKey;
	typedef EncryptKey<AES192Meta,AES192_KEY_SIZE,AES_BLOCK_SIZE,aes192_ctx,aes192_set_encrypt_key,aes192_encrypt> AES192EncryptKey;
	typedef EncryptKey<AES256Meta,AES256_KEY_SIZE,AES_BLOCK_SIZE,aes256_ctx,aes256_set_encrypt_key,aes256_encrypt> AES256EncryptKey;
}
#endif
