/*-
 * Copyright 2021 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef RSPAMD_LOCAL_SHARED_PTR_HXX
#define RSPAMD_LOCAL_SHARED_PTR_HXX

#pragma once

#include <memory>
#include <algorithm> // for std::swap
#include <cstddef>   // for std::size_t
#include <functional>// for std::less

/*
 * Smart pointers with no atomic refcounts to speed up Rspamd which is
 * apparently single threaded
 */
namespace rspamd {

namespace detail {

class ref_cnt {
public:
	using refcount_t = int;

	constexpr auto add_shared() -> refcount_t
	{
		return ++ref_shared;
	}
	constexpr auto add_weak() -> refcount_t
	{
		return ++ref_weak;
	}
	constexpr auto release_shared() -> refcount_t
	{
		return --ref_shared;
	}
	constexpr auto release_weak() -> refcount_t
	{
		return --ref_weak;
	}
	constexpr auto shared_count() const -> refcount_t
	{
		return ref_shared;
	}
	constexpr auto weak_count() const -> refcount_t
	{
		return ref_weak;
	}
	virtual ~ref_cnt()
	{
	}
	virtual void dispose() = 0;

private:
	refcount_t ref_weak = 0;
	refcount_t ref_shared = 1;
};

template<class T>
class obj_and_refcnt : public ref_cnt {
private:
	typedef typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type storage_type;
	storage_type storage;
	bool initialized;
	virtual void dispose() override
	{
		if (initialized) {
			T *p = reinterpret_cast<T *>(&storage);
			p->~T();
			initialized = false;
		}
	}

public:
	template<typename... Args>
	explicit obj_and_refcnt(Args &&...args)
		: initialized(true)
	{
		new (&storage) T(std::forward<Args>(args)...);
	}
	auto get(void) -> T *
	{
		if (initialized) {
			return reinterpret_cast<T *>(&storage);
		}

		return nullptr;
	}
	virtual ~obj_and_refcnt() = default;
};

template<class T, class D = typename std::default_delete<T>>
class ptr_and_refcnt : public ref_cnt {
private:
	T *ptr;
	D deleter;
	virtual void dispose() override
	{
		deleter(ptr);
		ptr = nullptr;
	}

public:
	explicit ptr_and_refcnt(T *_ptr, D &&d = std::default_delete<T>())
		: ptr(_ptr),
		  deleter(std::move(d))
	{
	}
	virtual ~ptr_and_refcnt() = default;
};

}// namespace detail

template<class T>
class local_weak_ptr;

template<class T>
class local_shared_ptr {
public:
	typedef T element_type;
	typedef local_weak_ptr<T> weak_type;

	// Simplified comparing to libc++, no custom deleter and no rebind here
	// constructors:
	constexpr local_shared_ptr() noexcept
		: px(nullptr), cnt(nullptr)
	{
	}

	template<class Y, typename std::enable_if<
						  std::is_convertible<Y *, element_type *>::value, bool>::type = true>
	explicit local_shared_ptr(Y *p)
		: px(p), cnt(new detail::ptr_and_refcnt(p))
	{
	}

	// custom deleter
	template<class Y, class D, typename std::enable_if<std::is_convertible<Y *, element_type *>::value, bool>::type = true>
	explicit local_shared_ptr(Y *p, D &&d)
		: px(p), cnt(new detail::ptr_and_refcnt<Y, D>(p, std::forward<D>(d)))
	{
	}

	local_shared_ptr(const local_shared_ptr &r) noexcept
		: px(r.px), cnt(r.cnt)
	{
		if (cnt) {
			cnt->add_shared();
		}
	}
	local_shared_ptr(local_shared_ptr &&r) noexcept
		: px(r.px), cnt(r.cnt)
	{
		r.px = nullptr;
		r.cnt = nullptr;
	}
	template<class Y>
	explicit local_shared_ptr(const local_weak_ptr<Y> &r)
		: px(r.px), cnt(r.cnt)
	{
		if (cnt) {
			cnt->add_shared();
		}
	}
	local_shared_ptr(std::nullptr_t)
		: local_shared_ptr()
	{
	}

	~local_shared_ptr()
	{
		if (cnt) {
			if (cnt->release_shared() <= 0) {
				cnt->dispose();

				if (cnt->weak_count() == 0) {
					delete cnt;
				}
			}
		}
	}

	// assignment:
	local_shared_ptr &operator=(const local_shared_ptr &r) noexcept
	{
		local_shared_ptr(r).swap(*this);
		return *this;
	}
	local_shared_ptr &operator=(local_shared_ptr &&r) noexcept
	{
		local_shared_ptr(std::move(r)).swap(*this);
		return *this;
	}

	// Mutators
	void swap(local_shared_ptr &r) noexcept
	{
		std::swap(this->cnt, r.cnt);
		std::swap(this->px, r.px);
	}
	void reset() noexcept
	{
		local_shared_ptr().swap(*this);
	}

	// Observers:
	T *get() const noexcept
	{
		return px;
	}

	T &operator*() const noexcept
	{
		return *px;
	}
	T *operator->() const noexcept
	{
		return px;
	}
	long use_count() const noexcept
	{
		if (cnt) {
			return cnt->shared_count();
		}

		return 0;
	}
	bool unique() const noexcept
	{
		return use_count() == 1;
	}

	explicit operator bool() const noexcept
	{
		return px != nullptr;
	}

	template<class Y, typename std::enable_if<
						  std::is_convertible<Y *, element_type *>::value, bool>::type = true>
	auto operator==(const local_shared_ptr<Y> &other) const -> bool
	{
		return px == other.px;
	}

	template<class Y, typename std::enable_if<
						  std::is_convertible<Y *, element_type *>::value, bool>::type = true>
	auto operator<(const local_shared_ptr<Y> &other) const -> auto
	{
		return *px < *other.px;
	}

private:
	T *px;// contained pointer
	detail::ref_cnt *cnt;

	template<class _T, class... Args>
	friend local_shared_ptr<_T> local_make_shared(Args &&...args);
	friend class local_weak_ptr<T>;
};

template<class T, class... Args>
local_shared_ptr<T> local_make_shared(Args &&...args)
{
	local_shared_ptr<T> ptr;
	auto tmp_object = new detail::obj_and_refcnt<T>(std::forward<Args>(args)...);
	ptr.px = tmp_object->get();
	ptr.cnt = tmp_object;

	return ptr;
}

template<class T>
class local_weak_ptr {
public:
	typedef T element_type;

	// constructors
	constexpr local_weak_ptr() noexcept
		: px(nullptr), cnt(nullptr)
	{
	}
	template<class Y, typename std::enable_if<
						  std::is_convertible<Y *, element_type *>::value, bool>::type = true>
	local_weak_ptr(local_shared_ptr<Y> const &r) noexcept
		: px(r.px), cnt(r.cnt)
	{
		if (cnt) {
			cnt->add_weak();
		}
	}

	local_weak_ptr(local_weak_ptr const &r) noexcept
		: px(r.px), cnt(r.cnt)
	{
		if (cnt) {
			cnt->add_weak();
		}
	}
	local_weak_ptr(local_weak_ptr &&r) noexcept
		: px(r.px), cnt(r.cnt)
	{
		r.px = nullptr;
		r.cnt = nullptr;
	}

	~local_weak_ptr()
	{
		if (cnt) {
			if (cnt->release_weak() <= 0 && cnt->shared_count() == 0) {
				delete cnt;
			}
		}
	}

	// assignment
	local_weak_ptr &operator=(local_weak_ptr const &r) noexcept
	{
		local_weak_ptr(r).swap(*this);
		return *this;
	}
	local_weak_ptr &operator=(local_shared_ptr<T> const &r) noexcept
	{
		local_weak_ptr(r).swap(*this);
		return *this;
	}

	template<class Y, typename std::enable_if<
						  std::is_convertible<Y *, element_type *>::value, bool>::type = true>
	local_weak_ptr &operator=(local_weak_ptr<Y> const &r) noexcept
	{
		local_weak_ptr(r).swap(*this);
		return *this;
	}
	local_weak_ptr &operator=(local_weak_ptr &&r) noexcept
	{
		local_weak_ptr(std::move(r)).swap(*this);
		return *this;
	}

	// modifiers
	void swap(local_weak_ptr &r) noexcept
	{
		std::swap(this->cnt, r.cnt);
		std::swap(this->px, r.px);
	}
	void reset() noexcept
	{
		local_weak_ptr().swap(*this);
	}

	// observers
	long use_count() const noexcept
	{
		if (cnt) {
			return cnt->shared_count();
		}
		return 0;
	}
	bool expired() const noexcept
	{
		if (cnt) {
			return cnt->shared_count() == 0;
		}

		return true;
	}

	local_shared_ptr<T> lock() const noexcept
	{
		local_shared_ptr<T> tmp;
		tmp.cnt = cnt;

		if (cnt) {
			cnt->add_shared();
			tmp.px = px;
		}

		return tmp;
	}

private:
	element_type *px;
	detail::ref_cnt *cnt;
};


}// namespace rspamd

/* Hashing stuff */
namespace std {
template<class T>
struct hash<rspamd::local_shared_ptr<T>> {
	inline auto operator()(const rspamd::local_shared_ptr<T> &p) const -> auto
	{
		if (!p) {
			throw std::logic_error("no hash for dangling pointer");
		}
		return hash<T>()(*p.get());
	}
};
template<class T>
struct hash<rspamd::local_weak_ptr<T>> {
	inline auto operator()(const rspamd::local_weak_ptr<T> &p) const -> auto
	{
		if (!p) {
			throw std::logic_error("no hash for dangling pointer");
		}
		return hash<T>()(*p.get());
	}
};

template<class T>
inline void swap(rspamd::local_shared_ptr<T> &x, rspamd::local_shared_ptr<T> &y) noexcept
{
	x.swap(y);
}

template<class T>
inline void swap(rspamd::local_weak_ptr<T> &x, rspamd::local_weak_ptr<T> &y) noexcept
{
	x.swap(y);
}

}// namespace std

#endif//RSPAMD_LOCAL_SHARED_PTR_HXX
