//! Extracts a subsequence of a text document, remembering the mapping of ranges
//! between original and extracted texts.
use std::ops::{self, Sub};

use stdx::equal_range_by;
use syntax::{TextRange, TextSize};

#[derive(Default)]
pub(super) struct Injector {
    buf: String,
    ranges: Vec<(TextRange, Option<Delta<TextSize>>)>,
}

impl Injector {
    pub(super) fn add(&mut self, text: &str, source_range: TextRange) {
        let len = TextSize::of(text);
        assert_eq!(len, source_range.len());
        self.add_impl(text, Some(source_range.start()));
    }
    pub(super) fn add_unmapped(&mut self, text: &str) {
        self.add_impl(text, None);
    }
    fn add_impl(&mut self, text: &str, source: Option<TextSize>) {
        let len = TextSize::of(text);
        let target_range = TextRange::at(TextSize::of(&self.buf), len);
        self.ranges.push((target_range, source.map(|it| Delta::new(target_range.start(), it))));
        self.buf.push_str(text);
    }

    pub(super) fn text(&self) -> &str {
        &self.buf
    }
    pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
        equal_range_by(&self.ranges, |&(r, _)| TextRange::ordering(r, range)).filter_map(move |i| {
            let (target_range, delta) = self.ranges[i];
            let intersection = target_range.intersect(range).unwrap();
            Some(intersection + delta?)
        })
    }
}

#[derive(Clone, Copy)]
enum Delta<T> {
    Add(T),
    Sub(T),
}

impl<T> Delta<T> {
    fn new(from: T, to: T) -> Delta<T>
    where
        T: Ord + Sub<Output = T>,
    {
        if to >= from {
            Delta::Add(to - from)
        } else {
            Delta::Sub(from - to)
        }
    }
}

impl ops::Add<Delta<TextSize>> for TextSize {
    type Output = TextSize;

    fn add(self, rhs: Delta<TextSize>) -> TextSize {
        match rhs {
            Delta::Add(it) => self + it,
            Delta::Sub(it) => self - it,
        }
    }
}

impl ops::Add<Delta<TextSize>> for TextRange {
    type Output = TextRange;

    fn add(self, rhs: Delta<TextSize>) -> TextRange {
        TextRange::at(self.start() + rhs, self.len())
    }
}
