//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#ifndef UTIL_SET_VECTOR_H
#define UTIL_SET_VECTOR_H

#include <Config.hpp>

#include <algorithm>
#include <functional>
#include <vector>

#ifdef USE_CXX11
#  include <initializer_list>
#  define NOEXCEPT noexcept
#  define STD_FORWARD(T, V) std::forward<T>(V)
#else
#  define NOEXCEPT
#  define STD_FORWARD(T, V) (V)
#endif

namespace mgx 
{
  /**
   * \class multiset_vector SetVector.hpp <SetVector.hpp>
   *
   * Implementation of a multiset using a sorted vector as container.
   */
  template <typename Key, typename Compare = std::less<Key>, class Allocator = std::allocator<Key> >
  class multiset_vector {
    typedef std::vector<Key, Allocator> content_t;
    content_t _content;
    Compare _compare;
  
  public:
    typedef Key key_type;
    typedef Key value_type;
    typedef Compare key_compare;
    typedef Compare value_compare;
    typedef Allocator allocator_type;
    typedef value_type& reference;
    typedef const value_type& const_reference;
  
  #ifdef USE_CXX11
    typedef typename std::allocator_traits<allocator_type>::pointer pointer;
    typedef typename std::allocator_traits<allocator_type>::const_pointer const_pointer;
  #else
    typedef typename allocator_type::pointer pointer;
    typedef typename allocator_type::const_pointer const_pointer;
  #endif
  
    typedef typename content_t::const_iterator iterator;
    typedef typename content_t::const_iterator const_iterator;
    typedef typename content_t::size_type size_type;
    typedef typename content_t::difference_type difference_type;
  
    typedef std::reverse_iterator<iterator> reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
  
    //@{
    ///\name Construct/copy/destroy
    explicit multiset_vector(const Compare& comp = Compare(), const Allocator& alloc = Allocator())
      : _content(alloc)
      , _compare(comp)
    {
    }
  
    template <typename InputIterator>
    multiset_vector(InputIterator first, InputIterator last, const Compare& comp = Compare(),
                    const Allocator& alloc = Allocator())
      : _content(first, last, alloc)
      , _compare(comp)
    {
      sort();
    }
  
  #ifdef USE_CXX11
    multiset_vector(const multiset_vector&) = default;
    multiset_vector(multiset_vector&&) = default;
  #else
    multiset_vector(const multiset_vector& copy)
      : _content(copy._content)
      , _compare(copy._compare)
    {
    }
  #endif
  
    explicit multiset_vector(const Allocator& alloc)
      : _content(alloc)
      , _compare()
    {
    }
  
    multiset_vector(const multiset_vector& copy, const Allocator& alloc)
      : _content(copy._content, alloc)
      , _compare(copy._compare)
    {
    }
  
  #ifdef USE_CXX11
    multiset_vector(multiset_vector&& copy, const Allocator& alloc)
      : _content(std::move(copy), alloc)
      , _compare(std::move(copy._compare))
    {
    }
  
    multiset_vector(std::initializer_list<value_type> values, const Compare& comp = Compare(),
                    const Allocator& alloc = Allocator())
      : multiset_vector(values.begin(), values.end(), comp, alloc)
    {
    }
  #endif
  
    ~multiset_vector() {
    }
  
  #ifdef USE_CXX11
    multiset_vector& operator=(const multiset_vector&) = default;
    multiset_vector& operator=(multiset_vector&&) = default;
    multiset_vector& operator=(std::initializer_list<value_type> content)
    {
      _content.assign(content.begin(), content.end());
      sort();
      return *this;
    }
  #else
    multiset_vector& operator=(const multiset_vector& copy)
    {
      _content = copy._content;
      _compare = copy._compare;
      return *this;
    }
  #endif
  
    allocator_type get_allocator() const NOEXCEPT {
      return _content.get_allocator();
    }
  
    //@}
    //@{
    ///\name Iterators
  
    iterator begin() NOEXCEPT {
      return _content.begin();
    }
    iterator end() NOEXCEPT {
      return _content.end();
    }
  
    const_iterator begin() const NOEXCEPT {
      return _content.begin();
    }
    const_iterator end() const NOEXCEPT {
      return _content.end();
    }
  
    reverse_iterator rbegin() NOEXCEPT {
      return _content.rbegin();
    }
    reverse_iterator rend() NOEXCEPT {
      return _content.rend();
    }
  
    const_reverse_iterator rbegin() const NOEXCEPT {
      return _content.rbegin();
    }
    const_reverse_iterator rend() const NOEXCEPT {
      return _content.rend();
    }
  
    const_iterator cbegin() const NOEXCEPT {
      return _content.begin();
    }
    const_iterator cend() const NOEXCEPT {
      return _content.end();
    }
  
    const_reverse_iterator crbegin() const NOEXCEPT {
      return _content.rbegin();
    }
    const_reverse_iterator crend() const NOEXCEPT {
      return _content.rend();
    }
  
    //@}
  
    //@{
    ///\name Vector-like access
  
    const_reference operator[](size_type i) const {
      return _content[i];
    }
  
    const_reference at(size_type i) const {
      return _content.at(i);
    }
  
    const_pointer data() const NOEXCEPT {
      return _content.data();
    }
  
    const_reference front() const {
      return _content.front();
    }
  
    const_reference back() const {
      return _content.back();
    }
  
    /// Return the underlying vector
    const content_t& vector() const {
      return _content;
    }
  
    //@}
  
    //@{
    ///\name Capacity
  
    bool empty() const NOEXCEPT {
      return _content.empty();
    }
    size_type size() const NOEXCEPT {
      return _content.size();
    }
    size_type max_size() const NOEXCEPT {
      return _content.max_size();
    }
  
  #ifdef USE_CXX11
    void shrink_to_fit() {
      _content.shrink_to_fit();
    }
  #endif
  
    void reserve(size_type n) {
      _content.reserve(n);
    }
  
  //@}
  //@{
  ///\name Modifiers
  
  #ifdef USE_CXX11
    template <typename ... Args> iterator emplace(Args&& ... args)
    {
      auto value = value_type(std::forward<Args>(args) ...);
      return this->_insert(std::move(value));
    }
    template <typename ... Args> iterator emplace_hint(const_iterator hint, Args&& ... args)
    {
      auto value = value_type(std::forward<Args>(args) ...);
      return this->_insert_hint(hint, std::move(value));
    }
  
    iterator insert(value_type&& value) {
      return this->_insert(std::move(value));
    }
  
    iterator insert(const_iterator pos, value_type&& value) {
      return this->_insert_hint(pos, value);
    }
  
    void insert(std::initializer_list<value_type> values)
    {
      for(const value_type& v : values)
        this->_insert(v);
    }
  
  #endif
  
    iterator insert(const value_type& value) {
      return this->_insert(value);
    }
  
    iterator insert(const_iterator pos, const value_type& value) {
      return this->_insert_hint(pos, value);
    }
  
    template <typename InputIterator> void insert(InputIterator first, InputIterator last)
    {
      if(this->empty()) {
        _content.assign(first, last);
        this->sort();
      } else {
        for(InputIterator it = first; it != last; ++it)
          this->_insert(*it);
      }
    }
  
    iterator erase(const_iterator position) {
      return _content.erase(this->remove_iterator_const(position));
    }
  
    size_type erase(const key_type& x)
    {
      const_iterator low = this->lower_bound(x);
      const_iterator up = this->upper_bound(x);
      if(low != up) {
        size_type n = std::distance(low, up);
        this->erase(low, up);
        return n;
      }
      return 0u;
    }
  
    iterator erase(const_iterator first, const_iterator last)
    {
      return _content.erase(remove_iterator_const(first), remove_iterator_const(last));
    }
  
    void swap(multiset_vector& other)
    {
      using std::swap;
      swap(_content, other._content);
    }
  
    void clear() NOEXCEPT {
      _content.clear();
    }
  
    //@}
    //@{
    ///\name Observers
  
    key_compare key_comp() const {
      return _compare;
    }
    value_compare value_comp() const {
      return _compare;
    }
  
    //@}
  
    //@{
    ///\name Set operations
  
    iterator find(const key_type& k)
    {
      return this->remove_iterator_const(const_cast<const multiset_vector*>(this)->find(k));
    }
  
    const_iterator find(const key_type& k) const
    {
      const_iterator pos = std::lower_bound(this->begin(), this->end(), k, this->key_comp());
      if(pos != this->end() and this->equiv_keys(*pos, k))
        return pos;
      return this->end();
    }
  
    size_type count(const key_type& k) const
    {
      const_iterator low = this->lower_bound(k);
      const_iterator up = this->upper_bound(k);
      return std::distance(low, up);
    }
  
    iterator lower_bound(const key_type& k)
    {
      return this->remove_iterator_const(const_cast<const multiset_vector*>(this)->lower_bound(k));
    }
  
    const_iterator lower_bound(const key_type& k) const
    {
      return std::lower_bound(this->begin(), this->end(), k, this->key_comp());
    }
  
    iterator upper_bound(const key_type& k)
    {
      return this->remove_iterator_const(const_cast<const multiset_vector*>(this)->upper_bound(k));
    }
  
    const_iterator upper_bound(const key_type& k) const
    {
      return std::upper_bound(this->begin(), this->end(), k, this->key_comp());
    }
  
    std::pair<iterator, iterator> equal_range(const key_type& k)
    {
      typedef std::pair<const_iterator, const_iterator> const_result_t;
      const_result_t res = const_cast<const multiset_vector*>(this)->equal_range(k);
      typedef std::pair<iterator, iterator> result_t;
      return result_t(this->remove_iterator_const(res.first), this->remove_iterator_const(res.second));
    }
  
    std::pair<const_iterator, const_iterator> equal_range(const key_type& k) const
    {
      typedef std::pair<const_iterator, const_iterator> result_t;
      return result_t(this->lower_bound(k), this->upper_bound(k));
    }
  
    friend bool operator==(const multiset_vector& v1, const multiset_vector& v2)
    {
      if(v1.size() != v2.size())
        return false;
      for(const_iterator it1 = v1.begin(), it2 = v2.begin(); it1 != v1.end(); ++it1, ++it2)
        if(v1.differ_keys(*it1, *it2))
          return false;
      return true;
    }
  
    friend bool operator!=(const multiset_vector& v1, const multiset_vector& v2) {
      return not (v1 == v2);
    }
  
    friend bool operator<(const multiset_vector& v1, const multiset_vector& v2)
    {
      const_iterator it1 = v1.begin(), it2 = v2.begin();
      const_iterator last_v1 = v1.end(), last_v2 = v2.end();
      for(; it1 != last_v1 and it2 != last_v2; ++it1, ++it2) {
        if(v1.compare(*it1, *it2))
          return true;
        else if(v1.compare(*it2, *it1))
          return false;
      }
      if(it2 != last_v2)
        return true;
      return false;
    }
  
    friend bool operator>(const multiset_vector& v1, const multiset_vector& v2)
    {
      const_iterator it1 = v1.begin(), it2 = v2.begin();
      const_iterator last_v1 = v1.end(), last_v2 = v2.end();
      for(; it1 != last_v1 and it2 != last_v2; ++it1, ++it2) {
        if(v1.compare(*it1, *it2))
          return false;
        else if(v1.compare(*it2, *it1))
          return true;
      }
      if(it1 != last_v1)
        return true;
      return false;
    }
  
    friend bool operator<=(const multiset_vector& v1, const multiset_vector& v2) {
      return not (v1 > v2);
    }
  
    friend bool operator>=(const multiset_vector& v1, const multiset_vector& v2) {
      return not (v1 < v2);
    }
  
  protected:
    typename content_t::iterator remove_iterator_const(const_iterator it) {
      return _content.begin() + (it - begin());
    }
  
    void sort()
    {
      if(_content.size() == 0)
        return;
      std::sort(_content.begin(), _content.end(), this->key_comp());
    }
  
    iterator _find_insert_range(const_iterator first, const_iterator last, const value_type& value) const
    {
      return std::upper_bound(first, last, value, this->key_comp());
    }
  
    const_iterator _find_insert_hint(const_iterator hint, const value_type& value)
    {
      if(hint == this->end()) {
        if(size() > 0) {
          if(this->compare(this->back(), value))
            return this->end();
          else
            return this->_find_insert_range(this->begin(), this->end(), value);
        }
        return this->end();
      }
      if(this->compare(value, *hint)) {     // Try just before
        if(hint == this->begin())
          return hint;
        const_iterator before = hint;
        if(this->compare(*(--before), value))
          return hint;
        return this->_find_insert_range(this->begin(), before, value);
      } else if(this->compare(*hint, value)) {     // Try just after
        const_iterator after = hint;
        if(++after == this->end() or this->compare(value, *after))
          return after;
        return this->_find_insert_range(after, this->end(), value);
      } else
        return hint;
    }
  
    template <typename T>
  #ifdef USE_CXX11
    iterator _insert_vector(const_iterator it, T&& value)
  #else
    iterator _insert_vector(const_iterator it, const T& value)
  #endif
    {
      return _content.insert(this->remove_iterator_const(it), STD_FORWARD(T, value));
    }
  
    template <typename T>
  #ifdef USE_CXX11
    iterator _insert(T&& value)
  #else
    iterator _insert(const T& value)
  #endif
    {
      iterator found = this->_find_insert_range(this->begin(), this->end(), value);
      return this->_insert_vector(found, STD_FORWARD(T, value));
    }
  
    template <typename T>
  #ifdef USE_CXX11
    iterator _insert_hint(const_iterator hint, T&& value)
  #else
    iterator _insert_hint(const_iterator hint, const T& value)
  #endif
    {
      iterator found = this->_find_insert_hint(hint, value);
      return this->_insert_vector(found, STD_FORWARD(T, value));
    }
  
    bool equiv_keys(const key_type& k1, const key_type& k2) const
    {
      return not (this->compare(k1, k2) or this->compare(k2, k1));
    }
  
    bool differ_keys(const key_type& k1, const key_type& k2) const
    {
      return this->compare(k1, k2) or this->compare(k2, k1);
    }
  
    bool compare(const key_type& k1, const key_type& k2) const {
      return _compare(k1, k2);
    }
  };
  
  template <typename Key, typename Compare, typename Allocator>
  void swap(multiset_vector<Key, Compare, Allocator>& v1, multiset_vector<Key, Compare, Allocator>& v2)
  {
    v1.swap(v2);
  }
  
  /**
   * \class set_vector SetVector.hpp <SetVector.hpp>
   *
   * Implementation of a set using a sorted vector as container.
   */
  template <typename Key, typename Compare = std::less<Key>, class Allocator = std::allocator<Key> >
  class set_vector : public multiset_vector<Key, Compare, Allocator> {
    typedef multiset_vector<Key, Compare, Allocator> super_type;
  
  public:
    typedef typename super_type::key_type key_type;
    typedef typename super_type::value_type value_type;
    typedef typename super_type::key_compare key_compare;
    typedef typename super_type::value_compare value_compare;
    typedef typename super_type::allocator_type allocator_type;
    typedef typename super_type::reference reference;
    typedef typename super_type::const_reference const_reference;
  
    typedef typename super_type::pointer pointer;
    typedef typename super_type::const_pointer const_pointer;
  
    typedef typename super_type::iterator iterator;
    typedef typename super_type::const_iterator const_iterator;
    typedef typename super_type::size_type size_type;
    typedef typename super_type::difference_type difference_type;
  
    typedef typename super_type::reverse_iterator reverse_iterator;
    typedef typename super_type::const_reverse_iterator const_reverse_iterator;
  
    //@{
    ///\name Construct/copy/destroy
    explicit set_vector(const Compare& comp = Compare(), const Allocator& alloc = Allocator())
      : super_type(comp, alloc)
    {
    }
  
    template <typename InputIterator>
    set_vector(InputIterator first, InputIterator last, const Compare& comp = Compare(),
               const Allocator& alloc = Allocator())
      : super_type(first, last, comp, alloc)
    {
      this->clean();
    }
  
  #ifdef USE_CXX11
    set_vector(const set_vector&) = default;
    set_vector(set_vector&&) = default;
  #else
    set_vector(const set_vector& copy)
      : super_type(copy)
    {
    }
  #endif
  
    explicit set_vector(const Allocator& alloc)
      : super_type(alloc)
    {
    }
  
    set_vector(const set_vector& copy, const Allocator& alloc)
      : super_type(copy, alloc)
    {
    }
  
  #ifdef USE_CXX11
    set_vector(set_vector&& copy, const Allocator& alloc)
      : super_type(std::move(copy), alloc)
    {
    }
  
    set_vector(std::initializer_list<value_type> values, const Compare& comp = Compare(),
               const Allocator& alloc = Allocator())
      : set_vector(values.begin(), values.end(), comp, alloc)
    {
    }
  #endif
  
    ~set_vector() {
    }
  
  #ifdef USE_CXX11
    set_vector& operator=(const set_vector&) = default;
    set_vector& operator=(set_vector&&) = default;
    set_vector& operator=(std::initializer_list<value_type> content)
    {
      super_type::operator=(content);
      this->clean();
      return *this;
    }
  #else
    set_vector& operator=(const set_vector& copy)
    {
      super_type::operator=(copy);
      return *this;
    }
  #endif
  
  //@}
  
  //@{
  ///\name Modifiers
  
  #ifdef USE_CXX11
    template <typename ... Args> std::pair<iterator, bool> emplace(Args&& ... args)
    {
      auto value = value_type(std::forward<Args>(args) ...);
      return this->_insert(std::move(value));
    }
    template <typename ... Args> std::pair<iterator, bool> emplace_hint(const_iterator hint, Args&& ... args)
    {
      auto value = value_type(std::forward<Args>(args) ...);
      return this->_insert_hint(hint, std::move(value));
    }
  
    std::pair<iterator, bool> insert(value_type&& value) {
      return this->_insert(std::move(value));
    }
  
    std::pair<iterator, bool> insert(const_iterator pos, value_type&& value) {
      return this->_insert_hint(pos, value);
    }
  
    void insert(std::initializer_list<value_type> values)
    {
      for(const value_type& v : values)
        this->_insert(v);
    }
  
  #endif
  
    std::pair<iterator, bool> insert(const value_type& value) {
      return this->_insert(value);
    }
  
    std::pair<iterator, bool> insert(const_iterator pos, const value_type& value)
    {
      return this->_insert_hint(pos, value);
    }
  
    template <typename InputIterator> void insert(InputIterator first, InputIterator last)
    {
      for(InputIterator it = first; it != last; ++it)
        this->_insert(*it);
    }
  
    using super_type::erase;
  
    size_type erase(const key_type& x)
    {
      const_iterator it = std::lower_bound(this->begin(), this->end(), x, this->key_comp());
      if(it != this->end() and this->equiv_keys(*it, x)) {
        this->erase(it);
        return 1u;
      }
      return 0u;
    }
  
    void swap(set_vector& other) {
      super_type::swap(other);
    }
  
    //@}
  
  private:
    void clean()
    {
      size_type s = this->size();
      if(s == 0)
        return;
      for(size_t i = s - 1; i > 0; --i) {
        if(this->equiv_keys((*this)[i], (*this)[i - 1]))
          this->erase(this->begin() + i);
      }
    }
  
    std::pair<iterator, bool> _find_insert_range(const_iterator first, const_iterator last,
                                                 const value_type& value) const
    {
      typedef std::pair<const_iterator, bool> result_t;
      const_iterator pos = std::lower_bound(first, last, value, this->key_comp());
      return result_t(pos, (pos == last or this->differ_keys(*pos, value)));
    }
  
    std::pair<const_iterator, bool> _find_insert_hint(const_iterator hint, const value_type& value)
    {
      typedef std::pair<iterator, bool> result_t;
      if(hint == this->end()) {
        if(this->size() > 0) {
          if(this->compare(this->back(), value))
            return result_t(this->end(), true);
          else
            return _find_insert_range(this->begin(), this->end(), value);
        }
        return result_t(this->end(), value);
      }
      if(this->compare(value, *hint)) {     // Try just before
        if(hint == this->begin())
          return result_t(hint, true);
        const_iterator before = hint;
        if(this->compare(*(--before), value))
          return result_t(hint, true);
        return this->_find_insert_range(this->begin(), before, value);
      } else if(this->compare(*hint, value)) {     // Try just after
        const_iterator after = hint;
        if(++after == this->end() or this->compare(value, *after))
          return result_t(after, true);
        return this->_find_insert_range(after, this->end(), value);
      } else
        return result_t(hint, false);
    }
  
  #ifdef USE_CXX11
    template <typename T> std::pair<iterator, bool> _insert(T&& value)
  #else
    std::pair<iterator, bool> _insert(const value_type& value)
  #endif
    {
      std::pair<iterator, bool> found = this->_find_insert_range(this->begin(), this->end(), value);
      if(found.second)
        found.first = this->_insert_vector(found.first, STD_FORWARD(T, value));
      return found;
    }
  
  #ifdef USE_CXX11
    template <typename T> std::pair<iterator, bool> _insert_hint(const_iterator hint, T&& value)
  #else
    std::pair<iterator, bool> _insert_hint(const_iterator hint, const value_type& value)
  #endif
    {
      std::pair<iterator, bool> found = this->_find_insert_hint(hint, value);
      if(found.second)
        found.first = this->_insert_vector(found.first, STD_FORWARD(T, value));
      return found;
    }
  };
  
  template <typename Key, typename Compare, typename Allocator>
  void swap(set_vector<Key, Compare, Allocator>& v1, set_vector<Key, Compare, Allocator>& v2)
  {
    v1.swap(v2);
  }
  
  #undef STD_FORWARD
  #undef NOEXCEPT
}
#endif
