#pragma once

#include "Common.h"
#include "System/SystemException.h"
#include <type_traits>
#include <utility>

#if defined(_WIN32)
#ifndef _INTERLOCKED_DECLARED_
#define _INTERLOCKED_DECLARED_
extern "C" long __cdecl _InterlockedIncrement(long volatile* Addend);
extern "C" long __cdecl _InterlockedDecrement(long volatile* Addend);
#pragma intrinsic(_InterlockedIncrement)
#pragma intrinsic(_InterlockedDecrement)
#endif
#endif

namespace DotNetDupe {
    namespace System {
        namespace Internal {
            inline long AtomicIncrement(volatile long* pLocation) {
#if defined(_WIN32)
                return _InterlockedIncrement(pLocation);
#else
                return __sync_add_and_fetch(pLocation, 1);
#endif
            }

            inline long AtomicDecrement(volatile long* pLocation) {
#if defined(_WIN32)
                return _InterlockedDecrement(pLocation);
#else
                return __sync_sub_and_fetch(pLocation, 1);
#endif
            }
        }

        // Helper trait to check if a type is complete at compile time
        template <typename T, typename = void>
        struct IsComplete : std::false_type {};

        template <typename T>
        struct IsComplete<T, std::void_t<decltype(sizeof(T))>> : std::true_type {};

        /**
         * @brief A unified Smart Pointer that supports both unique and shared ownership semantics.
         * 
         * Improvised Interface:
         * - SmartPointer<T> p;       -> Automatically allocates new T() (if T is not abstract). Unique ownership.
         * - SmartPointer<T> p(true); -> Automatically allocates new T() and enables Shared ownership.
         * - SmartPointer<T> p(ptr);  -> Takes ownership of an existing raw pointer. Unique ownership.
         */
        template <typename T>
        class SmartPointer {
            template <typename U>
            friend class SmartPointer;
        public:
            // --- Auto-Allocating Constructors ---

            /**
             * @brief Default constructor. 
             * For concrete types: Automatically allocates a new instance of T.
             * For abstract types: Initializes to nullptr.
             */
            SmartPointer() {
                if constexpr (IsComplete<T>::value) {
                    if constexpr (!std::is_abstract_v<T> && std::is_default_constructible_v<T>) {
                        m_pObject = new T();
                        m_pnRefCount = nullptr;
                    } else {
                        m_pObject = nullptr;
                        m_pnRefCount = nullptr;
                    }
                } else {
                    m_pObject = nullptr;
                    m_pnRefCount = nullptr;
                }
            }

            /**
             * @brief Constructor with ownership mode flag.
             * Automatically allocates a new instance of T.
             * @param bIsShared If true, enables reference counting (Shared mode).
             */
            explicit SmartPointer(bool bIsShared) {
                if constexpr (IsComplete<T>::value) {
                    if constexpr (!std::is_abstract_v<T> && std::is_default_constructible_v<T>) {
                        m_pObject = new T();
                        m_pnRefCount = bIsShared ? new long(1) : nullptr;
                    } else {
                        m_pObject = nullptr;
                        m_pnRefCount = nullptr;
                    }
                } else {
                    m_pObject = nullptr;
                    m_pnRefCount = nullptr;
                }
            }

            // --- Raw Pointer / Explicit Constructors ---

            /**
             * @brief Constructor for explicit raw pointer attachment.
             * @param pPtr The raw pointer to take ownership of.
             */
            explicit SmartPointer(T* pPtr) : m_pObject(pPtr), m_pnRefCount(nullptr) {}

            /**
             * @brief Constructor that specifies ownership mode for a raw pointer.
             * @param pPtr The raw pointer to take ownership of.
             * @param bIsShared If true, enables reference counting (Shared mode).
             */
            SmartPointer(T* pPtr, bool bIsShared)
                : m_pObject(pPtr),
                  m_pnRefCount((bIsShared && pPtr != nullptr) ? new long(1) : nullptr) {}

            /**
             * @brief Explicit null constructor.
             */
            SmartPointer(std::nullptr_t) : m_pObject(nullptr), m_pnRefCount(nullptr) {}

            /**
             * @brief Destructor. Cleans up the managed object based on ownership mode.
             */
            ~SmartPointer() {
                InternalCleanup();
            }

            // --- Copy Semantics ---

            /**
             * @brief Copy constructor. Only permitted if the source is in Shared mode.
             * @throw SystemException If the source pointer is in Unique mode.
             */
            SmartPointer(const SmartPointer& objOther) : m_pObject(nullptr), m_pnRefCount(nullptr) {
                if (objOther.m_pnRefCount == nullptr && objOther.m_pObject != nullptr) {
                    throw SystemException("Cannot copy a Unique SmartPointer. Use Move semantics or initialize as Shared.");
                }
                m_pObject = objOther.m_pObject;
                m_pnRefCount = objOther.m_pnRefCount;
                if (m_pnRefCount != nullptr) {
                    Internal::AtomicIncrement(m_pnRefCount);
                }
            }

            template <typename U>
            SmartPointer(const SmartPointer<U>& objOther) : m_pObject(objOther.m_pObject), m_pnRefCount(objOther.m_pnRefCount) {
                if (objOther.m_pnRefCount == nullptr && objOther.m_pObject != nullptr) {
                    throw SystemException("Cannot copy a Unique SmartPointer. Use Move semantics or initialize as Shared.");
                }
                m_pObject = objOther.m_pObject;
                m_pnRefCount = objOther.m_pnRefCount;
                if (m_pnRefCount != nullptr) {
                    Internal::AtomicIncrement(m_pnRefCount);
                }
            }

            /**
             * @brief Copy assignment operator. Only permitted if the source is in Shared mode.
             */
            SmartPointer& operator=(const SmartPointer& objOther) {
                if (this != &objOther) {
                    if (objOther.m_pnRefCount == nullptr && objOther.m_pObject != nullptr) {
                        throw SystemException("Cannot copy a Unique SmartPointer.");
                    }
                    InternalCleanup();
                    m_pObject = objOther.m_pObject;
                    m_pnRefCount = objOther.m_pnRefCount;
                    if (m_pnRefCount != nullptr) {
                        Internal::AtomicIncrement(m_pnRefCount);
                    }
                }
                return *this;
            }

            template <typename U>
            SmartPointer& operator=(const SmartPointer<U>& objOther) {
                if (objOther.m_pnRefCount == nullptr && objOther.m_pObject != nullptr) {
                    throw SystemException("Cannot copy a Unique SmartPointer.");
                }
                InternalCleanup();
                m_pObject = objOther.m_pObject;
                m_pnRefCount = objOther.m_pnRefCount;
                if (m_pnRefCount != nullptr) {
                    Internal::AtomicIncrement(m_pnRefCount);
                }
                return *this;
            }

            // --- Move Semantics ---

            /**
             * @brief Move constructor. Transfers ownership from the source.
             */
            SmartPointer(SmartPointer&& objOther) noexcept 
                : m_pObject(objOther.m_pObject), m_pnRefCount(objOther.m_pnRefCount) {
                objOther.m_pObject = nullptr;
                objOther.m_pnRefCount = nullptr;
            }

            template <typename U>
            SmartPointer(SmartPointer<U>&& objOther) noexcept 
                : m_pObject(objOther.m_pObject), m_pnRefCount(objOther.m_pnRefCount) {
                objOther.m_pObject = nullptr;
                objOther.m_pnRefCount = nullptr;
            }

            /**
             * @brief Move assignment operator. Transfers ownership from the source.
             */
            SmartPointer& operator=(SmartPointer&& objOther) noexcept {
                if (this != &objOther) {
                    InternalCleanup();
                    m_pObject = objOther.m_pObject;
                    m_pnRefCount = objOther.m_pnRefCount;
                    objOther.m_pObject = nullptr;
                    objOther.m_pnRefCount = nullptr;
                }
                return *this;
            }

            template <typename U>
            SmartPointer& operator=(SmartPointer<U>&& objOther) noexcept {
                if (this != reinterpret_cast<const SmartPointer<T>*>(&objOther)) {
                    InternalCleanup();
                    m_pObject = objOther.m_pObject;
                    m_pnRefCount = objOther.m_pnRefCount;
                    objOther.m_pObject = nullptr;
                    objOther.m_pnRefCount = nullptr;
                }
                return *this;
            }

            // --- Factory Methods ---

            /**
             * @brief Creates a Unique SmartPointer, default constructing T.
             */
            static SmartPointer<T> NewUnique() {
                return SmartPointer<T>(new T(), false);
            }

            /**
             * @brief Creates a Unique SmartPointer, forwarding arguments to T's constructor.
             */
            template <typename Arg1, typename... Args>
            static SmartPointer<T> NewUnique(Arg1&& arg1, Args&&... args) {
                return SmartPointer<T>(new T(std::forward<Arg1>(arg1), std::forward<Args>(args)...), false);
            }

            /**
             * @brief Creates a Shared SmartPointer, default constructing T.
             */
            static SmartPointer<T> NewShared() {
                return SmartPointer<T>(new T(), true);
            }

            /**
             * @brief Creates a Shared SmartPointer, forwarding arguments to T's constructor.
             */
            template <typename Arg1, typename... Args>
            static SmartPointer<T> NewShared(Arg1&& arg1, Args&&... args) {
                return SmartPointer<T>(new T(std::forward<Arg1>(arg1), std::forward<Args>(args)...), true);
            }

            // --- Static Factory Helpers (C#-like instantiation) ---

            /**
             * @brief Creates a new SmartPointer (default construction).
             */
            static SmartPointer<T> New() {
                return NewUnique();
            }

            /**
             * @brief Creates a new SmartPointer with variadic arguments for T's constructor.
             */
            template <typename Arg1, typename... Args>
            static SmartPointer<T> New(Arg1&& arg1, Args&&... args) {
                return NewUnique(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
            }

            // --- Conversion / Compatibility Aliases ---

            /**
             * @brief Alias for NewUnique (default construction).
             */
            static SmartPointer<T> MakeUnique() {
                return NewUnique();
            }

            /**
             * @brief Alias for NewUnique. Provided for compatibility.
             */
            template <typename Arg1, typename... Args>
            static SmartPointer<T> MakeUnique(Arg1&& arg1, Args&&... args) {
                return NewUnique(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
            }

            /**
             * @brief Alias for NewShared (default construction).
             */
            static SmartPointer<T> MakeShared() {
                return NewShared();
            }

            /**
             * @brief Alias for NewShared. Provided for compatibility.
             */
            template <typename Arg1, typename... Args>
            static SmartPointer<T> MakeShared(Arg1&& arg1, Args&&... args) {
                return NewShared(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
            }

            // --- API Methods ---

            /**
             * @brief Attaches a new raw pointer to the SmartPointer.
             * @param pPtr The new pointer to manage.
             * @param bIsShared Ownership mode for the new pointer.
             */
            void Attach(T* pPtr, bool bIsShared = false) {
                Reset(pPtr, bIsShared);
            }

            // --- Utility Methods ---

            /**
             * @brief Resets the SmartPointer to null or a new object in Unique mode.
             * @param pPtr Optional new raw pointer to manage.
             */
            void Reset(T* pPtr = nullptr) {
                InternalCleanup();
                m_pObject = pPtr;
                m_pnRefCount = nullptr;
            }

            /**
             * @brief Resets the SmartPointer with a specific ownership mode.
             * @param pPtr The raw pointer to manage.
             * @param bIsShared If true, enables reference counting.
             */
            void Reset(T* pPtr, bool bIsShared) {
                InternalCleanup();
                m_pObject = pPtr;
                m_pnRefCount = (bIsShared && pPtr != nullptr) ? new long(1) : nullptr;
            }

            /**
             * @brief Detaches the managed object and returns it.
             * The SmartPointer will no longer own the object.
             * @return The raw pointer to the object.
             */
            T* Detach() {
                T* pTemp = m_pObject;
                m_pnRefCount = nullptr;
                m_pObject = nullptr;
                return pTemp;
            }

            /**
             * @brief Gets the raw pointer.
             */
            T* Get() const { return m_pObject; }

            /**
             * @brief Checks if the SmartPointer is null.
             */
            bool IsNull() const { return m_pObject == nullptr; }

            /**
             * @brief Dynamically casts the managed pointer to another type U and returns a new SmartPointer<U> sharing ownership.
             */
            template <typename U>
            SmartPointer<U> DynamicCast() const {
                U* pCast = dynamic_cast<U*>(m_pObject);
                if (!pCast) return SmartPointer<U>(nullptr);
                
                SmartPointer<U> spRet(nullptr);
                spRet.m_pObject = pCast;
                spRet.m_pnRefCount = m_pnRefCount;
                if (m_pnRefCount != nullptr) {
                    Internal::AtomicIncrement(m_pnRefCount);
                }
                return spRet;
            }

            /**
             * @brief Gets the current reference count. Returns 0 for Unique or Null pointers.
             */
            int GetRefCount() const {
                return (m_pnRefCount != nullptr) ? static_cast<int>(*m_pnRefCount) : 0;
            }

            // --- Operators ---

            T& operator*() const { return *m_pObject; }
            T* operator->() const { return m_pObject; }
            explicit operator bool() const { return m_pObject != nullptr; }

            template <typename U>
            bool operator==(const SmartPointer<U>& other) const {
                return m_pObject == other.Get();
            }

            template <typename U>
            bool operator!=(const SmartPointer<U>& other) const {
                return m_pObject != other.Get();
            }

        private:
            void InternalCleanup() {
                if (m_pnRefCount != nullptr) {
                    if (Internal::AtomicDecrement(m_pnRefCount) == 0) {
                        if (m_pObject != nullptr) delete m_pObject;
                        delete const_cast<long*>(m_pnRefCount);
                    }
                } else if (m_pObject != nullptr) {
                    delete m_pObject;
                }
                m_pObject = nullptr;
                m_pnRefCount = nullptr;
            }

            T* m_pObject;
            volatile long* m_pnRefCount;
        };
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)