// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial

#pragma once

#include <cstddef>
#include <new>
#include <algorithm>
#include <optional>
#include <atomic>

#ifdef __APPLE__
#    include <AvailabilityMacros.h>
#endif

namespace vtable {

template<typename T>
struct VRefMut
{
    const T *vtable;
    void *instance;
};

struct Layout
{
    std::size_t size;
    std::size_t align;
};

// For the C++'s purpose, they are all the same
template<typename T>
using VRef = VRefMut<T>;

template<typename T>
using Pin = T;

template<typename T>
struct VBox
{
    const T *vtable = nullptr;
    void *instance = nullptr;
    VBox(const VBox &) = delete;
    VBox() = default;
    VBox &operator=(const VBox &) = delete;
    ~VBox()
    {
        if (vtable && instance) {
            vtable->drop({ vtable, instance });
        }
    }
};

struct AllowPin;

template<typename Base, typename T, typename Flag = void>
struct VOffset
{
    const T *vtable;
    std::uintptr_t offset;
};

template<typename VTable, typename X>
struct VRcInner
{
    template<typename VTable_, typename X_>
    friend class VRc;
    template<typename VTable_, typename X_>
    friend class VWeak;

private:
    VRcInner() : layout {} { }
    const VTable *vtable = &X::static_vtable;
    std::atomic<int> strong_ref = 1;
    std::atomic<int> weak_ref = 1;
    std::uint16_t data_offset = offsetof(VRcInner, data);
    union {
        X data;
        Layout layout;
    };

    void *data_ptr() { return reinterpret_cast<char *>(this) + data_offset; }
    ~VRcInner() = delete;
};

struct Dyn
{
};

template<typename VTable, typename X = Dyn>
class VRc
{
    VRcInner<VTable, X> *inner;
    VRc(VRcInner<VTable, X> *inner) : inner(inner) { }
    template<typename VTable_, typename X_>
    friend class VWeak;

public:
    ~VRc()
    {
        if (!--inner->strong_ref) {
            Layout layout = inner->vtable->drop_in_place({ inner->vtable, &inner->data });
            layout.size += inner->data_offset;
            layout.align = std::max<size_t>(layout.align, alignof(VRcInner<VTable, Dyn>));
            inner->layout = layout;
            if (!--inner->weak_ref) {
                inner->vtable->dealloc(inner->vtable, reinterpret_cast<uint8_t *>(inner), layout);
            }
        }
    }
    VRc(const VRc &other) : inner(other.inner) { inner->strong_ref++; }
    VRc &operator=(const VRc &other)
    {
        if (inner == other.inner)
            return *this;
        this->~VRc();
        new (this) VRc(other);
        return *this;
    }
    /// Construct a new VRc holding an X.
    ///
    /// The type X must have a static member `static_vtable` of type VTable
    template<typename... Args>
    static VRc make(Args... args)
    {
#if !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14
        auto mem = ::operator new(sizeof(VRcInner<VTable, X>),
                                  static_cast<std::align_val_t>(alignof(VRcInner<VTable, X>)));
#else
        auto mem = ::operator new(sizeof(VRcInner<VTable, X>));
#endif
        auto inner = new (mem) VRcInner<VTable, X>;
        new (&inner->data) X(args...);
        return VRc(inner);
    }

    const X *operator->() const
    {
        return &inner->data;
    }
    const X &operator*() const
    {
        return inner->data;
    }
    X *operator->()
    {
        return &inner->data;
    }
    X &operator*()
    {
        return inner->data;
    }

    const VRc<VTable, Dyn> &into_dyn() const
    {
        return *reinterpret_cast<const VRc<VTable, Dyn> *>(this);
    }

    VRef<VTable> borrow() const
    {
        return { inner->vtable, inner->data_ptr() };
    }

    friend bool operator==(const VRc &a, const VRc &b)
    {
        return a.inner == b.inner;
    }
    friend bool operator!=(const VRc &a, const VRc &b)
    {
        return a.inner != b.inner;
    }
    const VTable *vtable() const
    {
        return inner->vtable;
    }
};

template<typename VTable, typename X = Dyn>
class VWeak
{
    VRcInner<VTable, X> *inner = nullptr;

public:
    VWeak() = default;
    ~VWeak()
    {
        if (inner && !--inner->weak_ref) {
            inner->vtable->dealloc(inner->vtable, reinterpret_cast<uint8_t *>(inner),
                                   inner->layout);
        }
    }
    VWeak(const VWeak &other) : inner(other.inner) { inner && inner->weak_ref++; }
    VWeak(const VRc<VTable, X> &other) : inner(other.inner) { inner && inner->weak_ref++; }
    VWeak &operator=(const VWeak &other)
    {
        if (inner == other.inner)
            return *this;
        this->~VWeak();
        new (this) VWeak(other);
        return *this;
    }

    std::optional<VRc<VTable, X>> lock() const
    {
        if (!inner || inner->strong_ref == 0)
            return {};
        inner->strong_ref++;
        return { VRc<VTable, X>(inner) };
    }

    const VWeak<VTable, Dyn> &into_dyn() const
    {
        return *reinterpret_cast<const VWeak<VTable, Dyn> *>(this);
    }

    friend bool operator==(const VWeak &a, const VWeak &b) { return a.inner == b.inner; }
    friend bool operator!=(const VWeak &a, const VWeak &b) { return a.inner != b.inner; }
    const VTable *vtable() const { return inner ? inner->vtable : nullptr; }
};

} // namespace vtable
