#ifndef ASP_SHARED_PTR_H
#define ASP_SHARED_PTR_H
#include "AspClientDefine.h"
#include <iostream>
#include <memory>
#include <atomic>

namespace wuying {

namespace asp {

class AspRefCounted {

public:
    AspRefCounted() : ref_count_(0) {}

    void addRef() const {
        ref_count_.fetch_add(1, std::memory_order_relaxed);
    }

    void release() const {
        if (ref_count_.fetch_sub(1, std::memory_order_relaxed) == 1) {
            delete this;
        }
    }

protected:
    virtual ~AspRefCounted() = default;

private:
    mutable std::atomic<int> ref_count_;
};

template <typename T>
class shared_ptr {
public:
    explicit shared_ptr(T* ptr = nullptr) : ptr_(ptr) {
        if (ptr_) {
            ptr_->addRef();
        }
    }

    shared_ptr(const shared_ptr& other) : ptr_(other.ptr_) {
        if (ptr_) {
            ptr_->addRef();
        }
    }

    shared_ptr(shared_ptr&& other) noexcept : ptr_(other.ptr_) {
        other.ptr_ = nullptr;
    }

    template <typename U>
    shared_ptr(const shared_ptr<U>& other) : ptr_(dynamic_cast<T*>(other.get())) {
        static_assert(std::is_base_of<U, T>::value || std::is_base_of<T, U>::value,
                      "Template parameter U must be related to T by inheritance");
        if (!ptr_) {
            throw std::bad_cast();
        }
        if (ptr_) {
            ptr_->addRef();
        }
    }

    ~shared_ptr() {
        if (ptr_) {
            ptr_->release();
        }
    }

    shared_ptr& operator=(const shared_ptr& other) {
        if (this != &other) {
            reset(other.ptr_);
        }
        return *this;
    }

    shared_ptr& operator=(shared_ptr&& other) noexcept {
        if (this != &other) {
            reset();
            ptr_ = other.ptr_;
            other.ptr_ = nullptr;
        }
        return *this;
    }

    template <typename U>
    shared_ptr& operator=(const shared_ptr<U>& other) {
        static_assert(std::is_base_of<T, U>::value,
                      "Template parameter U must be derived from T");
        reset(other.get());
        return *this;
    }

    void reset(T* ptr = nullptr) {
        if (ptr_ != ptr) {
            if (ptr_) {
                ptr_->release();
            }
            ptr_ = ptr;
            if (ptr_) {
                ptr_->addRef();
            }
        }
    }

    T* get() const {
        return ptr_;
    }

    T& operator*() const {
        return *ptr_;
    }

    T* operator->() const {
        return ptr_;
    }

    bool operator==(const shared_ptr& other) const {
        return ptr_ == other.ptr_;
    }

    explicit operator bool() const {
        return ptr_ != nullptr;
    }

    void swap(T** pp) {
        T* p = ptr_;
        ptr_ = *pp;
        *pp = p;
    }

    void swap(shared_ptr<T>& other) { swap(&other.ptr_); }

private:
    T* ptr_;
};

}
}

#endif //ASP_SHARED_PTR_H