use std::{
    convert::TryInto,
    fmt::{Debug, Formatter},
    path::Path,
};
use crate::{
    file::{self, commit::Commit, COMMIT_DATA_ENTRY_SIZE_SANS_HASH},
    File,
};
impl File {
    pub fn base_graph_count(&self) -> u8 {
        self.base_graph_count
    }
    pub fn commit_at(&self, pos: file::Position) -> Commit<'_> {
        Commit::new(self, pos)
    }
    pub fn object_hash(&self) -> gix_hash::Kind {
        self.object_hash
    }
    pub fn id_at(&self, pos: file::Position) -> &gix_hash::oid {
        assert!(
            pos.0 < self.num_commits(),
            "expected lexigraphical position less than {}, got {}",
            self.num_commits(),
            pos.0
        );
        let pos: usize = pos
            .0
            .try_into()
            .expect("an architecture able to hold 32 bits of integer");
        let start = self.oid_lookup_offset + (pos * self.hash_len);
        gix_hash::oid::from_bytes_unchecked(&self.data[start..][..self.hash_len])
    }
    pub fn iter_base_graph_ids(&self) -> impl Iterator<Item = &gix_hash::oid> {
        let start = self.base_graphs_list_offset.unwrap_or(0);
        let base_graphs_list = &self.data[start..][..self.hash_len * usize::from(self.base_graph_count)];
        base_graphs_list
            .chunks(self.hash_len)
            .map(gix_hash::oid::from_bytes_unchecked)
    }
    pub fn iter_commits(&self) -> impl Iterator<Item = Commit<'_>> {
        (0..self.num_commits()).map(move |i| self.commit_at(file::Position(i)))
    }
    pub fn iter_ids(&self) -> impl Iterator<Item = &gix_hash::oid> {
        (0..self.num_commits()).map(move |i| self.id_at(file::Position(i)))
    }
    pub fn lookup(&self, id: impl AsRef<gix_hash::oid>) -> Option<file::Position> {
        let id = id.as_ref();
        let first_byte = usize::from(id.first_byte());
        let mut upper_bound = self.fan[first_byte];
        let mut lower_bound = if first_byte != 0 { self.fan[first_byte - 1] } else { 0 };
        while lower_bound < upper_bound {
            let mid = (lower_bound + upper_bound) / 2;
            let mid_sha = self.id_at(file::Position(mid));
            use std::cmp::Ordering::*;
            match id.cmp(mid_sha) {
                Less => upper_bound = mid,
                Equal => return Some(file::Position(mid)),
                Greater => lower_bound = mid + 1,
            }
        }
        None
    }
    pub fn num_commits(&self) -> u32 {
        self.fan[255]
    }
    pub fn path(&self) -> &Path {
        &self.path
    }
}
impl File {
    pub(crate) fn commit_data_bytes(&self, pos: file::Position) -> &[u8] {
        assert!(
            pos.0 < self.num_commits(),
            "expected lexigraphical position less than {}, got {}",
            self.num_commits(),
            pos.0
        );
        let pos: usize = pos
            .0
            .try_into()
            .expect("an architecture able to hold 32 bits of integer");
        let entry_size = self.hash_len + COMMIT_DATA_ENTRY_SIZE_SANS_HASH;
        let start = self.commit_data_offset + (pos * entry_size);
        &self.data[start..][..entry_size]
    }
    pub(crate) fn extra_edges_data(&self) -> Option<&[u8]> {
        Some(&self.data[self.extra_edges_list_range.clone()?])
    }
}
impl Debug for File {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, r#"File("{:?}")"#, self.path.display())
    }
}