use crate::error::Result;
use crate::pos::Position;
use ropey::Rope;
use std::fmt::Display;
use std::hash::Hash;

/// Range like selection therefore endposition is exclusive
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Range {
    pub start: Position,
    pub end: Position,
}

impl Range {
    pub const ZERO: Range = Range {
        start: Position::ZERO,
        end: Position::ZERO,
    };

    pub fn from_lsp(
        range: &tower_lsp::lsp_types::Range,
        rope: &Rope,
        encoding: &tower_lsp::lsp_types::PositionEncodingKind,
    ) -> Result<Self> {
        Ok(Range {
            start: Position::from_lsp(range.start, rope, encoding)?,
            end: Position::from_lsp(range.end, rope, encoding)?,
        })
    }

    pub fn into_lsp(
        self,
        rope: &Rope,
        encoding: &tower_lsp::lsp_types::PositionEncodingKind,
    ) -> Result<tower_lsp::lsp_types::Range> {
        Ok(tower_lsp::lsp_types::Range {
            start: self.start.into_lsp(rope, encoding)?,
            end: self.end.into_lsp(rope, encoding)?,
        })
    }

    pub fn len_lsp(
        self,
        rope: &Rope,
        encoding: &tower_lsp::lsp_types::PositionEncodingKind,
    ) -> usize {
        let start_byte = self.start.byte_index(rope);
        let end_byte = self.end.byte_index(rope);
        let slice = rope.byte_slice(start_byte..end_byte);
        match encoding.as_str() {
            "utf-8" => slice.len_chars(),
            "utf-16" => slice.len_utf16_cu(),
            e => unimplemented!("encoding {e} not implemented"),
        }
    }
}

impl Display for Range {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}:{} - {}:{}",
            self.start.line(),
            self.start.character_byte(),
            self.end.line(),
            self.end.character_byte()
        )
    }
}

impl From<tree_sitter_c2rust::Range> for Range {
    fn from(value: tree_sitter_c2rust::Range) -> Self {
        Range {
            start: value.start_point.into(),
            end: value.end_point.into(),
        }
    }
}

impl From<Range> for std::ops::Range<tree_sitter_c2rust::Point> {
    fn from(value: Range) -> std::ops::Range<tree_sitter_c2rust::Point> {
        value.start.into()..value.end.into()
    }
}
