/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use fbthrift::compact_protocol;
use sorted_vector_map::SortedVectorMap;

use crate::blob::Blob;
use crate::blob::BlobstoreValue;
use crate::blob::FsnodeBlob;
use crate::errors::ErrorKind;
use crate::file_change::FileType;
use crate::hash::Sha1;
use crate::hash::Sha256;
use crate::path::MPathElement;
use crate::thrift;
use crate::typed_hash::ContentId;
use crate::typed_hash::FsnodeId;
use crate::typed_hash::FsnodeIdContext;

/// An fsnode is a manifest node containing summary information about the
/// files in the manifest that is useful in the implementation of
/// filesystems.
///
/// Fsnodes only exist for trees, and each fsnode is a structure that contains:
/// * A list of its children, containing for each child:
///   - Name
///   - Type (regular file, executable file, symlink or sub-directory)
///   - The content id, size, and content hashes for files
///   - The fsnode id, simple format hashes, and summary counts for directories
/// * The simple format hashes and summary counts for the directory itself
///
/// Simple format hashes are a SHA-1 or SHA-256 hash of the directory's
/// contents rendered in the following simple form: "HASH TYPE NAME\0"
/// for each entry in the directory, where "TYPE" is one of 'file', 'exec',
/// 'link', or 'tree', and HASH is the corresponding SHA-1 or SHA-256 hash
/// of the entry (content for files, simple format for directories).
///
/// The purpose of simple format hashes is to allow clients to construct
/// their own hashes of data they have available locally, in order to do a
/// whole-tree comparison with data stored in Mononoke.
///
/// The summary counts stored for each directory are:
/// * count of immediate child files
/// * count of immediate child sub-directories
/// * recursive count of descendant files
/// * total size of immediate child files
/// * rercursive total size of descendant files
///
/// Unlike unodes, fsnodes are not repository-wide unique. If the same set of
/// files and directories appear at different places in the commit graph,
/// they will share fsnodes.

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Fsnode {
    subentries: SortedVectorMap<MPathElement, FsnodeEntry>,
    summary: FsnodeSummary,
}

impl Fsnode {
    pub fn new(
        subentries: SortedVectorMap<MPathElement, FsnodeEntry>,
        summary: FsnodeSummary,
    ) -> Self {
        Self {
            subentries,
            summary,
        }
    }

    pub fn lookup(&self, basename: &MPathElement) -> Option<&FsnodeEntry> {
        self.subentries.get(basename)
    }

    pub fn list(&self) -> impl Iterator<Item = (&MPathElement, &FsnodeEntry)> {
        self.subentries.iter()
    }

    pub fn into_subentries(self) -> SortedVectorMap<MPathElement, FsnodeEntry> {
        self.subentries
    }

    pub fn summary(&self) -> &FsnodeSummary {
        &self.summary
    }

    pub(crate) fn from_thrift(t: thrift::Fsnode) -> Result<Fsnode> {
        let subentries = t
            .subentries
            .into_iter()
            .map(|(basename, fsnode_entry)| {
                let basename = MPathElement::from_thrift(basename)?;
                let fsnode_entry = FsnodeEntry::from_thrift(fsnode_entry)?;
                Ok((basename, fsnode_entry))
            })
            .collect::<Result<_>>()?;
        let summary = FsnodeSummary::from_thrift(t.summary)?;
        Ok(Fsnode {
            subentries,
            summary,
        })
    }

    pub(crate) fn into_thrift(self) -> thrift::Fsnode {
        let subentries: SortedVectorMap<_, _> = self
            .subentries
            .into_iter()
            .map(|(basename, fsnode_entry)| (basename.into_thrift(), fsnode_entry.into_thrift()))
            .collect();
        let summary = self.summary.into_thrift();
        thrift::Fsnode {
            subentries,
            summary,
        }
    }

    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
        let thrift_tc = compact_protocol::deserialize(bytes)
            .with_context(|| ErrorKind::BlobDeserializeError("Fsnode".into()))?;
        Self::from_thrift(thrift_tc)
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum FsnodeEntry {
    File(FsnodeFile),
    Directory(FsnodeDirectory),
}

impl FsnodeEntry {
    pub(crate) fn from_thrift(t: thrift::FsnodeEntry) -> Result<FsnodeEntry> {
        match t {
            thrift::FsnodeEntry::File(fsnode_file) => {
                let fsnode_file = FsnodeFile::from_thrift(fsnode_file)?;
                Ok(FsnodeEntry::File(fsnode_file))
            }
            thrift::FsnodeEntry::Directory(fsnode_directory) => {
                let fsnode_directory = FsnodeDirectory::from_thrift(fsnode_directory)?;
                Ok(FsnodeEntry::Directory(fsnode_directory))
            }
            thrift::FsnodeEntry::UnknownField(unknown) => bail!(
                "Unknown field encountered when parsing thrift::FsnodeEntry: {}",
                unknown,
            ),
        }
    }

    pub(crate) fn into_thrift(self) -> thrift::FsnodeEntry {
        match self {
            FsnodeEntry::File(fsnode_file) => thrift::FsnodeEntry::File(fsnode_file.into_thrift()),
            FsnodeEntry::Directory(fsnode_directory) => {
                thrift::FsnodeEntry::Directory(fsnode_directory.into_thrift())
            }
        }
    }
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FsnodeFile {
    content_id: ContentId,
    file_type: FileType,
    size: u64,
    content_sha1: Sha1,
    content_sha256: Sha256,
}

impl From<FsnodeFile> for (ContentId, FileType) {
    fn from(f: FsnodeFile) -> Self {
        (f.content_id, f.file_type)
    }
}

impl FsnodeFile {
    pub fn new(
        content_id: ContentId,
        file_type: FileType,
        size: u64,
        content_sha1: Sha1,
        content_sha256: Sha256,
    ) -> Self {
        Self {
            content_id,
            file_type,
            size,
            content_sha1,
            content_sha256,
        }
    }

    pub fn content_id(&self) -> &ContentId {
        &self.content_id
    }

    pub fn file_type(&self) -> &FileType {
        &self.file_type
    }

    pub fn size(&self) -> u64 {
        self.size
    }

    pub fn content_sha1(&self) -> &Sha1 {
        &self.content_sha1
    }

    pub fn content_sha256(&self) -> &Sha256 {
        &self.content_sha256
    }

    pub(crate) fn from_thrift(t: thrift::FsnodeFile) -> Result<FsnodeFile> {
        let content_id = ContentId::from_thrift(t.content_id)?;
        let file_type = FileType::from_thrift(t.file_type)?;
        let size = t.size as u64;
        let content_sha1 = Sha1::from_bytes(t.content_sha1.0)?;
        let content_sha256 = Sha256::from_bytes(t.content_sha256.0)?;
        Ok(FsnodeFile {
            content_id,
            file_type,
            size,
            content_sha1,
            content_sha256,
        })
    }

    pub(crate) fn into_thrift(self) -> thrift::FsnodeFile {
        thrift::FsnodeFile {
            content_id: self.content_id.into_thrift(),
            file_type: self.file_type.into_thrift(),
            size: self.size as i64,
            content_sha1: self.content_sha1.into_thrift(),
            content_sha256: self.content_sha256.into_thrift(),
        }
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FsnodeDirectory {
    id: FsnodeId,
    summary: FsnodeSummary,
}

impl FsnodeDirectory {
    pub fn new(id: FsnodeId, summary: FsnodeSummary) -> Self {
        Self { id, summary }
    }

    pub fn id(&self) -> &FsnodeId {
        &self.id
    }

    pub fn summary(&self) -> &FsnodeSummary {
        &self.summary
    }

    pub(crate) fn from_thrift(t: thrift::FsnodeDirectory) -> Result<FsnodeDirectory> {
        let id = FsnodeId::from_thrift(t.id)?;
        let summary = FsnodeSummary::from_thrift(t.summary)?;
        Ok(FsnodeDirectory { id, summary })
    }

    pub(crate) fn into_thrift(self) -> thrift::FsnodeDirectory {
        thrift::FsnodeDirectory {
            id: self.id.into_thrift(),
            summary: self.summary.into_thrift(),
        }
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FsnodeSummary {
    pub simple_format_sha1: Sha1,
    pub simple_format_sha256: Sha256,
    pub child_files_count: u64,
    pub child_files_total_size: u64,
    pub child_dirs_count: u64,
    pub descendant_files_count: u64,
    pub descendant_files_total_size: u64,
}

impl FsnodeSummary {
    pub(crate) fn from_thrift(t: thrift::FsnodeSummary) -> Result<FsnodeSummary> {
        let simple_format_sha1 = Sha1::from_bytes(t.simple_format_sha1.0)?;
        let simple_format_sha256 = Sha256::from_bytes(t.simple_format_sha256.0)?;
        let child_files_count = t.child_files_count as u64;
        let child_files_total_size = t.child_files_total_size as u64;
        let child_dirs_count = t.child_dirs_count as u64;
        let descendant_files_count = t.descendant_files_count as u64;
        let descendant_files_total_size = t.descendant_files_total_size as u64;
        Ok(FsnodeSummary {
            simple_format_sha1,
            simple_format_sha256,
            child_files_count,
            child_files_total_size,
            child_dirs_count,
            descendant_files_count,
            descendant_files_total_size,
        })
    }

    pub(crate) fn into_thrift(self) -> thrift::FsnodeSummary {
        thrift::FsnodeSummary {
            simple_format_sha1: self.simple_format_sha1.into_thrift(),
            simple_format_sha256: self.simple_format_sha256.into_thrift(),
            child_files_count: self.child_files_count as i64,
            child_files_total_size: self.child_files_total_size as i64,
            child_dirs_count: self.child_dirs_count as i64,
            descendant_files_count: self.descendant_files_count as i64,
            descendant_files_total_size: self.descendant_files_total_size as i64,
        }
    }
}

impl BlobstoreValue for Fsnode {
    type Key = FsnodeId;

    fn into_blob(self) -> FsnodeBlob {
        let thrift = self.into_thrift();
        let data = compact_protocol::serialize(&thrift);
        let mut context = FsnodeIdContext::new();
        context.update(&data);
        let id = context.finish();
        Blob::new(id, data)
    }

    fn from_blob(blob: Blob<Self::Key>) -> Result<Self> {
        Self::from_bytes(blob.data().as_ref())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::u64;

use abomonation_derive::Abomonation;
use serde_derive::Serialize;

/// Generation number
///
/// The generation number for a changeset is defined as the max of the changeset's parents'
/// generation number plus 1; if there are no parents then it's 1.
#[derive(
    Abomonation,
    Debug,
    Copy,
    Clone,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Hash,
    Serialize
)]
pub struct Generation(u64);

pub const FIRST_GENERATION: Generation = Generation(1);

impl Generation {
    /// Creates new generation number
    pub fn new(gen: u64) -> Self {
        Generation(gen)
    }

    /// Create a maximum possible generation number
    pub fn max_gen() -> Self {
        Generation(u64::MAX)
    }

    pub fn value(&self) -> u64 {
        self.0
    }

    /// The difference from this generation to the other as the difference in their
    /// generation numbers.
    /// If this Generation is smaller than the other, return None.
    pub fn difference_from(&self, other: Generation) -> Option<u64> {
        let Generation(self_gen) = self;
        let Generation(other_gen) = other;
        self_gen.checked_sub(other_gen)
    }

    /// Subtracts value from generation number, returns None if the result is not a valid
    /// generation number.
    pub fn checked_sub(&self, value: u64) -> Option<Generation> {
        let Generation(self_gen) = self;
        let res = self_gen.checked_sub(value);
        if res == Some(0) {
            return None;
        }
        res.map(Generation::new)
    }

    /// Adds a value to generation number
    pub fn add(&self, value: u64) -> Generation {
        let Generation(self_gen) = self;
        Generation::new(self_gen + value)
    }
}

impl AsRef<u64> for Generation {
    fn as_ref(&self) -> &u64 {
        &self.0
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fmt;
use std::fmt::Display;
use std::str;
use std::str::FromStr;

use abomonation_derive::Abomonation;
use anyhow::bail;
use anyhow::Error;
use anyhow::Result;
use edenapi_types::CommitId as EdenapiCommitId;
use sql::mysql;

use crate::BonsaiChangeset;
use crate::BonsaiChangesetMut;

pub const GLOBALREV_EXTRA: &str = "global_rev";

// Globalrev of first commit when globalrevs were introduced in Mercurial.
// To get globalrev from commit we want to check whether there exists "global_rev" key in bcs extras
// and is not less than START_COMMIT_GLOBALREV.
// Otherwise we try to fetch "convert_revision" key, and parse svnrev from it.
pub const START_COMMIT_GLOBALREV: u64 = 1000147970;

// Changeset globalrev.
#[derive(Abomonation, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(mysql::OptTryFromRowField)]
pub struct Globalrev(u64);

impl Globalrev {
    pub const fn start_commit() -> Self {
        Self(START_COMMIT_GLOBALREV)
    }

    #[inline]
    pub const fn new(rev: u64) -> Self {
        Self(rev)
    }

    #[inline]
    pub fn id(&self) -> u64 {
        self.0
    }

    // ex. svn:uuid/path@1234
    pub fn parse_svnrev(svnrev: &str) -> Result<u64> {
        let at_pos = svnrev
            .rfind('@')
            .ok_or_else(|| Error::msg("Wrong convert_revision value"))?;
        let result = svnrev[1 + at_pos..].parse::<u64>()?;
        Ok(result)
    }

    pub fn from_bcs(bcs: &BonsaiChangeset) -> Result<Self> {
        match (
            bcs.hg_extra().find(|(key, _)| key == &GLOBALREV_EXTRA),
            bcs.hg_extra().find(|(key, _)| key == &"convert_revision"),
        ) {
            (Some((_, globalrev)), Some((_, svnrev))) => {
                let globalrev = str::from_utf8(globalrev)?.parse::<u64>()?;
                let svnrev = Globalrev::parse_svnrev(str::from_utf8(svnrev)?)?;
                if globalrev >= START_COMMIT_GLOBALREV {
                    Ok(Self::new(globalrev))
                } else {
                    Ok(Self::new(svnrev))
                }
            }
            (Some((_, globalrev)), None) => {
                let globalrev = str::from_utf8(globalrev)?.parse::<u64>()?;
                if globalrev < START_COMMIT_GLOBALREV {
                    bail!("Bonsai cs {:?} without globalrev", bcs)
                } else {
                    Ok(Self::new(globalrev))
                }
            }
            (None, Some((_, svnrev))) => {
                let svnrev = Globalrev::parse_svnrev(str::from_utf8(svnrev)?)?;
                Ok(Self::new(svnrev))
            }
            (None, None) => bail!("Bonsai cs {:?} without globalrev", bcs),
        }
    }

    pub fn set_on_changeset(&self, bcs: &mut BonsaiChangesetMut) {
        bcs.hg_extra.insert(
            GLOBALREV_EXTRA.into(),
            format!("{}", self.id()).into_bytes(),
        );
    }

    #[must_use = "increment does not modify the generation object"]
    pub fn increment(self) -> Self {
        Self(self.0 + 1)
    }
}

impl Display for Globalrev {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&self.0, fmt)
    }
}

impl FromStr for Globalrev {
    type Err = <u64 as FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        u64::from_str(s).map(Globalrev::new)
    }
}

impl From<Globalrev> for EdenapiCommitId {
    fn from(value: Globalrev) -> Self {
        EdenapiCommitId::Globalrev(value.id())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::io::Write;
use std::str::FromStr;

use abomonation_derive::Abomonation;
use anyhow::bail;
use anyhow::Error;
use anyhow::Result;
use ascii::AsciiStr;
use ascii::AsciiString;
use blake2::digest::Update;
use blake2::digest::VariableOutput;
use blake2::VarBlake2b;
use edenapi_types::Blake3 as EdenapiBlake3;
use edenapi_types::CommitId as EdenapiCommitId;
use edenapi_types::GitSha1 as EdenapiGitSha1;
use edenapi_types::Sha1 as EdenapiSha1;
use edenapi_types::Sha256 as EdenapiSha256;
use faster_hex::hex_decode;
use faster_hex::hex_encode;
use quickcheck::empty_shrinker;
use quickcheck::Arbitrary;
use quickcheck::Gen;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use sql::mysql;

use crate::errors::ErrorKind;
use crate::thrift;

// There is no NULL_HASH for Blake2 hashes. Any places that need a null hash should use an
// Option type, or perhaps a list as desired.

/// Raw BLAKE2b hash.
///
/// Mononoke's internal hashes are based on the BLAKE2b format, used to generate 256-bit (32-byte)
/// hashes.
///
/// This type is not used directly in most cases -- it is only used to build more specific typed
/// hashes.
///
/// For more on BLAKE2b, see <https://blake2.net/>

pub const BLAKE2_HASH_LENGTH_BYTES: usize = 32;
pub const BLAKE2_HASH_LENGTH_HEX: usize = BLAKE2_HASH_LENGTH_BYTES * 2;

#[derive(
    Abomonation,
    Clone,
    Copy,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Hash,
    Serialize,
    Deserialize,
    mysql::OptTryFromRowField
)]
pub struct Blake2([u8; BLAKE2_HASH_LENGTH_BYTES]);

impl Blake2 {
    /// Construct a `Blake2` from an array of BLAKE2_HASH_LENGTH_BYTES bytes containing a
    /// BLAKE2b hash (ie, *not* a hash of the bytes).
    pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Self> {
        let bytes = bytes.as_ref();
        if bytes.len() != BLAKE2_HASH_LENGTH_BYTES {
            bail!(ErrorKind::InvalidBlake2Input(format!(
                "need exactly {} bytes",
                BLAKE2_HASH_LENGTH_BYTES
            )));
        } else {
            let mut ret = [0; BLAKE2_HASH_LENGTH_BYTES];
            ret.copy_from_slice(bytes);
            Ok(Blake2(ret))
        }
    }

    /// Construct a `Blake2` from an array of BLAKE2_HASH_LENGTH_BYTES bytes.
    #[inline]
    pub const fn from_byte_array(arr: [u8; BLAKE2_HASH_LENGTH_BYTES]) -> Self {
        Blake2(arr)
    }

    #[inline]
    pub fn from_thrift(b: thrift::Blake2) -> Result<Self> {
        if b.0.len() != BLAKE2_HASH_LENGTH_BYTES {
            bail!(ErrorKind::InvalidThrift(
                "Blake2".into(),
                format!(
                    "wrong length: expected {}, got {}",
                    BLAKE2_HASH_LENGTH_BYTES,
                    b.0.len()
                )
            ));
        }
        // BigEndian here is matched with `to_thrift` below.
        let mut arr = [0u8; BLAKE2_HASH_LENGTH_BYTES];
        arr.copy_from_slice(&b.0[..]);
        Ok(Blake2(arr))
    }

    /// Construct a `Blake2` from a hex-encoded `AsciiStr`.
    #[inline]
    pub fn from_ascii_str(s: &AsciiStr) -> Result<Self> {
        Self::from_str(s.as_str())
    }

    pub fn to_hex(&self) -> AsciiString {
        let mut v = vec![0; BLAKE2_HASH_LENGTH_HEX];

        // This can only panic if buffer size of Vec isn't correct, which would be
        // a programming error.
        hex_encode(self.as_ref(), &mut v).expect("failed to hex encode");

        unsafe {
            // A hex string is always a pure ASCII string.
            AsciiString::from_ascii_unchecked(v)
        }
    }

    pub fn into_thrift(self) -> thrift::Blake2 {
        thrift::Blake2(self.0.into())
    }

    // Stable hash prefix for selection when sampling with modulus
    pub fn sampling_fingerprint(&self) -> u64 {
        let mut bytes: [u8; 8] = [0; 8];
        bytes.copy_from_slice(&self.0[0..8]);
        u64::from_le_bytes(bytes)
    }

    #[inline]
    pub fn into_inner(self) -> [u8; BLAKE2_HASH_LENGTH_BYTES] {
        self.0
    }
}

/// Context for incrementally computing a `Blake2` hash.
#[derive(Clone)]
pub struct Context(VarBlake2b);

impl Context {
    /// Construct a `Context`
    #[inline]
    pub fn new(key: &[u8]) -> Self {
        Context(VarBlake2b::new_keyed(key, BLAKE2_HASH_LENGTH_BYTES))
    }

    #[inline]
    pub fn update<T>(&mut self, data: T)
    where
        T: AsRef<[u8]>,
    {
        self.0.update(data.as_ref())
    }

    #[inline]
    pub fn finish(self) -> Blake2 {
        let mut ret = [0u8; BLAKE2_HASH_LENGTH_BYTES];
        self.0.finalize_variable(|res| {
            if let Err(e) = ret.as_mut().write_all(res) {
                panic!(
                    "{}-byte array must work with {}-byte blake2b: {:?}",
                    BLAKE2_HASH_LENGTH_BYTES, BLAKE2_HASH_LENGTH_BYTES, e
                );
            }
        });
        Blake2(ret)
    }
}

/// Get a reference to the underlying bytes of a `Blake2`
impl AsRef<[u8]> for Blake2 {
    fn as_ref(&self) -> &[u8] {
        &self.0[..]
    }
}

impl FromStr for Blake2 {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        if s.len() != BLAKE2_HASH_LENGTH_HEX {
            bail!(ErrorKind::InvalidBlake2Input(format!(
                "need exactly {} hex digits",
                BLAKE2_HASH_LENGTH_HEX
            )));
        }

        let mut ret = Blake2([0; BLAKE2_HASH_LENGTH_BYTES]);
        match hex_decode(s.as_bytes(), &mut ret.0) {
            Ok(_) => Ok(ret),
            Err(_) => bail!(ErrorKind::InvalidBlake2Input("bad hex character".into())),
        }
    }
}

impl Display for Blake2 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&self.to_hex(), fmt)
    }
}

/// Custom `Debug` output for `Blake2` so it prints in hex.
impl Debug for Blake2 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "Blake2({})", self)
    }
}

#[derive(
    Abomonation,
    Clone,
    Copy,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Hash,
    Serialize,
    Deserialize
)]
pub struct Blake2Prefix(Blake2, Blake2);

impl Blake2Prefix {
    /// Construct a `Blake2Prefix` from an array of bytes.
    pub fn from_bytes<B: AsRef<[u8]> + ?Sized>(bytes: &B) -> Result<Self> {
        let bytes = bytes.as_ref();
        if bytes.len() > BLAKE2_HASH_LENGTH_BYTES {
            bail!(ErrorKind::InvalidBlake2Input(format!(
                "prefix needs to be less or equal to {} bytes",
                BLAKE2_HASH_LENGTH_BYTES
            )))
        } else {
            let min_tail: Vec<u8> = vec![0x00; BLAKE2_HASH_LENGTH_BYTES - bytes.len()];
            let max_tail: Vec<u8> = vec![0xff; BLAKE2_HASH_LENGTH_BYTES - bytes.len()];
            Ok(Blake2Prefix(
                Blake2::from_bytes(bytes.iter().chain(&min_tail).cloned().collect::<Vec<_>>())?,
                Blake2::from_bytes(bytes.iter().chain(&max_tail).cloned().collect::<Vec<_>>())?,
            ))
        }
    }

    #[inline]
    /// Get a reference to the underlying bytes of the `Blake2` lower bound object.
    pub fn min_as_ref(&self) -> &[u8] {
        self.0.as_ref()
    }

    #[inline]
    /// Get a reference to the underlying bytes of the `Blake2` inclusive upper bound object.
    pub fn max_as_ref(&self) -> &[u8] {
        self.1.as_ref()
    }

    #[inline]
    /// Get the lower bound `Blake2`.
    pub fn min_bound(&self) -> Blake2 {
        self.0
    }

    #[inline]
    /// Get the inclusive upper bound `Blake2`.
    pub fn max_bound(&self) -> Blake2 {
        self.1
    }

    /// Convert into blake2 if it is the full prefix of the hash.
    #[inline]
    pub fn into_blake2(self) -> Option<Blake2> {
        if self.0 == self.1 { Some(self.0) } else { None }
    }

    pub fn to_hex(&self) -> AsciiString {
        let mut v_min_hex = vec![0; BLAKE2_HASH_LENGTH_HEX];
        hex_encode(self.0.as_ref(), &mut v_min_hex).expect("failed to hex encode");
        let mut v_max_hex = vec![0; BLAKE2_HASH_LENGTH_HEX];
        hex_encode(self.1.as_ref(), &mut v_max_hex).expect("failed to hex encode");
        for i in 0..BLAKE2_HASH_LENGTH_HEX {
            if v_min_hex[i] != v_max_hex[i] {
                v_min_hex.truncate(i);
                break;
            }
        }
        unsafe {
            // A hex string is always a pure ASCII string.
            AsciiString::from_ascii_unchecked(v_min_hex)
        }
    }
}

/// Construct a `Blake2Prefix` from a hex-encoded string.
impl FromStr for Blake2Prefix {
    type Err = Error;
    fn from_str(s: &str) -> Result<Blake2Prefix> {
        if s.len() > BLAKE2_HASH_LENGTH_HEX {
            bail!(ErrorKind::InvalidBlake2Input(format!(
                "prefix needs to be less or equal {} hex digits",
                BLAKE2_HASH_LENGTH_HEX
            )));
        }
        let min_tail: String = String::from_utf8(vec![b'0'; BLAKE2_HASH_LENGTH_HEX - s.len()])?;
        let max_tail: String = String::from_utf8(vec![b'f'; BLAKE2_HASH_LENGTH_HEX - s.len()])?;
        Ok(Blake2Prefix(
            Blake2::from_str(&(s.to_owned() + &min_tail))?,
            Blake2::from_str(&(s.to_owned() + &max_tail))?,
        ))
    }
}

impl Display for Blake2Prefix {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&self.to_hex(), fmt)
    }
}

/// Custom `Debug` output for `Blake2Prefix` so it prints in hex.
impl Debug for Blake2Prefix {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "Blake2Prefix({})", self)
    }
}

macro_rules! impl_hash {
    ($type:ident, $size:literal, $error:ident) => {
        #[derive(
            Abomonation,
            Clone,
            Copy,
            Eq,
            PartialEq,
            Ord,
            PartialOrd,
            Hash,
            Serialize,
            Deserialize
        )]
        pub struct $type([u8; $size]);

        impl $type {
            pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
                let bytes = bytes.as_ref();
                if bytes.len() != $size {
                    Err(ErrorKind::$error(format!("need exactly {} bytes", $size)).into())
                } else {
                    let mut ret = [0; $size];
                    ret.copy_from_slice(bytes);
                    Ok($type(ret))
                }
            }

            pub const fn from_byte_array(arr: [u8; $size]) -> Self {
                $type(arr)
            }

            pub fn to_hex(&self) -> AsciiString {
                let mut v = vec![0; $size * 2];

                // This can only panic if buffer size of Vec isn't correct, which would be
                // a programming error.
                hex_encode(self.as_ref(), &mut v).expect("failed to hex encode");

                unsafe { AsciiString::from_ascii_unchecked(v) }
            }

            pub fn to_brief(&self) -> AsciiString {
                self.to_hex().into_iter().take(8).collect()
            }

            #[inline]
            pub fn from_ascii_str(s: &AsciiStr) -> Result<Self> {
                Self::from_str(s.as_str())
            }

            pub fn into_thrift(self) -> thrift::$type {
                thrift::$type(self.0.into())
            }

            pub fn into_inner(self) -> [u8; $size] {
                self.0
            }

            /// Return a stable hash fingerprint that can be used for sampling
            #[inline]
            pub fn sampling_fingerprint(&self) -> u64 {
                let mut bytes: [u8; 8] = [0; 8];
                bytes.copy_from_slice(&&self.0[0..8]);
                u64::from_le_bytes(bytes)
            }
        }

        impl From<[u8; $size]> for $type {
            fn from(slice: [u8; $size]) -> Self {
                Self::from_byte_array(slice)
            }
        }

        impl Default for $type {
            fn default() -> Self {
                let bytes = [0; $size];
                $type(bytes)
            }
        }

        impl FromStr for $type {
            type Err = Error;

            fn from_str(s: &str) -> Result<Self> {
                if s.len() != $size * 2 {
                    bail!(ErrorKind::$error(format!(
                        "must be {} hex digits",
                        $size * 2
                    )));
                }

                let mut ret = $type([0; $size]);

                let ret = match hex_decode(s.as_bytes(), &mut ret.0) {
                    Ok(_) => ret,
                    Err(_) => bail!(ErrorKind::$error("bad digit".into())),
                };

                Ok(ret)
            }
        }

        /// Get a reference to the underlying bytes of a `$type`
        impl AsRef<[u8]> for $type {
            fn as_ref(&self) -> &[u8] {
                &self.0[..]
            }
        }

        /// Custom `Debug` output for `$type` so it prints in hex.
        impl Debug for $type {
            fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
                write!(fmt, concat!(stringify!($type), "({})"), self.to_hex())
            }
        }

        /// Custom `Display` output for `$type` so it prints in hex.
        impl Display for $type {
            fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
                Display::fmt(&self.to_hex(), fmt)
            }
        }

        impl_arbitrary_for_hash!($type, $size);
    };
}

macro_rules! impl_arbitrary_for_hash {
    ($type:ident, $size:literal) => {
        impl Arbitrary for $type {
            fn arbitrary(g: &mut Gen) -> Self {
                let mut bytes = [0; $size];
                for b in bytes.iter_mut() {
                    *b = u8::arbitrary(g);
                }
                $type(bytes)
            }

            fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
                empty_shrinker()
            }
        }
    };
}

impl_hash!(Sha256, 32, InvalidSha256Input);
impl_hash!(Sha1, 20, InvalidSha1Input);
impl_hash!(GitSha1, 20, InvalidGitSha1Input);
impl_hash!(Blake3, 32, InvalidBlake3Input);
impl_arbitrary_for_hash!(Blake2, 32);

impl Blake3 {
    #[inline]
    pub fn from_thrift(b: thrift::Blake3) -> Result<Self> {
        Blake3::from_bytes(b.0)
    }
}

impl From<Blake3> for EdenapiBlake3 {
    fn from(v: Blake3) -> Self {
        EdenapiBlake3::from(v.0)
    }
}

impl From<EdenapiBlake3> for Blake3 {
    fn from(v: EdenapiBlake3) -> Self {
        Blake3::from_byte_array(v.into())
    }
}

/// Git-style content blob hashes. Same as SHA-1 but with "<type> NNNN\0" appended to the front,
/// where <type> is the object type (blob, tree, etc), and NNNN is the blob size as a decimal
/// string. Given that we know what the prefix is, we never explicitly store it so the objects
/// can be shared with non-Git uses.
#[derive(
    Clone,
    Copy,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Hash,
    Serialize,
    Deserialize
)]
pub struct RichGitSha1 {
    sha1: GitSha1,
    ty: &'static str,
    size: u64,
}

impl RichGitSha1 {
    pub fn from_bytes(bytes: impl AsRef<[u8]>, ty: &'static str, size: u64) -> Result<Self> {
        Ok(Self::from_sha1(GitSha1::from_bytes(bytes)?, ty, size))
    }

    pub const fn from_byte_array(bytes: [u8; 20], ty: &'static str, size: u64) -> Self {
        Self::from_sha1(GitSha1::from_byte_array(bytes), ty, size)
    }

    pub const fn from_sha1(sha1: GitSha1, ty: &'static str, size: u64) -> Self {
        RichGitSha1 { sha1, ty, size }
    }

    pub fn sha1(&self) -> GitSha1 {
        self.sha1
    }

    pub fn ty(&self) -> &'static str {
        self.ty
    }

    pub fn size(&self) -> u64 {
        self.size
    }

    pub fn to_hex(&self) -> AsciiString {
        self.sha1.to_hex()
    }

    /// Return the Git prefix bytes
    pub fn prefix(&self) -> Vec<u8> {
        format!("{} {}\0", self.ty, self.size).into_bytes()
    }

    pub fn into_thrift(self) -> thrift::GitSha1 {
        thrift::GitSha1(self.sha1.0.into())
    }
}

impl AsRef<[u8]> for RichGitSha1 {
    fn as_ref(&self) -> &[u8] {
        self.sha1.as_ref()
    }
}

impl Debug for RichGitSha1 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("GitSha1")
            .field("sha1", &self.sha1.to_hex())
            .field("ty", &self.ty)
            .field("size", &self.size)
            .finish()
    }
}

impl Display for RichGitSha1 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&self.to_hex(), fmt)
    }
}

impl From<GitSha1> for EdenapiGitSha1 {
    fn from(v: GitSha1) -> Self {
        EdenapiGitSha1::from(v.0)
    }
}

impl From<EdenapiGitSha1> for GitSha1 {
    fn from(v: EdenapiGitSha1) -> Self {
        GitSha1::from_byte_array(v.into())
    }
}

impl From<Sha1> for EdenapiSha1 {
    fn from(v: Sha1) -> Self {
        EdenapiSha1::from(v.0)
    }
}

impl From<EdenapiSha1> for Sha1 {
    fn from(v: EdenapiSha1) -> Self {
        Sha1::from_byte_array(v.into())
    }
}

impl From<Sha256> for EdenapiSha256 {
    fn from(v: Sha256) -> Self {
        EdenapiSha256::from(v.0)
    }
}

impl From<EdenapiSha256> for Sha256 {
    fn from(v: EdenapiSha256) -> Self {
        Sha256::from_byte_array(v.into())
    }
}

impl From<Sha256> for lfs_protocol::Sha256 {
    fn from(v: Sha256) -> Self {
        lfs_protocol::Sha256(v.0)
    }
}

impl From<lfs_protocol::Sha256> for Sha256 {
    fn from(v: lfs_protocol::Sha256) -> Self {
        Sha256::from_byte_array(v.0)
    }
}

impl From<GitSha1> for EdenapiCommitId {
    fn from(value: GitSha1) -> Self {
        EdenapiCommitId::GitSha1(value.into())
    }
}

#[cfg(test)]
mod test {
    use quickcheck::quickcheck;
    use quickcheck::TestResult;

    use super::*;

    macro_rules! quickcheck_hash {
        ($type:ident, $size:literal) => {
            paste::item! {
                quickcheck! {
                    fn [<parse_roundtrip_for_ $type:lower>](v: Vec<u8>) -> TestResult {
                        if v.len() != $size {
                            return TestResult::discard()
                        }
                        let h = $type::from_bytes(v).unwrap();
                        let s = format!("{}", h);
                        let sh = s.parse().unwrap();

                        TestResult::from_bool(h == sh)
                    }

                    fn [<to_hex_roundtrip_for_ $type:lower>](h: $type) -> bool {
                        let v = h.to_hex();
                        let sh = $type::from_ascii_str(&v).unwrap();
                        h == sh
                    }

                    fn [<thrift_roundtrip_for_ $type:lower>](h: $type) -> bool {
                        let v = h.into_thrift();
                        let sh = $type::from_thrift(v).expect("converting a valid Thrift structure should always work");
                        h == sh
                    }
                }
            }
        };
    }

    // NULL is not exposed because no production code should use it.
    const NULL: Blake2 = Blake2([0; BLAKE2_HASH_LENGTH_BYTES]);

    // This hash is from https://asecuritysite.com/encryption/blake.
    #[rustfmt::skip]
    const NILHASH: Blake2 = Blake2([0x0e, 0x57, 0x51, 0xc0,
                                    0x26, 0xe5, 0x43, 0xb2,
                                    0xe8, 0xab, 0x2e, 0xb0,
                                    0x60, 0x99, 0xda, 0xa1,
                                    0xd1, 0xe5, 0xdf, 0x47,
                                    0x77, 0x8f, 0x77, 0x87,
                                    0xfa, 0xab, 0x45, 0xcd,
                                    0xf1, 0x2f, 0xe3, 0xa8]);

    #[test]
    fn test_nil() {
        let context = Context::new(b"");
        let nil = context.finish();
        assert_eq!(nil, NILHASH);
    }

    #[test]
    fn parse_ok() {
        assert_eq!(
            NULL,
            Blake2::from_str("0000000000000000000000000000000000000000000000000000000000000000")
                .unwrap()
        );
        assert_eq!(
            NILHASH,
            Blake2::from_str("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8")
                .unwrap()
        );
        assert_eq!(
            NILHASH,
            Blake2::from_str("0E5751C026E543B2E8AB2EB06099DAA1D1E5DF47778F7787FAAB45CDF12FE3A8")
                .unwrap()
        );
    }

    #[test]
    fn parse_and_display_prefix_ok() {
        // max length
        assert_eq!(
            "0000000000000000000000000000000000000000000000000000000000000000",
            format!(
                "{}",
                Blake2Prefix::from_str(
                    "0000000000000000000000000000000000000000000000000000000000000000"
                )
                .unwrap()
            )
        );
        // empty
        assert_eq!("", format!("{}", Blake2Prefix::from_str("").unwrap()));
        // capital case
        assert_eq!(
            "0e5751c026",
            format!("{}", Blake2Prefix::from_str("0E5751C026").unwrap())
        );
        // odd length
        assert_eq!(
            "0e5751c02",
            format!("{}", Blake2Prefix::from_str("0e5751c02").unwrap())
        );
        // even length
        assert_eq!(
            "0e5751c0",
            format!("{}", Blake2Prefix::from_str("0e5751c0").unwrap())
        );
    }

    #[test]
    fn parse_thrift() {
        let null_thrift = thrift::Blake2(vec![0; BLAKE2_HASH_LENGTH_BYTES].into());
        assert_eq!(NULL, Blake2::from_thrift(null_thrift.clone()).unwrap());
        assert_eq!(NULL.into_thrift(), null_thrift);

        let nil_thrift = thrift::Blake2(NILHASH.0.into());
        assert_eq!(NILHASH, Blake2::from_thrift(nil_thrift.clone()).unwrap());
        assert_eq!(NILHASH.into_thrift(), nil_thrift);
    }

    #[test]
    fn test_display() {
        assert_eq!(
            format!("{}", NULL),
            "0000000000000000000000000000000000000000000000000000000000000000"
        );
        assert_eq!(
            format!("{}", NILHASH),
            "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8"
        );
    }

    #[test]
    fn parse_bad() {
        Blake2::from_str("").expect_err("unexpected OK - zero len");
        Blake2::from_str("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a")
            .expect_err("unexpected OK - trunc");
        Blake2::from_str("xe5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a")
            .expect_err("unexpected OK - badchar beginning");
        Blake2::from_str("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3x")
            .expect_err("unexpected OK - badchar end");
        Blake2::from_str("0e5751c026e543b2e8ab2eb06099daa1d1x5df47778f7787faab45cdf12fe3a")
            .expect_err("unexpected OK - badchar middle");
    }

    #[test]
    fn parse_blake3_bad() {
        Blake3::from_str("").expect_err("unexpected OK - zero len");
        Blake3::from_str("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a")
            .expect_err("unexpected OK - trunc");
        Blake3::from_str("xe5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a")
            .expect_err("unexpected OK - badchar beginning");
        Blake3::from_str("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3x")
            .expect_err("unexpected OK - badchar end");
        Blake3::from_str("0e5751c026e543b2e8ab2eb06099daa1d1x5df47778f7787faab45cdf12fe3a")
            .expect_err("unexpected OK - badchar middle");
    }

    #[test]
    fn parse_thrift_bad() {
        Blake2::from_thrift(thrift::Blake2(vec![].into())).expect_err("unexpected OK - zero len");
        Blake2::from_thrift(thrift::Blake2(vec![0; 31].into()))
            .expect_err("unexpected OK - too short");
        Blake2::from_thrift(thrift::Blake2(vec![0; 33].into()))
            .expect_err("unexpected Ok - too long");
    }

    #[test]
    fn parse_blake3_thrift_bad() {
        Blake3::from_thrift(thrift::Blake3(vec![].into())).expect_err("unexpected OK - zero len");
        Blake3::from_thrift(thrift::Blake3(vec![0; 31].into()))
            .expect_err("unexpected OK - too short");
        Blake3::from_thrift(thrift::Blake3(vec![0; 33].into()))
            .expect_err("unexpected Ok - too long");
    }

    quickcheck_hash!(Blake2, 32);
    quickcheck_hash!(Blake3, 32);

    #[test]
    fn test_parse_sha1() {
        let sha1: Sha1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709".parse().unwrap();

        assert_eq!(
            sha1,
            Sha1::from_bytes([
                0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60,
                0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
            ])
            .unwrap()
        )
    }

    #[test]
    fn test_parse_sha256() {
        let sha256: Sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
            .parse()
            .unwrap();

        assert_eq!(
            sha256,
            Sha256::from_bytes([
                0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
                0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
                0x78, 0x52, 0xb8, 0x55,
            ])
            .unwrap()
        )
    }

    #[test]
    fn test_parse_blake3() {
        let blake3: Blake3 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
            .parse()
            .unwrap();

        assert_eq!(
            blake3,
            Blake3::from_bytes([
                0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
                0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
                0x78, 0x52, 0xb8, 0x55,
            ])
            .unwrap()
        )
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Base types used throughout Mononoke.
#![feature(round_char_boundary)]

pub mod basename_suffix_skeleton_manifest;
pub mod blame_v2;
pub mod blob;
pub mod bonsai_changeset;
pub mod content_chunk;
pub mod content_metadata_v2;
pub mod datetime;
pub mod deleted_manifest_common;
pub mod deleted_manifest_v2;
pub mod errors;
pub mod fastlog_batch;
pub mod file_change;
pub mod file_contents;
pub mod fsnode;
pub mod generation;
pub mod globalrev;
pub mod hash;
pub mod path;
pub mod rawbundle2;
pub mod redaction_key_list;
pub mod repo;
pub mod sha1_hash;
pub mod sharded_map;
pub mod skeleton_manifest;
pub mod sql_types;
pub mod svnrev;
pub mod thrift_convert;
pub mod typed_hash;
pub mod unode;

pub use blame_v2::BlameRejected;
pub use blame_v2::BlameV2Id;
pub use blob::Blob;
pub use blob::BlobstoreValue;
pub use blob::ChangesetBlob;
pub use blob::ContentBlob;
pub use blob::RawBundle2Blob;
pub use blobstore::BlobstoreBytes;
pub use bonsai_changeset::BonsaiChangeset;
pub use bonsai_changeset::BonsaiChangesetMut;
pub use content_chunk::ContentChunk;
pub use content_metadata_v2::ends_in_newline;
pub use content_metadata_v2::first_line;
pub use content_metadata_v2::is_ascii;
pub use content_metadata_v2::is_binary;
pub use content_metadata_v2::is_generated;
pub use content_metadata_v2::is_partially_generated;
pub use content_metadata_v2::is_utf8;
pub use content_metadata_v2::newline_count;
pub use content_metadata_v2::ContentAlias;
pub use content_metadata_v2::ContentMetadataV2;
pub use datetime::DateTime;
pub use datetime::Timestamp;
pub use file_change::BasicFileChange;
pub use file_change::FileChange;
pub use file_change::FileType;
pub use file_change::TrackedFileChange;
pub use file_contents::ChunkedFileContents;
pub use file_contents::ContentChunkPointer;
pub use file_contents::FileContents;
pub use generation::Generation;
pub use generation::FIRST_GENERATION;
pub use globalrev::Globalrev;
pub use path::check_case_conflicts;
pub use path::mpath_element_iter;
pub use path::path_bytes_from_mpath;
pub use path::MPath;
pub use path::MPathElement;
pub use path::MPathHash;
pub use path::PrefixTrie;
pub use path::RepoPath;
pub use rawbundle2::RawBundle2;
pub use redaction_key_list::RedactionKeyList;
pub use repo::RepositoryId;
pub use repo::REPO_PREFIX_REGEX;
pub use svnrev::Svnrev;
pub use thrift_convert::ThriftConvert;
pub use typed_hash::BasenameSuffixSkeletonManifestId;
pub use typed_hash::BlobstoreKey;
pub use typed_hash::ChangesetId;
pub use typed_hash::ChangesetIdPrefix;
pub use typed_hash::ChangesetIdsResolvedFromPrefix;
pub use typed_hash::ContentChunkId;
pub use typed_hash::ContentId;
pub use typed_hash::ContentMetadataV2Id;
pub use typed_hash::DeletedManifestV2Id;
pub use typed_hash::FastlogBatchId;
pub use typed_hash::FileUnodeId;
pub use typed_hash::FsnodeId;
pub use typed_hash::ManifestUnodeId;
pub use typed_hash::MononokeId;
pub use typed_hash::RawBundle2Id;
pub use typed_hash::SkeletonManifestId;

mod macros;

pub mod thrift {
    pub use mononoke_types_thrift::*;
}

pub mod private {
    pub use anyhow;
    pub use ascii::AsciiStr;
    pub use ascii::AsciiString;
    pub use bytes::Bytes;
    pub use quickcheck::empty_shrinker;
    pub use quickcheck::Arbitrary;
    pub use quickcheck::Gen;
    pub use serde::de::Deserialize;
    pub use serde::de::Deserializer;
    pub use serde::de::Error as DeError;
    pub use serde::Serialize;
    pub use serde::Serializer;

    pub use crate::errors::ErrorKind;
    pub use crate::hash::Blake2;
    pub use crate::thrift;
    pub use crate::typed_hash::Blake2HexVisitor;
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

// Extract optional Thrift field into a Result for easy ?
#[macro_export]
macro_rules! thrift_field {
    ($struct:ident, $thrift:ident, $field:ident) => {
        $thrift
            .$field
            .ok_or($crate::errors::ErrorKind::InvalidThrift(
                stringify!($struct).into(),
                format!("Missing field: {}", stringify!($field)),
            ))
    };
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::cmp;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Display;
use std::io;
use std::io::Write;
use std::iter::once;
use std::iter::Once;
use std::slice::Iter;

use abomonation_derive::Abomonation;
use anyhow::bail;
use anyhow::Context as _;
use anyhow::Error;
use anyhow::Result;
use ascii::AsciiString;
use lazy_static::lazy_static;
use quickcheck::Arbitrary;
use quickcheck::Gen;
use quickcheck_arbitrary_derive::Arbitrary;
use regex::bytes::Regex as BytesRegex;
use regex::Regex;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use smallvec::SmallVec;

use crate::bonsai_changeset::BonsaiChangeset;
use crate::errors::ErrorKind;
use crate::hash::Blake2;
use crate::hash::Context;
use crate::thrift;

// Filesystems on Linux commonly limit path *elements* to 255 bytes. Enforce this on MPaths as well
// as a repository that cannot be checked out isn't very useful.
const MPATH_ELEMENT_MAX_LENGTH: usize = 255;

/// A path or filename within Mononoke, with information about whether
/// it's the root of the repo, a directory or a file.
#[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Abomonation, Serialize, Deserialize)]
pub enum RepoPath {
    // It is now *completely OK* to create a RepoPath directly. All MPaths are valid once
    // constructed.
    RootPath,
    DirectoryPath(MPath),
    FilePath(MPath),
}

impl RepoPath {
    #[inline]
    pub fn root() -> Self {
        RepoPath::RootPath
    }

    pub fn dir<P>(path: P) -> Result<Self>
    where
        P: TryInto<MPath>,
        Error: From<P::Error>,
    {
        let path = path.try_into()?;
        Ok(RepoPath::DirectoryPath(path))
    }

    pub fn file<P>(path: P) -> Result<Self>
    where
        P: TryInto<MPath>,
        Error: From<P::Error>,
    {
        let path = path.try_into()?;
        Ok(RepoPath::FilePath(path))
    }

    /// Whether this path represents the root.
    #[inline]
    pub fn is_root(&self) -> bool {
        match *self {
            RepoPath::RootPath => true,
            _ => false,
        }
    }

    /// Whether this path represents a directory that isn't the root.
    #[inline]
    pub fn is_dir(&self) -> bool {
        match *self {
            RepoPath::DirectoryPath(_) => true,
            _ => false,
        }
    }

    /// Whether this patch represents a tree (root or other directory).
    #[inline]
    pub fn is_tree(&self) -> bool {
        match *self {
            RepoPath::RootPath => true,
            RepoPath::DirectoryPath(_) => true,
            _ => false,
        }
    }

    /// Whether this path represents a file.
    #[inline]
    pub fn is_file(&self) -> bool {
        match *self {
            RepoPath::FilePath(_) => true,
            _ => false,
        }
    }

    #[allow(clippy::len_without_is_empty)]
    /// Get the length of this repo path in bytes. `RepoPath::Root` has length 0.
    pub fn len(&self) -> usize {
        match *self {
            RepoPath::RootPath => 0,
            RepoPath::DirectoryPath(ref path) => path.len(),
            RepoPath::FilePath(ref path) => path.len(),
        }
    }

    pub fn mpath(&self) -> Option<&MPath> {
        match *self {
            RepoPath::RootPath => None,
            RepoPath::DirectoryPath(ref path) => Some(path),
            RepoPath::FilePath(ref path) => Some(path),
        }
    }

    pub fn into_mpath(self) -> Option<MPath> {
        match self {
            RepoPath::RootPath => None,
            RepoPath::DirectoryPath(path) => Some(path),
            RepoPath::FilePath(path) => Some(path),
        }
    }

    /// Serialize this RepoPath into a string. This shouldn't (yet) be considered stable if the
    /// definition of RepoPath changes.
    pub fn serialize(&self) -> Vec<u8> {
        bincode::serialize(self).expect("serialize for RepoPath cannot fail")
    }

    /// Serialize this RepoPath into a writer. This shouldn't (yet) be considered stable if the
    /// definition of RepoPath changes.
    pub fn serialize_into<W: Write>(&self, writer: &mut W) -> Result<()> {
        Ok(bincode::serialize_into(writer, self)?)
    }

    pub fn from_thrift(path: thrift::RepoPath) -> Result<Self> {
        let path = match path {
            thrift::RepoPath::RootPath(_) => Self::root(),
            thrift::RepoPath::DirectoryPath(path) => Self::dir(MPath::from_thrift(path)?)?,
            thrift::RepoPath::FilePath(path) => Self::file(MPath::from_thrift(path)?)?,
            thrift::RepoPath::UnknownField(unknown) => bail!(
                "Unknown field encountered when parsing thrift::RepoPath: {}",
                unknown,
            ),
        };
        Ok(path)
    }

    pub fn into_thrift(self) -> thrift::RepoPath {
        match self {
            // dummy false here is required because thrift doesn't support mixing enums with and
            // without payload
            RepoPath::RootPath => thrift::RepoPath::RootPath(false),
            RepoPath::DirectoryPath(path) => {
                thrift::RepoPath::DirectoryPath(MPath::into_thrift(path))
            }
            RepoPath::FilePath(path) => thrift::RepoPath::FilePath(MPath::into_thrift(path)),
        }
    }
}

impl Display for RepoPath {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            RepoPath::RootPath => write!(f, "(root path)"),
            RepoPath::DirectoryPath(ref path) => write!(f, "directory '{}'", path),
            RepoPath::FilePath(ref path) => write!(f, "file '{}'", path),
        }
    }
}

/// This trait impl allows passing in a &RepoPath where `Into<RepoPath>` is requested.
impl<'a> From<&'a RepoPath> for RepoPath {
    fn from(path: &'a RepoPath) -> RepoPath {
        path.clone()
    }
}

/// An element of a path or filename within Mercurial.
///
/// Mercurial treats pathnames as sequences of bytes, but the manifest format
/// assumes they cannot contain zero bytes. The bytes are not necessarily utf-8
/// and so cannot be converted into a string (or - strictly speaking - be displayed).
///
/// Internally using SmallVec as many path elements are directory names and thus
/// quite short, avoiding need for heap alloc. Its stack storage size is set to 24
/// as with the union feature the smallvec is 32 bytes on stack which is same as previous
/// Bytes member stack sise (Bytes will usually have heap as well of course)
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[derive(Abomonation, Serialize, Deserialize)]
pub struct MPathElement(SmallVec<[u8; 24]>);

impl MPathElement {
    #[inline]
    pub fn new(element: Vec<u8>) -> Result<MPathElement> {
        Self::verify(&element)?;
        Ok(MPathElement(SmallVec::from(element)))
    }

    #[inline]
    pub fn from_smallvec(element: SmallVec<[u8; 24]>) -> Result<MPathElement> {
        Self::verify(&element)?;
        Ok(MPathElement(element))
    }

    #[inline]
    pub fn new_from_slice(element: &[u8]) -> Result<MPathElement> {
        Self::verify(element)?;
        Ok(MPathElement(SmallVec::from(element)))
    }

    #[inline]
    pub fn from_thrift(element: thrift::MPathElement) -> Result<MPathElement> {
        Self::verify(&element.0).with_context(|| {
            ErrorKind::InvalidThrift("MPathElement".into(), "invalid path element".into())
        })?;
        Ok(MPathElement(element.0))
    }

    fn verify(p: &[u8]) -> Result<()> {
        if p.is_empty() {
            bail!(ErrorKind::InvalidPath(
                "".into(),
                "path elements cannot be empty".into()
            ));
        }
        if p.contains(&0) {
            bail!(ErrorKind::InvalidPath(
                String::from_utf8_lossy(p).into_owned(),
                "path elements cannot contain '\\0'".into(),
            ));
        }
        if p.contains(&1) {
            // MPath can not contain '\x01', in particular if mpath ends with '\x01'
            // and it is part of move metadata, because key-value pairs are separated
            // by '\n', you will get '\x01\n' which is also metadata separator.
            bail!(ErrorKind::InvalidPath(
                String::from_utf8_lossy(p).into_owned(),
                "path elements cannot contain '\\1'".into(),
            ));
        }
        if p.contains(&b'/') {
            bail!(ErrorKind::InvalidPath(
                String::from_utf8_lossy(p).into_owned(),
                "path elements cannot contain '/'".into(),
            ));
        }
        if p.contains(&b'\n') {
            bail!(ErrorKind::InvalidPath(
                String::from_utf8_lossy(p).into_owned(),
                "path elements cannot contain '\\n'".into(),
            ));
        }
        if p == b"." || p == b".." {
            bail!(ErrorKind::InvalidPath(
                String::from_utf8_lossy(p).into_owned(),
                "path elements cannot be . or .. to avoid traversal attacks".into(),
            ));
        }
        Self::check_len(p)?;
        Ok(())
    }

    fn check_len(p: &[u8]) -> Result<()> {
        if p.len() > MPATH_ELEMENT_MAX_LENGTH {
            bail!(ErrorKind::InvalidPath(
                String::from_utf8_lossy(p).into_owned(),
                format!(
                    "path elements cannot exceed {} bytes",
                    MPATH_ELEMENT_MAX_LENGTH
                )
            ));
        }

        Ok(())
    }

    #[allow(clippy::len_without_is_empty)]
    #[inline]
    pub fn len(&self) -> usize {
        self.0.len()
    }

    #[inline]
    pub fn into_thrift(self) -> thrift::MPathElement {
        thrift::MPathElement(self.0)
    }

    /// Returns true if this path element is valid UTF-8.
    pub fn is_utf8(&self) -> bool {
        std::str::from_utf8(self.0.as_ref()).is_ok()
    }

    /// Returns the length of the path element in WCHARs, if the path element
    /// is re-interpreted as a Windows filename.
    ///
    /// For UTF-8 path elements, this is the length of the UTF-16 encoding.
    /// For other path elementss, it is assumed that a Windows 8-bit encoding
    /// is in use and each byte corresponds to one WCHAR.
    pub fn wchar_len(&self) -> usize {
        match std::str::from_utf8(self.0.as_ref()) {
            Ok(s) => s.encode_utf16().count(),
            Err(_) => self.0.len(),
        }
    }

    /// Returns the lowercased version of this MPath element if it is valid
    /// UTF-8.
    pub fn to_lowercase_utf8(&self) -> Option<String> {
        let s = std::str::from_utf8(self.0.as_ref()).ok()?;
        let s = s.to_lowercase();
        Some(s)
    }

    /// Returns whether this path element is a valid filename on Windows.
    /// ```text
    ///
    /// Invalid filenames on Windows are:
    ///
    /// * Any filename containing a control character in the range 0-31, or
    ///   any character in the set `< > : " / \\ | ? *`.
    /// * Any filename ending in a `.` or a space.
    /// * Any filename that is `CON`, `PRN`, `AUX`, `NUL`, `COM1-9` or
    ///   `LPT1-9`, with or without an extension.
    /// ```
    pub fn is_valid_windows_filename(&self) -> bool {
        // File names containing any of <>:"/\|?* or control characters are invalid.
        let is_invalid = |c: &u8| *c < b' ' || b"<>:\"/\\|?*".iter().any(|i| i == c);
        if self.0.iter().any(is_invalid) {
            return false;
        }

        // File names ending in . or space are invalid.
        if let Some(b' ') | Some(b'.') = self.0.last() {
            return false;
        }

        // CON, PRN, AUX, NUL, COM[1-9] and LPT[1-9] are invalid, with or
        // without extension.
        if INVALID_WINDOWS_FILENAME_REGEX.is_match(self.0.as_ref()) {
            return false;
        }

        true
    }

    /// Returns whether potential_suffix is a suffix of this path element.
    /// For example, if the element is "file.extension", "n", "tension",
    /// "extension", ".extension", "file.extension" are suffixes of the
    /// basename, but "file" is not.
    #[inline]
    pub fn has_suffix(&self, potential_suffix: &[u8]) -> bool {
        self.0.ends_with(potential_suffix)
    }

    #[inline]
    pub fn starts_with(&self, prefix: &[u8]) -> bool {
        self.0.starts_with(prefix)
    }

    /// Reverse this path element inplace
    pub fn reverse(&mut self) {
        self.0.reverse()
    }
}

// Regex for looking for invalid windows filenames
lazy_static! {
    static ref INVALID_WINDOWS_FILENAME_REGEX: BytesRegex =
        BytesRegex::new("^((?i)CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])([.][^.]*|)$")
            .expect("invalid windows filename regex should be valid");
}

impl AsRef<[u8]> for MPathElement {
    #[inline]
    fn as_ref(&self) -> &[u8] {
        self.0.as_ref()
    }
}

impl From<MPathElement> for MPath {
    fn from(element: MPathElement) -> Self {
        MPath {
            elements: vec![element],
        }
    }
}

/// A path or filename within Mononoke (typically within manifests or changegroups).
///
/// This is called `MPath` so that it can be differentiated from `std::path::Path`.
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[derive(Abomonation, Serialize, Deserialize)]
pub struct MPath {
    elements: Vec<MPathElement>,
}

impl Extend<MPathElement> for Option<MPath> {
    fn extend<T: IntoIterator<Item = MPathElement>>(&mut self, iter: T) {
        match self {
            Some(ref mut path) => {
                path.elements.extend(iter);
            }
            None => {
                let elements = Vec::from_iter(iter);
                if elements.is_empty() {
                    *self = None;
                } else {
                    *self = Some(MPath { elements });
                }
            }
        }
    }
}

impl MPath {
    pub fn new<P: AsRef<[u8]>>(p: P) -> Result<MPath> {
        let p = p.as_ref();
        let elements: Vec<_> = p
            .split(|c| *c == b'/')
            .filter(|e| !e.is_empty())
            .map(MPathElement::new_from_slice)
            .collect::<Result<_, _>>()?;
        if elements.is_empty() {
            bail!(ErrorKind::InvalidPath(
                String::from_utf8_lossy(p).into_owned(),
                "path cannot be empty".into()
            ));
        }
        Ok(MPath { elements })
    }

    /// Same as `MPath::new`, except the input bytes may be empty.
    pub fn new_opt<P: AsRef<[u8]>>(p: P) -> Result<Option<MPath>> {
        let p = p.as_ref();
        if p.is_empty() {
            Ok(None)
        } else {
            Ok(Some(MPath::new(p)?))
        }
    }

    pub fn from_thrift(mpath: thrift::MPath) -> Result<MPath> {
        let elements: Result<Vec<_>> = mpath.0.into_iter().map(MPathElement::from_thrift).collect();
        let elements = elements?;

        if elements.is_empty() {
            bail!("Unexpected empty path in thrift::MPath")
        } else {
            Ok(MPath { elements })
        }
    }

    pub fn join<'a, Elements: IntoIterator<Item = &'a MPathElement>>(
        &self,
        another: Elements,
    ) -> MPath {
        let mut newelements = self.elements.clone();
        newelements.extend(
            another
                .into_iter()
                .filter(|elem| !elem.0.is_empty())
                .cloned(),
        );
        MPath {
            elements: newelements,
        }
    }

    pub fn join_element(&self, element: Option<&MPathElement>) -> MPath {
        match element {
            Some(element) => self.join(element),
            None => self.clone(),
        }
    }

    pub fn join_opt<'a, Elements: IntoIterator<Item = &'a MPathElement>>(
        path: Option<&Self>,
        another: Elements,
    ) -> Option<Self> {
        match path {
            Some(path) => Some(path.join(another)),
            None => {
                let elements: Vec<MPathElement> = another
                    .into_iter()
                    .filter(|elem| !elem.0.is_empty())
                    .cloned()
                    .collect();
                if elements.is_empty() {
                    None
                } else {
                    Some(MPath { elements })
                }
            }
        }
    }

    pub fn is_prefix_of_opt<'a, E: IntoIterator<Item = &'a MPathElement>>(
        prefix: Option<&MPath>,
        other: E,
    ) -> bool {
        match prefix {
            Some(prefix) => prefix.is_prefix_of(other),
            None => true,
        }
    }

    pub fn join_opt_element(path: Option<&Self>, element: &MPathElement) -> Self {
        match path {
            Some(path) => path.join_element(Some(element)),
            None => MPath {
                elements: vec![element.clone()],
            },
        }
    }

    pub fn join_element_opt(path: Option<&Self>, element: Option<&MPathElement>) -> Option<Self> {
        match element {
            Some(element) => Self::join_opt(path, element),
            None => path.cloned(),
        }
    }

    pub fn iter_opt(path: Option<&Self>) -> Iter<MPathElement> {
        match path {
            Some(path) => path.into_iter(),
            None => [].iter(),
        }
    }

    pub fn into_iter_opt(path: Option<Self>) -> ::std::vec::IntoIter<MPathElement> {
        match path {
            Some(path) => path.into_iter(),
            None => (vec![]).into_iter(),
        }
    }

    /// The number of components in this path.
    pub fn num_components(&self) -> usize {
        self.elements.len()
    }

    /// The number of leading components that are common.
    pub fn common_components<'a, E: IntoIterator<Item = &'a MPathElement>>(
        &self,
        other: E,
    ) -> usize {
        self.elements
            .iter()
            .zip(other)
            .take_while(|&(e1, e2)| e1 == e2)
            .count()
    }

    /// Whether this path is a path prefix of the given path.
    /// `foo` is a prefix of `foo/bar`, but not of `foo1`.
    #[inline]
    pub fn is_prefix_of<'a, E: IntoIterator<Item = &'a MPathElement>>(&self, other: E) -> bool {
        self.common_components(other.into_iter()) == self.num_components()
    }

    /// The final component of this path.
    pub fn basename(&self) -> &MPathElement {
        self.elements
            .last()
            .expect("MPaths have at least one component")
    }

    /// Create a new path with the number of leading components specified.
    pub fn take_prefix_components(&self, components: usize) -> Result<Option<MPath>> {
        match components {
            0 => Ok(None),
            x if x > self.num_components() => bail!(
                "taking {} components but path only has {}",
                components,
                self.num_components()
            ),
            _ => Ok(Some(MPath {
                elements: self.elements[..components].to_vec(),
            })),
        }
    }

    /// Create a new path, removing `prefix`. Returns `None` if `prefix` is not a strict
    /// prefix of this path - i.e. having removed `prefix`, there are no elements left.
    /// For the intended use case of stripping a directory prefix from a file path,
    /// this is the correct behaviour, since it should not be possible to have
    /// `self == prefix`.
    pub fn remove_prefix_component<'a, E: IntoIterator<Item = &'a MPathElement>>(
        &self,
        prefix: E,
    ) -> Option<MPath> {
        let mut self_iter = self.elements.iter();
        for elem in prefix {
            if Some(elem) != self_iter.next() {
                return None;
            }
        }
        let elements: Vec<_> = self_iter.cloned().collect();
        if elements.is_empty() {
            None
        } else {
            Some(Self { elements })
        }
    }

    pub fn generate<W: Write>(&self, out: &mut W) -> io::Result<()> {
        out.write_all(&self.to_vec())
    }

    pub fn to_vec(&self) -> Vec<u8> {
        let ret: Vec<_> = self.elements.iter().map(|e| e.0.as_ref()).collect();
        ret.join(&b'/')
    }

    #[allow(clippy::len_without_is_empty)]
    /// The length of this path, including any slashes in it.
    pub fn len(&self) -> usize {
        // n elements means n-1 slashes
        let slashes = self.elements.len() - 1;
        let elem_len: usize = self.elements.iter().map(|elem| elem.len()).sum();
        slashes + elem_len
    }

    // Private because it does not validate elements - you must ensure that it's non-empty
    fn from_elements<'a, I>(elements: I) -> Self
    where
        I: Iterator<Item = &'a MPathElement>,
    {
        Self {
            elements: elements.cloned().collect(),
        }
    }

    /// Split an MPath into dirname (if possible) and file name
    pub fn split_dirname(&self) -> (Option<MPath>, &MPathElement) {
        let (filename, dirname_elements) = self
            .elements
            .split_last()
            .expect("MPaths should never be empty");

        if dirname_elements.is_empty() {
            (None, filename)
        } else {
            (
                Some(MPath::from_elements(dirname_elements.iter())),
                filename,
            )
        }
    }

    /// Split an MPath into first path component and the rest
    pub fn split_first(&self) -> (&MPathElement, Option<MPath>) {
        let (first, file_elements) = self
            .elements
            .split_first()
            .expect("MPaths should never be empty");

        if file_elements.is_empty() {
            (first, None)
        } else {
            (first, Some(MPath::from_elements(file_elements.iter())))
        }
    }

    pub fn into_thrift(self) -> thrift::MPath {
        thrift::MPath(
            self.elements
                .into_iter()
                .map(|elem| elem.into_thrift())
                .collect(),
        )
    }

    pub fn display_opt<'a>(path_opt: Option<&'a MPath>) -> DisplayOpt<'a> {
        DisplayOpt(path_opt)
    }

    pub fn get_path_hash(&self) -> MPathHash {
        let mut context = MPathHashContext::new();
        let num_el = self.elements.len();
        if num_el > 0 {
            if num_el > 1 {
                for e in &self.elements[..num_el - 1] {
                    context.update(e.as_ref());
                    context.update([b'/'])
                }
            }
            context.update(self.elements[num_el - 1].as_ref());
        } else {
            context.update([])
        }
        context.finish()
    }

    /// Get an iterator over the parent directories of this `MPath`
    /// Note: it contains the `self` as the first element
    pub fn into_parent_dir_iter(self) -> ParentDirIterator {
        ParentDirIterator {
            current: Some(self),
        }
    }

    pub fn matches_regex(&self, re: &Regex) -> bool {
        let s: String = format!("{}", self);
        re.is_match(&s)
    }
}

pub fn path_bytes_from_mpath(path: Option<&MPath>) -> Vec<u8> {
    match path {
        Some(path) => path.to_vec(),
        None => vec![],
    }
}

impl AsRef<[MPathElement]> for MPath {
    fn as_ref(&self) -> &[MPathElement] {
        &self.elements
    }
}

pub fn mpath_element_iter<'a>(
    mpath: &'a Option<MPath>,
) -> Box<dyn Iterator<Item = &MPathElement> + 'a> {
    match mpath {
        Some(ref path) => Box::new(path.into_iter()),
        None => Box::new(std::iter::empty()),
    }
}

/// Iterator over parent directories of a given `MPath`
pub struct ParentDirIterator {
    current: Option<MPath>,
}

impl Iterator for ParentDirIterator {
    type Item = MPath;

    fn next(&mut self) -> Option<Self::Item> {
        let maybe_current = self.current.take();
        match maybe_current {
            None => None,
            Some(current) => {
                let (maybe_dirname, _) = current.split_dirname();
                self.current = maybe_dirname;
                Some(current)
            }
        }
    }
}

/// Hash of the file path (used in unode)
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct MPathHash(Blake2);

impl MPathHash {
    pub fn from_thrift(thrift_path: thrift::MPathHash) -> Result<MPathHash> {
        match thrift_path.0 {
            thrift::IdType::Blake2(blake2) => Ok(MPathHash(Blake2::from_thrift(blake2)?)),
            thrift::IdType::UnknownField(x) => bail!(ErrorKind::InvalidThrift(
                "MPathHash".into(),
                format!("unknown id type field: {}", x)
            )),
        }
    }

    pub fn into_thrift(self) -> thrift::MPathHash {
        thrift::MPathHash(thrift::IdType::Blake2(self.0.into_thrift()))
    }

    pub fn to_hex(&self) -> AsciiString {
        self.0.to_hex()
    }

    pub fn sampling_fingerprint(&self) -> u64 {
        self.0.sampling_fingerprint()
    }
}

/// Context for incrementally computing a hash.
#[derive(Clone)]
pub struct MPathHashContext(Context);

impl MPathHashContext {
    /// Construct a context.
    #[inline]
    pub fn new() -> Self {
        Self(Context::new("mpathhash".as_bytes()))
    }

    #[inline]
    pub fn update<T>(&mut self, data: T)
    where
        T: AsRef<[u8]>,
    {
        self.0.update(data)
    }

    #[inline]
    pub fn finish(self) -> MPathHash {
        MPathHash(self.0.finish())
    }
}

pub struct DisplayOpt<'a>(Option<&'a MPath>);

impl<'a> Display for DisplayOpt<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.0 {
            Some(path) => write!(f, "{}", path),
            None => write!(f, "(none)"),
        }
    }
}

/// Check that a sorted list of (MPath, is_changed) pairs is path-conflict-free. This means that
/// no changed path in the list (is_changed is true) is a directory of another path.
pub fn check_pcf<'a, I>(sorted_paths: I) -> Result<()>
where
    I: IntoIterator<Item = (&'a MPath, bool)>,
{
    let mut last_changed_path: Option<&MPath> = None;
    // The key observation to make here is that in a sorted list, "foo" will always appear before
    // "foo/bar", which in turn will always appear before "foo1".
    // The loop invariant is that last_changed_path at any point has no prefixes in the list.
    for (path, is_changed) in sorted_paths {
        if let Some(last_changed_path) = last_changed_path {
            if last_changed_path.is_prefix_of(path) {
                bail!(ErrorKind::NotPathConflictFree(
                    last_changed_path.clone(),
                    path.clone(),
                ));
            }
        }
        if is_changed {
            last_changed_path = Some(path);
        }
    }

    Ok(())
}

impl IntoIterator for MPath {
    type Item = MPathElement;
    type IntoIter = ::std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.elements.into_iter()
    }
}

impl<'a> IntoIterator for &'a MPath {
    type Item = &'a MPathElement;
    type IntoIter = Iter<'a, MPathElement>;

    fn into_iter(self) -> Self::IntoIter {
        self.elements.iter()
    }
}

impl<'a> IntoIterator for &'a MPathElement {
    type Item = &'a MPathElement;
    type IntoIter = Once<&'a MPathElement>;

    fn into_iter(self) -> Self::IntoIter {
        once(self)
    }
}

impl<'a> From<&'a MPath> for Vec<u8> {
    fn from(path: &MPath) -> Self {
        path.to_vec()
    }
}

impl<'a> TryFrom<&'a [u8]> for MPath {
    type Error = Error;

    fn try_from(value: &[u8]) -> Result<Self> {
        MPath::new(value)
    }
}

impl<'a> TryFrom<&'a str> for MPath {
    type Error = Error;

    fn try_from(value: &str) -> Result<Self> {
        MPath::new(value.as_bytes())
    }
}

impl TryFrom<Vec<MPathElement>> for MPath {
    type Error = Error;

    fn try_from(elements: Vec<MPathElement>) -> Result<Self> {
        if elements.is_empty() {
            bail!("mpath can not be empty");
        }
        Ok(MPath { elements })
    }
}

lazy_static! {
    static ref COMPONENT_CHARS: Vec<u8> = (2..b'\n')
        .chain((b'\n' + 1)..b'/')
        .chain((b'/' + 1)..255)
        .collect();
}

impl Arbitrary for MPathElement {
    fn arbitrary(g: &mut Gen) -> Self {
        let size = cmp::max(g.size(), 1);
        let size = cmp::min(size, MPATH_ELEMENT_MAX_LENGTH);
        let mut element = SmallVec::with_capacity(size);
        // Keep building possible MPathElements until we get a valid one
        while MPathElement::verify(&element).is_err() {
            element.clear();
            for _ in 0..size {
                let c = g.choose(&COMPONENT_CHARS[..]).unwrap();
                element.push(*c);
            }
        }
        MPathElement(element)
    }
}

impl Arbitrary for MPath {
    #[inline]
    fn arbitrary(g: &mut Gen) -> Self {
        let size = g.size();
        // Up to size components
        //
        // TODO: do we really want a uniform distribution over component chars
        // here?
        let mut path = Vec::new();

        for i in 0..size {
            if i > 0 {
                path.push(b'/');
            }
            let element = MPathElement::arbitrary(g);
            path.extend(&element.0);
        }

        MPath::new(path).unwrap()
    }
}

impl Display for MPathElement {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "{}", String::from_utf8_lossy(&self.0))
    }
}

impl Display for MPath {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "{}", String::from_utf8_lossy(&self.to_vec()))
    }
}

// Implement our own Debug so that strings are displayed properly
impl fmt::Debug for MPathElement {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(
            fmt,
            "MPathElement(\"{}\")",
            String::from_utf8_lossy(&self.0)
        )
    }
}

impl fmt::Debug for MPath {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "MPath(\"{}\")", self)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrefixTrie {
    Included,
    Children(HashMap<MPathElement, PrefixTrie>),
}

impl PrefixTrie {
    /// Create a new, empty, prefix trie.
    pub fn new() -> PrefixTrie {
        PrefixTrie::Children(HashMap::new())
    }

    /// Add a path prefix to the prefix trie.  Returns true if the prefix
    /// wasn't already present.
    pub fn add<'p, P: IntoIterator<Item = &'p MPathElement>>(&mut self, path: P) -> bool {
        match self {
            PrefixTrie::Included => false,
            PrefixTrie::Children(children) => {
                let mut iter = path.into_iter();
                match iter.next() {
                    None => {
                        *self = PrefixTrie::Included;
                        true
                    }
                    Some(element) => {
                        if let Some(child) = children.get_mut(element) {
                            return child.add(iter);
                        }
                        children
                            .entry(element.clone())
                            .or_insert_with(PrefixTrie::new)
                            .add(iter)
                    }
                }
            }
        }
    }

    /// Returns true if any path prefix of the given path has previously been
    /// added to the prefix trie.
    pub fn contains_prefix<'p, P: IntoIterator<Item = &'p MPathElement>>(&self, path: P) -> bool {
        match self {
            PrefixTrie::Included => true,
            PrefixTrie::Children(children) => {
                let mut iter = path.into_iter();
                match iter.next() {
                    None => false,
                    Some(element) => {
                        if let Some(child) = children.get(element) {
                            return child.contains_prefix(iter);
                        }
                        false
                    }
                }
            }
        }
    }

    /// Returns true if this trie contains all paths.
    pub fn contains_everything(&self) -> bool {
        self == &PrefixTrie::Included
    }
}

impl Default for PrefixTrie {
    fn default() -> PrefixTrie {
        PrefixTrie::Children(HashMap::new())
    }
}

impl Extend<Option<MPath>> for PrefixTrie {
    fn extend<T: IntoIterator<Item = Option<MPath>>>(&mut self, iter: T) {
        for path in iter {
            if let Some(path) = path {
                self.add(&path);
            } else {
                // The empty path means all paths are included.
                *self = PrefixTrie::Included;
            }
        }
    }
}

impl FromIterator<Option<MPath>> for PrefixTrie {
    fn from_iter<I: IntoIterator<Item = Option<MPath>>>(iter: I) -> Self {
        let mut trie = PrefixTrie::new();
        trie.extend(iter);
        trie
    }
}

pub struct CaseConflictTrie<'a> {
    children: HashMap<MPathElement, CaseConflictTrie<'a>>,
    lowercase_to_original: HashMap<String, MPathElement>,
    exclusions: &'a PrefixTrie,
}

impl<'a> CaseConflictTrie<'a> {
    fn new(exclusions: &'a PrefixTrie) -> CaseConflictTrie {
        CaseConflictTrie {
            children: Default::default(),
            lowercase_to_original: Default::default(),
            exclusions,
        }
    }

    fn is_empty(&self) -> bool {
        self.children.is_empty()
    }

    /// Returns Ok(()) if element was added successfully, or Err
    /// if trie already contains case conflicting entry.
    fn add<'p, P>(&mut self, path: P) -> Result<(), ReverseMPath>
    where
        P: IntoIterator<Item = &'p MPathElement> + Clone,
    {
        if self.exclusions.contains_prefix(path.clone().into_iter()) {
            return Ok(());
        }

        self.add_internal(path)
    }

    fn add_internal<'p, P>(&mut self, path: P) -> Result<(), ReverseMPath>
    where
        P: IntoIterator<Item = &'p MPathElement>,
    {
        let mut iter = path.into_iter();
        match iter.next() {
            None => Ok(()),
            Some(element) => {
                if let Some(child) = self.children.get_mut(element) {
                    return child.add_internal(iter).map_err(|mut e| {
                        e.elements.push(element.clone());
                        e
                    });
                }

                if let Some(lower) = element.to_lowercase_utf8() {
                    if let Some(conflict) = self.lowercase_to_original.get(&lower) {
                        return Err(ReverseMPath {
                            elements: vec![conflict.clone()],
                        });
                    } else {
                        self.lowercase_to_original.insert(lower, element.clone());
                    }
                }

                self.children
                    .entry(element.clone())
                    .or_insert_with(|| CaseConflictTrie::new(self.exclusions))
                    .add_internal(iter)
            }
        }
    }

    /// Remove path from a trie
    ///
    /// Returns `true` if path was removed, otherwise `false`.
    fn remove<'p, P>(&mut self, path: P) -> bool
    where
        P: IntoIterator<Item = &'p MPathElement> + Clone,
    {
        if self.exclusions.contains_prefix(path.clone().into_iter()) {
            return true;
        }

        self.remove_internal(path)
    }

    fn remove_internal<'p, P>(&mut self, path: P) -> bool
    where
        P: IntoIterator<Item = &'p MPathElement>,
    {
        let mut iter = path.into_iter();
        match iter.next() {
            None => true,
            Some(element) => {
                let (found, remove) = match self.children.get_mut(element) {
                    None => return false,
                    Some(child) => (child.remove_internal(iter), child.is_empty()),
                };
                if remove {
                    self.children.remove(element);

                    if let Some(lower) = element.to_lowercase_utf8() {
                        self.lowercase_to_original.remove(&lower);
                    }
                }
                found
            }
        }
    }
}

struct ReverseMPath {
    /// Elements that are found to conflict. This is in reverse order.
    elements: Vec<MPathElement>,
}

impl ReverseMPath {
    pub fn into_mpath(self) -> MPath {
        let Self { mut elements } = self;
        elements.reverse();
        MPath { elements }
    }
}

pub trait CaseConflictTrieUpdate {
    /// Add this to the CaseConflictTrie. If this results in a case conflict, report the two paths
    /// that conflicted, in the order in which they were added to the CaseConflictTrie.
    fn apply(self, trie: &mut CaseConflictTrie) -> Option<(MPath, MPath)>;
}

impl<'a> CaseConflictTrieUpdate for &'a MPath {
    fn apply(self, trie: &mut CaseConflictTrie) -> Option<(MPath, MPath)> {
        match trie.add(self) {
            Ok(()) => None,
            Err(conflict) => Some((conflict.into_mpath(), self.clone())),
        }
    }
}

impl CaseConflictTrieUpdate for MPath {
    fn apply(self, trie: &mut CaseConflictTrie) -> Option<(MPath, MPath)> {
        match trie.add(&self) {
            Ok(()) => None,
            Err(conflict) => Some((conflict.into_mpath(), self)),
        }
    }
}

impl<'a> CaseConflictTrieUpdate for &'a BonsaiChangeset {
    fn apply(self, trie: &mut CaseConflictTrie) -> Option<(MPath, MPath)> {
        // we need apply deletion first
        for (path, change) in self.file_changes() {
            if change.is_removed() {
                trie.remove(path);
            }
        }
        for (path, change) in self.file_changes() {
            if change.is_changed() {
                if let Some(conflict) = path.apply(trie) {
                    return Some(conflict);
                }
            }
        }
        None
    }
}

/// Returns first path pair that would introduce a case-conflict, if any. The first element is the
/// first one that was added into the Trie, and the second is the last.
pub fn check_case_conflicts<P, I>(iter: I, exclusions: &PrefixTrie) -> Option<(MPath, MPath)>
where
    P: CaseConflictTrieUpdate,
    I: IntoIterator<Item = P>,
{
    let mut trie = CaseConflictTrie::new(exclusions);
    for update in iter {
        let conflict = update.apply(&mut trie);
        if conflict.is_some() {
            return conflict;
        }
    }
    None
}

#[cfg(test)]
mod test {
    use std::mem::size_of;

    use quickcheck::quickcheck;
    use quickcheck::TestResult;

    use super::*;

    #[test]
    fn test_mpath_element_size() {
        // MPathElement size is important as we have a lot of them.
        // Test so we are aware of any change.
        assert_eq!(32, size_of::<MPathElement>());
    }

    #[test]
    fn get_path_hash_multiple_elem() {
        let path = MPath::new("foo/bar/baz").unwrap();
        assert_eq!(
            format!("{}", path.get_path_hash().to_hex()).as_str(),
            "4b2cfeded9f9499ffecfed9cea1a36eab97511b241a74c2c84ab8cff45932d1e"
        );
    }

    #[test]
    fn get_path_hash_single_elem() {
        let path = MPath::new("foo").unwrap();
        assert_eq!(
            format!("{}", path.get_path_hash().to_hex()).as_str(),
            "108cf7fc2bbc482daeab0ad8a9af2703cf041ba22ae728df26e1a33d51a3efb0"
        );
    }

    quickcheck! {
        /// Verify that instances generated by quickcheck are valid.
        fn path_gen(p: MPath) -> bool {
            p.elements
                .iter()
                .map(|elem| MPathElement::verify(elem.as_ref()))
                .all(|res| res.is_ok())
        }

        /// Verify that MPathElement instances generated by quickcheck are valid.
        fn pathelement_gen(p: MPathElement) -> bool {
            MPathElement::verify(p.as_ref()).is_ok()
        }

        fn elements_to_path(elements: Vec<MPathElement>) -> TestResult {
            if elements.is_empty() {
                return TestResult::discard();
            }

            let joined = elements.iter().map(|elem| elem.0.clone())
                .collect::<Vec<_>>()
                .join(&b'/');
            let expected_len = joined.len();
            let path = MPath::new(joined).unwrap();
            TestResult::from_bool(elements == path.elements && path.to_vec().len() == expected_len)
        }

        fn path_len(p: MPath) -> bool {
            p.len() == p.to_vec().len()
        }

        fn repo_path_thrift_roundtrip(p: RepoPath) -> bool {
            let thrift_path = p.clone().into_thrift();
            let p2 = RepoPath::from_thrift(thrift_path)
                .expect("converting a valid Thrift structure should always work");
            p == p2
        }

        fn path_thrift_roundtrip(p: MPath) -> bool {
            let thrift_path = p.clone().into_thrift();
            let p2 = MPath::from_thrift(thrift_path)
                .expect("converting a valid Thrift structure should always work");
            p == p2
        }

        fn pathelement_thrift_roundtrip(p: MPathElement) -> bool {
            let thrift_pathelement = p.clone().into_thrift();
            let p2 = MPathElement::from_thrift(thrift_pathelement)
                .expect("converting a valid Thrift structure should always works");
            p == p2
        }
    }

    #[test]
    fn path_make() {
        let path = MPath::new(b"1234abc");
        assert!(MPath::new(b"1234abc").is_ok());
        assert_eq!(path.unwrap().to_vec().len(), 7);
    }

    #[test]
    fn repo_path_make() {
        let path = MPath::new(b"abc").unwrap();
        assert_eq!(
            RepoPath::dir(path.clone()).unwrap(),
            RepoPath::dir("abc").unwrap()
        );
        assert_ne!(RepoPath::dir(path).unwrap(), RepoPath::file("abc").unwrap());
    }

    #[test]
    fn empty_paths() {
        fn assert_empty(path: &str) {
            MPath::new(path).expect_err(&format!(
                "unexpected OK - path '{}' is logically empty",
                path,
            ));
        }
        assert_empty("");
        assert_empty("/");
        assert_empty("//");
        assert_empty("///");
        assert_empty("////");
    }

    #[test]
    fn parent_dir_iterator() {
        fn path(p: &str) -> MPath {
            MPath::new(p).unwrap()
        }

        fn parent_vec(p: &str) -> Vec<MPath> {
            path(p).into_parent_dir_iter().collect()
        }

        assert_eq!(parent_vec("a"), vec![path("a")]);
        assert_eq!(parent_vec("a/b"), vec![path("a/b"), path("a")]);
        assert_eq!(
            parent_vec("a/b/c"),
            vec![path("a/b/c"), path("a/b"), path("a")]
        );
    }

    #[test]
    fn components() {
        let foo = MPath::new("foo").unwrap();
        let foo_bar1 = MPath::new("foo/bar1").unwrap();
        let foo_bar12 = MPath::new("foo/bar12").unwrap();
        let baz = MPath::new("baz").unwrap();

        assert_eq!(foo.common_components(&foo), 1);
        assert_eq!(foo.common_components(&foo_bar1), 1);
        assert_eq!(foo.common_components(&foo_bar12), 1);
        assert_eq!(foo_bar1.common_components(&foo_bar1), 2);
        assert_eq!(foo.common_components(&baz), 0);
        assert_eq!(foo.common_components(MPath::iter_opt(None)), 0);

        assert_eq!(foo_bar1.take_prefix_components(0).unwrap(), None);
        assert_eq!(foo_bar1.take_prefix_components(1).unwrap(), Some(foo));
        assert_eq!(
            foo_bar1.take_prefix_components(2).unwrap(),
            Some(foo_bar1.clone())
        );
        foo_bar1
            .take_prefix_components(3)
            .expect_err("unexpected OK - too many components");
    }

    #[test]
    fn remove_prefix_component() {
        let foo = MPath::new("foo").unwrap();
        let foo_bar1 = MPath::new("foo/bar1").unwrap();
        let foo_bar12 = MPath::new("foo/bar1/2").unwrap();
        let baz = MPath::new("baz").unwrap();
        let bar1 = MPath::new("bar1").unwrap();
        let bar12 = MPath::new("bar1/2").unwrap();
        let two = MPath::new("2").unwrap();

        assert_eq!(baz.remove_prefix_component(&foo), None);
        assert_eq!(foo_bar1.remove_prefix_component(&foo), Some(bar1));
        assert_eq!(foo_bar12.remove_prefix_component(&foo), Some(bar12));
        assert_eq!(foo_bar12.remove_prefix_component(&foo_bar1), Some(two));
    }

    #[test]
    fn bad_path() {
        assert!(MPath::new(b"\0").is_err());
    }
    #[test]
    fn bad_path2() {
        assert!(MPath::new(b"abc\0").is_err());
    }
    #[test]
    fn bad_path3() {
        assert!(MPath::new(b"ab\0cde").is_err());
    }
    #[test]
    fn bad_path4() {
        let p = vec![97; 255];
        assert!(MPath::new(p).is_ok());

        let p = vec![97; 256];
        assert!(MPath::new(p).is_err());
    }

    #[test]
    fn bad_path_element() {
        let p = vec![97; 255];
        assert!(MPathElement::new(p).is_ok());

        let p = vec![97; 256];
        assert!(MPathElement::new(p).is_err());
    }

    #[test]
    fn bad_path_thrift() {
        let bad_thrift = thrift::MPath(vec![thrift::MPathElement(b"abc\0".to_vec().into())]);
        MPath::from_thrift(bad_thrift).expect_err("unexpected OK - embedded null");

        let bad_thrift = thrift::MPath(vec![thrift::MPathElement(b"def/ghi".to_vec().into())]);
        MPath::from_thrift(bad_thrift).expect_err("unexpected OK - embedded slash");
    }

    #[test]
    fn path_cmp() {
        let a = MPath::new(b"a").unwrap();
        let b = MPath::new(b"b").unwrap();

        assert!(a < b);
        assert!(a == a);
        assert!(b == b);
        assert!(a <= a);
        assert!(a <= b);
    }

    #[test]
    fn pcf() {
        check_pcf_paths(vec![("foo", true), ("bar", true)])
            .expect("unexpected Err - no directories");
        check_pcf_paths(vec![("foo", true), ("foo/bar", true)])
            .expect_err("unexpected OK - foo is a prefix of foo/bar");
        check_pcf_paths(vec![("foo", false), ("foo/bar", true)])
            .expect("unexpected Err - foo is a prefix of foo/bar but is_changed is false");
        check_pcf_paths(vec![("foo", true), ("foo/bar", false)])
            .expect_err("unexpected OK - foo/bar's is_changed state does not matter");
        check_pcf_paths(vec![("foo", true), ("foo1", true)])
            .expect("unexpected Err - foo is not a path prefix of foo1");
        check_pcf_paths::<_, &str>(vec![])
            .expect("unexpected Err - empty path list has no prefixes");
        // '/' is ASCII 0x2f
        check_pcf_paths(vec![
            ("foo/bar", true),
            ("foo/bar\x2e", true),
            ("foo/bar/baz", true),
            ("foo/bar\x30", true),
        ])
        .expect_err("unexpected OK - other paths and prefixes");
    }

    #[test]
    fn case_conflicts() {
        fn m(mpath: &str) -> MPath {
            MPath::new(mpath).unwrap()
        }

        let exclusions = Default::default();
        let mut trie = CaseConflictTrie::new(&exclusions);
        assert!(trie.add(&m("a/b/c")).is_ok());
        assert!(trie.add(&m("a/d")).is_ok());
        assert!(trie.add(&m("c/d/a")).is_ok());
        assert!(trie.add(&m("a/b/c")).is_ok());
        assert!(trie.add(&m("a/B/d")).is_err());
        assert!(trie.add(&m("a/b/C")).is_err());
        assert!(trie.remove(&m("a/b/c")));
        assert!(trie.add(&m("a/B/c")).is_ok());

        let paths = vec![
            m("a/b/c"),
            m("a/b/c"), // not a case conflict
            m("a/d"),
            m("a/B/d"),
            m("a/c"),
        ];
        assert_eq!(
            check_case_conflicts(paths.iter(), &Default::default()), // works from &MPath
            Some((m("a/b"), m("a/B/d"))),
        );
        assert_eq!(
            check_case_conflicts(paths.into_iter(), &Default::default()), // works from MPath
            Some((m("a/b"), m("a/B/d"))),
        );
    }

    fn check_pcf_paths<I, T>(paths: I) -> Result<()>
    where
        I: IntoIterator<Item = (T, bool)>,
        MPath: TryFrom<T, Error = Error>,
    {
        let res: Result<Vec<_>> = paths
            .into_iter()
            .map(|(path, is_changed)| Ok((path.try_into()?, is_changed)))
            .collect();
        let mut paths = res.expect("invalid input path");
        // The input calls for a *sorted* list -- this is important.
        paths.sort_unstable();
        check_pcf(paths.iter().map(|(path, is_changed)| (path, *is_changed)))
    }

    #[test]
    fn prefix_trie() {
        let mut prefixes = PrefixTrie::new();

        let path = |path| MPath::new(path).unwrap();

        // Add some paths
        assert!(prefixes.add(&path("a/b/c")));
        assert!(prefixes.add(&path("a/b/d")));
        assert!(prefixes.add(&path("e")));

        // These paths are already covered by existing prefixes
        assert!(!prefixes.add(&path("a/b/c")));
        assert!(!prefixes.add(&path("e/f")));

        // Expanding a prefix with a more general one is okay
        assert!(prefixes.add(&path("g/h/i")));
        assert!(prefixes.add(&path("g/h")));

        // These paths should match
        assert!(prefixes.contains_prefix(&path("a/b/c/d")));
        assert!(prefixes.contains_prefix(&path("a/b/d/e/f/g")));
        assert!(prefixes.contains_prefix(&path("a/b/d")));
        assert!(prefixes.contains_prefix(&path("e/a")));
        assert!(prefixes.contains_prefix(&path("e/f/g")));
        assert!(prefixes.contains_prefix(&path("e")));
        assert!(prefixes.contains_prefix(&path("g/h")));
        assert!(prefixes.contains_prefix(&path("g/h/i/j")));
        assert!(prefixes.contains_prefix(&path("g/h/k")));

        // These paths should not match
        assert!(!prefixes.contains_prefix(&path("a/b")));
        assert!(!prefixes.contains_prefix(&path("a/b/cc")));
        assert!(!prefixes.contains_prefix(&path("a/b/e")));
        assert!(!prefixes.contains_prefix(&path("a/c")));
        assert!(!prefixes.contains_prefix(&path("a")));
        assert!(!prefixes.contains_prefix(&path("b")));
        assert!(!prefixes.contains_prefix(&path("d")));
        assert!(!prefixes.contains_prefix(&path("abc")));
        assert!(!prefixes.contains_prefix(&path("g")));
        assert!(!prefixes.contains_prefix(&path("g/i")));
        assert!(!prefixes.contains_prefix(&path("g/i/h")));
        assert!(!prefixes.contains_everything());

        // Adding the empty path makes the trie contain everything
        assert!(prefixes.add(&None));
        assert!(prefixes.contains_prefix(&path("a/b/c")));
        assert!(prefixes.contains_prefix(&path("x/y/z")));
        assert!(prefixes.contains_everything());
    }

    #[test]
    fn has_suffix_suffix() {
        let path = |path| MPath::new(path).unwrap();

        // Assert that when the suffix equals the basename the result is
        // correct.
        assert!(&path("a/b/foo.bar").basename().has_suffix(b"foo.bar"));

        // Assert that when the suffix contains is not the basename, the result
        // is correct.
        assert!(!&path("a/b/c").basename().has_suffix(b"b"));

        // Assert when the potential suffix is a suffix the result is correct.
        assert!(&path("a/b/c.bar").basename().has_suffix(b"r"));
        assert!(&path("a/b/c.bar").basename().has_suffix(b"bar"));
        assert!(&path("a/b/c.bar").basename().has_suffix(b".bar"));
        assert!(&path("a/b/c.bar").basename().has_suffix(b"c.bar"));

        // Assert when the potential suffix is not a suffix the result is
        // correct.
        assert!(!&path("a/b/file.bar").basename().has_suffix(b".baz"));
        assert!(!&path("a/b/file.bar").basename().has_suffix(b"baz"));
        assert!(!&path("a/b/c.bar").basename().has_suffix(b"c.baz"));

        // Test case when potential suffix is longer than entire path.
        assert!(
            !&path("a/b/foo.bar")
                .basename()
                .has_suffix(b"file.very_very_very_long_extension")
        );
    }
}