#ifndef _MEMORY_HPP__
#define _MEMORY_HPP__

namespace std {

template <typename T, typename Enable = void>
class cow_shared_ptr;

class cow_shared_ptr_base {
public:
	cow_shared_ptr_base() : m_UseCount(0) {}

private:
	int m_UseCount;

	template<typename T, typename Enable> friend class cow_shared_ptr;
};

template<class T>
class cow_shared_ptr<T, typename std::enable_if< std::is_base_of<cow_shared_ptr_base, T>::value >::type> {
public:
	cow_shared_ptr ( ) {
		attach ( NULL );
	}

	cow_shared_ptr ( T * data ) {
		if(data) data->m_UseCount = 0;
		attach ( data );
	}

	cow_shared_ptr ( const cow_shared_ptr<T> & other ) {
		attach ( other.m_Data );
	}

	cow_shared_ptr ( cow_shared_ptr<T> && other ) noexcept {
		m_Data = other.m_Data;
		other.m_Data = NULL;
	}

	~cow_shared_ptr ( ) noexcept {
		detach ( );
	}

	cow_shared_ptr<T> & operator = ( const cow_shared_ptr<T> & other ) {
		if(this == &other) return *this;

		detach ( );
		attach ( other.m_Data );

		return *this;
	}

	cow_shared_ptr<T> & operator = ( cow_shared_ptr<T> && other ) noexcept {
		swap(*this, other);
		return *this;
	}

	T * operator -> ( ) {
		make_unique( );
		return m_Data;
	}

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

	T & operator * ( ) {
		make_unique( );
		return *m_Data;
	}

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

	T * get () {
		make_unique( );
		return m_Data;
	}

	T const * get () const {
		return m_Data;
	}

	bool unique ( ) const {
		return m_Data == NULL || m_Data->m_UseCount == 1;
	}

	int getUseCount() const {
		if(m_Data == NULL) return 0;
		return m_Data->m_UseCount;
	}

private:
	void attach ( T * data ) {
		m_Data = data;
		if(m_Data) m_Data->m_UseCount++;
	}

	void detach ( ) {
		if(m_Data && --(m_Data->m_UseCount) <= 0) delete m_Data;
		m_Data = NULL;
	}

	void make_unique( ) {
		if(unique( )) return;

		T* tmp = m_Data;
		detach( );
		tmp = std::clone(tmp);
		tmp->m_UseCount = 0;
		attach(tmp);
	}

	T* m_Data;

	friend void swap ( cow_shared_ptr<T> & first, cow_shared_ptr<T> & second ) {
		T* tmp = first.m_Data;
		first.m_Data = second.m_Data;
		second.m_Data = tmp;
	}
};

template<class T>
class cow_shared_ptr<T, typename std::enable_if< ! std::is_base_of<cow_shared_ptr_base, T>::value >::type> {
	struct cow_shared_ptr_data {
		T * m_Data;
		int m_UseCount;
		cow_shared_ptr_data ( T * data ) : m_Data ( data ), m_UseCount( 0 ) { }
		~cow_shared_ptr_data ( ) {
			delete m_Data;
		}
	};

public:
	cow_shared_ptr ( ) {
		attach ( NULL );
	}

	cow_shared_ptr ( T * data ) {
		attach ( new cow_shared_ptr_data( data ) );
	}

	cow_shared_ptr ( const cow_shared_ptr<T> & other ) {
		attach ( other.m_Data );
	}

	cow_shared_ptr ( cow_shared_ptr<T> && other ) noexcept {
		m_Data = other.m_Data;
		other.m_Data = NULL;
	}

	~cow_shared_ptr ( ) noexcept {
		detach ( );
	}

	cow_shared_ptr<T> & operator = ( const cow_shared_ptr<T> & other ) {
		if(this == &other) return *this;

		detach ( );
		attach ( other.m_Data );

		return *this;
	}

	cow_shared_ptr<T> & operator = ( cow_shared_ptr<T> && other ) noexcept {
		swap(*this, other);
		return *this;
	}

	T * operator -> ( ) {
		make_unique( );
		return m_Data->m_Data;
	}

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

	T & operator * ( ) {
		make_unique( );
		return *(m_Data->m_Data);
	}

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

	T * get () {
		make_unique( );
		return m_Data->m_Data;
	}

	T const * get () const {
		return m_Data->m_Data;
	}

	bool unique ( ) const {
		return m_Data == NULL || m_Data->m_UseCount == 1;
	}

private:
	void attach ( typename cow_shared_ptr<T>::cow_shared_ptr_data * data ) {
		m_Data = data;
		if(m_Data) m_Data->m_UseCount++;
	}

	void detach ( ) {
		if(m_Data && --(m_Data->m_UseCount) <= 0) delete m_Data;
		m_Data = NULL;
	}

	void make_unique( ) {
		if(unique( )) return;

		typename cow_shared_ptr<T>::cow_shared_ptr_data* tmp = m_Data;
		detach( );
		attach(new cow_shared_ptr_data(std::clone(tmp->m_Data)));
	}

	cow_shared_ptr_data * m_Data;

	friend void swap ( cow_shared_ptr<T> & first, cow_shared_ptr<T> & second ) {
		typename cow_shared_ptr<T>::cow_shared_ptr_data* tmp = first.m_Data;
		first.m_Data = second.m_Data;
		second.m_Data = tmp;
	}
};

template<class T>
const cow_shared_ptr<T>& make_const(cow_shared_ptr<T>& ptr) {
	return static_cast<const cow_shared_ptr<T>&>(ptr);
}

template<class T>
const cow_shared_ptr<T>& make_const(const cow_shared_ptr<T>& ptr) {
	return ptr;
}

} /* namespace std */

#endif // _MEMORY_HPP__