/* A range adapter that wraps multiple ranges
   Copyright (C) 2022-2023 Free Software Foundation, Inc.

   This file is part of GDB.

   This program 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.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */

#ifndef GDBSUPPORT_RANGE_CHAIN_H
#define GDBSUPPORT_RANGE_CHAIN_H

/* A range adapter that presents a number of ranges as if it were a
   single range.  That is, iterating over a range_chain will iterate
   over each sub-range in order.  */
template<typename Range>
struct range_chain
{
  /* The type of the iterator that is created by this range.  */
  class iterator
  {
  public:

    iterator (const std::vector<Range> &ranges, size_t idx)
      : m_index (idx),
	m_ranges (ranges)
    {
      skip_empty ();
    }

    bool operator== (const iterator &other) const
    {
      if (m_index != other.m_index || &m_ranges != &other.m_ranges)
	return false;
      if (m_current.has_value () != other.m_current.has_value ())
	return false;
      if (m_current.has_value ())
	return *m_current == *other.m_current;
      return true;
    }

    bool operator!= (const iterator &other) const
    {
      return !(*this == other);
    }

    iterator &operator++ ()
    {
      ++*m_current;
      if (*m_current == m_ranges[m_index].end ())
	{
	  ++m_index;
	  skip_empty ();
	}
      return *this;
    }

    typename Range::iterator::value_type operator* () const
    {
      return **m_current;
    }

  private:
    /* Skip empty sub-ranges.  If this finds a valid sub-range,
       m_current is updated to point to its start; otherwise,
       m_current is reset.  */
    void skip_empty ()
    {
      for (; m_index < m_ranges.size (); ++m_index)
	{
	  m_current = m_ranges[m_index].begin ();
	  if (*m_current != m_ranges[m_index].end ())
	    return;
	}
      m_current.reset ();
    }

    /* Index into the vector indicating where the current iterator
       comes from.  */
    size_t m_index;
    /* The current iterator into one of the vector ranges.  If no
       value then this (outer) iterator is at the end of the overall
       range.  */
    gdb::optional<typename Range::iterator> m_current;
    /* Vector of ranges.  */
    const std::vector<Range> &m_ranges;
  };

  /* Create a new range_chain.  */
  template<typename T>
  range_chain (T &&ranges)
    : m_ranges (std::forward<T> (ranges))
  {
  }

  iterator begin () const
  {
    return iterator (m_ranges, 0);
  }

  iterator end () const
  {
    return iterator (m_ranges, m_ranges.size ());
  }

private:

  /* The sub-ranges.  */
  std::vector<Range> m_ranges;
};

#endif /* GDBSUPPORT_RANGE_CHAIN_H */
