/*
 * 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: Apr 1, 2013
 * Author: Jan Travnicek
 */

#ifndef __ARRAY_HPP_
#define __ARRAY_HPP_

#include <array>

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

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

namespace std {

namespace experimental {

namespace details {

template <class D, class...> struct return_type_helper { using type = D; };
template <class... Types>
struct return_type_helper<void, Types...> : std::common_type<Types...> {};

template <class D, class... Types>
using return_type = std::array<typename return_type_helper<D, Types...>::type,
                                 sizeof...(Types)>;
}

template < class D = void, class... Types>
constexpr details::return_type<D, Types...> make_array(Types&&... t) {
  return {{std::forward<Types>(t)... }};
}

} /* namespace experimental */

} /* namespace std */ //FIXME remove whole thing when make_array gets from experimental to the standard

namespace ext {

/**
 * \brief
 * Class extending the array class from the standard library. Original reason is to allow printing of the container with overloaded operator <<.
 *
 * The class mimics the behavior of the array from the standatd library.
 *
 * \tparam T the type of values in the array
 * \tparam N the size of the array
 */
template < class T, std::size_t N >
class array : public std::array < T, N > {
public:
	/**
	 * Inherit constructors of the standard array
	 */
	using std::array < T, N >::array; // NOLINT(modernize-use-equals-default)

	/**
	 * Inherit operator = of the standard array
	 */
	using std::array < T, N >::operator =;

#ifndef __clang__

	/**
	 * Default constructor needed by g++ since it is not inherited
	 */
	array ( ) = default;

	/**
	 * Copy constructor needed by g++ since it is not inherited
	 */
	array ( const array & other ) = default;

	/**
	 * Move constructor needed by g++ since it is not inherited
	 */
	array ( array && other ) = default;

	/**
	 * Copy operator = needed by g++ since it is not inherited
	 */
	array & operator = ( array && other ) = default;

	/**
	 * Move operator = needed by g++ since it is not inherited
	 */
	array & operator = ( const array & other ) = default;

#endif

	/**
	 * Constructor of array from list of values.
	 */
	template < class ... Types, typename std::enable_if < ( std::is_same < T, typename std::remove_reference < Types >::type >::value && ... ) >::type * = nullptr >
	explicit array ( Types && ... args ) : std::array < T, N > ( std::experimental::make_array ( std::forward < Types > ( args ) ... ) ) {
	}

	/**
	 * \brief
	 * Inherited behavior of begin for non-const instance.
	 *
	 * \return iterator the first element of array
	 */
	auto begin ( ) & {
		return std::array < T, N >::begin ( );
	}

	/**
	 * \brief
	 * Inherited behavior of begin for const instance.
	 *
	 * \return const_iterator the first element of array
	 */
	auto begin ( ) const & {
		return std::array < T, N >::begin ( );
	}

	/**
	 * \brief
	 * Inherited behavior of begin for rvalues.
	 *
	 * \return move_iterator the first element of array
	 */
	auto begin ( ) && {
		return make_move_iterator ( this->begin ( ) );
	}

	/**
	 * \brief
	 * Inherited behavior of end for non-const instance.
	 *
	 * \return iterator to one after the last element of array
	 */
	auto end ( ) & {
		return std::array < T, N >::end ( );
	}

	/**
	 * \brief
	 * Inherited behavior of end for const instance.
	 *
	 * \return const_iterator to one after the last element of array
	 */
	auto end ( ) const & {
		return std::array < T, N >::end ( );
	}

	/**
	 * \brief
	 * Inherited behavior of end for rvalues.
	 *
	 * \return move_iterator to one after the last element of array
	 */
	auto end ( ) && {
		return make_move_iterator ( this->end ( ) );
	}

	/**
	 * \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
 * 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::array < T, N > & array) {
	out << "[";

	bool first = true;
	for(const T& item : 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::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::array < T, N > & first, const ext::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::array < T, N > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

/**
 * \brief
 * Equivalent to the make_array from standard library but produces the ext::array.
 *
 * \param first the required value of the array
 * \param other ... other values to be placed to the array
 *
 * \tparam Base the type of the first value
 * \tparam Types the types of remaining types (they should be the same or convertible to Base type)
 *
 * \return array containing first and other values of size equal to the number of parameters.
 */
template < typename Base, typename ... Types >
constexpr array < typename std::remove_reference < Base >::type, sizeof ... ( Types ) + 1 > make_array ( Base && first, Types && ... other ) {
	return array < typename std::remove_reference < Base >::type, sizeof ... ( Types ) + 1 > ( std::forward < Base > ( first ), std::forward < Types > ( other ) ... );
}

/**
 * \brief
 * Special case of make_array handling zero parameters. Technically calls default constructor.
 *
 * \tparam Base the type of array values
 *
 * \return array of zero size with the type of values set to Base type.
 */
template < typename Base >
constexpr array < typename std::remove_reference < Base >::type, 0 > make_array ( ) {
	return array < typename std::remove_reference < Base >::type, 0 > ( );
}

} /* namespace ext */

#endif /* __ARRAY_HPP_ */
