/*
 * tuple.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
 */

#pragma once

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

#include <extensions/common/tuple_common.hpp>
#include <extensions/container/pair.hpp>

namespace ext {

/**
 * \brief
 * Class extending the tuple class from the standard library. Original reason is to allow printing of the container with overloaded operator <<.
 *
 * The class mimics the behavior of the tuple from the standatd library.
 *
 * \tparam Types ... the types inside the tuple
 */
template < typename ... Ts >
class tuple : public std::tuple < Ts ... > {
public:
	/**
	 * Inherit constructors of the standard set
	 */
	using std::tuple< Ts ... >::tuple; // NOLINT(modernize-use-equals-default)

	/**
	 * Inherit operator = of the standard set
	 */
	using std::tuple< Ts ... >::operator =;
#ifndef __clang__

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

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

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

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

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

#ifdef _LIBCPP_VERSION // FIXME remove when libc++ gets standard conforming implementation of tuple's operator = (see: https://reviews.llvm.org/D50106)
	template < typename T, typename R >
	void assignSingle ( T & t, R & r ) {
		t = r;
	}

	template < typename ... Rs, size_t ... Indexes >
	void assign ( const tuple < Rs ... > & other, std::index_sequence < Indexes ... > ) {
		static_assert ( sizeof ... ( Ts ) == sizeof ... ( Rs ) );
		( assignSingle ( std::get < Indexes > ( * this ), std::get < Indexes > ( other ) ), ... );
	}

	template < typename ... Rs >
	tuple & operator = ( const tuple < Rs ... > & other ) {
		assign ( other, std::make_index_sequence < sizeof ... ( Rs ) > ( ) );
		return * this;
	}

	template < typename T1, typename R2 >
	tuple & operator = ( const pair < T1, R2 > & other ) {
		static_assert ( 2 == sizeof ... ( Ts ) );
		std::get < 0 > ( * this ) = other.first;
		std::get < 1 > ( * this ) = other.second;
		return * this;
	}
#endif

	/**
	 * \brief
	 * Inherited behavior of get for non-const instance.
	 *
	 * \tparam I the index to retrieve
	 *
	 * \return reference to the value at index I
	 */
	template < std::size_t I >
	auto & get ( ) & {
		return std::get < I > ( * this );
	}

	/**
	 * \brief
	 * New behavior of get for rvalue referenced instance.
	 *
	 * \tparam I the index to retrieve
	 *
	 * \return rvalue reference to the value at index I
	 */
	template < std::size_t I >
	auto && get ( ) && {
		return std::get < I > ( std::move ( * this ) );
	}

	/**
	 * \brief
	 * Inherited behavior of get fro const instance.
	 *
	 * \tparam I the index to retrieve
	 *
	 * \return const reference to the value at index I
	 */
	template < std::size_t I >
	const auto & get ( ) const & {
		return std::get < I > ( * this );
	}

	/**
	 * \brief
	 * Allow cast of tuple of two values to pair.
	 *
	 * \return casted pair
	 */
	template < class T, class R >
	requires ( sizeof ... ( Ts ) == 2 )
	operator ext::pair < T, R > ( ) const {
		return ext::pair < T, R > ( std::get < 0 > ( * this ), std::get < 1 > ( * this ) );
	}
};

/**
 * \brief
 * Operator to print the tuple to the output stream.
 *
 * \param out the output stream
 * \param tuple the tuple to print
 *
 * \tparam Ts ... the pack of value type of the tuple
 *
 * \return the output stream from the \p out
 */
template< class... Ts>
std::ostream& operator<<(std::ostream& out, const ext::tuple<Ts...>& tuple) {
	out << "(";

	if constexpr ( sizeof ... ( Ts ) > 0 ) {
		auto outCallback = [ & ] ( const auto & first, const auto & ... other ) {
			out << first;
			( ( out << ", " << other ), ... );
		};

		std::apply ( outCallback, tuple );
	}

	out << ")";
	return out;
}

/**
 * \brief
 * Overload of to_string function.
 *
 * \param value the tuple to be converted to string
 *
 * \tparam Ts ... the pack of value type of the tuple
 *
 * \return string representation
 */
template < class ... Ts >
std::string to_string ( const ext::tuple < Ts ... > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

/**
 * \brief
 * Helper of extended tuple construction. The tuple is constructed from values pack, types are deduced.
 *
 * \tparam Elements of types inside the tuple
 *
 * \param args the tuple content
 *
 * \result tuple containing values from arguments
 */
template < typename ... Elements >
constexpr auto make_tuple ( Elements && ... args ) {
	return tuple < typename ext::strip_reference_wrapper < std::decay_t < Elements > >::type ... > ( std::forward < Elements > ( args ) ... );
}

/**
 * \brief
 * Helper of extended tuple of references construction. The tuple is constructed to reffer to values in the parameter pack, types are deduced.
 *
 * \tparam Elements of types of references inside the tuple
 *
 * \param args values to refer to
 *
 * \result tuple containing references to values from arguments
 */
template<typename ... Elements >
constexpr tuple < Elements & ... > tie ( Elements & ... args ) noexcept {
	return tuple < Elements & ... > ( args ... );
}

template < class T1, class T2, class T3, class T4 >
inline constexpr bool operator < ( const std::pair < T1, T2 > & first, const std::tuple < T3, T4 > & second ) {
	return std::tie ( first.first, first.second ) < second;
}

template < class T1, class T2, class T3, class T4 >
inline constexpr bool operator < ( const std::tuple < T1, T2 > & first, const std::pair < T3, T4 > & second ) {
	return first < std::tie ( second.first, second.second ) ;
}

} /* namespace ext */

namespace std {

/**
 * \brief
 * Specialisation of tuple_size for extended tuple.
 *
 * \tparam Types the types of tuple values
 */
template < class ... Types >
struct tuple_size < ext::tuple < Types ... > > : public std::integral_constant < std::size_t, sizeof...( Types ) > { };

/**
 * \brief
 * Specialisation of tuple_element for extended tuple.
 *
 * The class provides type field representing selected type.
 *
 * \tparam I the index into tuple types
 * \tparam ... Types the pack of tuple types
 */
template < std::size_t I, class... Types >
struct tuple_element < I, ext::tuple < Types... > > {
	typedef typename std::tuple_element < I, std::tuple < Types ... > >::type type;
};

} /* namespace std */
