use std::ops::Range;

use tinyvec::TinyVec;

use crate::table;

pub(crate) struct Buffer {
    buffer: TinyVec<[(u8, char); 4]>,
    ready: Range<usize>,
}

impl Buffer {
    pub(crate) fn new() -> Self {
        Self {
            buffer: TinyVec::new(),
            ready: 0..0,
        }
    }

    #[inline(always)]
    pub(crate) fn is_ready(&self) -> bool {
        self.ready.end != 0
    }

    #[inline(always)]
    pub(crate) fn is_empty(&self) -> bool {
        self.buffer.is_empty()
    }

    #[inline(always)]
    pub(crate) fn finish(&mut self) {
        self.sort_pending();
        self.ready.end = self.buffer.len();
    }

    #[inline(always)]
    pub(crate) fn push(&mut self, ch: char, class: u8) {
        if class == 0 {
            self.sort_pending();

            self.buffer.push((class, ch));
            self.ready.end = self.buffer.len();
        } else {
            self.buffer.push((class, ch));
        }
    }

    #[inline(always)]
    pub(crate) fn push_back(&mut self, ch: char) {
        self.push(ch, table::lookup(ch).combining_class());
    }

    #[inline(always)]
    pub(crate) fn pop(&mut self) -> char {
        let (_, ch) = self.buffer[self.ready.start];

        let next = self.ready.start + 1;

        if next == self.ready.end {
            self.reset();
        } else {
            self.ready.start = next;
        }

        ch
    }

    #[inline(always)]
    fn sort_pending(&mut self) {
        self.buffer[self.ready.end..].sort_by_key(|k| k.0);
    }

    #[inline(always)]
    fn reset(&mut self) {
        let pending = self.buffer.len() - self.ready.end;

        for i in 0..pending {
            self.buffer[i] = self.buffer[i + self.ready.end];
        }

        self.buffer.truncate(pending);
        self.ready = 0..0;
    }
}
