use bstr::ByteSlice;
use std::{ffi::OsStr, fs, io::Result, io::Write};
use std::ops::{Add, AddAssign, Deref, Index, Range, Sub};
use std::os::unix::ffi::OsStrExt;
use super::{annotate, io::WriteTo};

/// Buffer split to individual lines. Allows fast access to any line, to any range of
/// lines (LinesSlice) and to the full buffer data.
#[derive(Clone, derive_more::Debug)]
pub struct Lines {
    /// The full file data. Publicly facing, this is indexed by ByteIndex.
    #[debug("{} bytes", full.len())]
    full: Vec<u8>,
    /// The offsets of starts of the individual lines. Publicly facing, lines are indexed
    /// by LineIndex.
    #[debug("{} lines", offsets.len())]
    offsets: Vec<ByteIndex>,
}

/// Buffer split to individual lines that additionally specifies its file type. It also
/// records the path from which it was read.
#[derive(Clone, derive_more::Debug)]
pub struct TypedLines {
    /// The lines. TypedLines derefs to Lines; anything from Lines can be called on
    /// TypedLines.
    lines: Lines,
    /// File path. Relative to the repository root.
    #[debug("{:?}", path.as_bstr())]
    pub path: Vec<u8>,
    /// File type.
    pub file_type: annotate::FileType,
    /// Optional annotation tags.
    #[debug("{}",
        tags.as_ref().map(|v| format!("Some({} tags)", v.len()))
            .unwrap_or_else(|| "None".to_string())
    )]
    tags: Option<Vec<annotate::Tag>>,
}

/// Byte index (position) in Lines.
#[derive(
    Clone, Copy, Debug, Default, Hash,
    Eq, PartialEq, Ord, PartialOrd,
    derive_more::From, derive_more::Display,
)]
pub(crate) struct ByteIndex(usize);

/// Line index (position) in Lines.
#[derive(
    Clone, Copy, Debug, Default, Hash,
    Eq, PartialEq, Ord, PartialOrd,
    derive_more::From, derive_more::Display,
)]
pub struct LineIndex(usize);

/// A slice of lines.
#[derive(Clone, Debug)]
pub struct LinesSlice<'a> {
    lines: &'a Lines,
    range: Range<LineIndex>,
}

impl Lines {
    /// Create a new empty buffer.
    pub(crate) fn new() -> Self {
        Self {
            full: Vec::new(),
            offsets: Vec::new(),
        }
    }

    /// Create a new empty buffer. Reserve space for at least `bytes` bytes split into
    /// at least `line_count` lines.
    pub(crate) fn with_capacity(bytes: usize, line_count: usize) -> Self {
        Self {
            full: Vec::with_capacity(bytes),
            offsets: Vec::with_capacity(line_count),
        }
    }

    /// Return the offsets of line starts in the given `full`.
    fn build_offsets(full: &[u8]) -> Vec<ByteIndex> {
        full.iter()
            .enumerate()
            .scan(true, |state, (i, &b)| {
                let result = if *state { Some(i) } else { None };
                *state = b == b'\n';
                Some(result)
            })
            .filter_map(|r| r)
            .map(|index| ByteIndex::from(index))
            .collect()
    }

    /// Get the LineIndex pointing just after the last line.
    pub fn line_end(&self) -> LineIndex {
        self.offsets.len().into()
    }

    /// Get the ByteIndex pointing just after the full buffer.
    pub(crate) fn byte_end(&self) -> ByteIndex {
        self.full.len().into()
    }

    /// Get the slice of lines for the given `range`.
    pub fn line_slice(&self, range: Range<LineIndex>) -> LinesSlice<'_> {
        // Note: we can't implement the Index<Range<usize>> trait, since we need to
        // return a custom struct holding the slice. But the index() method in the
        // Index trait can return only a reference.
        LinesSlice {
            lines: self,
            range,
        }
    }

    /// Get the slice containing all lines.
    pub fn as_line_slice(&self) -> LinesSlice<'_> {
        LinesSlice {
            lines: self,
            range: 0.into() .. self.line_end(),
        }
    }

    /// Get the slice of all bytes in the given `range`.
    pub(crate) fn byte_slice(&self, range: Range<ByteIndex>) -> &[u8] {
        &self.full[range.start.0 .. range.end.0]
    }

    /// Add the given slice of lines to the end.
    pub(crate) fn push_lines(&mut self, slice: LinesSlice<'_>) {
        if slice.range.is_empty() {
            return;
        }
        if matches!(self.full.last(), Some(b) if *b != b'\n') {
            // The last line does not end with a new line. Add it to keep the struct
            // consistent.
            self.full.push(b'\n');
        }
        self.full.reserve(slice.byte_len());
        self.offsets.reserve(slice.line_len());
        for line in slice.into_iter() {
            self.offsets.push(self.byte_end());
            self.full.extend_from_slice(line);
        }
    }

    pub fn iter<'a>(&'a self) -> LinesIterator<'a> {
        LinesIterator::new(self)
    }

    /// Converts the byte index (pos) to the line index. Returns the line on which
    /// the byte position is located.
    pub(crate) fn calculate_line_index(&self, pos: ByteIndex) -> LineIndex {
        if pos == self.byte_end() {
            // For proper function of the binary search below, self.offsets would have to
            // have a self.byte_end() entry at the end. Emulate that case.
            return self.line_end();
        }
        match self.offsets.binary_search(&pos) {
            // beginning of the line
            Ok(index) => index.into(),
            // middle of the line
            Err(index) => index.saturating_sub(1).into()
        }
    }

    /// Converts the byte position (pos) to the line index of the next line. The byte
    /// position is first extended to the beginning of the next line; if it is already
    /// at the beginning of a line, it is not extended. This is useful for calculating
    /// the end of range of lines.
    pub(crate) fn calculate_line_index_next(&self, pos: ByteIndex) -> LineIndex {
        match self.offsets.binary_search(&pos) {
            // beginning of the line
            Ok(index) => index.into(),
            // middle of the line => return the next line
            Err(index) => index.into()
        }
    }

    /// Converts a byte position range to the corresponding line range.
    pub(crate) fn calculate_line_range(&self, range: Range<ByteIndex>) -> Range<LineIndex> {
        let res_start = self.calculate_line_index(range.start);
        let res_end = if range.is_empty() {
            // Special case: insertions only, no deletions. In this case we want to
            // return a range with zero length.
            res_start
        } else {
            self.calculate_line_index_next(range.end)
        };
        res_start .. res_end
    }

    /// Get the byte position of the start of the given line (the line at `index`).
    /// If the `index` is too large, return the position at the end of the buffer.
    pub(crate) fn get_byte_index(&self, index: LineIndex) -> ByteIndex {
        if index < self.line_end() {
            self.offsets[index.0]
        } else {
            self.byte_end()
        }
    }
}

impl Index<LineIndex> for Lines {
    type Output = [u8];

    /// Get the line with `index`.
    fn index(&self, index: LineIndex) -> &Self::Output {
        &self.byte_slice(self.get_byte_index(index) .. self.get_byte_index(index + 1))
    }
}

impl From<Vec<u8>> for Lines {
    fn from(value: Vec<u8>) -> Self {
        let offsets = Self::build_offsets(&value);
        Self {
            full: value,
            offsets,
        }
    }
}

impl From<TypedLines> for Lines {
    fn from(value: TypedLines) -> Self {
        value.lines
    }
}

impl ToString for Lines {
    fn to_string(&self) -> String {
        self.full.to_str_lossy().into_owned()
    }
}

impl TypedLines {
    /// Read data from the file at `path` and create a buffer with the data.
    pub fn from_file(path: &[u8]) -> Result<Self> {
        Ok(Self {
            lines: fs::read(OsStr::from_bytes(path))?.into(),
            path: path.to_vec(),
            file_type: annotate::FileType::from_path(path),
            tags: None,
        })
    }

    /// Create a typed buffer with the given data. Use the name to infer the type.
    pub fn from_vec_and_name(full: Vec<u8>, path: &[u8]) -> Self {
        Self {
            lines: full.into(),
            path: path.to_vec(),
            file_type: annotate::FileType::from_path(path),
            tags: None,
        }
    }

    /// Set the `path` as the file name and use it to infer the type.
    pub fn set_path(&mut self, path: &[u8]) {
        self.path = path.to_vec();
        self.file_type = annotate::FileType::from_path(path);
    }

    /// Anotate the lines.
    pub fn annotate(&mut self) {
        if self.tags.is_none() {
            self.tags = Some(
                annotate::annotate(&self.full, self.line_end().0, self.file_type)
            );
        }
    }

    pub fn tag_end(&self) -> Option<LineIndex> {
        self.tags.as_ref().map(|tags| tags.len().into())
    }

    pub fn tag(&self, index: LineIndex) -> Option<annotate::Tag> {
        self.tags.as_ref().and_then(|tags| tags.get(index.0).copied())
    }

    /// Get the slice of tags for the given line range.
    pub fn tag_slice(&self, range: Range<LineIndex>) -> Option<&[annotate::Tag]> {
        self.tags.as_ref().map(|tags| &tags[range.start.0 .. range.end.0])
    }
}

impl Deref for TypedLines {
    type Target = Lines;

    fn deref(&self) -> &Self::Target {
        &self.lines
    }
}

impl ByteIndex {
    /// Calculate offset delta (number of bytes) between self and other.
    pub(crate) fn delta(self, other: Self) -> usize {
        self.0.abs_diff(other.0)
    }
}

impl LineIndex {
    pub fn saturating_sub(self, rhs: usize) -> Self {
        self.0.saturating_sub(rhs).into()
    }

    /// Calculate offset delta (number of lines) between self and other.
    pub fn delta(self, other: Self) -> usize {
        self.0.abs_diff(other.0)
    }

    /// Calculate offset delta (number of lines) between self and other if self > other.
    /// Otherwise, the result is 0.
    pub(crate) fn saturating_delta(self, other: Self) -> usize {
        self.0.saturating_sub(other.0)
    }

    /// Get the index as usize. Intended only as an API glue for external crates.
    pub fn export(&self) -> usize {
        self.0
    }
}

impl Add<usize> for ByteIndex {
    type Output = Self;

    fn add(self, other: usize) -> Self {
        Self(self.0 + other)
    }
}

impl Sub<usize> for ByteIndex {
    type Output = Self;

    fn sub(self, other: usize) -> Self {
        Self(self.0 - other)
    }
}

impl Sub<ByteIndex> for ByteIndex {
    type Output = isize;

    fn sub(self, other: Self) -> isize {
        self.0 as isize - other.0 as isize
    }
}

impl Add<usize> for LineIndex {
    type Output = Self;

    fn add(self, other: usize) -> Self {
        Self(self.0 + other)
    }
}

impl Sub<usize> for LineIndex {
    type Output = Self;

    fn sub(self, other: usize) -> Self {
        Self(self.0 - other)
    }
}

impl Sub<LineIndex> for LineIndex {
    type Output = isize;

    fn sub(self, other: Self) -> isize {
        self.0 as isize - other.0 as isize
    }
}

impl AddAssign<usize> for LineIndex {
    fn add_assign(&mut self, other: usize) {
        self.0 += other;
    }
}

/// Helper trait to implement len() and iter() on Range<LineIndex>.
pub trait LineIndexRange {
    fn len(&self) -> usize;
    fn iter(&self) -> impl Iterator<Item = LineIndex>;
    fn from_u32(range: Range<u32>) -> Self;
}

impl LineIndexRange for Range<LineIndex> {
    fn len(&self) -> usize {
        self.end.0 - self.start.0
    }

    fn iter(&self) -> impl Iterator<Item = LineIndex> {
        (self.start.0 .. self.end.0).map(|n| LineIndex::from(n))
    }

    fn from_u32(range: Range<u32>) -> Self {
        (range.start as usize).into() .. (range.end as usize).into()
    }
}

impl<'a> LinesSlice<'a> {
    /// Get the number of lines in this slice.
    pub(crate) fn line_len(&self) -> usize {
        (self.range.end - self.range.start) as usize
    }

    /// Get the number of bytes in this slice. This includes newlines.
    pub(crate) fn byte_len(&self) -> usize {
        let range = self.byte_range();
        (range.end - range.start) as usize
    }

    /// Get the LineIndex pointing just after the last line in this slice.
    pub fn line_end(&self) -> LineIndex {
        self.line_len().into()
    }

    fn byte_range(&self) -> Range<ByteIndex> {
        self.lines.get_byte_index(self.range.start)
        ..
        self.lines.get_byte_index(self.range.end)
    }

    /// Get the slice of all bytes in this LineSlice.
    pub fn byte_slice(&self) -> &[u8] {
        self.lines.byte_slice(self.byte_range())
    }

    pub fn iter(&self) -> LinesIterator<'a> {
        LinesIterator::new_with_range(&self.lines, self.range.clone())
    }

    pub fn iter_strings(&self) -> impl Iterator<Item = String> + 'a {
        self.iter().map(|v| v.to_str_lossy().into_owned())
    }
}

impl<'a> WriteTo for LinesSlice<'a> {
    fn write_to(&self, f: &mut impl Write) -> Result<()> {
        f.write_all(&self.lines.byte_slice(self.byte_range()))
    }
}

impl<'a> Index<LineIndex> for LinesSlice<'a> {
    type Output = [u8];

    /// Get the line with `index`.
    fn index(&self, index: LineIndex) -> &Self::Output {
        assert!(index < self.range.end);
        let index = self.range.start + index.0;
        &self.lines.byte_slice(
            self.lines.get_byte_index(index) .. self.lines.get_byte_index(index + 1)
        )
    }
}

impl<'a> IntoIterator for LinesSlice<'a> {
    type Item = &'a [u8];
    type IntoIter = LinesIterator<'a>;

    fn into_iter(self) -> Self::IntoIter {
        LinesIterator {
            lines: self.lines,
            pos: self.range.start,
            end: self.range.end,
        }
    }
}

#[derive(Clone)]
pub struct LinesIterator<'a> {
    lines: &'a Lines,
    pos: LineIndex,
    end: LineIndex,
}

impl<'a> LinesIterator<'a> {
    fn new(lines: &'a Lines) -> Self {
        Self {
            lines: lines,
            pos: 0.into(),
            end: lines.line_end(),
        }
    }

    fn new_with_range(lines: &'a Lines, range: Range<LineIndex>) -> Self {
        Self {
            lines: lines,
            pos: range.start,
            end: range.end,
        }
    }
}

impl<'a> Iterator for LinesIterator<'a> {
    type Item = &'a [u8];

    fn next(&mut self) -> Option<Self::Item> {
        if self.pos >= self.end {
            None
        } else {
            let result = &self.lines[self.pos];
            self.pos += 1;
            Some(result)
        }
    }
}

impl<'a> imara_diff::TokenSource for LinesIterator<'a> {
    type Token = &'a [u8];
    type Tokenizer = Self;

    fn tokenize(&self) -> Self::Tokenizer {
        self.clone()
    }

    fn estimate_tokens(&self) -> u32 {
        ((self.end - self.pos) as usize).min(u32::MAX as usize) as u32
    }
}
