/*
 * multimap.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 __MULTIMAP_HPP_
#define __MULTIMAP_HPP_

#include <map>
#include <ostream>
#include <sstream>
#include <utility>
#include <string>

#include "pair.hpp"

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

namespace ext {

/**
 * \brief
 * Class extending the multimap class from the standard library. Original reason is to allow printing of the container with overloaded operator <<.
 *
 * The class mimics the behavior of the multimap from the standatd library.
 *
 * \tparam T the type of keys inside the multimap
 * \tparam R the type of values inside the multimap
 * \tparam Cmp the comparator type used to order keys
 * \tparam Alloc the allocator of values of type T
 */
template < typename T, typename R, typename Cmp = std::less < >, typename Alloc = std::allocator < std::pair < const T, R > > >
class multimap : public std::multimap < T, R, Cmp, Alloc > {
public:
	/**
	 * Inherit constructors of the standard multimap
	 */
	using std::multimap< T, R, Cmp, Alloc >::multimap; // NOLINT(modernize-use-equals-default)

	/**
	 * Inherit operator = of the standard multimap
	 */
	using std::multimap< T, R, Cmp, Alloc >::operator =;
#ifndef __clang__

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

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

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

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

	/**
	 * Move operator = needed by g++ since it is not inherited
	 */
	multimap & operator = ( const multimap & other ) = default;
#endif
	/**
	 * Constructor from range of values.
	 *
	 * \tparam Iterator the type of range iterator
	 *
	 * \param range the source range
	 */
	template < class Iterator >
	multimap ( const ext::iterator_range < Iterator > & range ) : multimap ( range.begin ( ), range.end ( ) ) {
	}

	/**
	 * \brief
	 * The iterator type is inheried.
	 */
	using iterator = typename std::multimap<T, R, Cmp, Alloc>::iterator;

	/**
	 * \brief
	 * Inherit all insert methods of the standard multimap.
	 */
	using std::multimap< T, R, Cmp, Alloc >::insert;

	/**
	 * \brief
	 * Insert variant with explicit key and value parameters.
	 *
	 * \param key the key
	 * \param value the value
	 *
	 * \return pair of iterator to inserted key-value pair and true if the value was inserted or false if the key already exited
	 */
	iterator insert ( const T & key, const R & value ) {
		return insert ( std::make_pair ( key, value ) );
	}

	/**
	 * \brief
	 * Insert variant with explicit key and value parameters.
	 *
	 * \param key the key
	 * \param value the value
	 *
	 * \return pair of iterator to inserted key-value pair and true if the value was inserted or false if the key already exited
	 */
	iterator insert ( const T & key, R && value ) {
		return insert ( std::make_pair ( key, std::move ( value ) ) );
	}

	/**
	 * \brief
	 * Insert variant with explicit key and value parameters.
	 *
	 * \param key the key
	 * \param value the value
	 *
	 * \return pair of iterator to inserted key-value pair and true if the value was inserted or false if the key already exited
	 */
	iterator insert ( T && key, const R & value ) {
		return insert ( std::make_pair ( std::move ( key ), value ) );
	}

	/**
	 * \brief
	 * Insert variant with explicit key and value parameters.
	 *
	 * \param key the key
	 * \param value the value
	 *
	 * \return pair of iterator to inserted key-value pair and true if the value was inserted or false if the key already exited
	 */
	iterator insert ( T && key, R && value ) {
		return insert ( std::make_pair ( std::move ( key ), std::move ( value ) ) );
	}

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

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

	/**
	 * \brief
	 * New variant of begin for rvalues.
	 *
	 * \return move_iterator the first element of multimap
	 */
	auto begin ( ) && {
		return make_map_move_iterator < T, R > ( this->begin ( ) );
	}

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

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

	/**
	 * \brief
	 * New variant of end for rvalues.
	 *
	 * \return move_iterator to one after the last element of multimap
	 */
	auto end ( ) && {
		return make_map_move_iterator < T, R > ( 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
	 * Make range of elements with key equal to the @p key.
	 *
	 * \tparam K the key used in the query
	 *
	 * \param key the value used in the query
	 *
	 * \return selected range of elements
	 */
	template < class K >
	auto equal_range ( K && key ) const & {
		auto range = std::multimap < T, R, Cmp, Alloc >::equal_range ( std::forward < K > ( key ) );
		return ext::iterator_range < decltype ( range.first ) > ( range.first, range.second );
	}

	/**
	 * \brief
	 * Make range of elements with key equal to the @p key.
	 *
	 * \tparam K the key used in the query
	 *
	 * \param key the value used in the query
	 *
	 * \return selected range of elements
	 */
	template < class K >
	auto equal_range ( K && key ) & {
		auto range = std::multimap < T, R, Cmp, Alloc >::equal_range ( std::forward < K > ( key ) );
		return ext::iterator_range < decltype ( range.first ) > ( range.first, range.second );
	}

	/**
	 * \brief
	 * Make range of elements with key equal to the @p key.
	 *
	 * \tparam K the key used in the query
	 *
	 * \param key the value used in the query
	 *
	 * \return selected range of elements
	 */
	template < class K >
	auto equal_range ( K && key ) && {
		auto range = std::multimap < T, R, Cmp, Alloc >::equal_range ( std::forward < K > ( key ) );
		return ext::make_iterator_range ( make_map_move_iterator < T, R > ( range.first ), make_map_move_iterator < T, R > ( range.second ) );
	}

	/**
	 * \brief
	 * Test whether the set contains a given key
	 *
	 * \param key the tested key
	 *
	 * \return true if the set contains the key, false otherwise
	 */
	bool contains ( const T & key ) const { // TODO remove after switching C++20
		return this->count ( key ) != 0U;
	}

	/**
	 * \brief
	 * Test whether the set contains a given key
	 *
	 * \tparam K the type of the key
	 *
	 * \param key the tested key
	 *
	 * \return true if the set contains the key, false otherwise
	 */
	template < class K >
	bool contains ( const K & key ) const { // TODO remove after switching C++20
		return this->count ( key ) != 0U;
	}

};

/**
 * \brief
 * Operator to print the multimap to the output stream.
 *
 * \param out the output stream
 * \param multimap the multimap to print
 *
 * \tparam T the type of keys inside the multimap
 * \tparam R the type of values inside the multimap
 * \tparam Ts ... remaining unimportant template parameters of the multimap
 *
 * \return the output stream from the \p out
 */
template< class T, class R, class ... Ts >
std::ostream& operator<<(std::ostream& out, const ext::multimap<T, R, Ts ... >& multimap) {
	out << "{";

	bool first = true;
	for(const std::pair<const T, R>& item : multimap) {
		if(!first) out << ", ";
		first = false;
		out << "(" << item.first << ", " << item.second << ")";
	}

	out << "}";
	return out;
}

/**
 * \brief
 * Specialisation of the compare structure implementing the three-way comparison.
 *
 * \tparam T the type of keys inside the multimap
 * \tparam R the type of values inside the multimap
 * \tparam Ts ... remaining unimportant template parameters of the multimap
 */
template < class T, class R, class ... Ts >
struct compare < ext::multimap < T, R, Ts ... > > {

	/**
	 * \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::multimap < T, R, Ts ... > & first, const ext::multimap < T, R, Ts ... > & second) const {
		if(first.size() < second.size()) return -1;
		if(first.size() > second.size()) return 1;

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

/**
 * \brief
 * Overload of to_string function.
 *
 * \param value the multimap to be converted to string
 *
 * \tparam T the type of values inside the multimap
 * \tparam Ts ... remaining unimportant template parameters of the multimap
 *
 * \return string representation
 */
template < class T, class R, class ... Ts >
std::string to_string ( const ext::multimap < T, R, Ts ... > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

} /* namespace ext */

#endif /* __MULTIMAP_HPP_ */
