// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.

#pragma once
#define HAVE_IRCD_JSON_TUPLE_H

#include "property.h"

namespace ircd::json
{
	struct tuple_base;

	template<class...>
	struct tuple;

	template<class>
	struct keys;
}

/// All tuple templates inherit from this non-template type for tagging.
struct ircd::json::tuple_base
{
	// EBO
};

/// A compile-time construct to describe a JSON object's members and types.
///
/// Member access by name is O(1) because of recursive constexpr function
/// inlining when translating a name to the index number which is then used
/// as the template argument to std::get() for the value.
///
/// Here we represent a JSON object with a named tuple, allowing the programmer
/// to create a structure specifying all of the potentially valid members of the
/// object. Thus at runtime, the tuple carries around its values like a
/// `struct`. Unlike a `struct`, the tuple is abstractly iterable and we have
/// implemented logic operating on all JSON tuples regardless of their makeup
/// without any effort from a developer when creating a new tuple.
///
/// The member structure for the tuple is called `property` because json::member
/// is already used to pair together runtime oriented json::values.
///
/// Create and use a tuple to efficiently extract members from a json::object.
/// The tuple will populate its own members during a single-pass iteration of
/// the JSON input.
///
/// But remember, the tuple carries very little information for you at runtime
/// which may make it difficult to represent all JS phenomena like "undefined"
/// and "null".
///
template<class... T>
struct ircd::json::tuple
:std::tuple<T...>
,tuple_base
{
	using tuple_type = std::tuple<T...>;
	using super_type = tuple<T...>;

	static constexpr size_t size() noexcept;

	operator json::value() const;
	operator crh::sha256::buf() const;

	/// For json::object constructions, the source JSON (string_view) is
	/// carried with the instance. This is important to convey additional
	/// keys not enumerated in the tuple. This will be default-initialized
	/// for other constructions when no source JSON buffer is available.
	json::object source;

	template<class name> constexpr decltype(auto) get(name&&) const noexcept;
	template<class name> constexpr decltype(auto) get(name&&) noexcept;
	template<class name> constexpr decltype(auto) at(name&&) const;
	template<class name> constexpr decltype(auto) at(name&&);

	template<class R, class name> R get(name&&, R def = {}) const noexcept;
	template<class R, class name> const R &at(name&&) const;
	template<class R, class name> R &at(name&&);

	template<class... U> explicit tuple(const tuple<U...> &);
	template<class U> explicit tuple(const json::object &, const json::keys<U> &);
	template<class U> explicit tuple(const tuple &, const json::keys<U> &);
	tuple(const json::object &);
	tuple(const json::iov &);
	tuple(const json::members &);
	tuple() = default;
};

namespace ircd {
namespace json {

template<class T>
constexpr bool
is_tuple()
{
	return std::is_base_of<tuple_base, T>::value;
}

template<class T,
         class R>
using enable_if_tuple = typename std::enable_if<is_tuple<T>(), R>::type;

template<class T,
         class test,
         class R>
using enable_if_tuple_and = typename std::enable_if<is_tuple<T>() && test(), R>::type;

template<class T>
using tuple_type = typename T::tuple_type;

template<class T>
using tuple_size = std::tuple_size<tuple_type<T>>;

template<class T,
         size_t i>
using tuple_element = typename std::tuple_element<i, tuple_type<T>>::type;

template<class T,
         size_t i>
using tuple_value_type = typename tuple_element<T, i>::value_type;

template<class T>
inline auto &
stdcast(const T &o)
{
	return static_cast<const typename T::tuple_type &>(o);
}

template<class T>
inline auto &
stdcast(T &o)
{
	return static_cast<typename T::tuple_type &>(o);
}

template<class T>
constexpr enable_if_tuple<T, size_t>
size()
{
	return tuple_size<T>::value;
}

/// Reference to the json::property wrapper of the key and value
template<size_t i,
         class tuple>
inline enable_if_tuple<tuple, tuple_element<tuple, i> &>
prop(tuple &t)
{
	return std::get<i>(t);
}

/// Reference to the json::property wrapper of the key and value
template<size_t i,
         class tuple>
inline enable_if_tuple<tuple, const tuple_element<tuple, i> &>
prop(const tuple &t)
{
	return std::get<i>(t);
}

} // namespace json
} // namespace ircd

#include "key.h"
#include "indexof.h"

namespace ircd {
namespace json {

/// Reference to the payload value directly at index
template<size_t i,
         class tuple>
inline enable_if_tuple<tuple, tuple_value_type<tuple, i> &>
val(tuple &t)
{
	return static_cast<tuple_value_type<tuple, i> &>(prop<i>(t));
}

/// Reference to the payload value directly at index
template<size_t i,
         class tuple>
inline enable_if_tuple<tuple, const tuple_value_type<tuple, i> &>
val(const tuple &t)
{
	return static_cast<const tuple_value_type<tuple, i> &>(prop<i>(t));
}

/// Convenience to determine if tuple has property by name.
template<class tuple>
inline bool
has_key(const string_view &key)
{
	return indexof<tuple>(key) < size<tuple>();
}

/// Convenience to determine if tuple has property by name.
template<class tuple>
inline bool
has_key(const tuple &t,
        const string_view &key)
{
	return has_key<tuple>(key);
}

} // namespace json
} // namespace ircd

#include "for_each.h"
#include "rfor_each.h"
#include "get.h"
#include "at.h"
#include "set.h"

namespace ircd {
namespace json {

template<class... T>
template<class U>
inline
tuple<T...>::tuple(const json::object &object,
                   const json::keys<U> &keys)
:source
{
	object
}
{
	for(const auto &[key, val] : object)
		if(keys.has(key))
			set(*this, key, val);
}

template<class... T>
inline
tuple<T...>::tuple(const json::object &object)
:source
{
	object
}
{
	for(const auto &[key, val] : object)
		if(has_key(*this, key))
			set(*this, key, val);
}

template<class... T>
inline
tuple<T...>::tuple(const json::iov &iov)
{
	for(const auto &[key, val] : iov)
		set(*this, key, val);
}

template<class... T>
inline
tuple<T...>::tuple(const json::members &members)
{
	for(const auto &[key, val] : members)
		set(*this, key, val);
}

template<class... T>
template<class U>
inline
tuple<T...>::tuple(const tuple &t,
                   const keys<U> &keys)
:source
{
	t.source
}
{
	for_each(t, [this, &keys]
	(const auto &key, const auto &val)
	{
		if(keys.has(key))
			set(*this, key, val);
	});
}

template<class... T>
template<class... U>
inline
tuple<T...>::tuple(const tuple<U...> &t)
:source
{
	t.source
}
{
	for_each(t, [this]
	(const auto &key, const auto &val)
	{
		if(has_key(*this, key))
			set(*this, key, val);
	});
}

template<class... T>
template<class name>
constexpr decltype(auto)
tuple<T...>::at(name&& n)
{
	constexpr const size_t hash
	{
		name_hash(n)
	};

	return json::at<hash>(*this);
}

template<class... T>
template<class name>
constexpr decltype(auto)
tuple<T...>::at(name&& n)
const
{
	constexpr const size_t hash
	{
		name_hash(n)
	};

	return json::at<hash>(*this);
}

template<class... T>
template<class name>
constexpr decltype(auto)
tuple<T...>::get(name&& n)
noexcept
{
	constexpr const size_t hash
	{
		name_hash(n)
	};

	return json::get<hash>(*this);
}

template<class... T>
template<class name>
constexpr decltype(auto)
tuple<T...>::get(name&& n)
const noexcept
{
	constexpr const size_t hash
	{
		name_hash(n)
	};

	return json::get<hash>(*this);
}

template<class... T>
template<class R,
         class name>
inline R
tuple<T...>::get(name&& n,
                 R ret)
const noexcept
{
	return json::get<R>(*this, n, ret);
}

template<class... T>
template<class R,
         class name>
inline const R &
tuple<T...>::at(name&& n)
const
{
	return json::at<R>(*this, n);
}

template<class... T>
template<class R,
         class name>
inline R &
tuple<T...>::at(name&& n)
{
	return json::at<R>(*this, n);
}

template<class... T>
constexpr size_t
tuple<T...>::size()
noexcept
{
	return std::tuple_size<tuple_type>();
}

} // namespace json
} // namespace ircd

#include "_key_transform.h"
#include "keys.h"
#include "_member_transform.h"
#include "tool.h"

template<class... T>
inline ircd::json::tuple<T...>::operator
crh::sha256::buf()
const
{
	//TODO: XXX
	const auto preimage
	{
		json::strung(*this)
	};

	return crh::sha256::buf
	{
		[&preimage](auto&& buf)
		{
			sha256{buf, const_buffer{preimage}};
		}
	};
}

template<class... T>
inline ircd::json::tuple<T...>::operator
json::value()
const
{
	json::value ret;
	ret.type = OBJECT;
	ret.create_string(serialized(*this), [this]
	(mutable_buffer buffer)
	{
		stringify(buffer, *this);
	});

	return ret;
}
