// Copyright (C) 2018 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include <memory>
#include <utility>

/**
 * @brief c++17 concept simulation
 */

template<class T>
class IPolymorhAllocator {
 public:
    virtual T *allocate(std::size_t n)  = 0;
    virtual void deallocate(T *p, std::size_t n)  = 0;
};

template<class T>
class allocator_polymorph;

template<class T>
class PolymorphAllocator {
    std::shared_ptr<IPolymorhAllocator<T>> _impl;
 public:
    explicit PolymorphAllocator(const std::shared_ptr<IPolymorhAllocator<T>> &impl) : _impl(impl) {}

    T *allocate(std::size_t n) {
        return _impl->allocate(n);
    }

    void deallocate(T *p, std::size_t n) {
        _impl->deallocate(p, n);
    }
};

/**
 * transform any allocator into polymorph type
 * @tparam origin
 */

template<class origin>
class polymorph_adapter : public IPolymorhAllocator<typename origin::value_type> {
    origin _impl;
    using T = typename origin::value_type;

 public:
    template<class ...Args>
    explicit polymorph_adapter(Args &&... args)
        :_impl(std::forward<Args>(args)...) {
    }
    T *allocate(std::size_t n) override {
        return _impl.allocate(n);
    }
    void deallocate(T *p, std::size_t n) override {
        _impl.deallocate(p, n);
    }
};

template<class T, class ...Args>
inline PolymorphAllocator<typename T::value_type> make_polymorph(Args &&... args) {
    auto sp = std::make_shared<polymorph_adapter<T>>(std::forward<Args>(args)...);
    auto ipoly = std::static_pointer_cast<IPolymorhAllocator<typename T::value_type>>(sp);

    return PolymorphAllocator<typename T::value_type>(ipoly);
}