// Copyright (c) 2021 by Apex.AI Inc. All rights reserved.
//
// 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.
//
// SPDX-License-Identifier: Apache-2.0

#ifndef IOX_HOOFS_RELOCATABLE_POINTER_RELOCATABLE_PTR_HPP
#define IOX_HOOFS_RELOCATABLE_POINTER_RELOCATABLE_PTR_HPP

#include <cstdint>
#include <type_traits>

namespace iox
{
namespace rp
{
/// @brief Smart pointer type that allows objects using it to able to be copied by memcpy
///        without invalidating the pointer.
///        This applies only if it points to memory owned by the object itself
///        (i.e. not to memory outside of the object).
///        This is useful to improve copy-efficiency and allow the types build with relocatable
///        pointers only to be stored in shared memory.
///        It is useable like a raw pointer of the corresponding type and can be implicily
///        converted to one.
///
/// @tparam T the native type wrapped by the relocatable_ptr, i.e. relocatable_ptr<T>
///         has native type T and corresponds to a raw pointer of type T*.
///
/// @note It is advisable to use relocatable_ptr only for storage (e.g. member variables),
///       not to pass them around as function arguments or as return value.
///       There should be no need for this, since as pass-around type
///       regular pointers do the job just fine and do not incur
///       the slight runtime overhead of a relocatable_ptr.
///       There should be no memory overhead on 64 bit systems.
/// @note relocatable_ptr is not trivially copyable since in general the copy constructor
///       requires additional logic. Hence obects that contain it re not trivially
///       copyable in the C++ sense. However, if the pointees of a host object containing the
///       relocatable ptr are all located inside the object and the obect is otherwise trivially
///       copyable it can be safely copied by memcpy.
/// @todo specialize for another pointer class for this use case once it is fully defined/understood
template <typename T>
class relocatable_ptr
{
  public:
    using ptr_t = T*;

    /// @brief Construct from raw pointer.
    relocatable_ptr(T* ptr = nullptr) noexcept;

    /// @brief Construct from other relocatable pointer.
    relocatable_ptr(const relocatable_ptr& other) noexcept;

    /// @brief Move construct from other relocatable pointer.
    relocatable_ptr(relocatable_ptr&& other);

    /// @brief Assign from relocatable pointer rhs.
    relocatable_ptr& operator=(const relocatable_ptr& rhs) noexcept;

    /// @brief Move assign from relocatable pointer rhs.
    relocatable_ptr& operator=(relocatable_ptr&& rhs) noexcept;

    /// @brief Get the corresponding raw pointer.
    /// @return corresponding raw pointer
    T* get() noexcept;

    /// @brief Get the corresponding raw pointer from const relocatable_ptr
    /// @return corresponding raw pointer
    const T* get() const noexcept;

    /// @brief Dereference a relocatable_ptr.
    /// @return reference to the pointee
    /// @note not available for T=void
    template <typename S = T>
    S& operator*() noexcept;

    /// @brief Dereference a const relocatable_ptr.
    /// @return reference to the pointee
    /// @note not available for T=void
    template <typename S = T>
    const S& operator*() const noexcept;

    /// @brief Get the corresponding raw pointer with arrow operator syntax.
    /// @return corresponding raw pointer
    T* operator->() noexcept;

    /// @brief Get the corresponding raw pointer with arrow operator syntax
    ///        from a const relocatable_ptr.
    /// @return corresponding raw pointer
    const T* operator->() const noexcept;

    /// @brief Convert to the corresponding raw pointer.
    /// @return corresponding raw pointer
    operator T*() noexcept;

    /// @brief Convert to the corresponding const raw pointer.
    /// @return corresponding const raw pointer
    operator const T*() const noexcept;

  private:
    using offset_t = uint64_t;

    offset_t self() const noexcept;

    offset_t to_offset(const void* ptr) const noexcept;

    T* from_offset(offset_t offset) const noexcept;

  private:
    // This is safe since it is equivalent to point to the relocatable pointer
    // second byte of the relocatable pointer itself which we define to be illegal.
    // (it is no reasonable use-case)
    // Note that 0 is equivalent to point to the relocatable pointer itself.
    // This is needed if say the relocatable ptr is the first member in some class like
    // struct Foo {
    //   relocatable_ptr<Foo> rp;
    // }
    // Since Foo and rp have the same address (this), we need the 0 offset to allow
    // rp to point to Foo itself.
    static constexpr offset_t NULL_POINTER_OFFSET = 1;

    offset_t m_offset;
};

/// @brief Compare relocatable_ptr with respect to logical equality.
/// @return true if rhs and lhs point to the same location, false otherwise
template <typename T>
bool operator==(const relocatable_ptr<T>& lhs, const relocatable_ptr<T>& rhs) noexcept;

/// @brief Compare relocatable_ptr with respect to logical inequality.
/// @return true if rhs and lhs point to a different location, false otherwise
template <typename T>
bool operator!=(const relocatable_ptr<T>& lhs, const relocatable_ptr<T>& rhs) noexcept;

} // namespace rp
} // namespace iox

#include "iceoryx_hoofs/internal/relocatable_pointer/relocatable_ptr.inl"

#endif // IOX_HOOFS_RELOCATABLE_POINTER_RELOCATABLE_PTR_HPP