// Copyright (c) 2021 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use std::ops::Range;

/// A set of ordered types.
///
/// Implemented as an ordered list of ranges that do not touch or overlap.
#[derive(Clone, Debug, Default)]
pub struct RangeSet<T>(Vec<Range<T>>);

impl<T: Ord + Copy> RangeSet<T> {
    /// Returns a new empty `RangeSet`.
    #[inline]
    pub fn new() -> Self {
        RangeSet(Vec::new())
    }

    /// Returns whether all elements of `range` are contained in the set.
    #[inline]
    pub fn contains(&self, elements: Range<T>) -> bool {
        self.0
            .iter()
            .any(|range| range.start <= elements.end && range.end >= elements.end)
    }

    /// Removes all ranges from the set.
    #[inline]
    pub fn clear(&mut self) {
        self.0.clear();
    }

    /// Inserts the elements of `range` into the set.
    pub fn insert(&mut self, elements: Range<T>) {
        // Find the first range that is not less than `elements`, and the first range that is greater.
        let index_start = self
            .0
            .iter()
            .position(|range| range.end >= elements.start)
            .unwrap_or(self.0.len());
        let index_end = self
            .0
            .iter()
            .position(|range| range.start > elements.end)
            .unwrap_or(self.0.len());

        if index_start == index_end {
            // `elements` fits in between index_start - 1 and index_start.
            self.0.insert(index_start, elements);
        } else {
            // `elements` touches the ranges between index_start and index_end.
            // Expand `elements` with the touched ranges, then store it in the first.
            self.0[index_start] = self.0[index_start..index_end]
                .iter()
                .fold(elements, |Range { start, end }, range| {
                    start.min(range.start)..end.max(range.end)
                });
            // Delete the remaining elements.
            self.0.drain(index_start + 1..index_end);
        }
    }
}
