/*
 * ptr_array.hpp
 *
 * This file is part of Algorithms library toolkit.
 * Copyright (C) 2017 Jan Travnicek (jan.travnicek@fit.cvut.cz)

 * Algorithms library toolkit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Algorithms library toolkit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Algorithms library toolkit.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Created on: Feb 28, 2014
 * Author: Jan Travnicek
 */

#ifndef __PTR_ARRAY_HPP_
#define __PTR_ARRAY_HPP_

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

#include "array.hpp"

#include <extensions/compare.hpp>
#include <extensions/range.hpp>
#include <extensions/clone.hpp>

namespace ext {

/**
 * \brief
 * Implementation of array storing dynamicaly allocated instances of given type. The class mimicks the iterface of the standard library array, but effectively allows polymorphic objects to be stored inside.
 *
 * \tparam T the type of stored values
 * \tparam N the size of the array
 */
template < class T, std::size_t N >
class ptr_array {
	/**
	 * \brief
	 * The array of pointers to stored values
	 */
	std::array < T *, N > m_data;

public:
	/**
	 * \brief
	 * The type of values.
	 */
	using value_type = T;

	/**
	 * \brief
	 * The type of sizes.
	 */
	using size_type = std::size_t;

	/**
	 * \brief
	 * The type of size differences.
	 */
	using diference_type = std::ptrdiff_t;

	/**
	 * \brief
	 * The type of reference to values.
	 */
	using reference = value_type &;

	/**
	 * \brief
	 * The type of reference to constant values.
	 */
	using const_reference = const value_type &;

	/**
	 * \brief
	 * The type of pointer to values.
	 */
	using pointer = T *;

	/**
	 * \brief
	 * The type of pointer to constant value.
	 */
	using const_pointer = const T *;

	/**
	 * \brief
	 * The type of values iterator.
	 */
	using iterator = dereferencing_iterator < typename std::array < T *, N >::iterator >;

	/**
	 * \brief
	 * The type of constant values iterator.
	 */
	using const_iterator = dereferencing_iterator < typename std::array < T *, N >::const_iterator >;

	/**
	 * \brief
	 * The type of reverse values iterator.
	 */
	using reverse_iterator = dereferencing_iterator < typename std::array < T *, N >::reverse_iterator >;

	/**
	 * \brief
	 * The type of constant reverse values iterator.
	 */
	using const_reverse_iterator = dereferencing_iterator < typename std::array < T *, N >::const_reverse_iterator >;

	/**
	 * \brief
	 * Constructor of the array of pointers from parameter values. The resulting array is of size of the parameter pack.
	 *
	 * \tparam Types ... the pack of source values type
	 *
	 * \param args ... the actual source parameters
	 */
	template < class ... Types >
	ptr_array ( Types && ... args ) : m_data ( make_array < T * > ( ext::clone ( std::forward < Types > ( args ) ) ... ) ) {
	}

	/**
	 * \brief
	 * The default constructor initializing the values of the array to the defaultly constructed value of T.
	 *
	 * \tparam R the actual value to default construct on every array index
	 */
	template < class R = T >
	ptr_array ( ) {
		if constexpr ( N != 0 )
			for ( size_type i = 0; i < N; ++ i )
				m_data.at ( i ) = new R ( );
	}

	/**
	 * \brief
	 * Copy constructor cloning all values in the source array.
	 *
	 * \param other the source array
	 */
	ptr_array ( const ptr_array & other ) {
		for ( size_type i = 0; i < N; ++ i )
			m_data.at ( i ) = ext::clone ( other.at ( i ) );
	}

	/**
	 * \brief
	 * Move constructor intended to transfer ownership of pointers from source to new instance.
	 *
	 * \param other the source array
	 */
	ptr_array ( ptr_array && other ) noexcept {
		for ( size_type i = 0; i < N; ++ i )
			m_data.at ( i ) = std::exchange ( other.m_data.at ( i ), nullptr );
	}

	/**
	 * \brief
	 * Destructor of the pointer array.
	 */
	~ptr_array ( ) noexcept {
		for ( size_type i = 0; i < N; ++ i )
			delete m_data.at ( i );
	}

	/**
	 * \brief
	 * Copy operator of assignment. The values of the source array are cloned to the new array.
	 *
	 * \param other the source instance
	 */
	ptr_array < T, N > & operator = ( const ptr_array < T, N > & other ) {
		if ( this == & other )
			return * this;

		for ( size_type i = 0; i < N; ++ i ) {
			delete m_data.at ( i );
			m_data.at ( i ) = ext::clone ( other.at ( i ) );
		}

		return *this;
	}

	/**
	 * \brief
	 * Move operator of assignment. The inner array of this and other instance are swapped.
	 *
	 * \param other the source instance
	 */
	ptr_array < T, N > & operator = ( ptr_array < T, N > && other ) noexcept {
		std::swap ( m_data, other.m_data );

		return *this;
	}

	/**
	 * \brief
	 * Fills the array with copies of \p value
	 *
	 * \param value the filling value
	 */
	template < class R >
	void fill ( const R & value ) {
		for ( size_type i = 0; i < N; ++ i )
			m_data.at ( i ) = ext::clone ( value );
	}

	/**
	 * \brief
	 * Getter of value on \p index.
	 *
	 * \param index the position to access
	 *
	 * \return reference to the value on given index
	 */
	reference at ( size_type index ) {
		return * m_data.at ( index );
	}

	/**
	 * \brief
	 * Getter of value on \p index.
	 *
	 * \param index the position to access
	 *
	 * \return reference to the value on given index
	 */
	const_reference at ( size_type index ) const {
		return * m_data.at ( index );
	}

	/**
	 * \brief
	 * Array subscript operator.
	 *
	 * \param the index to retrieve
	 *
	 * \return reference to the value on given index
	 */
	reference operator [ ] ( size_type index ) {
		return * m_data [ index ];
	}

	/**
	 * \brief
	 * Array subscript operator.
	 *
	 * \param the index to retrieve
	 *
	 * \return reference to the value on given index
	 */
	const_reference operator [ ] ( size_type index ) const {
		return * m_data [ index ];
	}

	/**
	 * \brief
	 * Getter of the first value in the array.
	 *
	 * \return reference to the first value in the array
	 */
	reference front ( ) {
		return * m_data.front ( );
	}

	/**
	 * \brief
	 * Getter of the first value in the array.
	 *
	 * \return reference to the first value in the array
	 */
	const_reference front ( ) const {
		return * m_data.front ( );
	}

	/**
	 * \brief
	 * Getter of the last value in the array.
	 *
	 * \return reference to the last value in the array
	 */
	reference back ( ) {
		return * m_data.back ( );
	}

	/**
	 * \brief
	 * Getter of the last value in the array.
	 *
	 * \return reference to the last value in the array
	 */
	const_reference back ( ) const {
		return * m_data.back ( );
	}

	/**
	 * \brief
	 * Test function whether pointer on \p index.
	 *
	 * \param index the tested index
	 *
	 * \return true if the pointer on \p index is null
	 */
	bool null ( size_type index ) const {
		return m_data.at ( index ) == nullptr;
	}

	/**
	 * \brief
	 * Iterator to the begining of the values range in the array.
	 *
	 * \return iterator to the begining
	 */
	iterator begin ( ) & noexcept {
		return dereferencer ( m_data.begin ( ) );
	}

	/**
	 * \brief
	 * Iterator to the begining of the values range in the array.
	 *
	 * \return iterator to the begining
	 */
	const_iterator begin ( ) const & noexcept {
		return dereferencer ( m_data.begin ( ) );
	}

	/**
	 * \brief
	 * Move iterator to the begining of the values range in the array.
	 *
	 * \return iterator to the begining
	 */
	auto begin ( ) && noexcept {
		return make_move_iterator ( this->end ( ) );
	}

	/**
	 * \brief
	 * Iterator to the begining of the values range in the array.
	 *
	 * \return iterator to the begining
	 */
	const_iterator cbegin ( ) const noexcept {
		return dereferencer ( m_data.cbegin ( ) );
	}

	/**
	 * \brief
	 * Iterator one past the last element of the values range in the array.
	 *
	 * \return iterator to the end
	 */
	iterator end ( ) & noexcept {
		return dereferencer ( m_data.end ( ) );
	}

	/**
	 * \brief
	 * Iterator one past the last element of the values range in the array.
	 *
	 * \return iterator to the end
	 */
	const_iterator end ( ) const & noexcept {
		return dereferencer ( m_data.end ( ) );
	}

	/**
	 * \brief
	 * Move iterator to the begining of the values range in the array.
	 *
	 * \return iterator to the begining
	 */
	auto end ( ) && noexcept {
		return make_move_iterator ( this->end ( ) );
	}

	/**
	 * \brief
	 * Iterator one past the last element of the values range in the array.
	 *
	 * \return iterator to the end
	 */
	const_iterator cend ( ) const noexcept {
		return dereferencer ( m_data.cend ( ) );
	}

	/**
	 * \brief
	 * Reverse iterator to the begining of the reversed range of values in the array.
	 *
	 * \return reverse iterator to the begining
	 */
	reverse_iterator rbegin ( ) noexcept {
		return dereferencer ( m_data.rbegin ( ) );
	}

	/**
	 * \brief
	 * Reverse iterator to the begining of the reversed range of values in the array.
	 *
	 * \return reverse iterator to the begining
	 */
	const_reverse_iterator rbegin ( ) const noexcept {
		return dereferencer ( m_data.rbegin ( ) );
	}

	/**
	 * \brief
	 * Reverse iterator to the begining of the reversed range of values in the array.
	 *
	 * \return reverse iterator to the begining
	 */
	const_reverse_iterator crbegin ( ) const noexcept {
		return dereferencer ( m_data.crbegin ( ) );
	}

	/**
	 * \brief
	 * Reverse iterator one past the last element of the reversed range of values in the array.
	 *
	 * \return reverse iterator to the end
	 */
	reverse_iterator rend ( ) noexcept {
		return dereferencer ( m_data.rend ( ) );
	}

	/**
	 * \brief
	 * Reverse iterator one past the last element of the reversed range of values in the array.
	 *
	 * \return reverse iterator to the end
	 */
	const_reverse_iterator rend ( ) const noexcept {
		return dereferencer ( m_data.rend ( ) );
	}

	/**
	 * \brief
	 * Reverse iterator one past the last element of the reversed range of values in the array.
	 *
	 * \return reverse iterator to the end
	 */
	const_reverse_iterator crend ( ) const noexcept {
		return dereferencer ( m_data.crend ( ) );
	}

	/**
	 * \brief
	 * Make range of non-const begin to end iterators.
	 *
	 * \return full range over container values
	 */
	auto range ( ) & {
		auto endIter = end ( );
		auto beginIter = begin ( );
		return ext::iterator_range < decltype ( endIter ) > ( beginIter, endIter );
	}

	/**
	 * \brief
	 * Make range of non-const begin to end iterators.
	 *
	 * \return full range over container values
	 */
	auto range ( ) const & {
		auto endIter = end ( );
		auto beginIter = begin ( );
		return ext::iterator_range < decltype ( endIter ) > ( beginIter, endIter );
	}

	/**
	 * \brief
	 * Make range of move begin to end iterators.
	 *
	 * \return full range over container values
	 */
	auto range ( ) && {
		auto endIter = std::move ( * this ).end ( );
		auto beginIter = std::move ( * this ).begin ( );
		return ext::iterator_range < decltype ( endIter ) > ( beginIter, endIter );
	}

	/**
	 * \brief
	 * Array emptines test.
	 *
	 * \return true it array is empty, false othervise
	 */
	bool empty ( ) const noexcept {
		return m_data.empty ( );
	}

	/**
	 * \brief
	 * Getter of the array size.
	 *
	 * \return the size of the array
	 */
	size_type size ( ) const noexcept {
		return m_data.size ( );
	}

	/**
	 * \brief
	 * Returns the maximal number of values possible to store inside the container.
	 *
	 * \return the maximal number of values
	 */
	size_type max_size ( ) const noexcept {
		return m_data.max_size ( );
	}

	/**
	 * \brief
	 * Setter of value in the array on position specified by iterator specified by \p pos. The value is cloned from the \p value.
	 *
	 * \tparam R the actual type of constructed value
	 *
	 * \param pos the iterator specifying position to set
	 * \param value the value to place at given position
	 *
	 * \return iterator to the set value
	 */
	template < class R >
	iterator set ( const_iterator pos, R && value ) {
		size_type dist = std::distance ( cbegin ( ), pos );

		// If the set value and value at position pos are same instances first clone, then delete
		delete std::exchange ( m_data.at ( dist ), ext::clone ( std::forward < R > ( value ) ) );

		return dereferencer ( m_data.begin ( ) + dist );
	}

	/**
	 * \brief
	 * Setter of value in the array on position specified by iterator specified by \p pos. The value is cloned from the \p value.
	 *
	 * \tparam R the actual type of constructed value
	 *
	 * \param pos the iterator specifying position to set
	 * \param value the value to place at given position
	 *
	 * \return iterator to the set value
	 */
	template < class R >
	iterator set ( const_reverse_iterator pos, R && value ) {
		size_type dist = std::distance ( crbegin ( ), pos );

		// If the set value and value at position pos are same instances first clone, then delete
		delete std::exchange ( m_data.at ( m_data.size ( ) - dist - 1 ), ext::clone ( std::forward < R > ( value ) ) );

		return dereferencer ( m_data.rbegin ( ) + dist );
	}

	/**
	 * \brief
	 * Setter of internaly constructed value into the array on position specified by iterator specified by \p pos. The value is constructed from paramter pack.
	 *
	 * \tparam R the actual type of constructed value
	 * \tparam Args ... types of value constructor parameters
	 *
	 * \param pos the iterator specifying position to set
	 * \param args ... value constructor parameters
	 *
	 * \return iterator to the set value
	 */
	template < class R, class ... Args >
	iterator emplace_set ( const_iterator pos, Args && ... args ) {
		return set ( pos, R ( std::forward < Args > ( args ) ... ) );
	}

	/**
	 * \brief
	 * Setter of internaly constructed value into the array on position specified by iterator specified by \p pos. The value is constructed from paramter pack.
	 *
	 * \tparam R the actual type of constructed value
	 * \tparam Args ... types of value constructor parameters
	 *
	 * \param pos the iterator specifying position to set
	 * \param args ... value constructor parameters
	 *
	 * \return iterator to the set value
	 */
	template < class R, class ... Args >
	iterator emplace_set ( const_reverse_iterator pos, Args && ... args ) {
		return set ( pos, R ( std::forward < Args > ( args ) ... ) );
	}

	/**
	 * Swaps two instances of pointer array
	 *
	 * \param x the other instance to swap with
	 */
	void swap ( ptr_array & other ) {
		swap ( this->m_data, other.m_data );
	}

	/**
	 * \brief
	 * Getter of value on index given by template paramter.
	 *
	 * \tparam I the index to access
	 *
	 * \return the value on specified index.
	 */
	template < std::size_t I >
	auto & get ( ) & {
		return * std::get < I > ( m_data );
	}

	/**
	 * \brief
	 * Getter of value on index given by template paramter.
	 *
	 * \tparam I the index to access
	 *
	 * \return the value on specified index.
	 */
	template < std::size_t I >
	const auto & get ( ) const & {
		return * std::get < I > ( m_data );
	}

	/**
	 * \brief
	 * Getter of value on index given by template paramter.
	 *
	 * \tparam I the index to access
	 *
	 * \return the value on specified index.
	 */
	template < std::size_t I >
	auto && get ( ) && {
		return std::move ( * std::get < I > ( m_data ) );
	}
};

} /* namespace ext */

namespace std {

/**
 * \brief
 * Specialisation of get function for pointer arrays
 *
 * \tparam I the index to access
 * \tparam Type the type of stored values
 * \tparam N the size of the array
 *
 * \param tpl the accessed tuple
 *
 * \return reference to value on given index
 */
template < std::size_t I, class Type, std::size_t N >
auto & get ( ext::ptr_array < Type, N > & tpl ) {
	return tpl.template get < I > ( );
}

/**
 * \brief
 * Specialisation of get function for pointer arrays
 *
 * \tparam I the index to access
 * \tparam Type the type of stored values
 * \tparam N the size of the array
 *
 * \param tpl the accessed tuple
 *
 * \return reference to value on given index
 */
template < std::size_t I, class Type, std::size_t N >
const auto & get ( const ext::ptr_array < Type, N > & tpl ) {
	return tpl.template get < I > ( );
}

/**
 * \brief
 * Specialisation of get function for pointer arrays
 *
 * \tparam I the index to access
 * \tparam Type the type of stored values
 * \tparam N the size of the array
 *
 * \param tpl the accessed tuple
 *
 * \return reference to value on given index
 */
template < std::size_t I, class Type, std::size_t N >
auto && get ( ext::ptr_array < Type, N > && tpl ) {
	return std::move ( tpl ).template get < I > ( );
}

} /* namespace std */

namespace ext {

/**
 * \brief
 * Operator to print the array to the output stream.
 *
 * \param out the output stream
 * \param array the array to print
 *
 * \tparam T the type of values inside the array
 * \tparam N the size of the array
 *
 * \return the output stream from the \p out
 */
template < class T, std::size_t N >
std::ostream& operator<<(std::ostream& out, const ext::ptr_array < T, N > & ptr_array) {
	out << "[";

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

	out << "]";
	return out;
}

/**
 * \brief
 * Specialisation of the compare structure implementing the three-way comparison.
 *
 * \tparam T the type of values inside the array
 * \tparam N the size of the array
 */
template < class T, std::size_t N >
struct compare < ext::ptr_array < T, N > > {

	/**
	 * \brief
	 * Implementation of the three-way comparison.
	 *
	 * \param first the left operand of the comparison
	 * \param second the right operand of the comparison
	 *
	 * \return negative value of left < right, positive value if left > right, zero if left == right
	 */
	int operator()(const ext::ptr_array < T, N > & first, const ext::ptr_array < T, N > & second) const {
		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;
	}
};

/**
 * \brief
 * Overload of to_string function.
 *
 * \param value the array to be converted to string
 *
 * \tparam T the type of values inside the array
 * \tparam N the size of the array
 *
 * \return string representation
 */
template < class T, std::size_t N >
std::string to_string ( const ext::ptr_array < T, N > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

/**
 * \brief
 * Specialisation of equality operator for pointer array.
 *
 * \tparam T the type of values inside the array
 * \tparam N the size of the array
 *
 * \param first the first compared value
 * \param second the second compared value
 *
 * \return true if compared values are the same, false othervise
 */
template < class T, std::size_t N >
bool operator == ( const ptr_array < T, N > & first, const ptr_array < T, N > & second ) {
	static compare < ptr_array < T, N > > comp;
	return comp ( first, second ) == 0;
}

/**
 * \brief
 * Specialisation of non-equality operator for pointer array.
 *
 * \tparam T the type of values inside the array
 * \tparam N the size of the array
 *
 * \param first the first compared value
 * \param second the second compared value
 *
 * \return true if compared values are not the same, false othervise
 */
template < class T, std::size_t N >
bool operator != ( const ptr_array < T, N > & first, const ptr_array < T, N > & second ) {
	return ! ( first == second );
}

/**
 * \brief
 * Specialisation of less than operator for pointer array.
 *
 * \tparam T the type of values inside the array
 * \tparam N the size of the array
 *
 * \param first the first compared value
 * \param second the second compared value
 *
 * \return true if the first value is smaller than the second, false othervise
 */
template < class T, std::size_t N >
bool operator < ( const ptr_array < T, N > & first, const ptr_array < T, N > & second ) {
	static compare < ptr_array < T, N > > comp;
	return comp ( first, second ) < 0;
}

/**
 * \brief
 * Specialisation of greater than operator for pointer array.
 *
 * \tparam T the type of values inside the array
 * \tparam N the size of the array
 *
 * \param first the first compared value
 * \param second the second compared value
 *
 * \return true if the first value is bigger than the second, false othervise
 */
template < class T, std::size_t N >
bool operator > ( const ptr_array < T, N > & first, const ptr_array < T, N > & second ) {
	return second < first;
}

/**
 * \brief
 * Specialisation of less than or equal operator for pointer array.
 *
 * \tparam T the type of values inside the array
 * \tparam N the size of the array
 *
 * \param first the first compared value
 * \param second the second compared value
 *
 * \return true if the first value is smaller or equal to the second, false othervise
 */
template < class T, std::size_t N >
bool operator <= ( const ptr_array < T, N > & first, const ptr_array < T, N > & second ) {
	return ! ( first > second );
}

/**
 * \brief
 * Specialisation of greater than or equal operator for pointer array.
 *
 * \tparam T the type of values inside the array
 * \tparam N the size of the array
 *
 * \param first the first compared value
 * \param second the second compared value
 *
 * \return true if the first value is bigger or equal to the second, false othervise
 */
template < class T, std::size_t N >
bool operator >= ( const ptr_array < T, N > & first, const ptr_array < T, N > & second ) {
	return ! ( first < second );
}

/**
 * \brief
 * Array construction helper. Array is constructed from provided values, type of stored elements is deduced from the first parameter-
 *
 * \tparam Base the type of the first parameter
 * \tparam Types ... pack of remaining parameter types
 *
 * \param first the first parameter
 * \param other ... pack of remaining parameters
 *
 * \return pointer array containing all parameters
 */
template < typename Base, typename ... Types >
constexpr ptr_array < typename std::remove_reference < Base >::type , sizeof ... ( Types ) + 1 > make_ptr_array ( Base && first, Types && ... other ) {
	return ptr_array < typename std::remove_reference < Base >::type, sizeof ... ( Types ) + 1 > ( std::forward < Base > ( first ), std::forward < Types > ( other ) ... );
}

/**
 * \brief
 * Specialisation of array construction helper for empty array. The type parameter of the array must be provided explicitly.
 *
 * \tparam Base the type parameter of the constructed array
 *
 * \return empty pointer array of specified values
 */
template < typename Base >
constexpr ptr_array < typename std::remove_reference < Base >::type, 0 > make_ptr_array ( ) {
	return ptr_array < typename std::remove_reference < Base >::type, 0 > ( );
}

} /* namespace ext */

#endif /* __PTR_ARRAY_HPP_ */
