//! Lines in a file.

use std::borrow::Cow;
use std::cmp::Ordering;
use std::str;
use std::sync::{Arc, Mutex};
use std::num::NonZeroUsize;

use lru::LruCache;
use regex::bytes::{NoExpand, Regex};
use smallvec::SmallVec;
use termwiz::cell::{CellAttributes, Intensity};
use termwiz::color::{AnsiColor, ColorAttribute};
use termwiz::escape::csi::{Edit, EraseInLine, Sgr, CSI};
use termwiz::escape::esc::{Esc, EscCode};
use termwiz::escape::osc::OperatingSystemCommand;
use termwiz::escape::parser::Parser;
use termwiz::escape::Action;
use termwiz::hyperlink::Hyperlink;
use termwiz::surface::{change::Change, Position};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

use crate::config::WrappingMode;
use crate::line_drawing;
use crate::overstrike;
use crate::search::{trim_trailing_newline, ESCAPE_SEQUENCE};
use crate::util;

const LEFT_ARROW: &str = "<";
const RIGHT_ARROW: &str = ">";
const TAB_SPACES: &str = "        ";

const WRAPS_CACHE_SIZE: usize = 4;

/// Line wrap in the cache are uniquely identified by index and wrapping mode.
type WrapCacheIndex = (usize, WrappingMode);

/// Line wraps in the cache are represented by a list of start and end offsets.
type WrapCacheItem = Vec<(usize, usize)>;

/// Line wraps in the cache are represented by a list of start and end offsets.
type WrapCacheItemRef<'a> = &'a [(usize, usize)];

/// Represents a single line in a displayed file.
#[derive(Debug, Clone)]
pub(crate) struct Line {
    spans: Box<[Span]>,
    wraps: Arc<Mutex<LruCache<WrapCacheIndex, WrapCacheItem>>>,
}

/// Style that is being applied.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OutputStyle {
    /// The source file's output style.
    File,
    /// Control characters style (inverse video).
    Control,
    /// A search match.
    Match,
    /// The currently selected search match.
    CurrentMatch,
}

/// Tracker of current attributes state.
struct AttributeState {
    /// Current attributes for the file
    attrs: CellAttributes,
    /// Whether DEC line drawing mode is currently enabled
    line_drawing: bool,
    /// Whether the file's attributes have changed
    changed: bool,
    /// What the currently applied style is.
    style: OutputStyle,
    /// What color the end of the line should be
    end_of_line: ColorAttribute,
}

impl AttributeState {
    /// Create a new color state tracker.
    fn new() -> AttributeState {
        AttributeState {
            attrs: CellAttributes::default(),
            line_drawing: false,
            changed: false,
            style: OutputStyle::File,
            end_of_line: ColorAttribute::default(),
        }
    }

    /// Apply a sequence of Sgr escape codes onto the attribute state.
    fn apply_sgr_sequence(&mut self, sgr_sequence: &[Sgr]) {
        for sgr in sgr_sequence.iter() {
            match *sgr {
                Sgr::Reset => {
                    // Reset doesn't clear the hyperlink.
                    let hyperlink = self.attrs.hyperlink().cloned();
                    self.attrs = CellAttributes::default();
                    self.attrs.set_hyperlink(hyperlink);
                }
                Sgr::Intensity(intensity) => {
                    self.attrs.set_intensity(intensity);
                }
                Sgr::Underline(underline) => {
                    self.attrs.set_underline(underline);
                }
                Sgr::Blink(blink) => {
                    self.attrs.set_blink(blink);
                }
                Sgr::Italic(italic) => {
                    self.attrs.set_italic(italic);
                }
                Sgr::Inverse(inverse) => {
                    self.attrs.set_reverse(inverse);
                }
                Sgr::Invisible(invis) => {
                    self.attrs.set_invisible(invis);
                }
                Sgr::StrikeThrough(strike) => {
                    self.attrs.set_strikethrough(strike);
                }
                Sgr::Foreground(color) => {
                    self.attrs.set_foreground(color);
                }
                Sgr::Background(color) => {
                    self.attrs.set_background(color);
                }
                Sgr::Font(_) => {}
                Sgr::UnderlineColor(color) => {
                    self.attrs.set_underline_color(color);
                }
                Sgr::Overline(enable) => {
                    self.attrs.set_overline(enable);
                }
                Sgr::VerticalAlign(align) => {
                    self.attrs.set_vertical_align(align);
                }
            }
        }
        self.changed = true;
    }

    /// Apply a hyperlink escape code onto the attribute state.
    fn apply_hyperlink(&mut self, hyperlink: &Option<Arc<Hyperlink>>) {
        self.attrs.set_hyperlink(hyperlink.clone());
        self.changed = true;
    }

    /// Switch to the given style.  The correct escape color sequences will be emitted.
    fn style(&mut self, style: OutputStyle) -> Option<Change> {
        if self.style != style || self.changed {
            let attrs = match style {
                OutputStyle::File => self.attrs.clone(),
                OutputStyle::Control => CellAttributes::default().set_reverse(true).clone(),
                OutputStyle::Match => self
                    .attrs
                    .clone()
                    .set_foreground(AnsiColor::Black)
                    .set_background(AnsiColor::Olive)
                    .set_intensity(Intensity::Normal)
                    .clone(),
                OutputStyle::CurrentMatch => self
                    .attrs
                    .clone()
                    .set_foreground(AnsiColor::Black)
                    .set_background(AnsiColor::Teal)
                    .set_intensity(Intensity::Normal)
                    .clone(),
            };
            self.style = style;
            self.changed = false;
            Some(Change::AllAttributes(attrs))
        } else {
            None
        }
    }
}

/// A span of text within a line.
#[derive(Debug, Clone, PartialEq, Eq)]
enum Span {
    /// Ordinary text.
    Text(String),
    /// Text that matches the current search, and the search match index.
    Match(String, usize),
    /// A control character.
    Control(u8),
    /// An invalid UTF-8 byte.
    Invalid(u8),
    /// An unprintable unicode grapheme cluster.
    Unprintable(String),
    /// A sequence of SGR escape codes.
    SgrSequence(SmallVec<[Sgr; 5]>),
    /// A hyperlink escape code.
    Hyperlink(Option<Arc<Hyperlink>>),
    /// A DEC line drawing mode escape code.
    LineDrawing(bool),
    /// Data that should be ignored.
    Ignore(SmallVec<[u8; 20]>),
    /// A tab control character.
    Tab,
    /// A terminating CRLF sequence.
    CrLf,
    /// A terminating LF sequence.
    Lf,
    /// An erase-to-end-of-line sequence.
    EraseToEndOfLine,
}

/// Produce `Change`s to output some text in the given style at the given
/// position, truncated to the start and end columns.
///
/// Returns the new position after the text has been rendered.
fn write_truncated(
    changes: &mut Vec<Change>,
    attr_state: &mut AttributeState,
    style: OutputStyle,
    text: &str,
    start: usize,
    end: usize,
    position: usize,
) -> usize {
    let text_width = text.width();
    if position + text_width > start && position < end {
        if let Some(change) = attr_state.style(style) {
            changes.push(change);
        }
        let start = start.saturating_sub(position);
        let end = end.saturating_sub(position);
        changes.push(Change::Text(util::truncate_string(
            text,
            start,
            end - start,
        )));
    }
    position + text_width
}

struct SplitWords<'t> {
    text: &'t str,
}

impl<'t> SplitWords<'t> {
    fn new(text: &'t str) -> Self {
        SplitWords { text }
    }
}

impl<'t> Iterator for SplitWords<'t> {
    type Item = (&'t str, &'t str);

    fn next(&mut self) -> Option<Self::Item> {
        let text = self.text;
        if text.is_empty() {
            return None;
        }
        for (i, ch) in text.char_indices() {
            if ch.is_whitespace() {
                for (j, ch) in text[i..].char_indices() {
                    if !ch.is_whitespace() {
                        self.text = &text[i + j..];
                        return Some((&text[..i], &text[i..i + j]));
                    }
                }
                let end = text.len();
                self.text = &text[end..end];
                return Some((&text[..i], &text[i..]));
            }
            if ch == '-' {
                let j = i + 1;
                self.text = &text[j..];
                return Some((&text[..j], &text[j..j]));
            }
        }
        let end = text.len();
        self.text = &text[end..end];
        Some((text, &text[end..end]))
    }
}

impl Span {
    /// Render the span at the given position in the terminal.
    fn render(
        &self,
        changes: &mut Vec<Change>,
        attr_state: &mut AttributeState,
        start: usize,
        end: usize,
        mut position: usize,
        search_index: Option<usize>,
    ) -> usize {
        match *self {
            Span::Text(ref t) => {
                let text = if attr_state.line_drawing {
                    Cow::Owned(line_drawing::convert_line_drawing(t.as_str()))
                } else {
                    Cow::Borrowed(t.as_str())
                };
                position = write_truncated(
                    changes,
                    attr_state,
                    OutputStyle::File,
                    text.as_ref(),
                    start,
                    end,
                    position,
                );
            }
            Span::Match(ref t, ref match_index) => {
                let style = if search_index == Some(*match_index) {
                    OutputStyle::CurrentMatch
                } else {
                    OutputStyle::Match
                };
                let text = if attr_state.line_drawing {
                    Cow::Owned(line_drawing::convert_line_drawing(t.as_str()))
                } else {
                    Cow::Borrowed(t.as_str())
                };
                position = write_truncated(
                    changes,
                    attr_state,
                    style,
                    text.as_ref(),
                    start,
                    end,
                    position,
                );
            }
            Span::Tab => {
                let tabchars = 8 - position % 8;
                position = write_truncated(
                    changes,
                    attr_state,
                    OutputStyle::File,
                    &TAB_SPACES[..tabchars],
                    start,
                    end,
                    position,
                );
            }
            Span::Control(c) | Span::Invalid(c) => {
                position = write_truncated(
                    changes,
                    attr_state,
                    OutputStyle::Control,
                    &format!("<{:02X}>", c),
                    start,
                    end,
                    position,
                );
            }
            Span::Unprintable(ref grapheme) => {
                for c in grapheme.chars() {
                    position = write_truncated(
                        changes,
                        attr_state,
                        OutputStyle::Control,
                        &format!("<U+{:04X}>", c as u32),
                        start,
                        end,
                        position,
                    );
                }
            }
            Span::SgrSequence(ref s) => attr_state.apply_sgr_sequence(s),
            Span::Hyperlink(ref l) => attr_state.apply_hyperlink(l),
            Span::LineDrawing(e) => attr_state.line_drawing = e,
            Span::EraseToEndOfLine => attr_state.end_of_line = attr_state.attrs.background(),
            _ => {}
        }
        position
    }

    fn split(
        &self,
        rows: &mut Vec<(usize, usize)>,
        start: usize,
        position: usize,
        width: usize,
        words: bool,
    ) -> (usize, usize) {
        match self {
            Span::Text(text) | Span::Match(text, _) => {
                let mut start = start;
                let mut position = position;
                if words {
                    for (word, sep) in SplitWords::new(text) {
                        let end = position + word.width() + sep.width();
                        if end - start <= width {
                            // This word fits within this row
                            position = end;
                        } else {
                            // This word wraps to the next row.
                            if start != position {
                                // Add the existing words to the row.
                                rows.push((start, position));
                                start = position;
                            }
                            if end - start > width {
                                // This word is at the start of the row and is longer than the whole
                                // row.  Break it at grapheme boundaries.
                                for grapheme in word.graphemes(true).chain(sep.graphemes(true)) {
                                    let end = position + grapheme.width();
                                    if end - start <= width {
                                        // This character fits within this row
                                        position = end;
                                    } else {
                                        // This character wraps to the next row
                                        rows.push((start, position));
                                        start = position;
                                        position = end;
                                    }
                                }
                            } else {
                                position = end;
                            }
                        }
                    }
                } else {
                    for grapheme in text.graphemes(true) {
                        let end = position + grapheme.width();
                        if end - start <= width {
                            // This character fits within this row
                            position = end;
                        } else {
                            // This character wraps to the next row
                            rows.push((start, position));
                            start = position;
                            position = end;
                        }
                    }
                }
                (start, position)
            }
            Span::Tab => {
                let tabchars = 8 - position % 8;
                let end = position + tabchars;
                if end - start <= width {
                    // This tab fits within this row
                    (start, end)
                } else {
                    // This tab completes the row
                    rows.push((start, end));
                    (end, end)
                }
            }
            Span::Control(_) | Span::Invalid(_) => {
                let end = position + 4;
                if end - start <= width {
                    // This character fits within this row
                    (start, end)
                } else {
                    // This character wraps to the next row
                    rows.push((start, position));
                    (position, end)
                }
            }
            Span::Unprintable(_) => {
                let end = position + 8;
                if end - start <= width {
                    // This character fits within this row
                    (start, end)
                } else {
                    // This character wraps to the next row
                    rows.push((start, position));
                    (position, end)
                }
            }
            _ => (start, position),
        }
    }
}

/// Parse data into an array of Spans.
fn parse_spans(data: &[u8], match_index: Option<usize>) -> Vec<Span> {
    let mut spans = Vec::new();
    let mut input = data;

    fn parse_unicode_span(data: &str, spans: &mut Vec<Span>, match_index: Option<usize>) {
        let mut text_start = None;
        let mut skip_to = None;
        for (index, grapheme) in data.grapheme_indices(true) {
            let mut span = None;

            // Skip past any escape sequence we've already extracted
            if let Some(end) = skip_to {
                if index < end {
                    continue;
                } else {
                    skip_to = None;
                }
            }

            if grapheme == "\x1B" {
                // Look ahead for an escape sequence
                let mut parser = Parser::new();
                let bytes = data.as_bytes();
                if let Some((actions, len)) = parser.parse_first_as_vec(&bytes[index..]) {
                    // Look at the sequence of actions this parsed to.  We
                    // assume this is one of:
                    //   - A sequence of SGR actions parse from a single SGR
                    //     sequence.
                    //   - A single Cursor or Edit action we want to ignore.
                    //   - A single OSC that contains a hyperlink.
                    //   - Something else that we don't want to parse.
                    let mut actions = actions.into_iter();
                    match actions.next() {
                        Some(Action::CSI(CSI::Sgr(sgr))) => {
                            // Collect all Sgr values
                            let mut sgr_sequence = SmallVec::new();
                            sgr_sequence.push(sgr);
                            for action in actions {
                                if let Action::CSI(CSI::Sgr(sgr)) = action {
                                    sgr_sequence.push(sgr);
                                }
                            }
                            span = Some(Span::SgrSequence(sgr_sequence));
                            skip_to = Some(index + len);
                        }
                        Some(Action::CSI(CSI::Edit(Edit::EraseInLine(
                            EraseInLine::EraseToEndOfLine,
                        )))) => {
                            span = Some(Span::EraseToEndOfLine);
                            skip_to = Some(index + len);
                        }
                        Some(Action::CSI(CSI::Cursor(_))) | Some(Action::CSI(CSI::Edit(_))) => {
                            span = Some(Span::Ignore(SmallVec::from_slice(
                                &bytes[index..index + len],
                            )));
                            skip_to = Some(index + len);
                        }
                        Some(Action::OperatingSystemCommand(osc)) => {
                            if let OperatingSystemCommand::SetHyperlink(hyperlink) = *osc {
                                span = Some(Span::Hyperlink(hyperlink.map(Arc::new)));
                                skip_to = Some(index + len);
                            }
                        }
                        Some(Action::Esc(Esc::Code(code))) => match code {
                            EscCode::DecLineDrawingG0 | EscCode::AsciiCharacterSetG0 => {
                                span = Some(Span::LineDrawing(code == EscCode::DecLineDrawingG0));
                                skip_to = Some(index + len);
                            }
                            _ => {}
                        },
                        _ => {}
                    }
                }
            }

            if grapheme == "\r\n" {
                span = Some(Span::CrLf);
                skip_to = Some(index + 2);
            }

            if grapheme == "\n" {
                span = Some(Span::Lf);
            }

            if grapheme == "\t" {
                span = Some(Span::Tab);
            }

            if span.is_none() && grapheme.len() == 1 {
                if let Some(ch) = grapheme.bytes().next() {
                    if ch < b' ' || ch == b'\x7F' {
                        span = Some(Span::Control(ch));
                    }
                }
            }

            if span.is_none() && grapheme.width() == 0 {
                span = Some(Span::Unprintable(grapheme.to_string()));
            }

            if let Some(span) = span {
                if let Some(start) = text_start {
                    if let Some(match_index) = match_index {
                        spans.push(Span::Match(data[start..index].to_string(), match_index));
                    } else {
                        spans.push(Span::Text(data[start..index].to_string()));
                    }
                    text_start = None;
                }
                spans.push(span);
            } else if text_start.is_none() {
                text_start = Some(index);
            }
        }
        if let Some(start) = text_start {
            if let Some(match_index) = match_index {
                spans.push(Span::Match(data[start..].to_string(), match_index));
            } else {
                spans.push(Span::Text(data[start..].to_string()));
            }
        }
    }

    loop {
        match str::from_utf8(input) {
            Ok(valid) => {
                parse_unicode_span(valid, &mut spans, match_index);
                break;
            }
            Err(error) => {
                let (valid, after_valid) = input.split_at(error.valid_up_to());
                if !valid.is_empty() {
                    unsafe {
                        parse_unicode_span(
                            str::from_utf8_unchecked(valid),
                            &mut spans,
                            match_index,
                        );
                    }
                }
                if let Some(len) = error.error_len() {
                    for byte in &after_valid[..len] {
                        spans.push(Span::Invalid(*byte));
                    }
                    input = &after_valid[len..];
                } else {
                    for byte in after_valid {
                        spans.push(Span::Invalid(*byte));
                    }
                    break;
                }
            }
        }
    }
    spans
}

impl Line {
    pub(crate) fn new(_index: usize, data: impl AsRef<[u8]>) -> Line {
        let data = overstrike::convert_overstrike(data.as_ref());
        let spans = parse_spans(&data[..], None).into_boxed_slice();
        let wraps = Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(WRAPS_CACHE_SIZE).unwrap())));
        Line { spans, wraps }
    }

    pub(crate) fn new_search(_index: usize, data: impl AsRef<[u8]>, regex: &Regex) -> Line {
        let data = overstrike::convert_overstrike(data.as_ref());
        let len = trim_trailing_newline(data.as_ref());
        let mut spans = Vec::new();
        let mut start = 0;
        let (data_without_escapes, convert_offset) = if ESCAPE_SEQUENCE.is_match(&data[..len]) {
            let mut escape_ranges = Vec::new();
            for match_range in ESCAPE_SEQUENCE.find_iter(&data[..len]) {
                escape_ranges.push((match_range.start(), match_range.end()));
            }
            (
                ESCAPE_SEQUENCE.replace_all(&data[..len], NoExpand(b"")),
                Some(move |offset| {
                    let mut original_offset = 0;
                    let mut remaining_offset = offset;
                    for (escape_start, escape_end) in escape_ranges.iter() {
                        if original_offset + remaining_offset < *escape_start {
                            break;
                        } else {
                            remaining_offset -= escape_start - original_offset;
                            original_offset = *escape_end;
                        }
                    }
                    original_offset + remaining_offset
                }),
            )
        } else {
            (Cow::Borrowed(&data[..len]), None)
        };
        for (match_index, match_range) in regex.find_iter(&data_without_escapes[..]).enumerate() {
            let (match_start, match_end) = if let Some(ref convert) = convert_offset {
                (convert(match_range.start()), convert(match_range.end()))
            } else {
                (match_range.start(), match_range.end())
            };
            if start < match_start {
                spans.append(&mut parse_spans(&data[start..match_start], None));
            }
            spans.append(&mut parse_spans(
                &data[match_start..match_end],
                Some(match_index),
            ));
            start = match_end;
        }
        if start < data.len() {
            spans.append(&mut parse_spans(&data[start..], None));
        }
        let spans = spans.into_boxed_slice();
        let wraps = Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(WRAPS_CACHE_SIZE).unwrap())));
        Line { spans, wraps }
    }

    /// Produce the `Change`s needed to render a slice of the line on a terminal.
    pub(crate) fn render(
        &self,
        changes: &mut Vec<Change>,
        start: usize,
        end: usize,
        search_index: Option<usize>,
    ) {
        let mut start = start;
        let mut attr_state = AttributeState::new();
        let mut position = 0;
        if start > 0 {
            changes.push(Change::AllAttributes(
                CellAttributes::default()
                    .set_foreground(AnsiColor::Navy)
                    .set_intensity(Intensity::Bold)
                    .clone(),
            ));
            changes.push(LEFT_ARROW.into());
            changes.push(Change::AllAttributes(CellAttributes::default()));
            start += 1;
        }
        for span in self.spans.iter() {
            position = span.render(changes, &mut attr_state, start, end, position, search_index);
        }
        match position.cmp(&end) {
            Ordering::Greater => {
                // There is more text after the end of the line, so we need to
                // render the right arrow.
                //
                // The cursor should be in the final column of the line.  However,
                // we need to work around strange terminal behaviour when setting
                // styles at the end of the line by backspacing and then moving
                // forwards.
                changes.push(Change::Text("\x08".into()));
                changes.push(Change::CursorPosition {
                    x: Position::Relative(1),
                    y: Position::Relative(0),
                });
                changes.push(Change::AllAttributes(
                    CellAttributes::default()
                        .set_foreground(AnsiColor::Navy)
                        .set_intensity(Intensity::Bold)
                        .clone(),
                ));
                changes.push(RIGHT_ARROW.into());
            }
            Ordering::Less => changes.push(Change::ClearToEndOfLine(attr_state.end_of_line)),
            Ordering::Equal => {}
        }
        changes.push(Change::AllAttributes(CellAttributes::default()));
    }

    /// Produce the `Change`s needed to render a row of the wrapped line on a terminal.
    pub(crate) fn render_wrapped(
        &self,
        changes: &mut Vec<Change>,
        first_row: usize,
        row_count: usize,
        width: usize,
        wrapping: WrappingMode,
        search_index: Option<usize>,
    ) {
        let (start, end) = {
            fn wrap_bounds_for_rows(
                rows: WrapCacheItemRef<'_>,
                first_row: usize,
                row_count: usize,
            ) -> (usize, usize) {
                let end = rows
                    .get(first_row + row_count - 1)
                    .map_or_else(|| rows.last().map_or(0, |r| r.1), |r| r.1);
                let start = rows.get(first_row).map_or(end, |r| r.0);
                (start, end)
            }
            let mut wraps = self.wraps.lock().unwrap();
            if let Some(rows) = wraps.get(&(width, wrapping)) {
                wrap_bounds_for_rows(rows, first_row, row_count)
            } else {
                let rows = self.make_wrap(width, wrapping);
                let (start, end) = wrap_bounds_for_rows(&rows, first_row, row_count);
                wraps.put((width, wrapping), rows);
                (start, end)
            }
        };
        let mut attr_state = AttributeState::new();
        let mut position = 0;
        for span in self.spans.iter() {
            position = span.render(changes, &mut attr_state, start, end, position, search_index);
        }
        if end - start < width * row_count {
            changes.push(Change::ClearToEndOfLine(attr_state.end_of_line));
        }
        changes.push(Change::AllAttributes(CellAttributes::default()));
    }

    /// Returns the start and end pairs for each row of the line if wrapped.
    fn make_wrap(&self, width: usize, wrapping: WrappingMode) -> Vec<(usize, usize)> {
        let mut rows = Vec::new();
        match wrapping {
            WrappingMode::Unwrapped => {
                rows.push((0, std::usize::MAX));
            }
            WrappingMode::GraphemeBoundary | WrappingMode::WordBoundary => {
                let mut start = 0;
                let mut position = 0;
                for span in self.spans.iter() {
                    let (new_start, new_position) = span.split(
                        &mut rows,
                        start,
                        position,
                        width,
                        wrapping == WrappingMode::WordBoundary,
                    );
                    start = new_start;
                    position = new_position;
                }
                if position > start || rows.is_empty() {
                    rows.push((start, position))
                }
            }
        }
        rows
    }

    /// Returns the number of rows for this line if wrapped at the given width
    pub(crate) fn height(&self, width: usize, wrapping: WrappingMode) -> usize {
        if wrapping == WrappingMode::Unwrapped {
            return 1;
        }
        let mut wraps = self.wraps.lock().unwrap();
        if let Some(rows) = wraps.get_mut(&(width, wrapping)) {
            return rows.len();
        }
        let rows = self.make_wrap(width, wrapping);
        let height = rows.len();
        wraps.put((width, wrapping), rows);
        height
    }
}

#[cfg(test)]
mod test {
    use super::Span::*;
    use super::*;
    use termwiz::color::ColorSpec;

    #[test]
    fn test_parse_spans() {
        assert_eq!(parse_spans(b"hello", None), vec![Text("hello".to_string())]);
        assert_eq!(
            parse_spans("WÃth ÃšnÃcÃ³dÃ©".as_bytes(), None),
            vec![Text("WÃth ÃšnÃcÃ³dÃ©".to_string())]
        );
        assert_eq!(
            parse_spans(b"Truncated\xE0", None),
            vec![Text("Truncated".to_string()), Invalid(224)]
        );
        assert_eq!(
            parse_spans(b"Truncated\xE0\x80", None),
            vec![Text("Truncated".to_string()), Invalid(224), Invalid(128)]
        );
        assert_eq!(
            parse_spans(b"Internal\xE0Error", None),
            vec![
                Text("Internal".to_string()),
                Invalid(224),
                Text("Error".to_string())
            ]
        );
        assert_eq!(
            parse_spans(b"\x84StartingError", None),
            vec![Invalid(132), Text("StartingError".to_string())]
        );
        assert_eq!(
            parse_spans(b"Internal\xE0\x80Error", None),
            vec![
                Text("Internal".to_string()),
                Invalid(224),
                Invalid(128),
                Text("Error".to_string())
            ]
        );
        assert_eq!(
            parse_spans(b"TerminatingControl\x1F", None),
            vec![Text("TerminatingControl".to_string()), Control(31)]
        );
        assert_eq!(
            parse_spans(b"Internal\x02Control", None),
            vec![
                Text("Internal".to_string()),
                Control(2),
                Text("Control".to_string())
            ]
        );
        assert_eq!(
            parse_spans(b"\x1AStartingControl", None),
            vec![Control(26), Text("StartingControl".to_string())]
        );
        assert_eq!(
            parse_spans(b"\x1B[1mBold!\x1B[m", None),
            vec![
                SgrSequence(SmallVec::from(&[Sgr::Intensity(Intensity::Bold)][..])),
                Text("Bold!".to_string()),
                SgrSequence(SmallVec::from(&[Sgr::Reset][..]))
            ]
        );
        assert_eq!(
            parse_spans(
                b"Multi\x1B[31;7m-colored \x1B[36;1mtext\x1B[42;1m line",
                None
            ),
            vec![
                Text("Multi".to_string()),
                SgrSequence(SmallVec::from(
                    &[
                        Sgr::Foreground(ColorSpec::PaletteIndex(1)),
                        Sgr::Inverse(true)
                    ][..]
                )),
                Text("-colored ".to_string()),
                SgrSequence(SmallVec::from(
                    &[
                        Sgr::Foreground(ColorSpec::PaletteIndex(6)),
                        Sgr::Intensity(Intensity::Bold)
                    ][..]
                )),
                Text("text".to_string()),
                SgrSequence(SmallVec::from(
                    &[
                        Sgr::Background(ColorSpec::PaletteIndex(2)),
                        Sgr::Intensity(Intensity::Bold)
                    ][..]
                )),
                Text(" line".to_string())
            ]
        );
        assert_eq!(
            parse_spans(b"Terminating LF\n", None),
            vec![Text("Terminating LF".to_string()), Lf]
        );
        assert_eq!(
            parse_spans(b"Terminating CRLF\r\n", None),
            vec![Text("Terminating CRLF".to_string()), CrLf]
        );

        assert_eq!(
            parse_spans(b"Terminating CR\r", None),
            vec![Text("Terminating CR".to_string()), Control(13)]
        );

        assert_eq!(
            parse_spans(b"Internal\rCR", None),
            vec![
                Text("Internal".to_string()),
                Control(13),
                Text("CR".to_string())
            ]
        );
        assert_eq!(
            parse_spans(b"Internal\nLF", None),
            vec![Text("Internal".to_string()), Lf, Text("LF".to_string())]
        );
        assert_eq!(
            parse_spans(b"Internal\r\nCRLF", None),
            vec![Text("Internal".to_string()), CrLf, Text("CRLF".to_string())]
        );
    }

    #[test]
    fn test_wrap() {
        let data = concat!(
            "A simple line with several words, including some superobnoxiously ",
            "big ones and some extra-confusingly-awkward hyphenated ones."
        );
        let data_wrapped_10 = vec![
            "A simple ",
            "line with ",
            "several ",
            "words, ",
            "including ",
            "some ",
            "superobnox",
            "iously ",
            "big ones ",
            "and some ",
            "extra-",
            "confusingl",
            "y-awkward ",
            "hyphenated",
            " ones.",
        ];
        let line = Line::new(0, data.as_bytes());
        assert_eq!(
            line.make_wrap(100, WrappingMode::Unwrapped),
            vec![(0, std::usize::MAX)],
        );
        assert_eq!(
            line.make_wrap(40, WrappingMode::GraphemeBoundary),
            vec![(0, 40), (40, 80), (80, 120), (120, 126)],
        );

        // The start and end values are positions, not string indices, but since data is pure ASCII
        // they will match.
        let line_wrapped_10: Vec<_> = line
            .make_wrap(10, WrappingMode::WordBoundary)
            .iter()
            .map(|(start, end)| &data[*start..*end])
            .collect();
        assert_eq!(line_wrapped_10, data_wrapped_10);

        // In this example, the control character doesn't fit into the 40 character width.
        let line = Line::new(
            0,
            "Some line with ÃšnÃcÃ³dÃ© and \x1B[31mcolors\x1B[m and \x01Control characters\r\n"
                .as_bytes(),
        );
        assert_eq!(
            line.make_wrap(40, WrappingMode::GraphemeBoundary),
            vec![(0, 38), (38, 60)],
        );
    }
}


//! Line Cache
//!
//! An LRU-cache for lines.

use std::borrow::Cow;
use std::num::NonZeroUsize;

use lru::LruCache;
use regex::bytes::Regex;

use crate::file::{File, FileInfo};
use crate::line::Line;

/// An LRU-cache for Lines.
pub(crate) struct LineCache(LruCache<usize, Line>);

impl LineCache {
    /// Create a new LineCache with the given capacity.
    pub(crate) fn new(capacity: NonZeroUsize) -> LineCache {
        LineCache(LruCache::new(capacity))
    }

    /// Get a line out of the line cache, or create it if it is not
    /// in the cache.
    pub(crate) fn get_or_create<'a>(
        &'a mut self,
        file: &File,
        line_index: usize,
        regex: Option<&Regex>,
    ) -> Option<Cow<'a, Line>> {
        let cache = &mut self.0;
        if cache.contains(&line_index) {
            Some(Cow::Borrowed(cache.get_mut(&line_index).unwrap()))
        } else {
            let line = file.with_line(line_index, |line| {
                if let Some(ref regex) = regex {
                    Line::new_search(line_index, line, regex)
                } else {
                    Line::new(line_index, line)
                }
            });
            if let Some(line) = line {
                // Don't cache the line if it's the last line of the file
                // and the file is still loading.  It might not be complete.
                if file.loaded() || line_index + 1 < file.lines() {
                    cache.put(line_index, line);
                    Some(Cow::Borrowed(cache.get_mut(&line_index).unwrap()))
                } else {
                    Some(Cow::Owned(line))
                }
            } else {
                None
            }
        }
    }

    /// Clear all entries in the line cache.
    pub(crate) fn clear(&mut self) {
        self.0.clear();
    }
}


//! Loaded files.
//!
//! Files where the data is loaded from somewhere.

use std::borrow::Cow;
use std::cmp::max;
#[cfg(feature = "load_file")]
use std::cmp::min;
use std::ffi::OsStr;
#[cfg(feature = "load_file")]
use std::fs::File as StdFile;
use std::io::Read;
#[cfg(feature = "load_file")]
use std::io::{Seek, SeekFrom};
#[cfg(feature = "load_file")]
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[cfg(feature = "load_file")]
use std::sync::mpsc;
use std::sync::{Arc, Condvar, Mutex, RwLock};
use std::thread;
#[cfg(feature = "load_file")]
use std::time::Duration;

#[cfg(feature = "load_file")]
use memmap2::Mmap;
#[cfg(feature = "load_file")]
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};

use crate::buffer::Buffer;
#[cfg(feature = "load_file")]
use crate::buffer_cache::BufferCache;
use crate::error::{Error, Result};
use crate::event::{Event, EventSender};
#[cfg(feature = "load_file")]
use crate::event::UniqueInstance;
use crate::file::{FileIndex, FileInfo, DEFAULT_NEEDED_LINES};

/// Buffer size to use when loading and parsing files.  This is also the block
/// size when parsing memory mapped files or caching files read from disk.
const BUFFER_SIZE: usize = 1024 * 1024;

#[cfg(feature = "load_file")]
/// Size of the file cache in buffers.
const CACHE_SIZE: usize = 16;

/// The data content of the file.
#[derive(Clone)]
enum FileData {
    /// Data content is being streamed from an input stream, and stored in a
    /// vector of buffers.
    Streamed { buffers: Arc<RwLock<Vec<Buffer>>> },

    #[cfg(feature = "load_file")]
    /// Data content should be read from a file on disk.
    File {
        buffer_cache: Arc<Mutex<BufferCache>>,
        events: mpsc::Sender<FileEvent>,
    },

    #[cfg(feature = "load_file")]
    /// Data content has been memory mapped.
    Mapped { mmap: Arc<Mmap> },

    #[cfg(feature = "load_file")]
    /// File is empty.
    Empty,

    /// Static content.
    Static { data: Arc<Cow<'static, [u8]>> },
}

/// Metadata about a file that is being loaded.
struct FileMeta {
    /// The index of the file.
    index: FileIndex,

    /// The loaded file's title.  Usually its name.
    title: String,

    /// Information about the file.
    info: RwLock<Vec<String>>,

    /// The length of the file that has been parsed.
    length: AtomicUsize,

    /// The offset of each newline in the file.
    newlines: RwLock<Vec<usize>>,

    /// During reload, the number of lines the file had before reloading.
    reload_old_line_count: RwLock<Option<usize>>,

    /// Set to true when the file has been loaded and parsed.
    finished: AtomicBool,

    /// Set to true when the file has been dropped. Checked by background
    /// threads to exit early.
    dropped: AtomicBool,

    /// The most recent error encountered when loading the file.
    error: RwLock<Option<Error>>,

    /// If needed_lines > newlines.len(), pause loading.
    needed_lines: AtomicUsize,

    /// CondVar to wake up file loading.
    waker: Condvar,

    /// Mutex used by waker.
    waker_mutex: Mutex<()>,
}

    #[cfg(feature = "load_file")]
/// Event triggered by changes to a file on disk.
#[derive(Clone, Copy, Debug)]
pub(crate) enum FileEvent {
    /// File has been appended to.
    Append,

    /// File has changed and needs reloading.
    Reload,
}

/// Guard to stop reading from a file when it is dropped
struct FileGuard {
    meta: Arc<FileMeta>,
}

impl FileMeta {
    /// Create new file metadata.
    fn new(index: FileIndex, title: String) -> FileMeta {
        FileMeta {
            index,
            title,
            info: RwLock::new(Vec::new()),
            length: AtomicUsize::new(0usize),
            newlines: RwLock::new(Vec::new()),
            reload_old_line_count: RwLock::new(None),
            finished: AtomicBool::new(false),
            dropped: AtomicBool::new(false),
            error: RwLock::new(None),
            needed_lines: AtomicUsize::new(DEFAULT_NEEDED_LINES),
            waker: Condvar::new(),
            waker_mutex: Mutex::new(()),
        }
    }
}

impl FileData {
    /// Create a new streamed file.
    ///
    /// A background thread is started to read from `input` and store the
    /// content in buffers.  Metadata about loading is written to `meta`.
    ///
    /// Returns `FileData` containing the buffers that the background thread
    /// is loading into.
    fn new_streamed(
        mut input: impl Read + Send + 'static,
        meta: Arc<FileMeta>,
        event_sender: EventSender,
    ) -> FileData {
        let buffers = Arc::new(RwLock::new(Vec::new()));
        thread::Builder::new()
            .name(format!("sp-stream-{}", meta.index))
            .spawn({
                let buffers = buffers.clone();
                move || -> Result<()> {
                    let mut offset = 0usize;
                    let mut total_buffer_size = 0usize;
                    let mut waker_mutex = meta.waker_mutex.lock().unwrap();
                    loop {
                        // Check if a new buffer must be allocated.
                        if offset == total_buffer_size {
                            let mut buffers = buffers.write().unwrap();
                            buffers.push(Buffer::new(BUFFER_SIZE));
                            total_buffer_size += BUFFER_SIZE;
                        }
                        let buffers = buffers.read().unwrap();
                        let mut write = buffers.last().unwrap().write();
                        match input.read(&mut write) {
                            Ok(0) => {
                                // The end of the file has been reached.  Complete.
                                meta.finished.store(true, Ordering::SeqCst);
                                event_sender.send(Event::Loaded(meta.index))?;
                                return Ok(());
                            }
                            Ok(len) => {
                                if meta.dropped.load(Ordering::SeqCst) {
                                    return Ok(());
                                }
                                // Some data has been read.  Parse its newlines.
                                let line_count = {
                                    let mut newlines = meta.newlines.write().unwrap();
                                    for i in 0..len {
                                        if write[i] == b'\n' {
                                            newlines.push(offset + i);
                                        }
                                    }
                                    // Mark that the data has been written.  This
                                    // needs to be done here before we drop the
                                    // lock for `newlines`.
                                    offset += len;
                                    write.written(len);
                                    meta.length.fetch_add(len, Ordering::SeqCst);
                                    newlines.len()
                                };
                                while line_count >= meta.needed_lines.load(Ordering::SeqCst) {
                                    // Enough data is loaded. Pause.
                                    waker_mutex = meta.waker.wait(waker_mutex).unwrap();
                                    if meta.dropped.load(Ordering::SeqCst) {
                                        return Ok(());
                                    }
                                }
                            }
                            Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
                            Err(e) => {
                                let mut error = meta.error.write().unwrap();
                                *error = Some(e.into());
                            }
                        }
                    }
                }
            })
            .unwrap();
        FileData::Streamed { buffers }
    }

    #[cfg(feature = "load_file")]
    /// Create a new file from disk.
    fn new_file<P: AsRef<Path>>(
        path: P,
        meta: Arc<FileMeta>,
        event_sender: EventSender,
    ) -> Result<FileData> {
        let path = path.as_ref();
        let mut file = Some(StdFile::open(path)?);
        let (events, event_rx) = mpsc::channel();
        let appending = Arc::new(AtomicBool::new(false));
        let buffer_cache = Arc::new(Mutex::new(BufferCache::new(path, BUFFER_SIZE, CACHE_SIZE)));

        // Create a thread to watch for file change notifications.
        thread::Builder::new()
            .name(format!("sp-fchg-{}", meta.index))
            .spawn({
                let events = events.clone();
                let appending = appending.clone();
                let meta = meta.clone();
                let path = path.to_path_buf();
                move || -> Result<()> {
                    loop {
                        let (tx, rx) = mpsc::channel();
                        let mut watcher: RecommendedWatcher =
                            Watcher::new(tx, Duration::from_millis(500)).expect("create watcher");
                        watcher
                            .watch(path.clone(), RecursiveMode::NonRecursive)
                            .expect("watch file");
                        loop {
                            if meta.dropped.load(Ordering::SeqCst) {
                                return Ok(());
                            }
                            let event = rx.recv();
                            match event {
                                Ok(DebouncedEvent::NoticeWrite(_)) => {
                                    appending.store(true, Ordering::SeqCst);
                                    events.send(FileEvent::Append)?;
                                }
                                Ok(DebouncedEvent::Write(_)) => {
                                    appending.store(false, Ordering::SeqCst);
                                    events.send(FileEvent::Append)?;
                                }
                                Ok(DebouncedEvent::Create(_)) => {
                                    events.send(FileEvent::Append)?;
                                }
                                Ok(DebouncedEvent::Rename(_, _)) => {
                                    events.send(FileEvent::Reload)?;
                                }
                                Ok(DebouncedEvent::NoticeRemove(_))
                                | Ok(DebouncedEvent::Chmod(_)) => {
                                    events.send(FileEvent::Reload)?;
                                    break;
                                }
                                Err(_) => {
                                    // The watcher failed for some reason.
                                    // Wait before retrying.
                                    thread::sleep(Duration::from_secs(1));
                                    break;
                                }
                                _ => {}
                            }
                        }
                    }
                }
            })
            .unwrap();

        // Create a thread to load the file.
        thread::Builder::new()
            .name(format!("sp-file-{}", meta.index))
            .spawn({
                let buffer_cache = buffer_cache.clone();
                let path = path.to_path_buf();
                move || -> Result<()> {
                    let loaded_instance = UniqueInstance::new();
                    let appending_instance = UniqueInstance::new();
                    let reloading_instance = UniqueInstance::new();
                    let mut total_length = 0;
                    let mut end_data = Vec::new();
                    loop {
                        meta.length.store(total_length, Ordering::SeqCst);
                        if let Some(mut file) = file.take() {
                            let mut buffer = Vec::new();
                            buffer.resize(BUFFER_SIZE, 0);
                            loop {
                                match file.read(buffer.as_mut_slice()) {
                                    Ok(0) => break,
                                    Ok(len) => {
                                        if meta.dropped.load(Ordering::SeqCst) {
                                            return Ok(());
                                        }
                                        let mut newlines = meta.newlines.write().unwrap();
                                        for (i, byte) in buffer.iter().enumerate().take(len) {
                                            if *byte == b'\n' {
                                                newlines.push(total_length + i);
                                            }
                                        }
                                        total_length += len;
                                        meta.length.store(total_length, Ordering::SeqCst);
                                    }
                                    Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
                                    Err(e) => {
                                        let mut error = meta.error.write().unwrap();
                                        *error = Some(e.into());
                                    }
                                }
                            }

                            // Attempt to read the last 4k of the file.  If the file changes, we will
                            // check this portion of the file to see if we need to reload the file.
                            let end_len = total_length.min(4096);
                            end_data.clear();
                            if file.seek(SeekFrom::End(-(end_len as i64))).is_ok() {
                                end_data.resize(end_len, 0);
                                if let Ok(len) = file.read(end_data.as_mut_slice()) {
                                    if len != end_len {
                                        end_data.clear();
                                    }
                                } else {
                                    end_data.clear();
                                }
                            }
                        }
                        let (send_event, mut reload) = if appending.load(Ordering::SeqCst) {
                            std::thread::sleep(Duration::from_millis(100));
                            (false, end_data.is_empty())
                        } else {
                            meta.finished.store(true, Ordering::SeqCst);
                            event_sender
                                .send_unique(Event::Loaded(meta.index), &loaded_instance)?;
                            {
                                let mut reload_old_line_count =
                                    meta.reload_old_line_count.write().unwrap();
                                *reload_old_line_count = None;
                            }
                            match event_rx.recv() {
                                Ok(FileEvent::Append) => (true, end_data.is_empty()),
                                Ok(FileEvent::Reload) => (true, true),
                                Err(e) => {
                                    let mut error = meta.error.write().unwrap();
                                    *error = Some(e.into());
                                    return Ok(());
                                }
                            }
                        };
                        match StdFile::open(&path) {
                            Ok(mut f) => {
                                if !reload {
                                    let mut new_data = Vec::new();
                                    new_data.resize(end_data.len(), 0);
                                    let offset = total_length - end_data.len();
                                    if f.seek(SeekFrom::Start(offset as u64)).is_ok()
                                        && f.read(new_data.as_mut_slice()).ok()
                                            == Some(end_data.len())
                                        && new_data == end_data
                                    {
                                        // We can continue where we left off
                                    } else {
                                        reload = true;
                                    }
                                }
                                file = Some(f);
                            }
                            Err(_) => {
                                reload = true;
                            }
                        }
                        if reload {
                            buffer_cache.lock().unwrap().clear();
                            let mut reload_old_line_count =
                                meta.reload_old_line_count.write().unwrap();
                            let mut newlines = meta.newlines.write().unwrap();
                            let count = max(
                                reload_old_line_count.unwrap_or(0),
                                line_count(newlines.as_slice(), total_length),
                            );
                            *reload_old_line_count = Some(count);
                            newlines.clear();
                            total_length = 0;
                            if send_event {
                                event_sender.send_unique(
                                    Event::Reloading(meta.index),
                                    &reloading_instance,
                                )?;
                            }
                        } else if send_event {
                            event_sender
                                .send_unique(Event::Appending(meta.index), &appending_instance)?;
                        }
                        meta.finished.store(false, Ordering::SeqCst);
                    }
                }
            })
            .unwrap();

        Ok(FileData::File {
            buffer_cache,
            events,
        })
    }

    #[cfg(feature = "load_file")]
    /// Create a new memory mapped file.
    ///
    /// The `file` is memory mapped and then a background thread is started to
    /// parse the newlines in the file.  The parsing progress is stored in
    /// `meta`.
    ///
    /// Returns `FileData` containing the memory map.
    fn new_mapped(
        file: StdFile,
        meta: Arc<FileMeta>,
        event_sender: EventSender,
    ) -> Result<FileData> {
        // We can't mmap empty files, so just return an empty filedata if the
        // file's length is 0.
        if file.metadata()?.len() == 0 {
            meta.finished.store(true, Ordering::SeqCst);
            event_sender.send(Event::Loaded(meta.index))?;
            return Ok(FileData::Empty);
        }
        let mmap = Arc::new(unsafe { Mmap::map(&file)? });
        thread::Builder::new()
            .name(format!("sp-mmap-{}", meta.index))
            .spawn({
                let mmap = mmap.clone();
                move || -> Result<()> {
                    let len = mmap.len();
                    let blocks = (len + BUFFER_SIZE - 1) / BUFFER_SIZE;
                    for block in 0..blocks {
                        if meta.dropped.load(Ordering::SeqCst) {
                            return Ok(());
                        }
                        let mut newlines = meta.newlines.write().unwrap();
                        for i in block * BUFFER_SIZE..min((block + 1) * BUFFER_SIZE, len) {
                            if mmap[i] == b'\n' {
                                newlines.push(i);
                            }
                        }
                    }
                    meta.length.store(len, Ordering::SeqCst);
                    meta.finished.store(true, Ordering::SeqCst);
                    event_sender.send(Event::Loaded(meta.index))?;
                    Ok(())
                }
            })
            .unwrap();
        Ok(FileData::Mapped { mmap })
    }

    /// Create a new file from static data.
    ///
    /// Returns `FileData` containing the static data.
    fn new_static(
        data: impl Into<Cow<'static, [u8]>>,
        meta: Arc<FileMeta>,
        event_sender: EventSender,
    ) -> FileData {
        let data = Arc::new(data.into());
        thread::Builder::new()
            .name(format!("sp-static-{}", meta.index))
            .spawn({
                let data = data.clone();
                move || -> Result<()> {
                    let len = data.len();
                    let blocks = (len + BUFFER_SIZE - 1) / BUFFER_SIZE;
                    for block in 0..blocks {
                        if meta.dropped.load(Ordering::SeqCst) {
                            return Ok(());
                        }
                        let mut newlines = meta.newlines.write().unwrap();
                        for (i, byte) in data
                            .iter()
                            .enumerate()
                            .skip(block * BUFFER_SIZE)
                            .take(BUFFER_SIZE)
                        {
                            if *byte == b'\n' {
                                newlines.push(i);
                            }
                        }
                    }
                    meta.length.store(len, Ordering::SeqCst);
                    meta.finished.store(true, Ordering::SeqCst);
                    event_sender.send(Event::Loaded(meta.index))?;
                    Ok(())
                }
            })
            .unwrap();
        FileData::Static { data }
    }

    /// Runs the `call` function, passing it a slice of the data from `start` to `end`.
    /// Tries to avoid copying the data if possible.
    fn with_slice<T, F>(&self, start: usize, end: usize, mut call: F) -> T
    where
        F: FnMut(Cow<'_, [u8]>) -> T,
    {
        match self {
            FileData::Streamed { buffers } => {
                let start_buffer = start / BUFFER_SIZE;
                let end_buffer = (end - 1) / BUFFER_SIZE;
                let buffers = buffers.read().unwrap();
                if start_buffer == end_buffer {
                    let data = buffers[start_buffer].read();
                    call(Cow::Borrowed(
                        &data[start % BUFFER_SIZE..=(end - 1) % BUFFER_SIZE],
                    ))
                } else {
                    // The data spans multiple buffers, so we must make a copy to make it contiguous.
                    let mut v = Vec::with_capacity(end - start);
                    v.extend_from_slice(&buffers[start_buffer].read()[start % BUFFER_SIZE..]);
                    for b in start_buffer + 1..end_buffer {
                        v.extend_from_slice(buffers[b].read());
                    }
                    v.extend_from_slice(&buffers[end_buffer].read()[..=(end - 1) % BUFFER_SIZE]);
                    call(Cow::Owned(v))
                }
            }
    #[cfg(feature = "load_file")]
            FileData::File {
                events,
                buffer_cache,
                ..
            } => {
                let mut buffer_cache = buffer_cache.lock().unwrap();
                buffer_cache
                    .with_slice(start, end, |data| {
                        if data
                            .iter()
                            .take(data.len().saturating_sub(1))
                            .any(|c| *c == b'\n')
                        {
                            events.send(FileEvent::Reload).unwrap();
                        }
                        call(data)
                    })
                    .unwrap()
            }
    #[cfg(feature = "load_file")]
            FileData::Mapped { mmap } => call(Cow::Borrowed(&mmap[start..end])),
    #[cfg(feature = "load_file")]
            FileData::Empty => call(Cow::Borrowed(&[])),
            FileData::Static { data } => call(Cow::Borrowed(&data[start..end])),
        }
    }
}

/// A loaded file.
pub(crate) struct LoadedFile {
    /// The data for the file.
    data: FileData,

    /// Metadata about the loading of the file.
    meta: Arc<FileMeta>,

    /// Guard to stop loading the file when the original reference to it is dropped.
    _guard: Option<FileGuard>,
}

impl Clone for LoadedFile {
    fn clone(&self) -> LoadedFile {
        LoadedFile {
            data: self.data.clone(),
            meta: self.meta.clone(),
            _guard: None,
        }
    }
}

impl LoadedFile {
    fn new(data: FileData, meta: Arc<FileMeta>) -> Self {
        let _guard = Some(FileGuard { meta: meta.clone() });
        LoadedFile { data, meta, _guard }
    }

    /// Load stream.
    pub(crate) fn new_streamed(
        index: FileIndex,
        stream: impl Read + Send + 'static,
        title: &str,
        event_sender: EventSender,
    ) -> LoadedFile {
        let meta = Arc::new(FileMeta::new(index, title.to_string()));
        let data = FileData::new_streamed(stream, meta.clone(), event_sender);
        LoadedFile::new(data, meta)
    }

    #[cfg(feature = "load_file")]
    pub(crate) fn new_file(
        index: FileIndex,
        filename: &OsStr,
        event_sender: EventSender,
    ) -> Result<LoadedFile> {
        let title = filename.to_string_lossy().into_owned();
        let meta = Arc::new(FileMeta::new(index, title.to_string()));
        let mut file = StdFile::open(filename).map_err(|err| Error::from(err).with_file(title))?;
        // Determine whether this file is a real file, or some kind of pipe, by
        // attempting to do a no-op seek.  If it fails, we won't be able to seek
        // around and load parts of the file at will, so treat it as a stream.
        let data = match file.seek(SeekFrom::Current(0)) {
            Ok(_) => FileData::new_file(filename, meta.clone(), event_sender)?,
            Err(_) => FileData::new_streamed(file, meta.clone(), event_sender),
        };
        Ok(LoadedFile::new(data, meta))
    }

    #[cfg(feature = "load_file")]
    /// Load a file by memory mapping it if possible.
    #[allow(unused)]
    pub(crate) fn new_mapped(
        index: FileIndex,
        filename: &OsStr,
        event_sender: EventSender,
    ) -> Result<LoadedFile> {
        let title = filename.to_string_lossy().into_owned();
        let meta = Arc::new(FileMeta::new(index, title.clone()));
        let mut file = StdFile::open(filename).map_err(|err| Error::from(err).with_file(title))?;
        // Determine whether this file is a real file, or some kind of pipe, by
        // attempting to do a no-op seek.  If it fails, assume we can't mmap
        // it.
        let data = match file.seek(SeekFrom::Current(0)) {
            Ok(_) => FileData::new_mapped(file, meta.clone(), event_sender)?,
            Err(_) => FileData::new_streamed(file, meta.clone(), event_sender),
        };
        Ok(LoadedFile::new(data, meta))
    }

    /// Load the output and error of a command
    pub(crate) fn new_command<I, S>(
        index: FileIndex,
        command: &OsStr,
        args: I,
        title: &str,
        event_sender: EventSender,
    ) -> Result<(LoadedFile, LoadedFile)>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
    {
        let title_err = format!("STDERR for {}", title);
        let mut process = Command::new(command)
            .args(args)
            .stdin(Stdio::null())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .map_err(|err| Error::from(err).with_command(command))?;
        let out = process.stdout.take().unwrap();
        let err = process.stderr.take().unwrap();
        let out_file = LoadedFile::new_streamed(index, out, &title, event_sender.clone());
        let err_file = LoadedFile::new_streamed(index + 1, err, &title_err, event_sender.clone());
        thread::Builder::new()
            .name(format!("sp-cmd-{}", index))
            .spawn({
                let out_file = out_file.clone();
                move || -> Result<()> {
                    if let Ok(rc) = process.wait() {
                        if !rc.success() {
                            let mut info = out_file.meta.info.write().unwrap();
                            match rc.code() {
                                Some(code) => info.push(format!("rc: {}", code)),
                                None => info.push("killed!".to_string()),
                            }
                            event_sender.send(Event::RefreshOverlay)?;
                        }
                    }
                    Ok(())
                }
            })
            .unwrap();
        Ok((out_file, err_file))
    }

    /// Load a file from static data.
    pub(crate) fn new_static(
        index: FileIndex,
        title: &str,
        data: impl Into<Cow<'static, [u8]>>,
        event_sender: EventSender,
    ) -> LoadedFile {
        let meta = Arc::new(FileMeta::new(index, title.to_string()));
        let data = FileData::new_static(data, meta.clone(), event_sender);
        LoadedFile::new(data, meta)
    }
}

impl FileInfo for LoadedFile {
    /// The file's index.
    fn index(&self) -> FileIndex {
        self.meta.index
    }

    /// The file's title.
    fn title(&self) -> Cow<'_, str> {
        Cow::Borrowed(&self.meta.title)
    }

    /// The file's info.
    fn info(&self) -> Cow<'_, str> {
        let info = self.meta.info.read().unwrap();
        Cow::Owned(info.join(" "))
    }

    /// True once the file is loaded and all newlines have been parsed.
    fn loaded(&self) -> bool {
        self.meta.finished.load(Ordering::SeqCst)
    }

    /// Returns the number of lines in the file.
    fn lines(&self) -> usize {
        let lines = if !self.meta.finished.load(Ordering::SeqCst) {
            let reload_old_line_count = self.meta.reload_old_line_count.read().unwrap();
            reload_old_line_count.unwrap_or(0)
        } else {
            0
        };
        let newlines = self.meta.newlines.read().unwrap();
        max(
            lines,
            line_count(newlines.as_slice(), self.meta.length.load(Ordering::SeqCst)),
        )
    }

    /// Runs the `call` function, passing it the contents of line `index`.
    /// Tries to avoid copying the data if possible, however the borrowed
    /// line only lasts as long as the function call.
    fn with_line<T, F>(&self, index: usize, call: F) -> Option<T>
    where
        F: FnMut(Cow<'_, [u8]>) -> T,
    {
        let newlines = self.meta.newlines.read().unwrap();
        if index > newlines.len() {
            return None;
        }
        let start = if index == 0 {
            0
        } else {
            newlines[index - 1] + 1
        };
        let end = if index < newlines.len() {
            newlines[index] + 1
        } else {
            self.meta.length.load(Ordering::SeqCst)
        };
        if start == end {
            return None;
        }
        Some(self.data.with_slice(start, end, call))
    }

    /// Set how many lines are needed.
    ///
    /// If `self.lines()` exceeds that number, pause loading until
    /// `set_needed_lines` is called with a larger number.
    /// This is only effective for "streamed" input.
    fn set_needed_lines(&self, lines: usize) {
        // This can be simplified by `fetch_max` when it's stable.
        if self.meta.needed_lines.load(Ordering::SeqCst) >= lines {
            return;
        }
        self.meta.needed_lines.store(lines, Ordering::SeqCst);
        self.meta.waker.notify_all();
    }

    /// True if the loading thread has been paused.
    fn paused(&self) -> bool {
        !self.loaded() && self.meta.waker_mutex.try_lock().is_ok()
    }
}

impl Drop for FileGuard {
    fn drop(&mut self) {
        self.meta.dropped.store(true, Ordering::SeqCst);
        // The thread might be blocked. Wake it up so it can notice the change
        // in `dropped`.
        self.meta.waker.notify_all();
    }
}

fn line_count(newlines: &[usize], length: usize) -> usize {
    let mut lines = newlines.len();
    let after_last_newline_offset = if lines == 0 {
        0
    } else {
        newlines[lines - 1] + 1
    };
    if length > after_last_newline_offset {
        lines += 1;
    }
    lines
}


//! Overstrike Handling
//!
//! Typewriter-based terminals used to achieve bold and underlined text by
//! backspacing over the previous character and then overstriking either a copy
//! of the same letter (for bold) or an underscore (for underline). This
//! technique is still in use, in particular for man pages.
//!
//! Handle this by converting runs of overstruck letters into normal text,
//! bracketed by the far more modern SGR escape codes.

use std::borrow::Cow;
use std::str;

use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};

/// An overstrike style.
#[derive(Copy, Clone, PartialEq, Eq)]
enum Overstrike {
    Normal,
    Bold,
    Underline,
    BoldUnderline,
}

impl Overstrike {
    /// Make the overstrike style bold.
    fn bold(&mut self) {
        *self = match *self {
            Overstrike::Normal | Overstrike::Bold => Overstrike::Bold,
            Overstrike::Underline | Overstrike::BoldUnderline => Overstrike::BoldUnderline,
        }
    }

    /// Make the overstrike style underlined.
    fn underline(&mut self) {
        *self = match *self {
            Overstrike::Normal | Overstrike::Underline => Overstrike::Underline,
            Overstrike::Bold | Overstrike::BoldUnderline => Overstrike::BoldUnderline,
        }
    }

    /// Add SGR control sequences to `out` sufficient to switch from the `prev`
    /// overstrike style to this overstrike style.
    fn add_control_sequence(self, prev: Overstrike, out: &mut String) {
        match (prev, self) {
            (Overstrike::Normal, Overstrike::Bold) => out.push_str("\x1B[1m"),
            (Overstrike::Normal, Overstrike::Underline) => out.push_str("\x1B[4m"),
            (Overstrike::Normal, Overstrike::BoldUnderline) => out.push_str("\x1B[1;4m"),
            (Overstrike::Bold, Overstrike::Normal) => out.push_str("\x1B[22m"),
            (Overstrike::Bold, Overstrike::Underline) => out.push_str("\x1B[22;4m"),
            (Overstrike::Bold, Overstrike::BoldUnderline) => out.push_str("\x1B[4m"),
            (Overstrike::Underline, Overstrike::Normal) => out.push_str("\x1B[24m"),
            (Overstrike::Underline, Overstrike::Bold) => out.push_str("\x1B[24;1m"),
            (Overstrike::Underline, Overstrike::BoldUnderline) => out.push_str("\x1B[1m"),
            (Overstrike::BoldUnderline, Overstrike::Normal) => out.push_str("\x1B[22;24m"),
            (Overstrike::BoldUnderline, Overstrike::Bold) => out.push_str("\x1B[24m"),
            (Overstrike::BoldUnderline, Overstrike::Underline) => out.push_str("\x1B[22m"),
            _ => {}
        }
    }
}

/// Erase the last grapheme from the string.  If that's not possible, or if the
/// previous grapheme was a control character, add a backspace character to the
/// string.
fn backspace(out: &mut String) {
    let mut cursor = GraphemeCursor::new(out.len(), out.len(), true);
    if let Ok(Some(offset)) = cursor.prev_boundary(&out, 0) {
        if out[offset..]
            .chars()
            .next()
            .map(char::is_control)
            .unwrap_or(true)
        {
            out.push('\x08');
        } else {
            out.truncate(offset);
        }
    } else {
        out.push('\x08');
    }
}

/// Convert a span of unicode characters with overstrikes into a span with
/// escape sequences
fn convert_unicode_span(input: &str) -> String {
    let mut result = String::with_capacity(input.len());
    let mut prev_grapheme = None;
    let mut prev_overstrike = Overstrike::Normal;
    let mut overstrike = Overstrike::Normal;
    let mut graphemes = input.graphemes(true);
    while let Some(grapheme) = graphemes.next() {
        if grapheme == "\x08" {
            if prev_grapheme.is_some() {
                if let Some(next_grapheme) = graphemes.next() {
                    if next_grapheme == "\x08" {
                        backspace(&mut result);
                        prev_grapheme = None;
                        overstrike = Overstrike::Normal;
                    } else if prev_grapheme == Some(next_grapheme) {
                        if next_grapheme == "_" {
                            // Overstriking underscore with itself is
                            // ambiguous.  Prefer to continue the existing
                            // overstrike if there is any.
                            if overstrike == Overstrike::Normal {
                                if prev_overstrike != Overstrike::Normal {
                                    overstrike = prev_overstrike;
                                } else {
                                    overstrike.bold();
                                }
                            } else {
                                overstrike = Overstrike::BoldUnderline;
                            }
                        } else {
                            overstrike.bold()
                        }
                    } else if next_grapheme == "_" {
                        overstrike.underline();
                    } else if prev_grapheme == Some("_") {
                        overstrike.underline();
                        prev_grapheme = Some(next_grapheme);
                    } else {
                        overstrike = Overstrike::Normal;
                        prev_grapheme = Some(next_grapheme);
                    }
                } else {
                    prev_grapheme = None;
                }
            } else {
                backspace(&mut result);
                overstrike = Overstrike::Normal;
            }
        } else {
            if let Some(prev_grapheme) = prev_grapheme {
                overstrike.add_control_sequence(prev_overstrike, &mut result);
                result.push_str(prev_grapheme);
            }
            prev_overstrike = overstrike;
            prev_grapheme = Some(grapheme);
            overstrike = Overstrike::Normal;
        }
    }
    if let Some(prev_grapheme) = prev_grapheme {
        overstrike.add_control_sequence(prev_overstrike, &mut result);
        result.push_str(prev_grapheme);
        prev_overstrike = overstrike;
    }
    Overstrike::Normal.add_control_sequence(prev_overstrike, &mut result);
    result
}

/// Convert any overstrike sequences found in the `input` string into normal
/// text, bracketed by SGR escape sequences.
///
/// For example `"text in b\bbo\bol\bld\bd or l\b_i\b_n\b_e\b_d"` becomes
/// `"text in {bold-on}bold{bold-off} or {ul-on}lined{ul-off}"` (where
/// `\b` is a backspace and the text in braces is the corresponding SGR
/// sequence).
pub(crate) fn convert_overstrike(input: &[u8]) -> Cow<'_, [u8]> {
    if input.contains(&b'\x08') {
        let mut data = Vec::new();
        let mut input = input;
        loop {
            match str::from_utf8(input) {
                Ok(valid) => {
                    data.extend_from_slice(convert_unicode_span(valid).as_bytes());
                    break;
                }
                Err(error) => {
                    let (valid, after_valid) = input.split_at(error.valid_up_to());
                    if !valid.is_empty() {
                        data.extend_from_slice(
                            convert_unicode_span(unsafe { str::from_utf8_unchecked(valid) })
                                .as_bytes(),
                        );
                    }
                    if let Some(len) = error.error_len() {
                        data.extend_from_slice(&after_valid[..len]);
                        input = &after_valid[len..];
                    } else {
                        data.extend_from_slice(after_valid);
                        break;
                    }
                }
            }
        }
        Cow::Owned(data)
    } else {
        Cow::Borrowed(input)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_convert_unicode_span() {
        // For simplicity, we will use 'B' as backspace in these tests.
        let bs_re = regex::Regex::new("B").unwrap();
        let bs = move |s| bs_re.replace_all(s, "\x08").to_string();

        assert_eq!(convert_unicode_span("hello"), "hello");
        assert_eq!(
            convert_unicode_span(&bs("_Bh_Be_Bl_Bl_Bo")),
            "\x1B[4mhello\x1B[24m"
        );
        assert_eq!(
            convert_unicode_span(&bs("hBheBelBllBloBo")),
            "\x1B[1mhello\x1B[22m"
        );
        assert_eq!(
            convert_unicode_span(&bs(
                "support bBboBolBldBd, uB_nB__Bd_BÃ©rB_lB_ÃB_nB__Be and bB_BboBoB__BtBthB_BhBh!"
            )),
            "support \x1B[1mbold\x1B[22m, \x1B[4mundÃ©rlÃne\x1B[24m and \x1B[1;4mboth\x1B[22;24m!"
        );
        assert_eq!(
            convert_unicode_span(&bs("BBxBB can erase bBbBmistayBkes !!BBB.")),
            bs("BBB can erase mistakes.")
        );
        assert_eq!(
            convert_unicode_span(&bs("ambig _B_bBb_B_ _B_uB__B_ bBb_B_ uB__B_B_")),
            "ambig \x1B[1m_b_\x1B[22m \x1B[1m_\x1B[22;4mu_\x1B[24m \x1B[1mb_\x1B[22m \x1B[4mu\x1B[1m_\x1B[22;24m"
        );
        assert_eq!(
            convert_unicode_span(&bs("combining: a\u{301}Ba bBba\u{301}Ba\u{301}tBt bB_a\u{301}B__Ba\u{301}tB_ xa\u{301}a\u{301}BBx")),
            "combining: a \x1B[1mba\u{301}t\x1B[22m \x1B[4mba\u{301}a\u{301}t\x1B[24m xx"
        );
    }
}


//! The pager.

use std::ffi::OsStr;
use std::io::Read;
use std::sync::Arc;

use termwiz::caps::ColorLevel;
use termwiz::caps::{Capabilities, ProbeHints};
use termwiz::terminal::{SystemTerminal, Terminal};
use vec_map::VecMap;

use crate::action::ActionSender;
use crate::bindings::Keymap;
use crate::config::{Config, InterfaceMode, KeymapConfig, WrappingMode};
use crate::control::Controller;
use crate::error::{Error, Result};
use crate::event::EventStream;
use crate::file::{ControlledFile, File, FileIndex, FileInfo, LoadedFile};
use crate::progress::Progress;

/// The main pager state.
pub struct Pager {
    /// The Terminal.
    term: SystemTerminal,

    /// The Terminal's capabilites.
    caps: Capabilities,

    /// Event Stream to process.
    events: EventStream,

    /// Files to load.
    files: Vec<File>,

    /// Error file mapping.  Maps file indices to the associated error files.
    error_files: VecMap<File>,

    /// Progress indicators to display.
    progress: Option<Progress>,

    /// Configuration.
    config: Config,
}

/// Determine terminal capabilities.
fn termcaps() -> Result<Capabilities> {
    // Get terminal capabilities from the environment, but disable mouse
    // reporting, as we don't want to change the terminal's mouse handling.
    // Enable TrueColor support, which is backwards compatible with 16
    // or 256 colors. Applications can still limit themselves to 16 or
    // 256 colors if they want.
    let hints = ProbeHints::new_from_env()
        .color_level(Some(ColorLevel::TrueColor))
        .mouse_reporting(Some(false));
    let caps = Capabilities::new_with_hints(hints).map_err(Error::Termwiz)?;
    if cfg!(unix) && caps.terminfo_db().is_none() {
        Err(Error::TerminfoDatabaseMissing)
    } else {
        Ok(caps)
    }
}

impl Pager {
    /// Build a `Pager` using the system terminal.
    pub fn new_using_system_terminal() -> Result<Self> {
        Self::new_with_terminal_func(move |caps| SystemTerminal::new(caps).map_err(Error::Termwiz))
    }

    /// Build a `Pager` using the system stdio.
    pub fn new_using_stdio() -> Result<Self> {
        Self::new_with_terminal_func(move |caps| {
            SystemTerminal::new_from_stdio(caps).map_err(Error::Termwiz)
        })
    }

    #[cfg(unix)]
    /// Build a `Pager` using the specified terminal input and output.
    pub fn new_with_input_output(
        input: &impl std::os::unix::io::AsRawFd,
        output: &impl std::os::unix::io::AsRawFd,
    ) -> Result<Self> {
        Self::new_with_terminal_func(move |caps| {
            SystemTerminal::new_with(caps, input, output).map_err(Error::Termwiz)
        })
    }

    #[cfg(windows)]
    /// Build a `Pager` using the specified terminal input and output.
    pub fn new_with_input_output(
        input: impl std::io::Read + termwiz::istty::IsTty + std::os::windows::io::AsRawHandle,
        output: impl std::io::Write + termwiz::istty::IsTty + std::os::windows::io::AsRawHandle,
    ) -> Result<Self> {
        Self::new_with_terminal_func(move |caps| {
            SystemTerminal::new_with(caps, input, output).map_err(Error::Termwiz)
        })
    }

    fn new_with_terminal_func(
        create_term: impl FnOnce(Capabilities) -> Result<SystemTerminal>,
    ) -> Result<Self> {
        let caps = termcaps()?;
        let term = create_term(caps.clone())?;

        let events = EventStream::new(term.waker());
        let files = Vec::new();
        let error_files = VecMap::new();
        let progress = None;
        let config = Config::from_config_file().with_env();

        Ok(Self {
            term,
            caps,
            events,
            files,
            error_files,
            progress,
            config,
        })
    }

    /// Add a stream to be paged.
    pub fn add_stream(
        &mut self,
        stream: impl Read + Send + 'static,
        title: &str,
    ) -> Result<FileIndex> {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let file = LoadedFile::new_streamed(index, stream, title, event_sender);
        self.files.push(file.into());
        Ok(index)
    }

    /// Attach an error stream to the previously added output stream.
    pub fn add_error_stream(
        &mut self,
        stream: impl Read + Send + 'static,
        title: &str,
    ) -> Result<FileIndex> {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let file = LoadedFile::new_streamed(index, stream, title, event_sender);
        if let Some(out_file) = self.files.last() {
            self.error_files
                .insert(out_file.index(), file.clone().into());
        }
        self.files.push(file.into());
        Ok(index)
    }

    #[cfg(feature = "load_file")]
    /// Attach a file from disk.
    pub fn add_file(&mut self, filename: &OsStr) -> Result<FileIndex> {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let file = LoadedFile::new_file(index, filename, event_sender)?;
        self.files.push(file.into());
        Ok(index)
    }

    /// Attach a controlled file.
    pub fn add_controlled_file(&mut self, controller: &Controller) -> Result<FileIndex> {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let file = ControlledFile::new(controller, index, event_sender);
        self.files.push(file.into());
        Ok(index)
    }

    /// Attach the output and error streams from a subprocess.
    ///
    /// Returns the file index for each stream.
    pub fn add_subprocess<I, S>(
        &mut self,
        command: &OsStr,
        args: I,
        title: &str,
    ) -> Result<(FileIndex, FileIndex)>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
    {
        let index = self.files.len();
        let event_sender = self.events.sender();
        let (out_file, err_file) =
            LoadedFile::new_command(index, command, args, title, event_sender)?;
        self.error_files.insert(index, err_file.clone().into());
        self.files.push(out_file.into());
        self.files.push(err_file.into());
        Ok((index, index + 1))
    }

    /// Set the progress stream.
    pub fn set_progress_stream(&mut self, stream: impl Read + Send + 'static) {
        let event_sender = self.events.sender();
        self.progress = Some(Progress::new(stream, event_sender));
    }

    /// Set when to use full screen mode. See [`InterfaceMode`] for details.
    pub fn set_interface_mode(&mut self, value: impl Into<InterfaceMode>) {
        self.config.interface_mode = value.into();
    }

    /// Set whether scrolling can past end of file.
    pub fn set_scroll_past_eof(&mut self, value: bool) {
        self.config.scroll_past_eof = value;
    }

    /// Set how many lines to read ahead.
    pub fn set_read_ahead_lines(&mut self, lines: usize) {
        self.config.read_ahead_lines = lines;
    }

    /// Set whether to poll input during start-up (delayed or direct mode).
    pub fn set_startup_poll_input(&mut self, poll_input: bool) {
        self.config.startup_poll_input = poll_input;
    }

    /// Set whether to show the ruler by default.
    pub fn set_show_ruler(&mut self, show_ruler: bool) {
        self.config.show_ruler = show_ruler;
    }

    /// Set default wrapping mode. See [`WrappingMode`] for details.
    pub fn set_wrapping_mode(&mut self, value: impl Into<WrappingMode>) {
        self.config.wrapping_mode = value.into();
    }

    /// Set keymap name.
    pub fn set_keymap_name(&mut self, keymap: impl Into<String>) {
        self.config.keymap = KeymapConfig::Name(keymap.into());
    }

    /// Set keymap.
    pub fn set_keymap(&mut self, keymap: Keymap) {
        self.config.keymap = KeymapConfig::Keymap(Arc::new(keymap));
    }

    /// Create an action sender which can be used to send `Action`s to this pager.
    pub fn action_sender(&self) -> ActionSender {
        self.events.action_sender()
    }

    /// Run Stream Pager.
    pub fn run(self) -> Result<()> {
        crate::display::start(
            self.term,
            self.caps,
            self.events,
            self.files,
            self.error_files,
            self.progress,
            self.config,
        )
    }
}


//! Progress indicator.
//!
//! sp can accept another file descriptor from its parent process via the
//! `--pager-fd` option or the PAGER_PROGRESS_FD environment variable.  This
//! should be a pipe on which the parent process sends progress indicator pages.
//!
//! Progress indicator pages are blocks of text terminated by an ASCII form-feed
//! character.  The progress indicator will display the most recently received
//! page.

use std::io::{BufRead, BufReader, Read};
use std::sync::{Arc, RwLock};
use std::thread;

use crate::error::Result;
use crate::event::{Event, EventSender, UniqueInstance};

/// Initial buffer size for progress indicator pages.
const PROGRESS_BUFFER_SIZE: usize = 4096;

/// Inner struct for the progress indicator.
pub(crate) struct ProgressInner {
    /// Buffer containing the currently displayed page.
    buffer: Vec<u8>,

    /// Offsets of all the newlines in the current page.
    newlines: Vec<usize>,

    /// Whether the progress indicator is finished because the other
    /// end of the pipe closed.
    finished: bool,
}

/// A progress indicator.
#[derive(Clone)]
pub(crate) struct Progress {
    /// The inner progress indicator data.
    inner: Arc<RwLock<ProgressInner>>,
}

impl Progress {
    /// Create a new progress indicator that receives progress pages on the
    /// given file descriptor.  Progress events are sent on the event_sender
    /// whenever a new page is received.
    pub(crate) fn new(reader: impl Read + Send + 'static, event_sender: EventSender) -> Progress {
        let inner = Arc::new(RwLock::new(ProgressInner {
            buffer: Vec::new(),
            newlines: Vec::new(),
            finished: false,
        }));
        let mut input = BufReader::new(reader);
        thread::Builder::new()
            .name(String::from("sp-progress"))
            .spawn({
                let inner = inner.clone();
                let progress_unique = UniqueInstance::new();
                move || -> Result<()> {
                    loop {
                        let mut buffer = Vec::with_capacity(PROGRESS_BUFFER_SIZE);
                        match input.read_until(b'\x0C', &mut buffer) {
                            Ok(0) | Err(_) => {
                                let mut inner = inner.write().unwrap();
                                inner.buffer = Vec::new();
                                inner.newlines = Vec::new();
                                inner.finished = true;
                                return Ok(());
                            }
                            Ok(len) => {
                                buffer.truncate(len - 1);
                                let mut newlines = Vec::new();
                                for (i, byte) in buffer.iter().enumerate().take(len - 1) {
                                    if *byte == b'\n' {
                                        newlines.push(i);
                                    }
                                }
                                let mut inner = inner.write().unwrap();
                                inner.buffer = buffer;
                                inner.newlines = newlines;
                                event_sender.send_unique(Event::Progress, &progress_unique)?;
                            }
                        }
                    }
                }
            })
            .unwrap();
        Progress { inner }
    }

    /// Returns the number of lines in the current page.
    pub(crate) fn lines(&self) -> usize {
        let inner = self.inner.read().unwrap();
        if inner.finished {
            return 0;
        }
        let mut lines = inner.newlines.len();
        let after_last_newline_offset = if lines == 0 {
            0
        } else {
            inner.newlines[lines - 1] + 1
        };
        if inner.buffer.len() > after_last_newline_offset {
            lines += 1;
        }
        lines
    }

    /// Calls the callback `call` with the given line of the current page.
    pub(crate) fn with_line<T, F>(&self, index: usize, mut call: F) -> Option<T>
    where
        F: FnMut(&[u8]) -> T,
    {
        let inner = self.inner.read().unwrap();
        if index > inner.newlines.len() {
            return None;
        }
        let start = if index == 0 {
            0
        } else {
            inner.newlines[index - 1] + 1
        };
        let end = if index < inner.newlines.len() {
            inner.newlines[index] + 1
        } else {
            inner.buffer.len()
        };
        if start == end {
            return None;
        }
        Some(call(&inner.buffer[start..end]))
    }
}