/*
 * memoru.hpp
 *
 * Created on: May 1, 2015
 * Author: Jan Travnicek
 */

#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:
	explicit cow_shared_ptr ( ) {
		attach ( NULL );
	}

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

		attach ( data );
	}

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

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

	~cow_shared_ptr ( ) noexcept {
		detach ( );
	}

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

		detach ( );
		attach ( other.m_Data );

		return * this;
	}

	cow_shared_ptr & operator =( cow_shared_ptr && 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;
	}

	explicit operator bool( ) const {
		return getUseCount ( ) == 0;
	}

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 & first, cow_shared_ptr & 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:
	explicit cow_shared_ptr ( ) {
		attach ( NULL );
	}

	explicit cow_shared_ptr ( T * data ) {
		attach ( data ? new cow_shared_ptr_data ( data ) : NULL );
	}

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

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

	~cow_shared_ptr ( ) noexcept {
		detach ( );
	}

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

		detach ( );
		attach ( other.m_Data );

		return * this;
	}

	cow_shared_ptr & operator =( cow_shared_ptr && 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;
	}

	int getUseCount ( ) const {
		if ( m_Data == NULL ) return 0;

		return m_Data->m_UseCount;
	}

	explicit operator bool( ) const {
		return getUseCount ( ) == 0;
	}

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 & first, cow_shared_ptr & 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 >
class smart_ptr {
	T * m_Data;

public:
	explicit smart_ptr ( ) : m_Data ( NULL ) {
	}

	explicit smart_ptr ( T * data ) : m_Data ( data ) {
	}

	template < class R >
	smart_ptr ( smart_ptr < R > other ) : m_Data ( other.release ( ) ) {
	}

	smart_ptr ( const smart_ptr & other ) : m_Data ( std::clone ( other.m_Data ) ) {
	}

	smart_ptr ( smart_ptr && other ) noexcept : m_Data ( other.release ( ) ) {
	}

	~smart_ptr ( ) noexcept {
		delete m_Data;
	}

	smart_ptr & operator =( const smart_ptr & other ) {
		if ( this == & other ) return * this;

		delete m_Data;
		m_Data = std::clone ( other.m_Data );

		return * this;
	}

	smart_ptr & operator =( smart_ptr && other ) noexcept {
		swap ( this->m_Data, other.m_Data );
		return * this;
	}

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

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

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

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

	T * get ( ) {
		return m_Data;
	}

	T * get ( ) const {
		return m_Data;
	}

	T * release ( ) {
		T * res = m_Data;

		m_Data = nullptr;
		return res;
	}

	explicit operator bool( ) const {
		return m_Data;
	}

};

template < class T >
std::ostream & operator <<( std::ostream & out, const std::cow_shared_ptr < T > & ptr ) {
	out << * ptr;
	return out;
}

template < class T >
std::ostream & operator <<( std::ostream & out, const std::shared_ptr < T > & ptr ) {
	out << * ptr;
	return out;
}

template < class T >
std::ostream & operator <<( std::ostream & out, const std::unique_ptr < T > & ptr ) {
	out << * ptr;
	return out;
}

template < class T >
std::ostream & operator <<( std::ostream & out, const std::smart_ptr < T > & ptr ) {
	out << * ptr;
	return out;
}

template < class T >
struct compare < cow_shared_ptr < T > > {
	int operator ()( const cow_shared_ptr < T > & first, const cow_shared_ptr < T > & second ) const {
		if ( first.get ( ) == second.get ( ) ) return 0;

		if ( !first ) return -1;

		if ( !second ) return 1;

		compare < typename std::decay < T >::type > comp;
		return comp ( * first, * second );
	}

};

template < class T >
struct compare < shared_ptr < T > > {
	int operator ()( const shared_ptr < T > & first, const shared_ptr < T > & second ) const {
		if ( first.get ( ) == second.get ( ) ) return 0;

		if ( !first ) return -1;

		if ( !second ) return 1;

		compare < typename std::decay < T >::type > comp;
		return comp ( * first, * second );
	}

};

template < class T >
struct compare < unique_ptr < T > > {
	int operator ()( const unique_ptr < T > & first, const unique_ptr < T > & second ) const {
		if ( first.get ( ) == second.get ( ) ) return 0;

		if ( !first ) return -1;

		if ( !second ) return 1;

		compare < typename std::decay < T >::type > comp;
		return comp ( * first, * second );
	}

};

template < class T >
struct compare < smart_ptr < T > > {
	int operator ()( const smart_ptr < T > & first, const smart_ptr < T > & second ) const {
		if ( first.get ( ) == second.get ( ) ) return 0;

		if ( !first ) return -1;

		if ( !second ) return 1;

		compare < typename std::decay < T >::type > comp;
		return comp ( * first, * second );
	}

};

 // TODO remove after switching to c++14
template < typename T, typename ... Args >
std::unique_ptr < T > make_unique ( Args && ... args ) {
	return std::unique_ptr < T > ( new T ( std::forward < Args > ( args ) ... ) );
}

template < typename T, typename ... Args >
std::smart_ptr < T > make_smart ( Args && ... args ) {
	return std::smart_ptr < T > ( new T ( std::forward < Args > ( args ) ... ) );
}

} /* namespace std */

#endif /* __MEMORY_HPP_ */