/* Copyright 2015, Michele Santullo
 * This file is part of DoorKeeper.
 *
 * DoorKeeper 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.
 *
 * DoorKeeper 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 DoorKeeper.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace dk {
#if defined(USE_MANUAL_TYPENAMES)
	template <typename T>
	inline constexpr const char* manual_type_name() {
		static_assert(sizeof(T) == 0, "define an overload returning a const char* name for your type T");
		return nullptr;
	}
#endif

	template <typename T>
#if defined(IS_PRETTY_FUNC_CONSTEXPR) || defined(USE_MANUAL_TYPENAMES)
	constexpr
#endif
	inline HashType type_name_hash() {
#if defined(IS_PRETTY_FUNC_CONSTEXPR) || defined(USE_MANUAL_TYPENAMES)
		//static_assert(dk::tiger("a", 1, 0x01).a == 0xABF87E2EEFFBBE77ULL, "Bug in tiger hash");
		//static_assert(dk::tiger("message digest", 14, 0x01).a == 0x951A2078CBF881D9ULL, "Bug in tiger hash");

		// set padding to 0x80 for tiger V2
		return dk::tiger(type_name<T>().data(), type_name<T>().size(), 0x01);
#else
		const auto retstring = type_name<T>();
		return implem::hash_string(retstring.data(), retstring.size());
#endif
	}

	template <typename T>
#if defined(IS_PRETTY_FUNC_CONSTEXPR) || defined(USE_MANUAL_TYPENAMES)
	constexpr inline sprout::string<implem::type_name_len<T>()> type_name()
#else
	inline std::string type_name()
#endif
	{
#if defined(__GNUC__)
#	if defined(IS_PRETTY_FUNC_CONSTEXPR) || defined(USE_MANUAL_TYPENAMES)
		typedef sprout::string<implem::type_name_info<T>().len> sproutstring;
		typedef sprout::string<implem::type_name_len<T>()> sproutstringret;

		return sproutstringret(
			implem::type_name_info<T>().str + sproutstring(implem::type_name_info<T>().str).find(
				'=',
				sproutstring(implem::type_name_info<T>().str).find('[')
			) + 2,
			implem::type_name_len<T>() - 1
		);
#	else
		std::string pf(__PRETTY_FUNCTION__);
		const auto read_start = pf.find('=', pf.find('[') + 1) + 2;
#		if defined(__clang__)
		const std::size_t typename_end = pf.size() - 1;
#		else
		const auto typename_end = pf.find(';', read_start);
#endif
		return pf.substr(
			read_start,
			typename_end - read_start
		);
#	endif
#else
#	error "not implemented"
		const char* const pf = __PRETTY_FUNCTION__;
		const std::size_t len = sizeof(__PRETTY_FUNCTION__) - 1;
#endif
	}

	namespace implem {
#if defined(IS_PRETTY_FUNC_CONSTEXPR) || defined(USE_MANUAL_TYPENAMES)
		template <typename T>
		constexpr type_name_info<T>::type_name_info() :
#	if defined(__GNUC__)
#		if defined(USE_MANUAL_TYPENAMES)
			len(sprout::strlen(manual_type_name<T>())),
			str(manual_type_name<T>())
#		else
			len(sizeof(__PRETTY_FUNCTION__)),
			str(__PRETTY_FUNCTION__)
#		endif
#	else
#		error "not implemented"
#	endif
		{
		}

		template <typename T>
		constexpr std::size_t type_name_len() {
			typedef sprout::string<implem::type_name_info<T>().len> sproutstring;

#	if defined(__GNUC__)
			return type_name_info<T>().len - sproutstring(
				implem::type_name_info<T>().str).find('=', sproutstring(implem::type_name_info<T>().str).find('[')) - 3;
#	else
#		error "not implemented"
#	endif
		}
#endif
	} //namespace implem
} //namespace dk
