/*
 * 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.
 */

//! Main radix index implementation that maintains efficient Key to `KeyId` look ups.
//!
//! Practically, the index usually requires 2 buffers to be fully functional:
//!
//!  - An key buffer. It stores the actual key contents. It is usually an
//!    append-only buffer.
//!  - A radix buffer. It stores radix nodes and pointers (offsets) to the key
//!    buffer. It does not contain key contents. For operations that requires
//!    contents of keys (ex. looking up unknown keys; inserting keys), the key
//!    buffer and a function to convert `KeyId` (offset) to key content must
//!    be provided.
//!
//! A radix node consists of 16 "pointer"s (since we follow base-16 sequence
//! to do lookups). A pointer could be one of the following:
//!
//!  - Empty (0).
//!  - A `RadixOffset`. Pointing to another radix node. (LSB is 0)
//!  - A `KeyId`. Need to be resolved by an external-provided "key function".
//!    Usually the "key function" uses `KeyId` as an offset of the key buffer.
//!    (LSB is 1).
//!
//! The radix buffer could have multiple "root" radix nodes so it contains multiple
//! distinct indexes.
//!
//! A "key function" takes a `KeyId` and a "key argument" (usually the "key buffer")
//! and returns a slice reference in the "key argument" as a key. That means, full
//! key contents usually need to be written down in the key buffer, instead of going
//! through extra pre-processing logic. That said, the "key argument" does not always
//! have to be a "key buffer" so there could be some flexibility here.
//!
//! To give an more detailed example, suppose the "key function" is `FixedKey::read`
//! (fixed 20-byte keys) and the key buffer ([u8]) looks like:
//!
//! ```plain,ignore
//!     Offset Content
//!     0x100: 0x12 0x34 0x56 .... (key)
//!     0x114: 0x12 0x78 0x9a .... (another key)
//! ```
//!
//! With both of the keys inserted at radix offset 0x400, the radix buffer ([u32])
//! looks like:
//!
//! ```plain,ignore
//!     Offset Content
//!     0x400: 0 0x880 0 0 0 0 0 0 0 0 0 0 0 0 0 0 (raidx node, 16 pointers)
//!              ^^^^^
//!              0x1: RadixOffset 0x440
//!     0x440: 0 0 0x900 0 0 0 0 0 0 0 0 0 0 0 0 0 (another radix node)
//!                ^^^^^
//!                0x2: RadixOffset 0x480
//!     0x480: 0 0 0 0x201 0 0 0 0x229 0 0 0 0 0 0 0 0 (another radix node)
//!                  ^^^^^       ^^^^^
//!        0x3: KeyId 0x100      0x7: KeyId 0x114
//! ```
//!
//! Note the radix buffer does not contain full key contents (ex. it does not
//! have 0x34 0x56 or 0x78 0x9a). It only has the ambiguous prefix (0x12) stored.
//!
//! The index does not support deletion or iteration at present. It also forbids
//! a key being the prefix of another key, to make the format simpler and more
//! compact.
//!
//! Extra flexibility could be achieved by making the "key buffer" include
//! additional information. For example, instead of just storing plain, fixed
//! 20-byte keys one after another, a "key entry" could be
//! "20-byte key + 4-byte offset + ..." so those key entries could contain
//! additional data.

use crate::base16::Base16Iter;
use crate::errors::ErrorKind;
use crate::key::KeyId;
use crate::traits::Resize;
use crate::Result;

/// Number of children ("pointer"s) a radix node has
pub const RADIX_NCHILDREN: usize = 16;

/// Represent an offset to a radix node which contains 16 optional pointers to other
/// radix nodes, or `KeyId`s.
#[derive(Clone, Copy)]
struct RadixOffset(u32);

impl RadixOffset {
    #[inline]
    pub fn new(offset: u32) -> Self {
        RadixOffset(offset)
    }

    /// Append an empty `RadixNode` (`[u32; 16]`) at the end of a buffer.
    #[inline]
    pub fn create<R: Resize<u32> + AsRef<[u32]>>(vec: &mut R) -> Result<Self> {
        let pos = vec.as_ref().len();
        if (pos as u32) as usize != pos {
            return Err(ErrorKind::OffsetOverflow(pos as u64));
        }
        vec.resize(pos + RADIX_NCHILDREN, 0);
        Ok(RadixOffset(pos as u32))
    }

    /// Follow a base16 sequence. Return a tuple:
    ///   - The first item is `Some(key_id)` if a `KeyId` was found, or `None`
    ///   - The second item the "last follow state", useful for write operations
    ///
    /// The "last follow state" consists of 3 items:
    ///   - r: `RadixOffset`
    ///   - i: position reached within `seq`
    ///   - b: last base16 index number
    ///
    /// So `buf[r.0 + b]` points to the returned `KeyId` (if returned), and
    /// `seq.nth(i)` equals to `b`.
    ///
    /// For example, given the base16 sequence: [1, 2, 11, 12, 13, 14], and the
    /// following radix buffer:
    ///
    ///   - Offset   0: RadixNode({0: 100, 1: 0, ... 15: 0}) # This RadixNode
    ///   - Offset 100: RadixNode({..., 2: 200, ...})
    ///   - Offset 200: RadixNode({..., 11: 501, ...}) # 501 is `KeyId` since its LSB is 1
    ///
    /// This function will return `Ok(Some(501), (200, 2, 11))`. Note: the remaining
    /// part of the base16 sequence (starting from 12) are not verified against the
    /// key. It's up to the caller to verify it if needed.
    #[inline]
    pub fn follow<R: AsRef<[u32]>, I: Iterator<Item = u8>>(
        self,
        buf: &R,
        seq: I,
    ) -> Result<(Option<KeyId>, (RadixOffset, usize, u8))> {
        let buf = buf.as_ref();
        let mut radix = self;
        for (i, b) in seq.enumerate() {
            if b >= RADIX_NCHILDREN as u8 {
                return Err(ErrorKind::InvalidBase16(b));
            }

            let pos = radix.0 as usize + usize::from(b);
            if pos >= buf.len() {
                return Err(ErrorKind::OffsetOverflow(pos as u64));
            }

            let v = u32::from_be(buf[pos]);
            if v == 0 {
                // Missing
                return Ok((None, (radix, i, b)));
            } else if v & 1 != 0 {
                // KeyId
                return Ok((Some(KeyId::from(v >> 1)), (radix, i, b)));
            } else {
                // RadixOffset
                radix = RadixOffset::new(v >> 1);
            }
        }

        // The base16 sequence is too short and does not match a non-radix node.
        // NOTE: The error is not accurate if the prefix is empty and the radix tree is
        // also empty, or has exactly one entry. But without supporting that, the code
        // becomes much shorter. Since that is a rare case, we do not support it for now.
        Err(ErrorKind::AmbiguousPrefix.into())
    }

    /// Rewrite specified entry to point to another radix node.
    #[inline]
    pub fn write_radix<R: AsMut<[u32]>>(
        &self,
        vec: &mut R,
        index: u8,
        node: RadixOffset,
    ) -> Result<()> {
        if node.0 > 0x7fff_ffff {
            return Err(ErrorKind::OffsetOverflow(node.0 as u64));
        }
        self.write_raw(vec, index, node.0 << 1)
    }

    /// Rewrite specified entry to point to a `KeyId`.
    #[inline]
    pub fn write_key_id<R: AsMut<[u32]>>(
        &self,
        vec: &mut R,
        index: u8,
        key_id: KeyId,
    ) -> Result<()> {
        let id: u32 = key_id.into();
        if id > 0x7fff_ffff {
            return Err(ErrorKind::OffsetOverflow(key_id.into()));
        }
        self.write_raw(vec, index, (id << 1) | 1)
    }

    #[inline]
    fn write_raw<R: AsMut<[u32]>>(&self, vec: &mut R, index: u8, value: u32) -> Result<()> {
        debug_assert!(index < RADIX_NCHILDREN as u8);
        let vec = vec.as_mut();
        let pos = self.0 as usize + usize::from(index);
        if pos > vec.len() {
            return Err(ErrorKind::OffsetOverflow(pos as u64));
        }
        vec[pos] = value.to_be();
        Ok(())
    }
}

// Public APIs

/// Look up a given `Key`. Return an optional potentially matched `KeyId`.
/// `radix_buf` is a `[u32]` buffer that contains `RaidxNode`s.
/// `offset` is the offset of the root radix node within the radix buffer.
/// `key` is a base256 sequence.
/// The caller is responsible to check whether `KeyId` matches the given `Key` or not.
#[inline]
pub fn radix_lookup_unchecked<R, K>(radix_buf: &R, offset: u32, key: &K) -> Result<Option<KeyId>>
where
    R: AsRef<[u32]>,
    K: AsRef<[u8]>,
{
    let (key_id, _) = RadixOffset::new(offset).follow(radix_buf, Base16Iter::from_bin(&key))?;
    Ok(key_id)
}

// unfortunately rustfmt makes the parameter list longer than 100 chars so it's disabled for now.

/// Lookup a given `Key`. Return a verified `KeyId` or `None`.
/// `radix_buf` is a `[u32]` buffer that contains `RaidxNode`s.
/// `offset` is the offset of the root radix node within the radix buffer.
/// `key` is a base256 sequence.
/// `key_reader` and `key_reader_arg` decide how and where to read a key given a `KeyId`.
/// Unlike `radix_lookup_unchecked`. This function reads and checks the key.
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn radix_lookup<R, K, KR, KA>(
    radix_buf: &R, offset: u32, key: &K, key_reader: KR, key_reader_arg: &KA)
    -> Result<Option<KeyId>>
where
    R: AsRef<[u32]>,
    K: AsRef<[u8]>,
    KR: Fn(&KA, KeyId) -> Result<&[u8]>,
{
    let key_id = radix_lookup_unchecked(radix_buf, offset, key)?;
    if let Some(id) = key_id {
        let existing_key = key_reader(key_reader_arg, id)?;
        if existing_key != key.as_ref() {
            return Ok(None);
        }
    }
    Ok(key_id)
}

/// Lookup a unique `KeyId` given a prefix of a binary base16 sequence.
/// `radix_buf` is a `[u32]` buffer that contains `RaidxNode`s.
/// `offset` is the offset of the root radix node within the radix buffer.
/// `prefix` is a base16 sequence (not base256).
/// `key_reader` and `key_reader_arg` decide how and where to read a key given a `KeyId`.
///
/// Return `Err(ErrorKind::AmbiguousPrefix.into())` or `Err(ErrorKind::PrefixConflict.into())`
/// if there are multiple matches, or `prefix` is empty. Return `Ok(None)` if there
/// are no matches.
///
/// Return `Ok(key_id)` if there is a unique match. The `key_id` is guarnateed
/// that once resolved and converted to base16 sequence, has a prefix matching
/// the given `prefix`.
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn radix_prefix_lookup<R, P, KR, KA>(
    radix_buf: &R, offset: u32, prefix: P, key_reader: KR, key_reader_arg: &KA)
    -> Result<Option<KeyId>>
where
    R: AsRef<[u32]>,
    P: Iterator<Item = u8> + Clone,
    KR: Fn(&KA, KeyId) -> Result<&[u8]>,
{
    let root = RadixOffset::new(offset);
    let (key_id, (_radix, i, _b)) = root.follow(radix_buf, prefix.clone())?;
    if let Some(id) = key_id {
        let key = key_reader(key_reader_arg, id)?;
        let iter = Base16Iter::from_bin(&key);
        let matched = iter.clone().skip(i).zip(prefix.clone().skip(i)).all(|(b1, b2)| b1 == b2);
        if !matched || iter.count() < prefix.count() {
            return Ok(None);
        }
    }
    Ok(key_id)
}

/// Insert a `key_id`  into the radix tree that can be retrieved using its corresponding
/// key afterwards.
///
/// `radix_buf` is a `[u32]` buffer that contains `RaidxNode`s.
/// `offset` is the offset of the root radix node within the radix buffer.
/// `key_id` is the `KeyId`, which will be passed to `key_reader` to retrieve the actual key.
/// `key_reader` and `key_reader_arg` decide how and where to read a key given a `KeyId`.
///
/// Return `Ok(())` on success.
///
/// The key being inserted can neither be a prefix of an existing key, or has a prefix that equals
/// to an existing key. If the key already exists, `key_id` must match the existing `key_id`.
/// Otherwise it will cause `ErrorKind::PrefixConflict` error.
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn radix_insert<R, KR, KA>(
    radix_buf: &mut R, offset: u32, key_id: KeyId, key_reader: KR, key_reader_arg: &KA)
    -> Result<()>
where
    R: Resize<u32> + AsRef<[u32]> + AsMut<[u32]>,
    KR: Fn(&KA, KeyId) -> Result<&[u8]>,
{
    let new_key = key_reader(key_reader_arg, key_id)?;
    radix_insert_with_key(
        radix_buf,
        offset,
        key_id,
        &new_key,
        key_reader,
        key_reader_arg,
    )
}

/// Insert a `key_id`  into the radix tree that can be retrieved using `key` afterwards.
///
/// `radix_buf` is a `[u32]` buffer that contains `RaidxNode`s.
/// `offset` is the offset of the root radix node within the radix buffer.
/// `key_id` is the `KeyId` to insert.
/// `key` is the `Key` to be used. It must match provided `key_id`.
/// `key_reader` and `key_reader_arg` decide how and where to read a key given a `KeyId`.
///
/// Return `Ok(())` on success.
///
/// The key being inserted can neither be a prefix of an existing key, or has a prefix that equals
/// to an existing key. If the key already exists, `key_id` must match the existing `key_id`.
/// Otherwise it will cause `ErrorKind::PrefixConflict` error.
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn radix_insert_with_key<R, K, KR, KA>(
    radix_buf: &mut R, offset: u32, key_id: KeyId, key: &K, key_reader: KR, key_reader_arg: &KA)
    -> Result<()>
where
    R: Resize<u32> + AsRef<[u32]> + AsMut<[u32]>,
    K: AsRef<[u8]>,
    KR: Fn(&KA, KeyId) -> Result<&[u8]>,
{
    let new_key_id = key_id;
    let new_key = key;
    let root = RadixOffset::new(offset);
    let (old_key_id, (radix, i, b)) = root.follow(radix_buf, Base16Iter::from_bin(new_key))?;
    match old_key_id {
        Some(old_key_id) => {
            // No need to re-insert a same key
            if old_key_id == new_key_id {
                return Ok(());
            }

            // Need to do a leaf split
            let old_key = key_reader(key_reader_arg, old_key_id)?;

            // Find common prefix starting from the next base16 integer
            let mut common_len = 0;
            let old_iter = Base16Iter::from_bin(&old_key).skip(i + 1);
            let new_iter = Base16Iter::from_bin(new_key).skip(i + 1);
            for (b1, b2) in old_iter.zip(new_iter) {
                if b1 == b2 {
                    common_len += 1;
                } else {
                    // Got a chain of radix nodes to write back
                    // Write new `RadixNode`s in reversed order so:
                    // - Looking up `old_key` works in the mean time
                    // - There won't be invalid `RadixOffset` at any time
                    // - Write count is optimized
                    //   (won't write `KeyId` first and then change it to `RadixOffset`)
                    // The first two properties could help concurrent reads.
                    // Although we are not depending on that right now.
                    let mut node = RadixOffset::create(radix_buf)?;
                    node.write_key_id(radix_buf, b1, old_key_id)?;
                    node.write_key_id(radix_buf, b2, new_key_id)?;
                    let new_iter = Base16Iter::from_bin(new_key).skip(i + 1);
                    for k in new_iter.take(common_len).rev() {
                        let new_node = RadixOffset::create(radix_buf)?;
                        new_node.write_radix(radix_buf, k, node)?;
                        node = new_node;
                    }
                    return radix.write_radix(radix_buf, b, node);
                }
            }

            // new_key is a prefix of old_key, or vice-versa.
            // or they are the same but with different key_ids.
            if old_key.len() > new_key.as_ref().len() {
                Err(ErrorKind::PrefixConflict(new_key_id, old_key_id).into())
            } else {
                Err(ErrorKind::PrefixConflict(old_key_id, new_key_id).into())
            }
        }
        None => radix.write_key_id(radix_buf, b, new_key_id),
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashSet;
    use std::mem::transmute;

    use quickcheck::quickcheck;

    use super::*;
    use crate::key::FixedKey;
    use crate::key::VariantKey;

    #[test]
    fn test_errors() {
        let mut key_buf = vec![0u8; 10];
        let mut radix_buf = vec![0u32; 15];

        // KeyId exceeds format limit
        let key = [0u8; 20];
        let key_id = (1u32 << 31).into();
        let r = radix_insert_with_key(&mut radix_buf, 0, key_id, &key, FixedKey::read, &key_buf);
        let t = format!("{}", r.unwrap_err());
        assert_eq!(t, format!("{}", ErrorKind::OffsetOverflow(2147483648)));

        // KeyId exceeds key buffer length
        let key_id = 30u32.into();
        let r = radix_insert(&mut radix_buf, 0, key_id, FixedKey::read, &key_buf);
        let t = format!("{}", r.unwrap_err());
        assert_eq!(t, format!("{}", ErrorKind::InvalidKeyId(key_id)));

        // Radix root node offset exceeds radix buffer length
        let r = radix_insert_with_key(&mut radix_buf, 16, key_id, &key, FixedKey::read, &key_buf);
        let t = format!("{}", r.unwrap_err());
        assert_eq!(t, format!("{}", ErrorKind::OffsetOverflow(16)));

        // Radix node offset out of range during a lookup
        let prefix = [0xf].iter().cloned();
        let r = radix_prefix_lookup(&radix_buf, 0, prefix, FixedKey::read, &key_buf);
        let t = format!("{}", r.unwrap_err());
        assert_eq!(t, format!("{}", ErrorKind::OffsetOverflow(15)));

        // Base16 sequence overflow
        let prefix = [21].iter().cloned();
        let r = radix_prefix_lookup(&radix_buf, 0, prefix.clone(), FixedKey::read, &key_buf);
        let t = format!("{}", r.unwrap_err());
        assert_eq!(t, format!("{}", ErrorKind::InvalidBase16(21)));

        // Inserting a same key with a same `KeyId` is okay
        let key_id1 = VariantKey::append(&mut key_buf, &b"ab");
        let key_id2 = VariantKey::append(&mut key_buf, &b"ab");
        radix_insert(&mut radix_buf, 0, key_id1, VariantKey::read, &key_buf).expect("insert");
        radix_insert(&mut radix_buf, 0, key_id1, VariantKey::read, &key_buf).expect("insert");

        // But not okay if `KeyId` are different
        let r = radix_insert(&mut radix_buf, 0, key_id2, VariantKey::read, &key_buf);
        let t = format!("{}", r.unwrap_err());
        assert_eq!(
            t,
            format!("{}", ErrorKind::PrefixConflict(key_id1, key_id2))
        );

        // A key cannot be a prefix of another key
        let key_id4 = VariantKey::append(&mut key_buf, &b"a");
        let key_id5 = VariantKey::append(&mut key_buf, &b"abc");
        let r = radix_insert(&mut radix_buf, 0, key_id4, VariantKey::read, &key_buf);
        assert_eq!(
            format!("{}", r.unwrap_err()),
            format!("{}", ErrorKind::PrefixConflict(key_id4, key_id1))
        );
        let r = radix_insert(&mut radix_buf, 0, key_id5, VariantKey::read, &key_buf);
        assert_eq!(
            format!("{}", r.unwrap_err()),
            format!("{}", ErrorKind::PrefixConflict(key_id1, key_id5))
        );

        // Enforce a leaf split of key_id1
        let key_id3 = VariantKey::append(&mut key_buf, &b"ac");
        radix_insert(&mut radix_buf, 0, key_id3, VariantKey::read, &key_buf).expect("insert");

        // Still impossible to cause key prefix conflicts
        let r = radix_insert(&mut radix_buf, 0, key_id4, VariantKey::read, &key_buf);
        let t = format!("{}", r.unwrap_err());
        assert_eq!(t, format!("{}", ErrorKind::AmbiguousPrefix));
        let r = radix_insert(&mut radix_buf, 0, key_id5, VariantKey::read, &key_buf);
        let t = format!("{}", r.unwrap_err());
        assert_eq!(
            t,
            format!("{}", ErrorKind::PrefixConflict(key_id1, key_id5))
        );
    }

    #[test]
    fn test_prefix_lookup() {
        let mut key_buf: Vec<u8> = vec![];
        let mut radix_buf = vec![0u32; 16];

        let query = Base16Iter::from_bin(&b"01abc");

        // With a single key
        let key1 = b"01ab";
        let key1_id = VariantKey::append(&mut key_buf, &key1);
        radix_insert(&mut radix_buf, 0, key1_id, VariantKey::read, &key_buf).expect("insert");
        for i in 0..query.len() {
            let prefix = query.clone().take(i);
            let r = radix_prefix_lookup(&radix_buf, 0, prefix, VariantKey::read, &key_buf);
            if i == 0 {
                // This is sub-optimal. But see the NOTE in RadixOffset::follow.
                let t = format!("{}", r.unwrap_err());
                assert_eq!(t, format!("{}", ErrorKind::AmbiguousPrefix));
            } else if i <= key1.len() * 2 {
                assert_eq!(r.unwrap(), Some(key1_id));
            } else {
                assert_eq!(r.unwrap(), None);
            }
        }

        // With another key
        let key2 = b"01bbc";
        let key2_id = VariantKey::append(&mut key_buf, &key2);
        radix_insert(&mut radix_buf, 0, key2_id, VariantKey::read, &key_buf).expect("insert");
        for i in 0..query.len() {
            let prefix = query.clone().take(i);
            let r = radix_prefix_lookup(&radix_buf, 0, prefix, VariantKey::read, &key_buf);
            if i <= 5 {
                let t = format!("{}", r.unwrap_err());
                assert_eq!(t, format!("{}", ErrorKind::AmbiguousPrefix));
            } else if i <= key1.len() * 2 {
                assert_eq!(r.unwrap(), Some(key1_id));
            } else {
                assert_eq!(r.unwrap(), None);
            }
        }

        let query = Base16Iter::from_bin(&b"1");
        let r = radix_prefix_lookup(&radix_buf, 0, query, VariantKey::read, &key_buf);
        assert_eq!(r.unwrap(), None);

        let query = Base16Iter::from_bin(&b"01b");
        let r = radix_prefix_lookup(&radix_buf, 0, query, VariantKey::read, &key_buf);
        assert_eq!(r.unwrap(), Some(key2_id));
    }

    quickcheck! {
        fn test_compare_with_stdset_sparse(std_set: HashSet<u64>) -> bool {
            let std_set: HashSet<[u8; 10]> = std_set.iter().map(|&x|  {
                let mut buf = [0u8; 10];
                let slice: [u8; 8] = unsafe { transmute(x) };
                buf[0..8].copy_from_slice(&slice);
                buf
            }).collect();
            check_with_stdset(std_set)
        }

        fn test_compare_with_stdset_dense(std_set: HashSet<u16>) -> bool {
            let std_set: HashSet<[u8; 10]> = std_set.iter().map(|&x|  {
                let mut buf = [0u8; 10];
                let slice: [u8; 2] = unsafe { transmute(x) };
                buf[0..2].copy_from_slice(&slice);
                buf
            }).collect();
            check_with_stdset(std_set)
        }
    }

    // Compare with `HashSet`.
    fn check_with_stdset(std_set: HashSet<[u8; 10]>) -> bool {
        let mut key_buf = Vec::<u8>::with_capacity(std_set.len() * 11);
        let mut radix_buf = vec![0u32; 16];

        // Insert to radix tree
        for key in &std_set {
            let key_id = VariantKey::append(&mut key_buf, key);
            radix_insert(&mut radix_buf, 0, key_id, VariantKey::read, &key_buf).expect("insert");
        }

        // Test key existence
        std_set.iter().all(|key| {
            let r = radix_lookup(&radix_buf, 0, key, VariantKey::read, &key_buf);
            r.unwrap().is_some()
        })
    }
}


/*
 * 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.
 */

//! Traits that makes the API more flexible.

/// Support `resize` in-place. Used for appending new nodes.
pub trait Resize<T> {
    fn resize(&mut self, new_len: usize, value: T);
}

impl<T: Copy> Resize<T> for Vec<T> {
    #[inline]
    fn resize(&mut self, new_len: usize, value: T) {
        Vec::resize(self, new_len, value)
    }
}


/*
 * 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.
 */

//! # refencode
//!
//! Encode and decode commit references such as bookmarks, remotenames, and
//! visibleheads.

use std::collections::BTreeMap;
use std::io;
use std::str::FromStr;

pub use types::HgId;

/// Encode remote bookmarks like `[('remote/master', node), ...]` to bytes.
pub fn encode_remotenames(name_nodes: &BTreeMap<String, HgId>) -> Vec<u8> {
    let encoded = name_nodes
        .iter()
        .map(|(name, node)| format!("{} bookmarks {}\n", node.to_hex(), name))
        .collect::<Vec<_>>()
        .concat();
    encoded.into_bytes()
}

/// Decode remote bookmarks encoded by `encode_remotenames`.
pub fn decode_remotenames(bytes: &[u8]) -> io::Result<BTreeMap<String, HgId>> {
    let text = std::str::from_utf8(bytes).map_err(invalid)?;
    let mut decoded = BTreeMap::<String, HgId>::new();
    for line in text.lines() {
        let split: Vec<&str> = line.splitn(3, ' ').collect();
        if let [hex, kind, name] = split[..] {
            // See https://fburl.com/1rft34i8 for why ignore default-push/
            if kind == "bookmarks" && !name.starts_with("default-push/") {
                let node = HgId::from_str(hex).map_err(invalid)?;
                decoded.insert(name.to_string(), node);
            }
        } else {
            return Err(invalid(format!("corrupt entry in remotenames: {}", line)));
        }
    }
    Ok(decoded)
}

/// Encode local bookmarks.
pub fn encode_bookmarks(name_nodes: &BTreeMap<String, HgId>) -> Vec<u8> {
    let encoded = name_nodes
        .iter()
        .map(|(name, node)| format!("{} {}\n", node.to_hex(), name))
        .collect::<Vec<_>>()
        .concat();
    encoded.into_bytes()
}

/// Decode local bookmarks encoded by `encode_bookmarks`.
pub fn decode_bookmarks(bytes: &[u8]) -> io::Result<BTreeMap<String, HgId>> {
    let text = std::str::from_utf8(bytes).map_err(invalid)?;
    let mut decoded = BTreeMap::<String, HgId>::new();
    for line in text.lines() {
        let split: Vec<&str> = line.splitn(2, ' ').collect();
        if let [hex, name] = split[..] {
            let node = HgId::from_str(hex).map_err(invalid)?;
            decoded.insert(name.to_string(), node);
        } else {
            return Err(invalid(format!("corrupt entry in bookmarks: {}", line)));
        }
    }
    Ok(decoded)
}

/// Encode visible heads.
pub fn encode_visibleheads(heads: &[HgId]) -> Vec<u8> {
    let encoded = std::iter::once("v1\n".to_string())
        .chain(heads.iter().map(|h| format!("{}\n", h.to_hex())))
        .collect::<Vec<_>>()
        .concat();
    encoded.into_bytes()
}

/// Decode visible heads encoded by `encode_visibleheads`.
pub fn decode_visibleheads(bytes: &[u8]) -> io::Result<Vec<HgId>> {
    let text = std::str::from_utf8(bytes).map_err(invalid)?;
    let mut decoded = Vec::new();
    for (i, line) in text.lines().enumerate() {
        if i == 0 {
            if line != "v1" {
                return Err(invalid(format!("invalid visibleheads format: {}", line)));
            }
        } else {
            let node = HgId::from_str(line).map_err(invalid)?;
            decoded.push(node);
        }
    }
    Ok(decoded)
}

fn invalid(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
    io::Error::new(io::ErrorKind::InvalidData, error)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_encode_decode_remotenames() {
        let m = map();
        let encoded = encode_remotenames(&m);
        let decoded = decode_remotenames(&encoded).unwrap();
        assert_eq!(decoded, m);
    }

    #[test]
    fn test_encode_decode_bookmarks() {
        let m = map();
        let encoded = encode_bookmarks(&m);
        let decoded = decode_bookmarks(&encoded).unwrap();
        assert_eq!(decoded, m);
    }

    #[test]
    fn test_encode_decode_visibleheads() {
        let heads = map().values().cloned().collect::<Vec<HgId>>();
        let encoded = encode_visibleheads(&heads);
        let decoded = decode_visibleheads(&encoded).unwrap();
        assert_eq!(decoded, heads);
    }

    fn map() -> BTreeMap<String, HgId> {
        let mut m = BTreeMap::new();
        for i in 0..10 {
            let name = format!("foo/a{}", i);
            let node = HgId::from_byte_array([i * 11; HgId::len()]);
            m.insert(name, node);
        }
        m
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::marker::PhantomData;

use itertools::Itertools;

use super::output::OutputRendererOptions;
use super::render::Ancestor;
use super::render::GraphRow;
use super::render::LinkLine;
use super::render::NodeLine;
use super::render::PadLine;
use super::render::Renderer;

pub struct AsciiRenderer<N, R>
where
    R: Renderer<N, Output = GraphRow<N>> + Sized,
{
    inner: R,
    options: OutputRendererOptions,
    extra_pad_line: Option<String>,
    _phantom: PhantomData<N>,
}

impl<N, R> AsciiRenderer<N, R>
where
    R: Renderer<N, Output = GraphRow<N>> + Sized,
{
    pub(crate) fn new(inner: R, options: OutputRendererOptions) -> Self {
        AsciiRenderer {
            inner,
            options,
            extra_pad_line: None,
            _phantom: PhantomData,
        }
    }
}

impl<N, R> Renderer<N> for AsciiRenderer<N, R>
where
    N: Clone + Eq,
    R: Renderer<N, Output = GraphRow<N>> + Sized,
{
    type Output = String;

    fn width(&self, node: Option<&N>, parents: Option<&Vec<Ancestor<N>>>) -> u64 {
        self.inner
            .width(node, parents)
            .saturating_mul(2)
            .saturating_add(1)
    }

    fn reserve(&mut self, node: N) {
        self.inner.reserve(node);
    }

    fn next_row(
        &mut self,
        node: N,
        parents: Vec<Ancestor<N>>,
        glyph: String,
        message: String,
    ) -> String {
        let line = self.inner.next_row(node, parents, glyph, message);
        let mut out = String::new();
        let mut message_lines = line
            .message
            .lines()
            .pad_using(self.options.min_row_height, |_| "");
        let mut need_extra_pad_line = false;

        // Render the previous extra pad line
        if let Some(extra_pad_line) = self.extra_pad_line.take() {
            out.push_str(extra_pad_line.trim_end());
            out.push('\n');
        }

        // Render the nodeline
        let mut node_line = String::new();
        for entry in line.node_line.iter() {
            match entry {
                NodeLine::Node => {
                    node_line.push_str(&line.glyph);
                    node_line.push(' ');
                }
                NodeLine::Parent => node_line.push_str("| "),
                NodeLine::Ancestor => node_line.push_str(". "),
                NodeLine::Blank => node_line.push_str("  "),
            }
        }
        if let Some(msg) = message_lines.next() {
            node_line.push(' ');
            node_line.push_str(msg);
        }
        out.push_str(node_line.trim_end());
        out.push('\n');

        // Render the link line
        if let Some(link_row) = line.link_line {
            let mut link_line = String::new();
            let any_horizontal = link_row
                .iter()
                .any(|cur| cur.intersects(LinkLine::HORIZONTAL));
            for (cur, next) in link_row
                .iter()
                .chain(Some(LinkLine::empty()).iter())
                .tuple_windows()
            {
                // Draw the parent/ancestor line.
                if cur.intersects(LinkLine::HORIZONTAL) {
                    if cur.intersects(LinkLine::CHILD | LinkLine::ANY_FORK_OR_MERGE) {
                        link_line.push('+');
                    } else {
                        link_line.push('-');
                    }
                } else if cur.intersects(LinkLine::VERTICAL) {
                    if cur.intersects(LinkLine::ANY_FORK_OR_MERGE) && any_horizontal {
                        link_line.push('+');
                    } else if cur.intersects(LinkLine::VERT_PARENT) {
                        link_line.push('|');
                    } else {
                        link_line.push('.');
                    }
                } else if cur.intersects(LinkLine::ANY_MERGE) && any_horizontal {
                    link_line.push('\'');
                } else if cur.intersects(LinkLine::ANY_FORK) && any_horizontal {
                    link_line.push('.');
                } else {
                    link_line.push(' ');
                }

                // Draw the connecting line.
                if cur.intersects(LinkLine::HORIZONTAL) {
                    link_line.push('-');
                } else if cur.intersects(LinkLine::RIGHT_MERGE) {
                    if next.intersects(LinkLine::LEFT_FORK) && !any_horizontal {
                        link_line.push('\\');
                    } else {
                        link_line.push('-');
                    }
                } else if cur.intersects(LinkLine::RIGHT_FORK) {
                    if next.intersects(LinkLine::LEFT_MERGE) && !any_horizontal {
                        link_line.push('/');
                    } else {
                        link_line.push('-');
                    }
                } else {
                    link_line.push(' ');
                }
            }
            if let Some(msg) = message_lines.next() {
                link_line.push(' ');
                link_line.push_str(msg);
            }
            out.push_str(link_line.trim_end());
            out.push('\n');
        }

        // Render the term line
        if let Some(term_row) = line.term_line {
            let term_strs = ["| ", "~ "];
            for term_str in term_strs.iter() {
                let mut term_line = String::new();
                for (i, term) in term_row.iter().enumerate() {
                    if *term {
                        term_line.push_str(term_str);
                    } else {
                        term_line.push_str(match line.pad_lines[i] {
                            PadLine::Parent => "| ",
                            PadLine::Ancestor => ". ",
                            PadLine::Blank => "  ",
                        });
                    }
                }
                if let Some(msg) = message_lines.next() {
                    term_line.push(' ');
                    term_line.push_str(msg);
                }
                out.push_str(term_line.trim_end());
                out.push('\n');
            }
            need_extra_pad_line = true;
        }

        let mut base_pad_line = String::new();
        for entry in line.pad_lines.iter() {
            base_pad_line.push_str(match entry {
                PadLine::Parent => "| ",
                PadLine::Ancestor => ". ",
                PadLine::Blank => "  ",
            });
        }

        // Render any pad lines
        for msg in message_lines {
            let mut pad_line = base_pad_line.clone();
            pad_line.push(' ');
            pad_line.push_str(msg);
            out.push_str(pad_line.trim_end());
            out.push('\n');
            need_extra_pad_line = false;
        }

        if need_extra_pad_line {
            self.extra_pad_line = Some(base_pad_line);
        }

        out
    }
}

#[cfg(test)]
mod tests {
    use super::super::test_fixtures;
    use super::super::test_fixtures::TestFixture;
    use super::super::test_utils::render_string;
    use crate::GraphRowRenderer;

    fn render(fixture: &TestFixture) -> String {
        let mut renderer = GraphRowRenderer::new().output().build_ascii();
        render_string(fixture, &mut renderer)
    }

    #[test]
    fn basic() {
        assert_eq!(
            render(&test_fixtures::BASIC),
            r#"
            o  C
            |
            o  B
            |
            o  A"#
        );
    }

    #[test]
    fn branches_and_merges() {
        assert_eq!(
            render(&test_fixtures::BRANCHES_AND_MERGES),
            r#"
            o  W
            |
            o    V
            |\
            | o    U
            | |\
            | | o  T
            | | |
            | o |  S
            |   |
            o   |  R
            |   |
            o   |  Q
            |\  |
            | o |    P
            | +---.
            | | | o  O
            | | | |
            | | | o    N
            | | | |\
            | o | | |  M
            | | | | |
            | o | | |  L
            | | | | |
            o | | | |  K
            +-------'
            o | | |  J
            | | | |
            o | | |  I
            |/  | |
            o   | |  H
            |   | |
            o   | |  G
            +-----+
            |   | o  F
            |   |/
            |   o  E
            |   |
            o   |  D
            |   |
            o   |  C
            +---'
            o  B
            |
            o  A"#
        );
    }

    #[test]
    fn octopus_branch_and_merge() {
        assert_eq!(
            render(&test_fixtures::OCTOPUS_BRANCH_AND_MERGE),
            r#"
            o      J
            +-+-.
            | | o  I
            | | |
            | o |      H
            +-+-+-+-.
            | | | | o  G
            | | | | |
            | | | o |  E
            | | | |/
            | | o |  D
            | | |\|
            | o | |  C
            | +---'
            o | |  F
            |/  |
            o   |  B
            +---'
            o  A"#
        );
    }

    #[test]
    fn reserved_column() {
        assert_eq!(
            render(&test_fixtures::RESERVED_COLUMN),
            r#"
              o  Z
              |
              o  Y
              |
              o  X
             /
            | o  W
            |/
            o  G
            |
            o    F
            |\
            | o  E
            | |
            | o  D
            |
            o  C
            |
            o  B
            |
            o  A"#
        );
    }

    #[test]
    fn ancestors() {
        assert_eq!(
            render(&test_fixtures::ANCESTORS),
            r#"
              o  Z
              |
              o  Y
             /
            o  F
            .
            . o  X
            ./
            | o  W
            |/
            o  E
            .
            o    D
            |\
            | o  C
            | .
            o .  B
            |/
            o  A"#
        );
    }

    #[test]
    fn split_parents() {
        assert_eq!(
            render(&test_fixtures::SPLIT_PARENTS),
            r#"
                  o  E
            .-+-+-+
            . o | .  D
            ./ \| .
            |   o .  C
            |   |/
            o   |  B
            +---'
            o  A"#
        );
    }

    #[test]
    fn terminations() {
        assert_eq!(
            render(&test_fixtures::TERMINATIONS),
            r#"
              o  K
              |
              | o  J
              |/
              o    I
             /|\
            | | |
            | ~ |
            |   |
            o   |  E
            |   |
            |   o  H
            +---'
            o  D
            |
            ~

            o  C
            |
            o  B
            |
            ~"#
        );
    }

    #[test]
    fn long_messages() {
        assert_eq!(
            render(&test_fixtures::LONG_MESSAGES),
            r#"
            o      F
            +-+-.  very long message 1
            | | |  very long message 2
            | | ~  very long message 3
            | |
            | |    very long message 4
            | |    very long message 5
            | |    very long message 6
            | |
            | o  E
            | |
            | o  D
            | |
            o |  C
            |/   long message 1
            |    long message 2
            |    long message 3
            |
            o  B
            |
            o  A
            |  long message 1
            ~  long message 2
               long message 3"#
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::marker::PhantomData;

use itertools::Itertools;

use super::output::OutputRendererOptions;
use super::render::Ancestor;
use super::render::GraphRow;
use super::render::LinkLine;
use super::render::NodeLine;
use super::render::PadLine;
use super::render::Renderer;

pub struct AsciiLargeRenderer<N, R>
where
    R: Renderer<N, Output = GraphRow<N>> + Sized,
{
    inner: R,
    options: OutputRendererOptions,
    extra_pad_line: Option<String>,
    _phantom: PhantomData<N>,
}

impl<N, R> AsciiLargeRenderer<N, R>
where
    R: Renderer<N, Output = GraphRow<N>> + Sized,
{
    pub(crate) fn new(inner: R, options: OutputRendererOptions) -> Self {
        AsciiLargeRenderer {
            inner,
            options,
            extra_pad_line: None,
            _phantom: PhantomData,
        }
    }
}

impl<N, R> Renderer<N> for AsciiLargeRenderer<N, R>
where
    N: Clone + Eq,
    R: Renderer<N, Output = GraphRow<N>> + Sized,
{
    type Output = String;

    fn width(&self, node: Option<&N>, parents: Option<&Vec<Ancestor<N>>>) -> u64 {
        // The first column is only 2 characters wide.
        self.inner
            .width(node, parents)
            .saturating_mul(3)
            .saturating_sub(1)
            .saturating_add(1)
    }

    fn reserve(&mut self, node: N) {
        self.inner.reserve(node);
    }

    fn next_row(
        &mut self,
        node: N,
        parents: Vec<Ancestor<N>>,
        glyph: String,
        message: String,
    ) -> String {
        let line = self.inner.next_row(node, parents, glyph, message);
        let mut out = String::new();
        let mut message_lines = line
            .message
            .lines()
            .pad_using(self.options.min_row_height, |_| "");
        let mut need_extra_pad_line = false;

        // Render the previous extra pad line
        if let Some(extra_pad_line) = self.extra_pad_line.take() {
            out.push_str(extra_pad_line.trim_end());
            out.push('\n');
        }

        // Render the nodeline
        let mut node_line = String::new();
        for (i, entry) in line.node_line.iter().enumerate() {
            match entry {
                NodeLine::Node => {
                    if i > 0 {
                        node_line.push(' ');
                    }
                    node_line.push_str(&line.glyph);
                    node_line.push(' ');
                }
                NodeLine::Parent => node_line.push_str(if i > 0 { " | " } else { "| " }),
                NodeLine::Ancestor => node_line.push_str(if i > 0 { " . " } else { ". " }),
                NodeLine::Blank => node_line.push_str(if i > 0 { "   " } else { "  " }),
            }
        }
        if let Some(msg) = message_lines.next() {
            node_line.push(' ');
            node_line.push_str(msg);
        }
        out.push_str(node_line.trim_end());
        out.push('\n');

        // Render the link line
        if let Some(link_row) = line.link_line {
            let mut top_link_line = String::new();
            let mut bot_link_line = String::new();
            for (i, cur) in link_row.iter().enumerate() {
                // Top left
                if i > 0 {
                    if cur.intersects(LinkLine::LEFT_MERGE_PARENT) {
                        top_link_line.push('/');
                    } else if cur.intersects(LinkLine::LEFT_MERGE_ANCESTOR) {
                        top_link_line.push('.');
                    } else if cur.intersects(LinkLine::HORIZ_PARENT) {
                        top_link_line.push('_');
                    } else if cur.intersects(LinkLine::HORIZ_ANCESTOR) {
                        top_link_line.push('.');
                    } else {
                        top_link_line.push(' ');
                    }
                }

                // Top center
                if cur.intersects(LinkLine::VERT_PARENT) {
                    top_link_line.push('|');
                } else if cur.intersects(LinkLine::VERT_ANCESTOR) {
                    top_link_line.push('.');
                } else if cur.intersects(LinkLine::ANY_MERGE) {
                    top_link_line.push(' ');
                } else if cur.intersects(LinkLine::HORIZ_PARENT) {
                    top_link_line.push('_');
                } else if cur.intersects(LinkLine::HORIZ_ANCESTOR) {
                    top_link_line.push('.');
                } else {
                    top_link_line.push(' ');
                }

                // Top right
                if cur.intersects(LinkLine::RIGHT_MERGE_PARENT) {
                    top_link_line.push('\\');
                } else if cur.intersects(LinkLine::RIGHT_MERGE_ANCESTOR) {
                    top_link_line.push('.');
                } else if cur.intersects(LinkLine::HORIZ_PARENT) {
                    top_link_line.push('_');
                } else if cur.intersects(LinkLine::HORIZ_ANCESTOR) {
                    top_link_line.push('.');
                } else {
                    top_link_line.push(' ');
                }

                // Bottom left
                if i > 0 {
                    if cur.intersects(LinkLine::LEFT_FORK_PARENT) {
                        bot_link_line.push('\\');
                    } else if cur.intersects(LinkLine::LEFT_FORK_ANCESTOR) {
                        bot_link_line.push('.');
                    } else {
                        bot_link_line.push(' ');
                    }
                }

                // Bottom center
                if cur.intersects(LinkLine::VERT_PARENT) {
                    bot_link_line.push('|');
                } else if cur.intersects(LinkLine::VERT_ANCESTOR) {
                    bot_link_line.push('.');
                } else {
                    bot_link_line.push(' ');
                }

                // Bottom Right
                if cur.intersects(LinkLine::RIGHT_FORK_PARENT) {
                    bot_link_line.push('/');
                } else if cur.intersects(LinkLine::RIGHT_FORK_ANCESTOR) {
                    bot_link_line.push('.');
                } else {
                    bot_link_line.push(' ');
                }
            }
            if let Some(msg) = message_lines.next() {
                top_link_line.push(' ');
                top_link_line.push_str(msg);
            }
            if let Some(msg) = message_lines.next() {
                bot_link_line.push(' ');
                bot_link_line.push_str(msg);
            }
            out.push_str(top_link_line.trim_end());
            out.push('\n');
            out.push_str(bot_link_line.trim_end());
            out.push('\n');
        }

        // Render the term line
        if let Some(term_row) = line.term_line {
            let term_strs = ["| ", "~ "];
            for term_str in term_strs.iter() {
                let mut term_line = String::new();
                for (i, term) in term_row.iter().enumerate() {
                    if i > 0 {
                        term_line.push(' ');
                    }
                    if *term {
                        term_line.push_str(term_str);
                    } else {
                        term_line.push_str(match line.pad_lines[i] {
                            PadLine::Parent => "| ",
                            PadLine::Ancestor => ". ",
                            PadLine::Blank => "  ",
                        });
                    }
                }
                if let Some(msg) = message_lines.next() {
                    term_line.push(' ');
                    term_line.push_str(msg);
                }
                out.push_str(term_line.trim_end());
                out.push('\n');
            }
            need_extra_pad_line = true;
        }

        let mut base_pad_line = String::new();
        for (i, entry) in line.pad_lines.iter().enumerate() {
            base_pad_line.push_str(match entry {
                PadLine::Parent => {
                    if i > 0 {
                        " | "
                    } else {
                        "| "
                    }
                }
                PadLine::Ancestor => {
                    if i > 0 {
                        " . "
                    } else {
                        ". "
                    }
                }
                PadLine::Blank => {
                    if i > 0 {
                        "   "
                    } else {
                        "  "
                    }
                }
            });
        }

        // Render any pad lines
        for msg in message_lines {
            let mut pad_line = base_pad_line.clone();
            pad_line.push(' ');
            pad_line.push_str(msg);
            out.push_str(pad_line.trim_end());
            out.push('\n');
            need_extra_pad_line = false;
        }

        if need_extra_pad_line {
            self.extra_pad_line = Some(base_pad_line);
        }

        out
    }
}

#[cfg(test)]
mod tests {
    use super::super::test_fixtures;
    use super::super::test_fixtures::TestFixture;
    use super::super::test_utils::render_string;
    use crate::GraphRowRenderer;

    fn render(fixture: &TestFixture) -> String {
        let mut renderer = GraphRowRenderer::new()
            .output()
            .with_min_row_height(3)
            .build_ascii_large();
        render_string(fixture, &mut renderer)
    }

    #[test]
    fn basic() {
        assert_eq!(
            render(&test_fixtures::BASIC),
            r#"
            o  C
            |
            |
            o  B
            |
            |
            o  A"#
        );
    }

    #[test]
    fn branches_and_merges() {
        assert_eq!(
            render(&test_fixtures::BRANCHES_AND_MERGES),
            r#"
            o  W
            |
            |
            o     V
            |\
            | \
            |  o     U
            |  |\
            |  | \
            |  |  o  T
            |  |  |
            |  |  |
            |  o  |  S
            |     |
            |     |
            o     |  R
            |     |
            |     |
            o     |  Q
            |\    |
            | \   |
            |  o  |     P
            |  |\_|_
            |  |  | \
            |  |  |  o  O
            |  |  |  |
            |  |  |  |
            |  |  |  o     N
            |  |  |  |\
            |  |  |  | \
            |  o  |  |  |  M
            |  |  |  |  |
            |  |  |  |  |
            |  o  |  |  |  L
            |  |  |  |  |
            |  |  |  |  |
            o  |  |  |  |  K
            | _|__|__|_/
            |/ |  |  |
            o  |  |  |  J
            |  |  |  |
            |  |  |  |
            o  |  |  |  I
            | /   |  |
            |/    |  |
            o     |  |  H
            |     |  |
            |     |  |
            o     |  |  G
            |\____|_ |
            |     | \|
            |     |  o  F
            |     | /
            |     |/
            |     o  E
            |     |
            |     |
            o     |  D
            |     |
            |     |
            o     |  C
            | ___/
            |/
            o  B
            |
            |
            o  A"#
        );
    }

    #[test]
    fn octopus_branch_and_merge() {
        assert_eq!(
            render(&test_fixtures::OCTOPUS_BRANCH_AND_MERGE),
            r#"
            o        J
            |\___
            | \  \
            |  |  o  I
            |  |  |
            |  |  |
            |  o  |        H
            | /|\_|____
            |/ | \| \  \
            |  |  |  |  o  G
            |  |  |  |  |
            |  |  |  |  |
            |  |  |  o  |  E
            |  |  |  | /
            |  |  |  |/
            |  |  o  |  D
            |  |  |\ |
            |  |  | \|
            |  o  |  |  C
            |  | _|_/
            |  |/ |
            o  |  |  F
            | /   |
            |/    |
            o     |  B
            | ___/
            |/
            o  A"#
        );
    }

    #[test]
    fn reserved_column() {
        assert_eq!(
            render(&test_fixtures::RESERVED_COLUMN),
            r#"
               o  Z
               |
               |
               o  Y
               |
               |
               o  X
              /
             /
            |  o  W
            | /
            |/
            o  G
            |
            |
            o     F
            |\
            | \
            |  o  E
            |  |
            |  |
            |  o  D
            |
            |
            o  C
            |
            |
            o  B
            |
            |
            o  A"#
        );
    }

    #[test]
    fn ancestors() {
        assert_eq!(
            render(&test_fixtures::ANCESTORS),
            r#"
               o  Z
               |
               |
               o  Y
              /
             /
            o  F
            .
            .
            .  o  X
            . /
            ./
            |  o  W
            | /
            |/
            o  E
            .
            .
            o     D
            |.
            | .
            |  o  C
            |  .
            |  .
            o  .  B
            | .
            |.
            o  A"#
        );
    }

    #[test]
    fn split_parents() {
        assert_eq!(
            render(&test_fixtures::SPLIT_PARENTS),
            r#"
                     o  E
              ...___/.
             .  /  / .
            .  o  |  .  D
            . / \ |  .
            ./   \|  .
            |     o  .  C
            |     | .
            |     |.
            o     |  B
            | ___/
            |/
            o  A"#
        );
    }

    #[test]
    fn terminations() {
        assert_eq!(
            render(&test_fixtures::TERMINATIONS),
            r#"
               o  K
               |
               |
               |  o  J
               | /
               |/
               o     I
              /|\
             / | \
            |  |  |
            |  ~  |
            |     |
            o     |  E
            |     |
            |     |
            |     o  H
            | ___/
            |/
            o  D
            |
            ~

            o  C
            |
            |
            o  B
            |
            ~"#
        );
    }

    #[test]
    fn long_messages() {
        assert_eq!(
            render(&test_fixtures::LONG_MESSAGES),
            r#"
            o        F
            |\___    very long message 1
            | \  \   very long message 2
            |  |  |  very long message 3
            |  |  ~
            |  |     very long message 4
            |  |     very long message 5
            |  |     very long message 6
            |  |
            |  o  E
            |  |
            |  |
            |  o  D
            |  |
            |  |
            o  |  C
            | /   long message 1
            |/    long message 2
            |     long message 3
            |
            o  B
            |
            |
            o  A
            |  long message 1
            ~  long message 2
               long message 3"#
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Column<N> {
    Empty,
    Blocked,
    Reserved(N),
    Ancestor(N),
    Parent(N),
}

impl<N> Column<N>
where
    N: Clone,
{
    pub(crate) fn matches(&self, n: &N) -> bool
    where
        N: Eq,
    {
        match self {
            Column::Empty | Column::Blocked => false,
            Column::Reserved(o) => n == o,
            Column::Ancestor(o) => n == o,
            Column::Parent(o) => n == o,
        }
    }

    fn variant(&self) -> usize {
        match self {
            Column::Empty => 0,
            Column::Blocked => 1,
            Column::Reserved(_) => 2,
            Column::Ancestor(_) => 3,
            Column::Parent(_) => 4,
        }
    }

    pub(crate) fn merge(&mut self, other: &Column<N>) {
        if other.variant() > self.variant() {
            *self = other.clone();
        }
    }

    fn reset(&mut self) {
        match self {
            Column::Blocked => *self = Column::Empty,
            _ => {}
        }
    }
}

pub(crate) trait ColumnsExt<N> {
    fn find(&self, node: &N) -> Option<usize>;
    fn find_empty(&self, index: usize) -> Option<usize>;
    fn first_empty(&self) -> Option<usize>;
    fn new_empty(&mut self) -> usize;
    fn reset(&mut self);
}

impl<N> ColumnsExt<N> for Vec<Column<N>>
where
    N: Clone + Eq,
{
    fn find(&self, node: &N) -> Option<usize> {
        for (index, column) in self.iter().enumerate() {
            if column.matches(node) {
                return Some(index);
            }
        }
        None
    }

    fn find_empty(&self, index: usize) -> Option<usize> {
        if self.get(index) == Some(&Column::Empty) {
            return Some(index);
        }
        self.first_empty()
    }

    fn first_empty(&self) -> Option<usize> {
        for (i, column) in self.iter().enumerate() {
            if *column == Column::Empty {
                return Some(i);
            }
        }
        None
    }

    fn new_empty(&mut self) -> usize {
        self.push(Column::Empty);
        self.len() - 1
    }

    fn reset(&mut self) {
        for column in self.iter_mut() {
            column.reset();
        }
        while let Some(Column::Empty) = self.last() {
            self.pop();
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

mod ascii;
mod ascii_large;
mod box_drawing;
mod column;
mod output;
#[allow(clippy::module_inception)]
mod render;

#[cfg(test)]
mod test_fixtures;

#[cfg(test)]
mod test_utils;

pub use self::ascii::AsciiRenderer;
pub use self::ascii_large::AsciiLargeRenderer;
pub use self::box_drawing::BoxDrawingRenderer;
pub use self::render::Ancestor;
pub use self::render::GraphRow;
pub use self::render::GraphRowRenderer;
pub use self::render::LinkLine;
pub use self::render::NodeLine;
pub use self::render::PadLine;
pub use self::render::Renderer;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::marker::PhantomData;

use super::ascii::AsciiRenderer;
use super::ascii_large::AsciiLargeRenderer;
use super::box_drawing::BoxDrawingRenderer;
use super::render::GraphRow;
use super::render::Renderer;

pub(crate) struct OutputRendererOptions {
    pub(crate) min_row_height: usize,
}

pub struct OutputRendererBuilder<N, R>
where
    R: Renderer<N, Output = GraphRow<N>> + Sized,
{
    inner: R,
    options: OutputRendererOptions,
    _phantom: PhantomData<N>,
}

impl<N, R> OutputRendererBuilder<N, R>
where
    R: Renderer<N, Output = GraphRow<N>> + Sized,
{
    pub fn new(inner: R) -> Self {
        OutputRendererBuilder {
            inner,
            options: OutputRendererOptions { min_row_height: 2 },
            _phantom: PhantomData,
        }
    }

    pub fn with_min_row_height(mut self, min_row_height: usize) -> Self {
        self.options.min_row_height = min_row_height;
        self
    }

    pub fn build_ascii(self) -> AsciiRenderer<N, R> {
        AsciiRenderer::new(self.inner, self.options)
    }

    pub fn build_ascii_large(self) -> AsciiLargeRenderer<N, R> {
        AsciiLargeRenderer::new(self.inner, self.options)
    }

    pub fn build_box_drawing(self) -> BoxDrawingRenderer<N, R> {
        BoxDrawingRenderer::new(self.inner, self.options)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::collections::BTreeMap;
use std::ops::Range;

use bitflags::bitflags;
#[cfg(feature = "serialize")]
use serde::Serialize;

use super::column::Column;
use super::column::ColumnsExt;
use super::output::OutputRendererBuilder;

pub trait Renderer<N> {
    type Output;

    // Returns the width of the graph line, possibly including another node.
    fn width(&self, new_node: Option<&N>, new_parents: Option<&Vec<Ancestor<N>>>) -> u64;

    // Reserve a column for the given node.
    fn reserve(&mut self, node: N);

    // Render the next row.
    fn next_row(
        &mut self,
        node: N,
        parents: Vec<Ancestor<N>>,
        glyph: String,
        message: String,
    ) -> Self::Output;
}

/// Renderer for a DAG.
///
/// Converts a sequence of DAG node descriptions into rendered graph rows.
pub struct GraphRowRenderer<N> {
    columns: Vec<Column<N>>,
}

/// Ancestor type indication for an ancestor or parent node.
pub enum Ancestor<N> {
    /// The node is an eventual ancestor.
    Ancestor(N),

    /// The node is an immediate parent.
    Parent(N),

    /// The node is an anonymous ancestor.
    Anonymous,
}

impl<N> Ancestor<N> {
    fn to_column(&self) -> Column<N>
    where
        N: Clone,
    {
        match self {
            Ancestor::Ancestor(n) => Column::Ancestor(n.clone()),
            Ancestor::Parent(n) => Column::Parent(n.clone()),
            Ancestor::Anonymous => Column::Blocked,
        }
    }

    fn id(&self) -> Option<&N> {
        match self {
            Ancestor::Ancestor(n) => Some(&n),
            Ancestor::Parent(n) => Some(&n),
            Ancestor::Anonymous => None,
        }
    }

    fn is_direct(&self) -> bool {
        match self {
            Ancestor::Ancestor(_) => false,
            Ancestor::Parent(_) => true,
            Ancestor::Anonymous => true,
        }
    }

    fn to_link_line(&self, direct: LinkLine, indirect: LinkLine) -> LinkLine {
        if self.is_direct() { direct } else { indirect }
    }
}

struct AncestorColumnBounds {
    target: usize,
    min_ancestor: usize,
    min_parent: usize,
    max_parent: usize,
    max_ancestor: usize,
}

impl AncestorColumnBounds {
    fn new<N>(columns: &BTreeMap<usize, &Ancestor<N>>, target: usize) -> Option<Self> {
        if columns.is_empty() {
            return None;
        }
        let min_ancestor = columns
            .iter()
            .next()
            .map_or(target, |(index, _)| *index)
            .min(target);
        let max_ancestor = columns
            .iter()
            .next_back()
            .map_or(target, |(index, _)| *index)
            .max(target);
        let min_parent = columns
            .iter()
            .find(|(_, ancestor)| ancestor.is_direct())
            .map_or(target, |(index, _)| *index)
            .min(target);
        let max_parent = columns
            .iter()
            .rev()
            .find(|(_, ancestor)| ancestor.is_direct())
            .map_or(target, |(index, _)| *index)
            .max(target);
        Some(Self {
            target,
            min_ancestor,
            min_parent,
            max_parent,
            max_ancestor,
        })
    }

    fn range(&self) -> Range<usize> {
        if self.min_ancestor < self.max_ancestor {
            self.min_ancestor + 1..self.max_ancestor
        } else {
            Default::default()
        }
    }

    fn horizontal_line(&self, index: usize) -> LinkLine {
        if index == self.target {
            LinkLine::empty()
        } else if index > self.min_parent && index < self.max_parent {
            LinkLine::HORIZ_PARENT
        } else if index > self.min_ancestor && index < self.max_ancestor {
            LinkLine::HORIZ_ANCESTOR
        } else {
            LinkLine::empty()
        }
    }
}

impl<N> Column<N> {
    fn to_node_line(&self) -> NodeLine {
        match self {
            Column::Ancestor(_) => NodeLine::Ancestor,
            Column::Parent(_) => NodeLine::Parent,
            _ => NodeLine::Blank,
        }
    }

    fn to_link_line(&self) -> LinkLine {
        match self {
            Column::Ancestor(_) => LinkLine::VERT_ANCESTOR,
            Column::Parent(_) => LinkLine::VERT_PARENT,
            _ => LinkLine::empty(),
        }
    }

    fn to_pad_line(&self) -> PadLine {
        match self {
            Column::Ancestor(_) => PadLine::Ancestor,
            Column::Parent(_) => PadLine::Parent,
            _ => PadLine::Blank,
        }
    }
}

/// A column in the node row.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub enum NodeLine {
    /// Blank.
    Blank,

    /// Vertical line indicating an ancestor.
    Ancestor,

    /// Vertical line indicating a parent.
    Parent,

    /// The node for this row.
    Node,
}

/// A column in a padding row.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub enum PadLine {
    /// Blank.
    Blank,

    /// Vertical line indicating an ancestor.
    Ancestor,

    /// Vertical line indicating a parent.
    Parent,
}

bitflags! {
    /// A column in a linking row.
    #[derive(Default)]
    #[cfg_attr(feature = "serialize", derive(Serialize))]
    pub struct LinkLine: u16 {
        /// This cell contains a horizontal line that connects to a parent.
        const HORIZ_PARENT = 0b0_0000_0000_0001;

        /// This cell contains a horizontal line that connects to an ancestor.
        const HORIZ_ANCESTOR = 0b0_0000_0000_0010;

        /// The descendent of this cell is connected to the parent.
        const VERT_PARENT = 0b0_0000_0000_0100;

        /// The descendent of this cell is connected to an ancestor.
        const VERT_ANCESTOR = 0b0_0000_0000_1000;

        /// The parent of this cell is linked in this link row and the child
        /// is to the left.
        const LEFT_FORK_PARENT = 0b0_0000_0001_0000;

        /// The ancestor of this cell is linked in this link row and the child
        /// is to the left.
        const LEFT_FORK_ANCESTOR = 0b0_0000_0010_0000;

        /// The parent of this cell is linked in this link row and the child
        /// is to the right.
        const RIGHT_FORK_PARENT = 0b0_0000_0100_0000;

        /// The ancestor of this cell is linked in this link row and the child
        /// is to the right.
        const RIGHT_FORK_ANCESTOR = 0b0_0000_1000_0000;

        /// The child of this cell is linked to parents on the left.
        const LEFT_MERGE_PARENT = 0b0_0001_0000_0000;

        /// The child of this cell is linked to ancestors on the left.
        const LEFT_MERGE_ANCESTOR = 0b0_0010_0000_0000;

        /// The child of this cell is linked to parents on the right.
        const RIGHT_MERGE_PARENT = 0b0_0100_0000_0000;

        /// The child of this cell is linked to ancestors on the right.
        const RIGHT_MERGE_ANCESTOR = 0b0_1000_0000_0000;

        /// The target node of this link line is the child of this column.
        /// This disambiguates between the node that is connected in this link
        /// line, and other nodes that are also connected vertically.
        const CHILD = 0b1_0000_0000_0000;

        const HORIZONTAL = Self::HORIZ_PARENT.bits | Self::HORIZ_ANCESTOR.bits;
        const VERTICAL = Self::VERT_PARENT.bits | Self::VERT_ANCESTOR.bits;
        const LEFT_FORK = Self::LEFT_FORK_PARENT.bits | Self::LEFT_FORK_ANCESTOR.bits;
        const RIGHT_FORK = Self::RIGHT_FORK_PARENT.bits | Self::RIGHT_FORK_ANCESTOR.bits;
        const LEFT_MERGE = Self::LEFT_MERGE_PARENT.bits | Self::LEFT_MERGE_ANCESTOR.bits;
        const RIGHT_MERGE = Self::RIGHT_MERGE_PARENT.bits | Self::RIGHT_MERGE_ANCESTOR.bits;
        const ANY_MERGE = Self::LEFT_MERGE.bits | Self::RIGHT_MERGE.bits;
        const ANY_FORK = Self::LEFT_FORK.bits | Self::RIGHT_FORK.bits;
        const ANY_FORK_OR_MERGE = Self::ANY_MERGE.bits | Self::ANY_FORK.bits;
    }
}

/// An output graph row.
#[derive(Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct GraphRow<N> {
    /// The name of the node for this row.
    pub node: N,

    /// The glyph for this node.
    pub glyph: String,

    /// The message for this row.
    pub message: String,

    /// True if this row is for a merge commit.
    pub merge: bool,

    /// The node columns for this row.
    pub node_line: Vec<NodeLine>,

    /// The link columns for this row, if a link row is necessary.
    pub link_line: Option<Vec<LinkLine>>,

    /// The location of any terminators, if necessary.  Other columns should be
    /// filled in with pad lines.
    pub term_line: Option<Vec<bool>>,

    /// The pad columns for this row.
    pub pad_lines: Vec<PadLine>,
}

impl<N> GraphRowRenderer<N>
where
    N: Clone + Eq,
{
    /// Create a new renderer.
    pub fn new() -> Self {
        GraphRowRenderer {
            columns: Vec::new(),
        }
    }

    /// Build an output renderer from this renderer.
    pub fn output(self) -> OutputRendererBuilder<N, Self> {
        OutputRendererBuilder::new(self)
    }
}

impl<N> Renderer<N> for GraphRowRenderer<N>
where
    N: Clone + Eq,
{
    type Output = GraphRow<N>;

    fn width(&self, node: Option<&N>, parents: Option<&Vec<Ancestor<N>>>) -> u64 {
        let mut width = self.columns.len();
        let mut empty_columns = self
            .columns
            .iter()
            .filter(|&column| column == &Column::Empty)
            .count();
        if let Some(node) = node {
            // If the node is not already allocated, and there is no
            // space for the node, then adding the new node would create
            // a new column.
            if self.columns.find(&node).is_none() {
                if empty_columns == 0 {
                    width += 1;
                } else {
                    empty_columns = empty_columns.saturating_sub(1);
                }
            }
        }
        if let Some(parents) = parents {
            // Non-allocated parents will also need a new column (except
            // for one, which can take the place of the node, and any that could be allocated to
            // empty columns).
            let unallocated_parents = parents
                .iter()
                .filter(|parent| {
                    parent
                        .id()
                        .map_or(true, |parent| self.columns.find(&parent).is_none())
                })
                .count()
                .saturating_sub(empty_columns);
            width += unallocated_parents.saturating_sub(1);
        }
        width as u64
    }

    fn reserve(&mut self, node: N) {
        if self.columns.find(&node).is_none() {
            if let Some(index) = self.columns.first_empty() {
                self.columns[index] = Column::Reserved(node);
            } else {
                self.columns.push(Column::Reserved(node));
            }
        }
    }

    fn next_row(
        &mut self,
        node: N,
        parents: Vec<Ancestor<N>>,
        glyph: String,
        message: String,
    ) -> GraphRow<N> {
        // Find a column for this node.
        let column = self.columns.find(&node).unwrap_or_else(|| {
            self.columns
                .first_empty()
                .unwrap_or_else(|| self.columns.new_empty())
        });
        self.columns[column] = Column::Empty;

        // This row is for a merge if there are multiple parents.
        let merge = parents.len() > 1;

        // Build the initial node line.
        let mut node_line: Vec<_> = self.columns.iter().map(|c| c.to_node_line()).collect();
        node_line[column] = NodeLine::Node;

        // Build the initial link line.
        let mut link_line: Vec<_> = self.columns.iter().map(|c| c.to_link_line()).collect();
        let mut need_link_line = false;

        // Build the initial term line.
        let mut term_line: Vec<_> = self.columns.iter().map(|_| false).collect();
        let mut need_term_line = false;

        // Build the initial pad line.
        let mut pad_lines: Vec<_> = self.columns.iter().map(|c| c.to_pad_line()).collect();

        // Assign each parent to a column.
        let mut parent_columns = BTreeMap::new();
        for p in parents.iter() {
            // Check if the parent already has a column.
            if let Some(parent_id) = p.id() {
                if let Some(index) = self.columns.find(parent_id) {
                    self.columns[index].merge(&p.to_column());
                    parent_columns.insert(index, p);
                    continue;
                }
            }
            // Assign the parent to an empty column, preferring the column
            // the current node is going in, to maintain linearity.
            if let Some(index) = self.columns.find_empty(column) {
                self.columns[index].merge(&p.to_column());
                parent_columns.insert(index, p);
                continue;
            }
            // There are no empty columns left.  Make a new column.
            parent_columns.insert(self.columns.len(), p);
            node_line.push(NodeLine::Blank);
            pad_lines.push(PadLine::Blank);
            link_line.push(LinkLine::default());
            term_line.push(false);
            self.columns.push(p.to_column());
        }

        // Mark parent columns with anonymous parents as terminating.
        for (i, p) in parent_columns.iter() {
            if p.id().is_none() {
                term_line[*i] = true;
                need_term_line = true;
            }
        }

        // Check if we can move the parent to the current column.
        if parents.len() == 1 {
            if let Some((&parent_column, _)) = parent_columns.iter().next() {
                if parent_column > column {
                    // This node has a single parent which was already
                    // assigned to a column to the right of this one.
                    // Move the parent to this column.
                    self.columns.swap(column, parent_column);
                    let parent = parent_columns
                        .remove(&parent_column)
                        .expect("parent should exist");
                    parent_columns.insert(column, parent);

                    // Generate a line from this column to the old
                    // parent column.   We need to continue with the style
                    // that was being used for the parent column.
                    let was_direct = link_line[parent_column].contains(LinkLine::VERT_PARENT);
                    link_line[column] |= if was_direct {
                        LinkLine::RIGHT_FORK_PARENT
                    } else {
                        LinkLine::RIGHT_FORK_ANCESTOR
                    };
                    #[allow(clippy::needless_range_loop)]
                    for i in column + 1..parent_column {
                        link_line[i] |= if was_direct {
                            LinkLine::HORIZ_PARENT
                        } else {
                            LinkLine::HORIZ_ANCESTOR
                        };
                    }
                    link_line[parent_column] = if was_direct {
                        LinkLine::LEFT_MERGE_PARENT
                    } else {
                        LinkLine::LEFT_MERGE_ANCESTOR
                    };
                    need_link_line = true;
                    // The pad line for the old parent column is now blank.
                    pad_lines[parent_column] = PadLine::Blank;
                }
            }
        }

        // Connect the node column to all the parent columns.
        if let Some(bounds) = AncestorColumnBounds::new(&parent_columns, column) {
            // If the parents extend beyond the columns adjacent to the node, draw a horizontal
            // ancestor line between the two outermost ancestors.
            for i in bounds.range() {
                link_line[i] |= bounds.horizontal_line(i);
                need_link_line = true;
            }

            // If there is a parent or ancestor to the right of the node
            // column, the node merges from the right.
            if bounds.max_parent > column {
                link_line[column] |= LinkLine::RIGHT_MERGE_PARENT;
                need_link_line = true;
            } else if bounds.max_ancestor > column {
                link_line[column] |= LinkLine::RIGHT_MERGE_ANCESTOR;
                need_link_line = true;
            }

            // If there is a parent or ancestor to the left of the node column, the node merges from the left.
            if bounds.min_parent < column {
                link_line[column] |= LinkLine::LEFT_MERGE_PARENT;
                need_link_line = true;
            } else if bounds.min_ancestor < column {
                link_line[column] |= LinkLine::LEFT_MERGE_ANCESTOR;
                need_link_line = true;
            }

            // Each parent or ancestor forks towards the node column.
            #[allow(clippy::comparison_chain)]
            for (&i, p) in parent_columns.iter() {
                pad_lines[i] = self.columns[i].to_pad_line();
                if i < column {
                    link_line[i] |=
                        p.to_link_line(LinkLine::RIGHT_FORK_PARENT, LinkLine::RIGHT_FORK_ANCESTOR);
                } else if i == column {
                    link_line[i] |= LinkLine::CHILD
                        | p.to_link_line(LinkLine::VERT_PARENT, LinkLine::VERT_ANCESTOR);
                } else {
                    link_line[i] |=
                        p.to_link_line(LinkLine::LEFT_FORK_PARENT, LinkLine::LEFT_FORK_ANCESTOR);
                }
            }
        }

        // Now that we have assigned all the columns, reset their state.
        self.columns.reset();

        // Filter out the link line or term line if they are not needed.
        let link_line = Some(link_line).filter(|_| need_link_line);
        let term_line = Some(term_line).filter(|_| need_term_line);

        GraphRow {
            node,
            glyph,
            message,
            merge,
            node_line,
            link_line,
            term_line,
            pad_lines,
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

pub(crate) struct TestFixture {
    pub(crate) dag: &'static str,
    pub(crate) messages: &'static [(&'static str, &'static str)],
    pub(crate) heads: &'static [&'static str],
    pub(crate) reserve: &'static [&'static str],
    pub(crate) ancestors: &'static [(&'static str, &'static str)],
    pub(crate) missing: &'static [&'static str],
}

pub(crate) const BASIC: TestFixture = TestFixture {
    dag: "A-B-C",
    messages: &[],
    heads: &["C"],
    reserve: &[],
    ancestors: &[],
    missing: &[],
};

pub(crate) const BRANCHES_AND_MERGES: TestFixture = TestFixture {
    dag: r#"
                      T /---------------N--O---\           T
                     / /                        \           \
               /----E-F-\    /-------L--M--------P--\     S--U---\
            A-B-C-D------G--H--I--J--K---------------Q--R---------V--W
                                   \--N
    "#,
    messages: &[],
    heads: &["W"],
    reserve: &[],
    ancestors: &[],
    missing: &[],
};

pub(crate) const OCTOPUS_BRANCH_AND_MERGE: TestFixture = TestFixture {
    dag: r#"
                        /-----\
                       /       \
                      D /--C--\ I
                     / /---D---\ \
                    A-B----E----H-J
                       \---F---/ /
                        \--G--/ F
    "#,
    messages: &[],
    heads: &["J"],
    reserve: &[],
    ancestors: &[],
    missing: &[],
};

pub(crate) const RESERVED_COLUMN: TestFixture = TestFixture {
    dag: r#"
                   A-B-C-F-G----\
                    D-E-/   \-W  \-X-Y-Z
    "#,
    messages: &[],
    heads: &["W", "Z"],
    reserve: &["G"],
    ancestors: &[],
    missing: &[],
};

pub(crate) const ANCESTORS: TestFixture = TestFixture {
    dag: r#"
                   A----B-D-----E----------F-\
                    \-C--/       \-W  \-X     \-Y-Z
    "#,
    messages: &[],
    heads: &["W", "X", "Z"],
    reserve: &["F"],
    ancestors: &[("C", "A"), ("D", "C"), ("E", "D"), ("F", "E")],
    missing: &[],
};

pub(crate) const SPLIT_PARENTS: TestFixture = TestFixture {
    dag: r#"
                    /-B-\     A-\
                   A     D-E  B--E
                    \-C-/     C-/
    "#,
    messages: &[],
    heads: &["E"],
    reserve: &["B", "D", "C"],
    ancestors: &[("E", "A"), ("E", "B")],
    missing: &[],
};

pub(crate) const TERMINATIONS: TestFixture = TestFixture {
    dag: r#"
                   A-B-C  D-E-\
                            F---I--J
                        X-D-H-/  \-K
    "#,
    messages: &[],
    heads: &["C", "J", "K"],
    reserve: &["E"],
    ancestors: &[("B", "A")],
    missing: &["A", "F", "X"],
};

const LONG_MESSAGE: &str = "long message 1\nlong message 2\nlong message 3\n\n";
const VERY_LONG_MESSAGE: &str = "very long message 1\nvery long message 2\nvery long message 3\n\n\
     very long message 4\nvery long message 5\nvery long message 6\n\n";

pub(crate) const LONG_MESSAGES: TestFixture = TestFixture {
    dag: r#"
                         Y-\
                  Z-A-B-D-E-F
                       \-C-/
    "#,
    messages: &[
        ("A", LONG_MESSAGE),
        ("C", LONG_MESSAGE),
        ("F", VERY_LONG_MESSAGE),
    ],
    heads: &["F"],

    reserve: &[],
    ancestors: &[],
    missing: &["Y", "Z"],
};

pub(crate) const ORDERS1: TestFixture = TestFixture {
    dag: r#"
                    K
                   /|
                  F J
                 / /|
                | E I
                |/ /|
                | D H
                |/ /|
                | C G
                |/ /|
                | B Z
                |/
                A
    "#,
    messages: &[],
    heads: &["K"],
    reserve: &[],
    ancestors: &[],
    missing: &[],
};

// Unlike ORDERS1, the first-parent branch (KFEDCBA) is the longest path.
pub(crate) const ORDERS2: TestFixture = TestFixture {
    dag: r#"
                    K
                   /|
                  J F
                 / /|
                | I E
                |/ /|
                | H D
                |/ /|
                | G C
                |/ /|
                | B Z
                |/
                A
    "#,
    messages: &[],
    heads: &["K"],
    reserve: &[],
    ancestors: &[],
    missing: &[],
};


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::collections::HashMap;
use std::collections::HashSet;

use dag::namedag::MemNameDag;
use dag::nameset::SyncNameSetQuery;
use dag::ops::ImportAscii;
use dag::DagAlgorithm;
use dag::VertexName;
use nonblocking::non_blocking_result;
use unicode_width::UnicodeWidthStr;

use super::render::Ancestor;
use super::render::Renderer;
use super::test_fixtures::TestFixture;

pub(crate) fn render_string(
    fixture: &TestFixture,
    renderer: &mut dyn Renderer<VertexName, Output = String>,
) -> String {
    render_string_with_order(fixture, renderer, None)
}

pub(crate) fn render_string_with_order(
    fixture: &TestFixture,
    renderer: &mut dyn Renderer<VertexName, Output = String>,
    order: Option<&[&str]>,
) -> String {
    let TestFixture {
        dag: ascii,
        messages,
        heads,
        reserve,
        ancestors,
        missing,
    } = fixture;
    let mut dag = MemNameDag::new();
    dag.import_ascii_with_heads(ascii, Some(heads)).unwrap();
    // str -> VertexName
    let v = |s: &str| VertexName::copy_from(s.as_bytes());

    let ancestors: HashSet<_> = ancestors
        .iter()
        .map(|(desc, anc)| (v(desc), v(anc)))
        .collect();
    let missing: HashSet<_> = missing.iter().map(|s| v(s)).collect();

    reserve
        .iter()
        .cloned()
        .map(|s| v(s))
        .for_each(|s| renderer.reserve(s));

    let messages: HashMap<_, _> = messages.iter().cloned().collect();

    let iter: Vec<_> = match order {
        None => non_blocking_result(dag.all())
            .unwrap()
            .iter()
            .unwrap()
            .map(|v| v.unwrap())
            .collect(),
        Some(order) => order.iter().map(|name| v(name)).collect(),
    };

    let mut out = String::new();
    for node in iter {
        if missing.contains(&node) {
            continue;
        }
        let parents = non_blocking_result(dag.parent_names(node.clone()))
            .unwrap()
            .into_iter()
            .map(|parent| {
                if missing.contains(&parent) {
                    Ancestor::Anonymous
                } else if ancestors.contains(&(node.clone(), parent.clone())) {
                    Ancestor::Ancestor(parent)
                } else {
                    Ancestor::Parent(parent)
                }
            })
            .collect();
        let name = String::from_utf8(node.as_ref().to_vec()).unwrap();
        let message = match messages.get(name.as_str()) {
            Some(message) => format!("{}\n{}", name, message),
            None => name.clone(),
        };
        let width = renderer.width(Some(&node), Some(&parents));
        let row = renderer.next_row(node, parents, String::from("o"), message);
        let row_indent = row
            .lines()
            .filter_map(|line| line.find(&name).map(|offset| &line[..offset]))
            .next()
            .expect("name should be in the output");
        assert_eq!(
            row_indent.width() as u64,
            width,
            "indent '{}' for row for {} is the wrong width",
            row_indent,
            name
        );

        out.push_str(&row);
    }

    format!(
        "\n{}",
        out.trim_end()
            .lines()
            .map(|l| format!("            {}", l))
            .collect::<Vec<_>>()
            .join("\n")
    )
}


/*
 * 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::fs;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;

use edenapi::EdenApi;
use hgcommits::DagCommits;
use hgcommits::DoubleWriteCommits;
use hgcommits::Error as CommitError;
use hgcommits::GitSegmentedCommits;
use hgcommits::HgCommits;
use hgcommits::HybridCommits;
use hgcommits::RevlogCommits;
use metalog::MetaLog;
use parking_lot::RwLock;

use crate::repo::Repo;

macro_rules! concat_os_path {
    ($p1:literal, $p2:literal) => {
        // Cannot use std::path::MAIN_SEPARATOR inside concat! yet.
        if cfg!(windows) {
            concat!($p1, '\\', $p2)
        } else {
            concat!($p1, '/', $p2)
        }
    };
}

static HG_COMMITS_PATH: &str = concat_os_path!("hgcommits", "v1");
static LAZY_HASH_PATH: &str = "lazyhashdir";
static SEGMENTS_PATH: &str = concat_os_path!("segments", "v1");

static DOUBLE_WRITE_REQUIREMENT: &str = "doublewritechangelog";
static HYBRID_REQUIREMENT: &str = "hybridchangelog";
static LAZY_TEXT_REQUIREMENT: &str = "lazytextchangelog";
static GIT_STORE_REQUIREMENT: &str = "git-store";
static LAZY_STORE_REQUIREMENT: &str = "lazychangelog";
static SEGMENTS_REQUIREMENT: &str = "segmentedchangelog";

static GIT_FILE: &str = "gitdir";

pub(crate) fn open_dag_commits(
    repo: &mut Repo,
) -> anyhow::Result<Box<dyn DagCommits + Send + 'static>> {
    let commits = if repo.store_requirements.contains(GIT_STORE_REQUIREMENT) {
        let metalog = repo.metalog()?;
        tracing::info!(target: "changelog_info", changelog_backend="git");
        open_git(repo.store_path(), metalog)?
    } else if repo.store_requirements.contains(LAZY_STORE_REQUIREMENT) {
        let eden_api = repo.eden_api()?;
        tracing::info!(target: "changelog_info", changelog_backend="lazy");
        open_hybrid(repo.store_path(), eden_api, true, false)?
    } else if repo.store_requirements.contains(DOUBLE_WRITE_REQUIREMENT) {
        tracing::info!(target: "changelog_info", changelog_backend="doublewrite");
        open_double(repo.store_path())?
    } else if repo.store_requirements.contains(HYBRID_REQUIREMENT) {
        let eden_api = repo.eden_api()?;
        tracing::info!(target: "changelog_info", changelog_backend="hybrid");
        open_hybrid(repo.store_path(), eden_api, false, true)?
    } else if repo.store_requirements.contains(LAZY_TEXT_REQUIREMENT) {
        let eden_api = repo.eden_api()?;
        tracing::info!(target: "changelog_info", changelog_backend="lazytext");
        open_hybrid(repo.store_path(), eden_api, false, false)?
    } else if repo.store_requirements.contains(SEGMENTS_REQUIREMENT) {
        tracing::info!(target: "changelog_info", changelog_backend="segments");
        open_segments(repo.store_path())?
    } else {
        tracing::info!(target: "changelog_info", changelog_backend="rustrevlog");
        Box::new(RevlogCommits::new(repo.store_path())?)
    };
    Ok(commits)
}

fn open_git(
    store_path: &Path,
    metalog: Arc<RwLock<MetaLog>>,
) -> Result<Box<dyn DagCommits + Send + 'static>, CommitError> {
    let git_path =
        calculate_git_path(store_path).map_err(|err| CommitError::FileReadError("gitdir", err))?;
    let segments_path = calculate_segments_path(store_path);
    let git_segmented_commits = GitSegmentedCommits::new(&git_path, &segments_path)?;
    git_segmented_commits.git_references_to_metalog(&mut metalog.write())?;
    Ok(Box::new(git_segmented_commits))
}

fn open_double(store_path: &Path) -> Result<Box<dyn DagCommits + Send + 'static>, CommitError> {
    let segments_path = calculate_segments_path(store_path);
    let hg_commits_path = store_path.join(HG_COMMITS_PATH);
    let double_commits = DoubleWriteCommits::new(
        store_path,
        segments_path.as_path(),
        hg_commits_path.as_path(),
    )?;
    Ok(Box::new(double_commits))
}

fn open_hybrid(
    store_path: &Path,
    eden_api: Arc<dyn EdenApi>,
    lazy_hash: bool,
    use_revlog: bool,
) -> Result<Box<dyn DagCommits + Send + 'static>, CommitError> {
    let segments_path = calculate_segments_path(store_path);
    let hg_commits_path = store_path.join(HG_COMMITS_PATH);
    let lazy_hash_path = get_path_from_file(store_path, LAZY_HASH_PATH);
    let mut hybrid_commits = HybridCommits::new(
        if use_revlog { Some(store_path) } else { None },
        segments_path.as_path(),
        hg_commits_path.as_path(),
        eden_api,
    )?;
    if let Ok(lazy_path) = lazy_hash_path {
        hybrid_commits.enable_lazy_commit_hashes_from_local_segments(lazy_path.as_path())?;
    } else if lazy_hash {
        hybrid_commits.enable_lazy_commit_hashes();
    }
    Ok(Box::new(hybrid_commits))
}

fn calculate_git_path(store_path: &Path) -> Result<PathBuf, std::io::Error> {
    let git_file_contents = get_path_from_file(store_path, GIT_FILE)?;
    let git_path = PathBuf::from(&git_file_contents);
    if !git_path.is_absolute() {
        return Ok(store_path.join(git_path));
    }
    Ok(git_path)
}

fn calculate_segments_path(store_path: &Path) -> PathBuf {
    store_path.join(SEGMENTS_PATH)
}

fn get_path_from_file(store_path: &Path, target_file: &str) -> Result<PathBuf, std::io::Error> {
    let path_file = store_path.join(target_file);
    fs::read_to_string(path_file).map(PathBuf::from)
}

fn open_segments(store_path: &Path) -> Result<Box<dyn DagCommits + Send + 'static>, CommitError> {
    let commits = HgCommits::new(
        &calculate_segments_path(store_path),
        &store_path.join(HG_COMMITS_PATH),
    )?;
    Ok(Box::new(commits))
}


/*
 * 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::collections::HashSet;

use once_cell::sync::Lazy;

pub static REPONAME_FILE: &str = "reponame";
pub static CHANGELOG_FILE: &str = "00changelog.i";
pub static REQUIREMENTS_FILE: &str = "requires";
pub static STORE_PATH: &str = "store";
pub static SUPPORTED_DEFAULT_REQUIREMENTS: Lazy<HashSet<String>> = Lazy::new(|| {
    HashSet::from([
        "eden".to_owned(),
        "revlogv1".to_owned(),
        "generaldelta".to_owned(),
        "store".to_owned(),
        "fncache".to_owned(),
        "shared".to_owned(),
        "relshared".to_owned(),
        "dotencode".to_owned(),
        "treestate".to_owned(),
        "storerequirements".to_owned(),
        "lfs".to_owned(),
        "globalrevs".to_owned(),
        "lz4revlog".to_owned(),
        "globalrevs".to_owned(),
        "windowssymlinks".to_owned(),
        "hgsql".to_owned(),
        "remotefilelog".to_owned(),
    ])
});
pub static SUPPORTED_STORE_REQUIREMENTS: Lazy<HashSet<String>> = Lazy::new(|| {
    HashSet::from([
        "visibleheads".to_owned(),
        "narrowheads".to_owned(),
        "zstorecommitdata".to_owned(),
        "invalidatelinkrev".to_owned(),
        // python revlog
        "pythonrevlogchangelog".to_owned(),
        // rust revlog
        "rustrevlogchangelog".to_owned(),
        // pure segmented changelog (full idmap, full hgommits)
        "segmentedchangelog".to_owned(),
        // segmented changelog (full idmap, partial hgcommits) + revlog
        "doublewritechangelog".to_owned(),
        // hybrid changelog (full idmap, partial hgcommits) + revlog + edenapi
        "hybridchangelog".to_owned(),
        // use git format
        "git".to_owned(),
        // backed by git bare repo
        "git-store".to_owned(),
        // lazy commit message (full idmap, partial hgcommits) + edenapi
        "lazytextchangelog".to_owned(),
        // lazy commit message (sparse idmap, partial hgcommits) + edenapi
        "lazychangelog".to_owned(),
        // commit graph is truncated for emergency use-case. The first commit
        // has wrong parents.
        "emergencychangelog".to_owned(),
        // backed by Rust eagerepo::EagerRepo. Mainly used in tests or
        // fully local repos.
        "eagerepo".to_owned(),
    ])
});


/*
 * 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::path::PathBuf;

use thiserror::Error;
use util::errors::IOError;

#[derive(Debug, Error)]
#[error("repository {0} not found!")]
pub struct RepoNotFound(pub String);

#[derive(Debug, Error)]
#[error("sharedpath points to nonexistent directory {0}!")]
pub struct InvalidSharedPath(pub String);

#[derive(Debug, Error)]
#[error("remotenames key is not initalized in metalog")]
pub struct RemotenamesMetalogKeyError;

#[derive(Debug, Error)]
#[error("cannot initialize working copy: {0:?}")]
pub struct InvalidWorkingCopy(#[from] anyhow::Error);

#[derive(Debug, Error)]
#[error(
    "repository requires unknown features: {0}\n(see https://mercurial-scm.org/wiki/MissingRequirement for more information)"
)]
pub struct UnsupportedRequirements(pub String);

#[derive(Debug, Error)]
pub enum RequirementsOpenError {
    #[error(transparent)]
    IOError(#[from] IOError),

    #[error(transparent)]
    UnsupportedRequirements(#[from] UnsupportedRequirements),
}

#[derive(Error, Debug)]
pub enum InitError {
    #[error("repository `{0}` already exists")]
    ExistingRepoError(PathBuf),

    #[error("unable to create directory at `{0}`: `{1}`")]
    DirectoryCreationError(String, std::io::Error),

    #[error("unable to create file at `{0}`: `{1}`")]
    FileCreationError(PathBuf, std::io::Error),

    #[error("config loading error: `{0}`")]
    ConfigLoadingError(anyhow::Error),

    #[error(transparent)]
    UnsupportedRequirements(#[from] UnsupportedRequirements),
}