/*
 * (C) Copyright 1996- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#ifndef eckit_DenseMap_h
#define eckit_DenseMap_h

/// @author Tiago Quintino

#include <deque>
#include <algorithm>
#include <utility>

#include "eckit/exception/Exceptions.h"

//-----------------------------------------------------------------------------

namespace eckit {

//-----------------------------------------------------------------------------

template < typename K, typename V >
class DenseMap {
public: // types

	typedef K key_type;     ///< key type
	typedef V value_type;   ///< value type

	typedef std::pair<K, V> item_type; ///< (key, value) item type

private: // types

	typedef std::deque< item_type >      store_t;

public: // methods

	typedef typename store_t::iterator iterator;
	typedef typename store_t::const_iterator const_iterator;

	DenseMap() : sorted_(true) {}

	~DenseMap() {}

	void insert( const K& k, const V& v )
	{
		items_.push_back( std::make_pair(k, v) );
		sorted_ = false;
	}

    /// @TODO shoudl we implement this?
    //	size_t erase( const key_type& k );

	void replace( const K& k, const V& v )
	{
		iterator it = find(k);
		if( it != end() )
		{
			it->second = v;
		}
		else
		{
			insert(k,v);
		}
	}

	void clear()
	{
		items_.clear();
		sorted_ = true;
	}

	bool sorted() const { return sorted_; }

	size_t size() const { ASSERT(sorted_); return items_.size(); }
	bool empty() const { return items_.empty(); }

	void sort()
	{
		if(sorted_) return;

		std::stable_sort( items_.begin(), items_.end(), LessThan() );
		auto last = std::unique(items_.rbegin(), items_.rend(), Equals());
		auto position = std::distance(last, items_.rend());
		items_.erase(items_.begin(), items_.begin() + position);
		sorted_ = true;
	}

	iterator begin() { return items_.begin(); }
	const_iterator begin() const { return items_.begin(); }
	const_iterator cbegin() const { return items_.cbegin(); }

	iterator end() { return items_.end(); }
	const_iterator end() const { return items_.end(); }
	const_iterator cend() const { return items_.cend(); }

	bool contains( const K& k ) const { return find(k) != cend(); }

	const value_type& get( const K& k ) const { return find(k)->second; }
	value_type& get( const K& k ) { return find(k)->second; }

	const value_type& at( const size_t i ) const { ASSERT(i < items_.size()); return items_[i].second; }
	value_type& at( const size_t i ) { ASSERT(i < items_.size()); return items_[i].second; }

	const value_type& operator[] (const K& k ) const { return find(k)->second; }
	value_type& operator[] (const K& k ) { return find(k)->second; }

	const value_type& operator[] (const size_t& i ) const { return items_[i].second; }
	value_type& operator[] (const size_t& i ) { return items_[i].second; }

	iterator find( const K& k )
	{
		if( empty() )
			return end();

		ASSERT(sorted());
		iterator it = std::lower_bound( begin(), end(), k, Compare());
		if( it != end() && it->first == k )
		  return it;

		return end();
	}

	const_iterator find( const K& k ) const
	{
		if( empty() )
			return cend();

		ASSERT(sorted());
		const_iterator it = std::lower_bound( cbegin(), cend(), k, Compare());
		if( it != cend() && it->first == k )
		  return it;

		return cend();
	}

	void print(std::ostream& s) const
	{
		const_iterator it = cbegin();
		for( ; it != cend(); ++it )
			s << it->first << " " << it->second << std::endl;
	}

	friend std::ostream& operator<<(std::ostream& s, const DenseMap& m) { m.print(s);  return s; }

private: // types

	class LessThan {
	public:
		bool operator() (const item_type& e1, const item_type& e2) const
		{
			return (e1.first < e2.first) ? true : false;
		}
	};

	class Equals {
	public:
		bool operator() (const item_type& e1, const item_type& e2) const
		{
			return (e1.first == e2.first) ? true : false;
		}
	};

	class Compare {
	public:
		bool operator() (const item_type& e, const K& k) const
		{
			return (e.first < k) ? true : false;
		}
		bool operator() (const K& k, const item_type& e) const
		{
			return (e.first > k) ? true : false;
		}
	};

private: // members

	store_t   items_;   ///< storage of the items

	bool sorted_;

};

//-----------------------------------------------------------------------------

} // namespace eckit

#endif
