/*************************************************************************
 *
 * Copyright 2016 Realm Inc.
 *
 * 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 REALM_IMPL_DESTROY_GUARD_HPP
#define REALM_IMPL_DESTROY_GUARD_HPP

#include <realm/util/features.h>
#include <realm/array.hpp>

namespace realm {
class Array;
namespace _impl {


/// Calls `ptr->destroy()` if the guarded pointer (`ptr`) is not null
/// when the guard is destroyed. For arrays (`T` = `Array`) this means
/// that the array is destroyed in a shallow fashion. See
/// `DeepArrayDestroyGuard` for an alternative.
template <class T>
class DestroyGuard {
public:
    DestroyGuard() noexcept;

    DestroyGuard(T*) noexcept;

    ~DestroyGuard() noexcept;

    // Default implementations of copy/assign can trigger multiple destructions
    DestroyGuard(const DestroyGuard&) = delete;
    DestroyGuard& operator=(const DestroyGuard&) = delete;

    void reset(T*) noexcept;

    T* get() const noexcept;

    T* release() noexcept;

private:
    T* m_ptr;
};

using ShallowArrayDestroyGuard = DestroyGuard<Array>;


/// Calls `ptr->destroy_deep()` if the guarded Array pointer (`ptr`)
/// is not null when the guard is destroyed.
class DeepArrayDestroyGuard {
public:
    DeepArrayDestroyGuard() noexcept;

    DeepArrayDestroyGuard(Array*) noexcept;

    ~DeepArrayDestroyGuard() noexcept;

    // Default implementations of copy/assign can trigger multiple destructions
    DeepArrayDestroyGuard(const DeepArrayDestroyGuard&) = delete;
    DeepArrayDestroyGuard& operator=(const DeepArrayDestroyGuard&) = delete;

    void reset(Array*) noexcept;

    Array* get() const noexcept;

    Array* release() noexcept;

private:
    Array* m_ptr;
};


/// Calls `Array::destroy_deep(ref, alloc)` if the guarded 'ref'
/// (`ref`) is not zero when the guard is destroyed.
class DeepArrayRefDestroyGuard {
public:
    DeepArrayRefDestroyGuard(Allocator&) noexcept;

    DeepArrayRefDestroyGuard(ref_type, Allocator&) noexcept;

    ~DeepArrayRefDestroyGuard() noexcept;

    // Default implementations of copy/assign can trigger multiple destructions
    DeepArrayRefDestroyGuard(const DeepArrayRefDestroyGuard&) = delete;
    DeepArrayRefDestroyGuard& operator=(const DeepArrayRefDestroyGuard&) = delete;

    void reset(ref_type) noexcept;

    ref_type get() const noexcept;

    ref_type release() noexcept;

private:
    ref_type m_ref;
    Allocator& m_alloc;
};


// Implementation:

// DestroyGuard<T>

template <class T>
inline DestroyGuard<T>::DestroyGuard() noexcept
    : m_ptr(nullptr)
{
}

template <class T>
inline DestroyGuard<T>::DestroyGuard(T* ptr) noexcept
    : m_ptr(ptr)
{
}

template <class T>
inline DestroyGuard<T>::~DestroyGuard() noexcept
{
    if (m_ptr)
        m_ptr->destroy();
}

template <class T>
inline void DestroyGuard<T>::reset(T* ptr) noexcept
{
    if (m_ptr)
        m_ptr->destroy();
    m_ptr = ptr;
}

template <class T>
inline T* DestroyGuard<T>::get() const noexcept
{
    return m_ptr;
}

template <class T>
inline T* DestroyGuard<T>::release() noexcept
{
    T* ptr = m_ptr;
    m_ptr = nullptr;
    return ptr;
}


// DeepArrayDestroyGuard

inline DeepArrayDestroyGuard::DeepArrayDestroyGuard() noexcept
    : m_ptr(nullptr)
{
}

inline DeepArrayDestroyGuard::DeepArrayDestroyGuard(Array* ptr) noexcept
    : m_ptr(ptr)
{
}

inline DeepArrayDestroyGuard::~DeepArrayDestroyGuard() noexcept
{
    if (m_ptr)
        m_ptr->destroy_deep();
}

inline void DeepArrayDestroyGuard::reset(Array* ptr) noexcept
{
    if (m_ptr)
        m_ptr->destroy_deep();
    m_ptr = ptr;
}

inline Array* DeepArrayDestroyGuard::get() const noexcept
{
    return m_ptr;
}

inline Array* DeepArrayDestroyGuard::release() noexcept
{
    Array* ptr = m_ptr;
    m_ptr = nullptr;
    return ptr;
}


// DeepArrayRefDestroyGuard

inline DeepArrayRefDestroyGuard::DeepArrayRefDestroyGuard(Allocator& alloc) noexcept
    : m_ref(0)
    , m_alloc(alloc)
{
}

inline DeepArrayRefDestroyGuard::DeepArrayRefDestroyGuard(ref_type ref, Allocator& alloc) noexcept
    : m_ref(ref)
    , m_alloc(alloc)
{
}

inline DeepArrayRefDestroyGuard::~DeepArrayRefDestroyGuard() noexcept
{
    if (m_ref)
        Array::destroy_deep(m_ref, m_alloc);
}

inline void DeepArrayRefDestroyGuard::reset(ref_type ref) noexcept
{
    if (m_ref)
        Array::destroy_deep(m_ref, m_alloc);
    m_ref = ref;
}

inline ref_type DeepArrayRefDestroyGuard::get() const noexcept
{
    return m_ref;
}

inline ref_type DeepArrayRefDestroyGuard::release() noexcept
{
    ref_type ref = m_ref;
    m_ref = 0;
    return ref;
}


} // namespace _impl
} // namespace realm

#endif // REALM_IMPL_DESTROY_GUARD_HPP
