/*
 * ptr_vector.hpp
 *
 * Created on: Feb 28, 2014
 * Author: Jan Travnicek
 */

#ifndef __PTR_VECTOR_HPP_
#define __PTR_VECTOR_HPP_

#include <vector>
#include <ostream>
#include <sstream>
#include <string>

#include "compare.hpp"
#include "allocFix.hpp"
#include "iterator.hpp"
#include "clone.hpp"

namespace ext {

template < class T >
class ptr_vector {
	std::vector < T * > m_data;

public:

	using value_type = T;

	using size_type = std::size_t;
	using diference_type = std::ptrdiff_t;

	using reference = value_type &;
	using const_reference = const value_type &;
	using pointer = T *;
	using const_pointer = T * const;

	using iterator = dereferencing_iterator < typename std::vector < T * >::iterator >;
	using const_iterator = dereferencing_iterator < typename std::vector < T * >::const_iterator >;

	using reverse_iterator = dereferencing_iterator < typename std::vector < T * >::reverse_iterator >;
	using const_reverse_iterator = dereferencing_iterator < typename std::vector < T * >::const_reverse_iterator >;

	ptr_vector ( ) noexcept {
	}

	template < class R >
	ptr_vector ( std::initializer_list < R > init ) {
		insert ( cbegin ( ), std::move ( init ) );
	}

	ptr_vector ( std::initializer_list < T * > init ) : m_data ( init ) {
	}

	explicit ptr_vector ( size_type count ) {
		resize ( count );
	}

	template < class InputIt >
	ptr_vector ( InputIt first, InputIt last ) {
		insert ( cbegin ( ), first, last );
	}

	ptr_vector ( size_type count, const T& value ) {
		insert ( cbegin ( ), count, value );
	}

	ptr_vector ( const ptr_vector& other ) {
		insert ( cbegin ( ), other.begin ( ), other.end ( ) );
	}

	ptr_vector ( ptr_vector && other ) noexcept {
		std::swap ( m_data, other.m_data );
	}

	~ptr_vector ( ) noexcept {
		clear ( );
	}

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

		clear ( );
		insert ( cbegin ( ), other.begin ( ), other.end ( ) );

		return *this;
	}

	ptr_vector < T > & operator = ( ptr_vector < T > && other ) noexcept {
		std::swap ( m_data, other.m_data );

		return *this;
	}

	void assign ( size_type count, const T& value ) {
		clear ( );
		insert ( cbegin ( ), count, value );
	}

	template< class InputIt >
	void assign ( InputIt first, InputIt last ) {
		clear ( );
		insert ( cbegin ( ), first, last );
	}

	template < class R >
	void assign ( std::initializer_list < R > ilist ) {
		clear ( );
		insert ( cbegin ( ), std::move ( ilist ) );
	}

	reference at ( size_type index ) {
		return * m_data.at ( index );
	}

	const_reference at ( size_type index ) const {
		return * m_data.at ( index );
	}

	reference operator [ ] ( size_type index ) {
		return * m_data [ index ];
	}

	const_reference operator [ ] ( size_type index ) const {
		return * m_data [ index ];
	}

	reference front ( ) {
		return * m_data.front ( );
	}

	const_reference front ( ) const {
		return * m_data.front ( );
	}

	reference back ( ) {
		return * m_data.back ( );
	}

	const_reference back ( ) const {
		return * m_data.back ( );
	}

	bool null ( size_type index ) const {
		return m_data.at ( index ) == NULL;
	}

	/* T * const * data ( ) noexcept {
		return m_data.data ( );
	}

	T const * const * data ( ) const noexcept {
		return m_data.data ( );
	} */

	iterator begin ( ) noexcept {
		return dereferencer ( m_data.begin ( ) );
	}

	const_iterator begin ( ) const noexcept {
		return dereferencer ( m_data.begin ( ) );
	}

	const_iterator cbegin ( ) const noexcept {
		return dereferencer ( m_data.cbegin ( ) );
	}

	iterator end ( ) noexcept {
		return dereferencer ( m_data.end ( ) );
	}

	const_iterator end ( ) const noexcept {
		return dereferencer ( m_data.end ( ) );
	}

	const_iterator cend ( ) const noexcept {
		return dereferencer ( m_data.cend ( ) );
	}

	reverse_iterator rbegin ( ) noexcept {
		return dereferencer ( m_data.rbegin ( ) );
	}

	const_reverse_iterator rbegin ( ) const noexcept {
		return dereferencer ( m_data.rbegin ( ) );
	}

	const_reverse_iterator crbegin ( ) const noexcept {
		return dereferencer ( m_data.crbegin ( ) );
	}

	reverse_iterator rend ( ) noexcept {
		return dereferencer ( m_data.rend ( ) );
	}

	const_reverse_iterator rend ( ) const noexcept {
		return dereferencer ( m_data.rend ( ) );
	}

	const_reverse_iterator crend ( ) const noexcept {
		return dereferencer ( m_data.crend ( ) );
	}

	bool empty ( ) const noexcept {
		return m_data.empty ( );
	}

	size_type size ( ) const noexcept {
		return m_data.size ( );
	}

	size_type max_size ( ) const noexcept {
		return m_data.max_size ( );
	}

	void reserve ( size_type new_cap ) {
		m_data.reserve ( new_cap );
	}

	size_type capacity ( ) const noexcept {
		return m_data.capacity ( );
	}

	void shrink_to_fit ( ) {
		m_data.shrink_to_fit ( );
	}

	void clear ( ) noexcept {
		for ( T * data : m_data ) {
			delete data;
		}
		m_data.clear ( );
	}

	template < class R >
	iterator set ( const_iterator pos, R && value ) {
		return set ( pos, ext::clone ( std::forward < R > ( value ) ) );
	}

	iterator set ( const_iterator pos, T * value ) {
		delete m_data.at ( std::distance ( cbegin ( ), pos ) );
		m_data.at ( std::distance ( cbegin ( ), pos ) ) = value;
		return dereferencer ( m_data.begin ( ) + std::distance ( cbegin ( ), pos ) );
	}

	template < class R, class ... Args >
	iterator emplace_set ( const_iterator pos, Args && ... args ) {
		return set ( pos, new R ( std::forward < R > ( args ) ... ) );
	}

	template < class R >
	iterator insert ( const_iterator pos, R && value ) {
		return dereferencer ( m_data.insert ( pos.base ( ), ext::clone ( std::forward < R > ( value ) ) ) );
	}

	template < class R >
	iterator insert ( const_iterator pos, size_type count, const R & value ) {
		iterator res = dereferencer ( m_data.insert ( pos.base ( ), count, NULL ) );
		for ( size_type i = 0; i < count; ++ i ) {
			* ( res.base ( ) + i ) = ext::clone ( value );
		}
		return res;
	}

	template < class InputIt >
	iterator insert ( const_iterator pos, InputIt first, InputIt last ) {
		size_type size = std::distance ( first, last );
		iterator res = dereferencer ( m_data.insert ( pos.base ( ), size, NULL ) );

		for ( size_type i = 0; i < size; ++ first, ++ i )
			* ( res.base ( ) + i ) = ext::clone ( * first );

		return res;
	}

	template < class R >
	iterator insert ( const_iterator pos, std::initializer_list < R > ilist ) {
		return insert ( pos, ilist.begin ( ), ilist.end ( ) );
	}

	template < class R >
	iterator insert ( const_iterator pos, T * value ) {
		return m_data.insert ( pos.base ( ), value );
	}

	template < class R, class ... Args >
	iterator emplace ( const_iterator pos, Args && ... args ) {
		return dereferencer ( m_data.insert ( pos.base ( ), new R ( std::forward < Args > ( args ) ... ) ) );
	}

	iterator erase ( const_iterator pos ) {
		delete * pos.base ( );
		return dereferencer ( m_data.erase ( pos.base ( ) ) );
	}

	iterator erase ( const_iterator first, const_iterator last ) {
		for ( const_iterator first_copy = first; first_copy != last; ++ first_copy ) {
			delete * first_copy.base ( );
		}
		return dereferencer ( m_data.erase ( first.base ( ), last.base ( ) ) );
	}

	template < class R >
	void push_back ( R && value ) {
		insert ( cend ( ), std::forward < R > ( value ) );
	}

	template < class ... Args >
	reference emplace_back ( Args && ... args ) {
		return * emplace ( cend ( ), std::forward < Args > ( args ) ... );
	}

	void pop_back ( ) {
		T * value = m_data.back ( );
		delete value;
		m_data.pop_back ( );
	}

	void resize ( size_type count ) {
		for ( size_type i = count; i < m_data.size ( ); ++ i ) {
			delete m_data [ i ];
		}
		size_type i = m_data.size ( );
		m_data.resize ( count );
		for ( ; i < count; ++ i ) {
			m_data [ i ] = NULL;
		}
	}

	template < class R >
	void resize ( size_type count, const R & value ) {
		for ( size_type i = count; i < m_data.size ( ); ++ i ) {
			delete m_data [ i ];
		}
		size_type i = m_data.size ( );
		m_data.resize ( count );
		for ( ; i < count; ++ i ) {
			m_data [ i ] = ext::clone ( value );
		}
	}

	void swap ( ptr_vector & other ) {
		std::swap ( this->m_data, other.m_data );
	}

};

template< class T >
std::ostream& operator<<(std::ostream& out, const ext::ptr_vector < T > & ptr_vector) {
	out << "[";

	bool first = true;
	for(const T& item : ptr_vector) {
		if(!first) out << ", ";
		first = false;
		out << item;
	}

	out << "]";
	return out;
}

template<class T >
struct compare < ext::ptr_vector < T > > {
	int operator()(const ext::ptr_vector < T >& first, const ext::ptr_vector < T > & second) const {
		if(first.size() < second.size()) return -1;
		if(first.size() > second.size()) return 1;

		static compare<typename std::decay < T >::type > comp;
		for(auto iterF = first.begin(), iterS = second.begin(); iterF != first.end(); ++iterF, ++iterS) {
			int res = comp(*iterF, *iterS);
			if(res != 0) return res;
		}
		return 0;
	}
};

template < class T >
std::string to_string ( const ext::ptr_vector < T > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

template < class T >
bool operator == ( const ptr_vector < T > & first, const ptr_vector < T > & second ) {
	static compare < ptr_vector < T > > comp;
	return comp ( first, second ) == 0;
}

template < class T >
bool operator != ( const ptr_vector < T > & first, const ptr_vector < T > & second ) {
	return ! ( first == second );
}

template < class T >
bool operator < ( const ptr_vector < T > & first, const ptr_vector < T > & second ) {
	static compare < ptr_vector < T > > comp;
	return comp ( first, second ) < 0;
}

template < class T >
bool operator > ( const ptr_vector < T > & first, const ptr_vector < T > & second ) {
	return second < first;
}

template < class T >
bool operator <= ( const ptr_vector < T > & first, const ptr_vector < T > & second ) {
	return ! ( first > second );
}

template < class T >
bool operator >= ( const ptr_vector < T > & first, const ptr_vector < T > & second ) {
	return ! ( first < second );
}

} /* namespace ext */

#endif /* __PTR_VECTOR_HPP_ */