#include "pch.h"
#include "Extensions/DependencyInjection/ServiceProvider.h"
#include "System/Threading/Lock.h"
#include <unordered_map>
#include <vector>
#include <typeindex>

namespace DotNetDupe {
    namespace Extensions {
        namespace DependencyInjection {

            // Lightweight proxy class to safely wrap a ServiceProvider pointer
            // without ownership issues or double deletion.
            class ServiceProviderProxy : public DotNetDupe::System::IServiceProvider {
            public:
                ServiceProviderProxy(ServiceProvider* pProvider) : m_pProvider(pProvider) {}
                ~ServiceProviderProxy() override = default;

                DotNetDupe::System::SmartPointer<DotNetDupe::System::Object> GetService(const std::type_index& serviceType) override {
                    return m_pProvider->GetService(serviceType);
                }

            private:
                ServiceProvider* m_pProvider;
            };

            // --- ServiceProvider Implementation ---
            
            static DotNetDupe::System::SmartPointer<DotNetDupe::System::Object> InstantiateService(ServiceProvider* pSelf, const ServiceDescriptor& descriptor) {
                if (descriptor.GetInstance()) return descriptor.GetInstance();
                if (descriptor.GetFactory()) {
                    auto pProxy = DotNetDupe::System::SmartPointer<DotNetDupe::System::IServiceProvider>(
                        DotNetDupe::System::SmartPointer<ServiceProviderProxy>::NewShared(pSelf)
                    );
                    return descriptor.GetFactory()(pProxy);
                }
                return nullptr;
            }

            struct ServiceProvider::Impl {
                std::unordered_map<std::type_index, ServiceDescriptor> mapDescriptors;
                std::unordered_map<std::type_index, DotNetDupe::System::SmartPointer<DotNetDupe::System::Object>> pMapSingletons;
                std::unordered_map<std::type_index, DotNetDupe::System::SmartPointer<DotNetDupe::System::Object>> pMapScoped;
                std::vector<DotNetDupe::System::SmartPointer<DotNetDupe::System::IO::IDisposable>> pvDisposables;
                DotNetDupe::System::Threading::CriticalSection csLock;

                void Track(const DotNetDupe::System::SmartPointer<DotNetDupe::System::Object>& pInst) {
                    if (pInst.IsNull()) return;
                    auto pDisp = pInst.DynamicCast<DotNetDupe::System::IO::IDisposable>();
                    if (!pDisp.IsNull()) pvDisposables.push_back(pDisp);
                }

                DotNetDupe::System::SmartPointer<DotNetDupe::System::Object> ResolveSingleton(ServiceProvider* pSelf, const ServiceDescriptor& desc) {
                    DotNetDupe::System::Threading::CriticalSectionLock lock(csLock);
                    auto it = pMapSingletons.find(desc.GetServiceType());
                    if (it != pMapSingletons.end()) return it->second;
                    auto pInst = InstantiateService(pSelf, desc);
                    if (!pInst.IsNull()) {
                        pMapSingletons[desc.GetServiceType()] = pInst;
                        Track(pInst);
                    }
                    return pInst;
                }

                DotNetDupe::System::SmartPointer<DotNetDupe::System::Object> ResolveScoped(ServiceProvider* pSelf, const ServiceDescriptor& desc) {
                    DotNetDupe::System::Threading::CriticalSectionLock lock(csLock);
                    auto it = pMapScoped.find(desc.GetServiceType());
                    if (it != pMapScoped.end()) return it->second;
                    auto pInst = InstantiateService(pSelf, desc);
                    if (!pInst.IsNull()) {
                        pMapScoped[desc.GetServiceType()] = pInst;
                        Track(pInst);
                    }
                    return pInst;
                }

                DotNetDupe::System::SmartPointer<DotNetDupe::System::Object> ResolveTransient(ServiceProvider* pSelf, const ServiceDescriptor& desc) {
                    auto pInst = InstantiateService(pSelf, desc);
                    if (!pInst.IsNull()) {
                        DotNetDupe::System::Threading::CriticalSectionLock lock(csLock);
                        Track(pInst);
                    }
                    return pInst;
                }
            };

            ServiceProvider::ServiceProvider(const IServiceCollection& collection)
                : m_bIsRoot(true), m_pRootProvider(nullptr), m_pImpl(DotNetDupe::System::SmartPointer<Impl>::NewShared()) {
                for (int iIdx = 0; iIdx < collection.GetCount(); ++iIdx) {
                    const ServiceDescriptor& descriptor = collection[iIdx];
                    m_pImpl->mapDescriptors.insert({ descriptor.GetServiceType(), descriptor });
                }
            }

            ServiceProvider::ServiceProvider(ServiceProvider* pRootProvider)
                : m_bIsRoot(false), m_pRootProvider(pRootProvider), m_pImpl(DotNetDupe::System::SmartPointer<Impl>::NewShared()) {
            }

            ServiceProvider::~ServiceProvider() {
                Dispose();
            }

            DotNetDupe::System::SmartPointer<DotNetDupe::System::Object> ServiceProvider::GetService(const std::type_index& serviceType) {
                DotNetDupe::System::Threading::CriticalSectionLock lock(m_pImpl->csLock);

                if (serviceType == typeid(DotNetDupe::System::IServiceProvider)) {
                    return DotNetDupe::System::SmartPointer<DotNetDupe::System::Object>(
                        DotNetDupe::System::SmartPointer<ServiceProviderProxy>::NewShared(this)
                    );
                }
                if (serviceType == typeid(IServiceScopeFactory)) {
                    return DotNetDupe::System::SmartPointer<DotNetDupe::System::Object>(
                        DotNetDupe::System::SmartPointer<ServiceScopeFactory>::NewShared(this)
                    );
                }

                ServiceProvider* pRoot = m_bIsRoot ? this : m_pRootProvider;
                auto it = pRoot->m_pImpl->mapDescriptors.find(serviceType);
                if (it == pRoot->m_pImpl->mapDescriptors.end()) {
                    return nullptr;
                }

                return ResolveService(it->second);
            }

            DotNetDupe::System::SmartPointer<DotNetDupe::System::Object> ServiceProvider::ResolveService(const ServiceDescriptor& descriptor) {
                if (descriptor.GetLifetime() == ServiceLifetime::Singleton) {
                    ServiceProvider* pRoot = m_bIsRoot ? this : m_pRootProvider;
                    return pRoot->m_pImpl->ResolveSingleton(this, descriptor);
                }
                if (descriptor.GetLifetime() == ServiceLifetime::Scoped) {
                    if (m_bIsRoot) throw DotNetDupe::System::InvalidOperationException("Cannot resolve scoped service from root provider.");
                    return m_pImpl->ResolveScoped(this, descriptor);
                }
                return m_pImpl->ResolveTransient(this, descriptor);
            }

            void ServiceProvider::Dispose() {
                if (m_pImpl.IsNull()) return;
                DotNetDupe::System::Threading::CriticalSectionLock lock(m_pImpl->csLock);

                // Dispose in reverse order of registration
                for (auto it = m_pImpl->pvDisposables.rbegin(); it != m_pImpl->pvDisposables.rend(); ++it) {
                    if (!it->IsNull()) {
                        (*it)->Dispose();
                    }
                }
                m_pImpl->pvDisposables.clear();

                m_pImpl->pMapSingletons.clear();
                m_pImpl->pMapScoped.clear();
            }

            // --- ServiceScope Implementation ---

            ServiceScope::ServiceScope(DotNetDupe::System::SmartPointer<ServiceProvider> pProvider)
                : m_pProvider(std::move(pProvider)) {}

            ServiceScope::~ServiceScope() {
                Dispose();
            }

            DotNetDupe::System::SmartPointer<DotNetDupe::System::IServiceProvider> ServiceScope::GetServiceProvider() const {
                return m_pProvider;
            }

            void ServiceScope::Dispose() {
                if (!m_pProvider.IsNull()) {
                    m_pProvider->Dispose();
                    m_pProvider = nullptr;
                }
            }

            // --- ServiceScopeFactory Implementation ---

            ServiceScopeFactory::ServiceScopeFactory(ServiceProvider* pProvider)
                : m_pProvider(pProvider) {}

            DotNetDupe::System::SmartPointer<IServiceScope> ServiceScopeFactory::CreateScope() {
                auto spScopedProvider = DotNetDupe::System::SmartPointer<ServiceProvider>::NewShared(m_pProvider);
                auto spScope = DotNetDupe::System::SmartPointer<ServiceScope>::NewShared(std::move(spScopedProvider));
                return DotNetDupe::System::SmartPointer<IServiceScope>(spScope);
            }
        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)