/*
 * This file is part of OpenTTD.
 * OpenTTD 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, version 2.
 * OpenTTD 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 OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
 */

/** @file multimap.hpp Multimap with deterministic ordering of items with equal keys. */

#ifndef MULTIMAP_HPP
#define MULTIMAP_HPP

template <typename Tkey, typename Tvalue, typename Tcompare>
class MultiMap;

/**
 * STL-style iterator for MultiMap.
 * @tparam Tmap_iter Iterator type for the map in the MultiMap.
 * @tparam Tlist_iter Iterator type for the lists in the MultiMap.
 * @tparam Tkey Key type of the MultiMap.
 * @tparam Tvalue Value type of the MultMap.
 * @tparam Tcompare Comparator type for keys of the MultiMap.
 */
template <class Tmap_iter, class Tlist_iter, class Tkey, class Tvalue, class Tcompare>
class MultiMapIterator {
protected:
	friend class MultiMap<Tkey, Tvalue, Tcompare>;
	typedef MultiMapIterator<Tmap_iter, Tlist_iter, Tkey, Tvalue, Tcompare> Self;

	Tlist_iter list_iter; ///< Iterator pointing to current position in the current list of items with equal keys.
	Tmap_iter map_iter;   ///< Iterator pointing to the position of the current list of items with equal keys in the map.

	/**
	 * Flag to show that the iterator has just "walked" a step in the map.
	 * We cannot check the current list for that as we might have reached end() of the map. In that case we'd need to
	 * set list_iter to some sort of "invalid" state, but that's impossible as operator== yields undefined behaviour
	 * if the iterators don't belong to the same list and there is no list at end(). So if we created a static empty
	 * list and an "invalid" iterator in that we could not determine if the iterator is invalid while it's valid. We
	 * can also not determine if the map iterator is valid while we don't have the map; so in the end it's easiest to
	 * just introduce an extra flag.
	 */
	bool list_valid;

public:
	/**
	 * Simple, dangerous constructor to allow later assignment with operator=.
	 */
	MultiMapIterator() : list_valid(false) {}

	/**
	 * Constructor to allow possibly const iterators to be assigned from possibly
	 * non-const map iterators. You can assign end() like this.
	 * @tparam Tnon_const Iterator type assignable to Tmap_iter (which might be const).
	 * @param mi One such iterator.
	 */
	template <class Tnon_const>
	MultiMapIterator(Tnon_const mi) : map_iter(mi), list_valid(false) {}

	/**
	 * Constructor to allow specifying an exact position in map and list. You cannot
	 * construct end() like this as the constructor will actually check li and mi->second
	 * for list_valid.
	 * @param mi Iterator in the map.
	 * @param li Iterator in the list.
	 */
	MultiMapIterator(Tmap_iter mi, Tlist_iter li) : list_iter(li), map_iter(mi)
	{
		this->list_valid = (this->list_iter != this->map_iter->second.begin());
	}

	/**
	 * Assignment iterator like constructor with the same signature.
	 * @tparam Tnon_const Iterator type assignable to Tmap_iter (which might be const).
	 * @param mi One such iterator.
	 * @return This iterator.
	 */
	template <class Tnon_const>
	Self &operator=(Tnon_const mi)
	{
		this->map_iter = mi;
		this->list_valid = false;
		return *this;
	}

	/**
	 * Dereference operator. Works just like usual STL operator*() on various containers.
	 * Doesn't do a lot of checks for sanity, just like STL.
	 * @return The value associated with the item this iterator points to.
	 */
	Tvalue &operator*() const
	{
		assert(!this->map_iter->second.empty());
		return this->list_valid ?
				this->list_iter.operator*() :
				this->map_iter->second.begin().operator*();
	}

	/**
	 * Same as operator*(), but returns a pointer.
	 * @return Pointer to the value this iterator points to.
	 */
	Tvalue *operator->() const
	{
		assert(!this->map_iter->second.empty());
		return this->list_valid ?
				this->list_iter.operator->() :
				this->map_iter->second.begin().operator->();
	}

	inline const Tmap_iter &GetMapIter() const { return this->map_iter; }
	inline const Tlist_iter &GetListIter() const { return this->list_iter; }
	inline bool ListValid() const { return this->list_valid; }

	const Tkey &GetKey() const { return this->map_iter->first; }

	/**
	 * Prefix increment operator. Increment the iterator and set it to the
	 * next item in the MultiMap. This either increments the list iterator
	 * or the map iterator and sets list_valid accordingly.
	 * @return This iterator after incrementing.
	 */
	Self &operator++()
	{
		assert(!this->map_iter->second.empty());
		if (this->list_valid) {
			if (++this->list_iter == this->map_iter->second.end()) {
				++this->map_iter;
				this->list_valid = false;
			}
		} else {
			this->list_iter = ++(this->map_iter->second.begin());
			if (this->list_iter == this->map_iter->second.end()) {
				++this->map_iter;
			} else {
				this->list_valid = true;
			}
		}
		return *this;
	}

	/**
	 * Postfix increment operator. Same as prefix increment, but return the
	 * previous state.
	 * @param dummy param to mark postfix.
	 * @return This iterator before incrementing.
	 */
	Self operator++(int)
	{
		Self tmp = *this;
		this->operator++();
		return tmp;
	}

	/**
	 * Prefix decrement operator. Decrement the iterator and set it to the
	 * previous item in the MultiMap.
	 * @return This iterator after decrementing.
	 */
	Self &operator--()
	{
		assert(!this->map_iter->second.empty());
		if (!this->list_valid) {
			--this->map_iter;
			this->list_iter = this->map_iter->second.end();
			assert(!this->map_iter->second.empty());
		}

		this->list_valid = (--this->list_iter != this->map_iter->second.begin());
		return *this;
	}

	/**
	 * Postfix decrement operator. Same as prefix decrement, but return the
	 * previous state.
	 * @param dummy param to mark postfix.
	 * @return This iterator before decrementing.
	 */
	Self operator--(int)
	{
		Self tmp = *this;
		this->operator--();
		return tmp;
	}
	/**
	 * Compare two MultiMap iterators. Iterators are equal if
	 * 1. Their map iterators are equal.
	 * 2. They agree about list_valid.
	 * 3. If list_valid they agree about list_iter.
	 * Lots of template parameters to make all possible const and non-const types of MultiMap iterators
	 * (on maps with const and non-const values) comparable to each other.
	 * @param other Other iterator to compare to.
	 * @return If other is equal to this.
	 */
	template <class Tmap_iter_other, class Tlist_iter_other, class Tvalue_other>
	bool operator==(const MultiMapIterator<Tmap_iter_other, Tlist_iter_other, Tkey, Tvalue_other, Tcompare> &other) const
	{
		if (this->GetMapIter() != other.GetMapIter()) return false;
		if (!this->ListValid()) return !other.ListValid();
		return other.ListValid() ?
				this->GetListIter() == other.GetListIter() : false;
	}

	/**
	 * Check if a MultiMap iterator is at the begin of a list pointed to by the given map iterator.
	 * Lots of template parameters to make all possible const and non-const types of MultiMap iterators
	 * (on maps with const and non-const values) comparable to all possible types of map iterators.
	 * @param iter Map iterator.
	 * @return If this points to the begin of the list pointed to by iter.
	 */
	template <class Titer>
	bool operator==(const Titer &iter) const
	{
		return !this->ListValid() && this->GetMapIter() == iter;
	}
};

/**
 * Hand-rolled multimap as map of lists. Behaves mostly like a list, but is sorted
 * by Tkey so that you can easily look up ranges of equal keys. Those ranges are
 * internally ordered in a deterministic way (contrary to STL multimap). All
 * STL-compatible members are named in STL style, all others are named in OpenTTD
 * style.
 */
template <typename Tkey, typename Tvalue, typename Tcompare = std::less<Tkey> >
class MultiMap : public std::map<Tkey, std::list<Tvalue>, Tcompare > {
public:
	typedef typename std::list<Tvalue> List;
	typedef typename List::iterator ListIterator;
	typedef typename List::const_iterator ConstListIterator;

	typedef typename std::map<Tkey, List, Tcompare > Map;
	typedef typename Map::iterator MapIterator;
	typedef typename Map::const_iterator ConstMapIterator;

	typedef MultiMapIterator<MapIterator, ListIterator, Tkey, Tvalue, Tcompare> iterator;
	typedef MultiMapIterator<ConstMapIterator, ConstListIterator, Tkey, const Tvalue, Tcompare> const_iterator;

	/**
	 * Erase the value pointed to by an iterator. The iterator may be invalid afterwards.
	 * @param it Iterator pointing at some value.
	 * @return Iterator to the element after the deleted one (or invalid).
	 */
	iterator erase(iterator it)
	{
		List &list = it.map_iter->second;
		assert(!list.empty());
		if (it.list_valid) {
			it.list_iter = list.erase(it.list_iter);
			/* This can't be the first list element as otherwise list_valid would have
			 * to be false. So the list cannot be empty here. */
			if (it.list_iter == list.end()) {
				++it.map_iter;
				it.list_valid = false;
			}
		} else {
			list.erase(list.begin());
			if (list.empty()) this->Map::erase(it.map_iter++);
		}
		return it;
	}

	/**
	 * Insert a value at the end of the range with the specified key.
	 * @param key Key to be inserted at.
	 * @param val Value to be inserted.
	 */
	void Insert(const Tkey &key, const Tvalue &val)
	{
		List &list = (*this)[key];
		list.push_back(val);
		assert(!list.empty());
	}

	/**
	 * Count all items in this MultiMap. This involves iterating over the map.
	 * @return Number of items in the MultiMap.
	 */
	size_t size() const
	{
		size_t ret = 0;
		for (ConstMapIterator it = this->Map::begin(); it != this->Map::end(); ++it) {
			ret += it->second.size();
		}
		return ret;
	}

	/**
	 * Count the number of ranges with equal keys in this MultiMap.
	 * @return Number of ranges with equal keys.
	 */
	size_t MapSize() const
	{
		return this->Map::size();
	}

	/**
	 * Get a pair of iterators specifying a range of items with equal keys.
	 * @param key Key to look for.
	 * @return Range of items with given key.
	 */
	std::pair<iterator, iterator> equal_range(const Tkey &key)
	{
		MapIterator begin(this->lower_bound(key));
		if (begin != this->Map::end() && begin->first == key) {
			MapIterator end = begin;
			return std::make_pair(begin, ++end);
		}
		return std::make_pair(begin, begin);
	}

	/**
	 * Get a pair of constant iterators specifying a range of items with equal keys.
	 * @param key Key to look for.
	 * @return Constant range of items with given key.
	 */
	std::pair<const_iterator, const_iterator> equal_range(const Tkey &key) const
	{
		ConstMapIterator begin(this->lower_bound(key));
		if (begin != this->Map::end() && begin->first == key) {
			ConstMapIterator end = begin;
			return std::make_pair(begin, ++end);
		}
		return std::make_pair(begin, begin);
	}
};

#endif /* MULTIMAP_HPP */
