/*
 * ptr_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: Feb 28, 2014
 * Author: Jan Travnicek
 */

#pragma once

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

#include <extensions/iterator.hpp>
#include <extensions/clone.hpp>

#include <extensions/common/tuple_common.hpp>

namespace ext {

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

	/**
	 * \brief
	 * Implementation of copy constructor of the pointer tuple.
	 *
	 * \tparam Indices ... the pack of valid indices to the tuple
	 *
	 * \param tpl the source tuple
	 */
	template < std::size_t ... Indices >
	void copyCons ( std::index_sequence < Indices ... >, const ptr_tuple & tpl ) {
		( ( std::get < Indices > ( m_data ) = ext::clone ( std::get < Indices > ( * tpl.m_data ) ) ), ... );
	}

	/**
	 * \brief
	 * Implementation of move constructor of the pointer tuple.
	 *
	 * \tparam Indices ... the pack of valid indices to the tuple
	 *
	 * \param tpl the source tuple
	 */
	template < std::size_t ... Indices >
	void moveCons ( std::index_sequence < Indices ... >, ptr_tuple && tpl ) {
		( std::swap ( std::get < Indices > ( m_data ), std::get < Indices > ( tpl.m_data ) ), ... );
	}

	/**
	 * \brief
	 * Implementation of constructor of the pointer tuple from pack of source values.
	 *
	 * \tparam Indices ... the pack of valid indices to the tuple
	 * \tparam UTypes ... pack of types of source values
	 *
	 * \param elems ... pack of source values
	 */
	template < std::size_t ... Indices, class ... UTypes >
	void cons ( std::index_sequence < Indices ... >, UTypes && ... elems ) {
		( ( std::get < Indices > ( m_data ) = ext::clone ( std::forward < UTypes > ( elems ) ) ), ... );
	}

	/**
	 * \brief
	 * Implementation of the pointer tuple destructor.
	 *
	 * \tparam Indices ... the pack of valid indices to the tuple
	 */
	template < std::size_t ... Indices >
	void des ( std::index_sequence < Indices ... > ) {
		( delete std::get < Indices > ( m_data ), ... );
	}

public:
	/**
	 * \brief
	 * The copy constructor of the pointer tuple.
	 *
	 * \param tpl the source tuple
	 */
	ptr_tuple ( const ptr_tuple & tpl ) {
		copyCons ( std::make_index_sequence < sizeof ... ( Types ) > ( ), tpl );
	}

	/**
	 * \brief
	 * The move constructor of the pointer tuple.
	 *
	 * \param tpl the source tuple
	 */
	ptr_tuple ( ptr_tuple && tpl ) {
		moveCons ( std::make_index_sequence < sizeof ... ( Types ) > ( ), std::move ( tpl ) );
	}

/*	template < class ... UTypes>
	ptr_tuple ( const ptr_tuple < UTypes ... > & tpl ) {
	}

	template < class ... UTypes >
	ptr_tuple ( ptr_tuple < UTypes ... > && tpl ) {

	}*/

	/**
	 * \brief
	 * Tuple constructor from pack of source values
	 *
	 * \tparam UTypes ... pack of types of source values
	 *
	 * \param elems ... pack of source values
	 */
	template < class ... UTypes >
	explicit ptr_tuple ( UTypes && ... elems ) {
		cons ( std::make_index_sequence < sizeof ... ( Types ) > ( ), std::forward < UTypes > ( elems ) ... );
	}

	/**
	 * \brief
	 * The pointer tuple destructor
	 */
	~ptr_tuple ( ) {
		des ( std::make_index_sequence < sizeof ... ( Types ) > ( ) );
	}

	/**
	 * \brief
	 * The pointer tuple copy operator of assignemnt.
	 *
	 * \param other the source value
	 */
	ptr_tuple < Types ... > & operator = ( const ptr_tuple < Types ... > & other ) {
		if ( this == & other )
			return * this;

		des ( std::make_index_sequence < sizeof ... ( Types ) > ( ) );
		copyCons ( std::make_index_sequence < sizeof ... ( Types ) > ( ), other );

		return *this;
	}

	/**
	 * \brief
	 * The pointer tuple move operator of assignemnt.
	 *
	 * \param other the source value
	 */
	ptr_tuple < Types ... > & operator = ( ptr_tuple < Types ... > && other ) {
		std::swap ( m_data, other.m_data );

		return *this;
	}

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

	/**
	 * \brief
	 * Access method to inside the pointer tuple.
	 *
	 * \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 > ( m_data );
	}

	/**
	 * \brief
	 * Access method to inside the pointer tuple.
	 *
	 * \tparam I the index to retrieve
	 *
	 * \return reference to the value at index I
	 */
	template < std::size_t I >
	const auto & get ( ) const & {
		return * std::get < I > ( m_data );
	}

	/**
	 * \brief
	 * Access method to inside the pointer tuple.
	 *
	 * \tparam I the index to retrieve
	 *
	 * \return reference to the value at index I
	 */
	template < std::size_t I >
	auto && get ( ) && {
		return std::move ( * std::get < I > ( m_data ) );
	}

	/**
	 * \brief
	 * Setter method of a value inside the pointer tuple.
	 *
	 * \tparam R the actual type of value to store
	 * \tparam I the index to retrieve
	 */
	template < class R, std::size_t I >
	void set ( R && value ) {
		set < I > ( ext::clone ( std::forward < R > ( value ) ) );
	}

private:
	auto asTie ( ) const {
		auto accessor = [ & ] < std::size_t ... Is > ( std::index_sequence < Is ... > ) {
			return std::tie ( this->template get < Is > ( ) ... );
		};
		return accessor ( std::make_index_sequence < sizeof ... ( Types ) > ( ) );
	}

public:
	/**
	 * \brief
	 * Specialisation of the compare structure implementing the three-way comparison.
	 */
	bool operator == ( const ext::ptr_tuple < Types ... > & second ) const {
		return this->asTie ( ) == second.asTie ( );
	}

	/**
	 * \brief
	 * Specialisation of the compare structure implementing the three-way comparison.
	 */
	auto operator <=> ( const ext::ptr_tuple < Types ... > & second ) const {
		return this->asTie ( ) <=> second.asTie ( );
	}

};

/**
 * \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::ptr_tuple<Ts...>& tuple) {
	out << "(";

	bool first = true;
	auto outCallback = [ & ] ( const auto & arg0 ) {
		if ( ! first ) {
			out << ", ";
		} else {
			first = false;
		}
		out << arg0;
	};

	ext::foreach ( tuple, outCallback ); // TODO use std::apply like in ext::tuple, should work since C++20
	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::ptr_tuple < Ts ... > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

} /* namespace ext */

namespace std {

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

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

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

} /* namespace std */

namespace ext {

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

} /* 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::ptr_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::ptr_tuple < Types... > > {
	typedef typename std::tuple_element < I, std::tuple < Types ... > >::type type;
};

} /* namespace std */

