// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * (c) 2015 Alexander Warg <alexander.warg@kernkonzept.com>,
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */
#pragma once

#include <cxx/type_traits>

namespace cxx {

template<typename T>
class Protected_base
{
private:
  struct Null_ptr;
  T *_p;

protected:
  Protected_base() = default;
  explicit Protected_base(T *v) : _p(v) {}

public:
  typedef T Value_type;

  bool valid() const { return _p != nullptr; }
  operator Null_ptr const * () const
  { return valid() ? reinterpret_cast<Null_ptr const *>(1)
                   : reinterpret_cast<Null_ptr const *>(0); }

  T *unsafe_ptr() const { return _p; }
};

/**
 * A protected pointer is a pointer that cannot be dereferenced directly.
 *
 * Protected pointers can be used whenever it is not safe to directly
 * dereference a pointer, but other type semantics of a pointer are
 * desirable.  When READ_OK is true the pointer can be dereferenced to
 * read-only versions of the object, allowing direct read access but
 * restricting write access.
 */
template<typename T, bool READ_OK = false>
struct Protected;

template<typename T>
struct Protected<T, false> : Protected_base<T>
{
  Protected() = default;
  Protected(T *v) : Protected_base<T>(v) {}

  template<typename X,
           typename = enable_if_t<is_convertible_v<X, T>>>
  Protected(X *v) : Protected_base<T>(v) {}

  template<typename X,
           typename = enable_if_t<is_convertible_v<X, T>>>
  Protected(Protected<X, false> const &v)
  : Protected_base<T>(v.unsafe_ptr())
  {}
};

template<typename T>
struct Protected<T, true> : Protected_base<T>
{
  Protected() = default;
  Protected(T *v) : Protected_base<T>(v) {}

  template<typename X,
           typename = enable_if_t<is_convertible_v<X, T>>>
  Protected(X *v) : Protected_base<T>(v) {}

  template<typename X,
           typename = enable_if_t<is_convertible_v<X, T>>>
  Protected(Protected<X, true> const &v)
  : Protected_base<T>(v.unsafe_ptr())
  {}

  T get() const { return *this->unsafe_ptr(); }
  T const *operator -> () const { return this->unsafe_ptr(); }
  T const &operator * () const { return *this->unsafe_ptr(); }
};

}
