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

use crate::Component;
use crate::Dimensions;
use crate::DrawMode;
use crate::Lines;

/// Draw components vertically, one after the other.
pub struct DrawVertical {
    /// Original dimensions.
    dim: Dimensions,
    /// What we have drawn so far.
    lines: Lines,
}

impl DrawVertical {
    /// Construct rendered with given dimensions.
    pub fn new(dimensions: Dimensions) -> DrawVertical {
        DrawVertical {
            dim: dimensions,
            lines: Lines::new(),
        }
    }

    /// Add another component.
    /// New component `draw` is called with remaining dimensions.
    pub fn draw(&mut self, component: &dyn Component, mode: DrawMode) -> anyhow::Result<()> {
        // We call `draw` even if no space is left, but maybe we should not.
        let mut output = component.draw(
            Dimensions {
                width: self.dim.width,
                height: self.dim.height.saturating_sub(self.lines.0.len()),
            },
            mode,
        )?;
        self.lines.0.append(&mut output.0);
        Ok(())
    }

    /// Finish rendering, return the result.
    pub fn finish(mut self) -> Lines {
        // This should be no-op, but just in case.
        self.lines.shrink_lines_to_dimensions(self.dim);
        self.lines
    }
}

#[cfg(test)]
mod tests {
    use crate::components::DrawVertical;
    use crate::Component;
    use crate::Dimensions;
    use crate::DrawMode;
    use crate::Line;
    use crate::Lines;

    #[test]
    fn test_draw_vertical() {
        #[derive(Debug)]
        struct C0;
        #[derive(Debug)]
        struct C1;

        impl Component for C0 {
            fn draw_unchecked(
                &self,

                dimensions: Dimensions,
                _mode: DrawMode,
            ) -> anyhow::Result<Lines> {
                assert_eq!(
                    Dimensions {
                        width: 10,
                        height: 20
                    },
                    dimensions
                );
                Ok(Lines(vec![Line::sanitized("foo"), Line::sanitized("bar")]))
            }
        }

        impl Component for C1 {
            fn draw_unchecked(
                &self,

                dimensions: Dimensions,
                _mode: DrawMode,
            ) -> anyhow::Result<Lines> {
                assert_eq!(
                    Dimensions {
                        width: 10,
                        height: 18
                    },
                    dimensions
                );
                Ok(Lines(vec![
                    Line::sanitized("baz"),
                    Line::sanitized("qux"),
                    Line::sanitized("quux"),
                ]))
            }
        }

        let mut draw = DrawVertical::new(Dimensions {
            width: 10,
            height: 20,
        });
        draw.draw(&C0, DrawMode::Normal).unwrap();
        draw.draw(&C1, DrawMode::Normal).unwrap();
        let output = draw.finish();
        assert_eq!(
            Lines(vec![
                Line::sanitized("foo"),
                Line::sanitized("bar"),
                Line::sanitized("baz"),
                Line::sanitized("qux"),
                Line::sanitized("quux"),
            ]),
            output
        );
    }
}


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

#![cfg(test)]

use crate::components::Dimensions;
use crate::components::DrawMode;
use crate::Component;
use crate::Lines;

/// Component that repeats whatever lines are put into it.
/// Used in tests.
pub(crate) struct Echo(pub(crate) Lines);

impl Component for Echo {
    fn draw_unchecked(&self, _dimensions: Dimensions, _mode: DrawMode) -> anyhow::Result<Lines> {
        Ok(self.0.clone())
    }
}


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

use crate::components::Blank;
use crate::components::Dimensions;
use crate::components::DrawMode;
use crate::Component;
use crate::Lines;

/// The `Padded` [`Component`](Component) wraps its child by padding left, right, above, and below its content.
/// This can be used to shift the content to a different location and ensure that following content comes after a certain distance.
/// It is worth noting that this component will also *truncate* any content that is too long to fit in the given window at draw time.
/// However, components are expected to constrain themselves to the given window, anyway.
///
/// Content is truncated preferentially over padding.
#[derive(Debug)]
pub struct Padded<C: Component = Box<dyn Component>> {
    pub child: C,
    pub left: usize,
    pub right: usize,
    pub top: usize,
    pub bottom: usize,
}

impl Default for Padded {
    fn default() -> Self {
        Self {
            child: Box::new(Blank),
            left: 0,
            right: 0,
            top: 0,
            bottom: 0,
        }
    }
}

impl<C: Component> Padded<C> {
    pub fn new(child: C, left: usize, right: usize, top: usize, bottom: usize) -> Self {
        Self {
            child,
            left,
            right,
            top,
            bottom,
        }
    }
}

impl<C: Component> Component for Padded<C> {
    fn draw_unchecked(&self, dimensions: Dimensions, mode: DrawMode) -> anyhow::Result<Lines> {
        let mut output = self.child.draw(dimensions, mode)?;

        // ordering is important:
        // the top and bottom lines need to be padded horizontally too.
        output.pad_lines_top(self.top);
        // cut off enough space at the bottom for the bottom padding
        output.truncate_lines_bottom(dimensions.height.saturating_sub(self.bottom));
        output.pad_lines_bottom(self.bottom);

        output.pad_lines_left(self.left);
        // cut off enough space on the right for the right padding
        output.truncate_lines(dimensions.width.saturating_sub(self.right));
        output.pad_lines_right(self.right);

        Ok(output)
    }
}

#[cfg(test)]
mod tests {
    use derive_more::AsRef;

    use crate::components::echo::Echo;
    use crate::components::Padded;
    use crate::Component;
    use crate::Dimensions;
    use crate::DrawMode;
    use crate::Line;
    use crate::Lines;

    #[derive(Debug, AsRef)]
    struct Msg(Lines);

    #[test]
    fn test_pad_left() {
        let msg = Lines(vec![
            vec!["hello world"].try_into().unwrap(),
            vec!["ok"].try_into().unwrap(),
            Line::default(),
        ]);
        let padder = Padded {
            child: Echo(msg),
            left: 5,
            right: 0,
            top: 0,
            bottom: 0,
        };

        let drawing = padder
            .draw(Dimensions::new(20, 20), DrawMode::Normal)
            .unwrap();
        let expected = Lines(vec![
            vec![" ".repeat(5).as_ref(), "hello world"]
                .try_into()
                .unwrap(),
            vec![" ".repeat(5).as_ref(), "ok"].try_into().unwrap(),
            vec![" ".repeat(5)].try_into().unwrap(),
        ]);
        assert_eq!(drawing, expected);
    }

    #[test]
    fn test_pad_right() {
        let msg = Lines(vec![
            vec!["hello world"].try_into().unwrap(),
            vec!["ok"].try_into().unwrap(),
            Line::default(),
        ]);
        let padder = Padded {
            child: Echo(msg),
            right: 4,
            left: 0,
            top: 0,
            bottom: 0,
        };

        let drawing = padder
            .draw(Dimensions::new(20, 20), DrawMode::Normal)
            .unwrap();
        let expected = Lines(vec![
            vec!["hello world", &" ".repeat(4)].try_into().unwrap(),
            vec!["ok", &" ".repeat(4 + 9)].try_into().unwrap(),
            vec![" ".repeat(4 + 11)].try_into().unwrap(),
        ]);
        assert_eq!(drawing, expected);
    }

    #[test]
    fn test_pad_top() {
        let msg = Lines(vec![
            vec!["hello world"].try_into().unwrap(),
            vec!["ok"].try_into().unwrap(),
            Line::default(),
        ]);
        let padder = Padded {
            child: Echo(msg),
            top: 5,
            bottom: 0,
            left: 0,
            right: 0,
        };

        let drawing = padder
            .draw(Dimensions::new(15, 15), DrawMode::Normal)
            .unwrap();
        let expected = Lines(vec![
            Line::default(),
            Line::default(),
            Line::default(),
            Line::default(),
            Line::default(),
            vec!["hello world"].try_into().unwrap(),
            vec!["ok"].try_into().unwrap(),
            Line::default(),
        ]);

        assert_eq!(drawing, expected);
    }

    #[test]
    fn test_pad_bottom() {
        let msg = Lines(vec![
            vec!["hello world"].try_into().unwrap(),
            vec!["ok"].try_into().unwrap(),
            Line::default(),
        ]);
        let padder = Padded {
            child: Echo(msg),
            bottom: 5,
            top: 0,
            left: 0,
            right: 0,
        };

        let drawing = padder
            .draw(Dimensions::new(15, 15), DrawMode::Normal)
            .unwrap();
        let expected = Lines(vec![
            vec!["hello world"].try_into().unwrap(),
            vec!["ok"].try_into().unwrap(),
            Line::default(),
            Line::default(),
            Line::default(),
            Line::default(),
            Line::default(),
            Line::default(),
        ]);

        assert_eq!(drawing, expected);
    }

    #[test]
    fn test_no_pad() {
        let msg = Lines(vec![
            vec!["hello world"].try_into().unwrap(),
            vec!["ok"].try_into().unwrap(),
            Line::default(),
        ]);
        let padder = Padded {
            child: Echo(msg),
            top: 0,
            bottom: 0,
            left: 0,
            right: 0,
        };

        let drawing = padder
            .draw(Dimensions::new(15, 15), DrawMode::Normal)
            .unwrap();
        let expected = Lines(vec![
            vec!["hello world"].try_into().unwrap(),
            vec!["ok"].try_into().unwrap(),
            Line::default(),
        ]);

        assert_eq!(drawing, expected);
    }

    #[test]
    fn test_truncated() {
        let msg = Lines(vec![
            vec!["hello world"].try_into().unwrap(),
            vec!["ok"].try_into().unwrap(),
            Line::default(),
        ]);
        let padder = Padded {
            child: Echo(msg),
            left: 5,
            right: 3,
            top: 3,
            bottom: 3,
        };
        let drawing = padder
            .draw(Dimensions::new(10, 8), DrawMode::Normal)
            .unwrap();
        let expected = Lines(vec![
            // 5 rows of padding at the top
            vec![" ".repeat(5), " ".repeat(5)].try_into().unwrap(),
            vec![" ".repeat(5), " ".repeat(5)].try_into().unwrap(),
            vec![" ".repeat(5), " ".repeat(5)].try_into().unwrap(),
            // 2 rows of content, padded on left and right
            vec![" ".repeat(5).as_ref(), "he", &" ".repeat(3)]
                .try_into()
                .unwrap(),
            vec![" ".repeat(5).as_ref(), "ok", &" ".repeat(3)]
                .try_into()
                .unwrap(),
            vec![" ".repeat(5), " ".repeat(5)].try_into().unwrap(),
            vec![" ".repeat(5), " ".repeat(5)].try_into().unwrap(),
            vec![" ".repeat(5), " ".repeat(5)].try_into().unwrap(),
        ]);

        assert_eq!(drawing, expected);
    }
}


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

//! Splitting is one of the most primitive building blocks of a fully featured UI.
//! This module contains components and enums that allow for splits along either dimension.

use std::fmt::Debug;

use crate::Component;
use crate::Dimensions;
use crate::Direction;
use crate::DrawMode;
use crate::Lines;

/// Controls the way the splitter displays its children.
#[derive(Clone, Debug)]
pub enum SplitKind {
    // Each child component takes up area proportional to its size relative to the other sizes.
    Sized(Vec<f64>),
    // Each child component takes up an equal % of the area long the given dimension.
    Equal,
    // Each child may take as much space as it would like along the given dimension.
    // No extra padding is given between children.
    Adaptive,
}

/// Internally, we want to alias away the Equal case
#[derive(Clone, Debug)]
enum InternalSplitKind {
    SizedNormalized(Vec<f64>),
    Adaptive,
}

impl SplitKind {
    fn to_internal_split_kind(&self, children_len: usize) -> InternalSplitKind {
        match self {
            SplitKind::Sized(sizes) => {
                assert_eq!(
                    sizes.len(),
                    children_len,
                    "There must be an equal number of ratios and children."
                );

                let total: f64 = sizes.iter().sum();
                let normalized_sizes = sizes.iter().map(|size| size / total).collect();

                InternalSplitKind::SizedNormalized(normalized_sizes)
            }
            SplitKind::Equal => {
                InternalSplitKind::SizedNormalized(vec![1.0 / children_len as f64; children_len])
            }
            SplitKind::Adaptive => InternalSplitKind::Adaptive,
        }
    }
}

impl InternalSplitKind {
    pub fn draw<'a, C: Component + 'a>(
        &self,
        children: impl IntoIterator<Item = &'a C>,
        direction: Direction,

        dimensions: Dimensions,
        mode: DrawMode,
    ) -> anyhow::Result<Vec<Lines>> {
        match self {
            InternalSplitKind::SizedNormalized(sizes) => children
                .into_iter()
                .zip(sizes.iter())
                .map(|(child, size)| -> anyhow::Result<_> {
                    // allocate alloted size
                    let child_dimension = dimensions.multiply(*size, direction);
                    let mut output = child.draw(child_dimension, mode)?;

                    // bound non-splitting direction, pad splitting direction
                    match direction {
                        Direction::Horizontal => {
                            output.truncate_lines_bottom(child_dimension.height);
                            output.set_lines_to_exact_width(child_dimension.width);
                        }
                        Direction::Vertical => {
                            output.truncate_lines(child_dimension.width);
                            output.set_lines_to_exact_length(child_dimension.height);
                        }
                    }

                    Ok(output)
                })
                .collect(),
            InternalSplitKind::Adaptive => {
                let mut available = dimensions;
                children
                    .into_iter()
                    .map(|child| {
                        let mut output = child.draw(available, mode)?;
                        output.shrink_lines_to_dimensions(dimensions);

                        // decrease size by however much was just used
                        let used = Dimensions::dimension_from_output_truncated(&output, direction);
                        available = available.saturating_sub(used, direction);

                        Ok(output)
                    })
                    .collect()
            }
        }
    }
}

/// [`Splits`](SplitKind) along a given [`direction`](crate::Direction) for its child [`components`](Component).
/// Child components are truncated to the bounds passed to them.
pub struct Split<C = Box<dyn Component>> {
    children: Vec<C>,
    direction: Direction,
    split: InternalSplitKind,
}

impl<C> Debug for Split<C> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Split")
            .field("children", &self.children.len())
            .field("direction", &self.direction)
            .field("split", &self.split)
            .finish()
    }
}

impl<C: Component> Split<C> {
    /// Preconditions:
    /// * At least one child.
    /// * If Sized, then ratios must sum to approximately 1.
    /// * If Sized, then there must be as many ratios as components
    pub fn new(children: Vec<C>, direction: Direction, split: SplitKind) -> Self {
        let split = split.to_internal_split_kind(children.len());

        Self {
            children,
            direction,
            split,
        }
    }
}

impl<C: Component> Component for Split<C> {
    fn draw_unchecked(&self, dimensions: Dimensions, mode: DrawMode) -> anyhow::Result<Lines> {
        let outputs = self
            .split
            .draw(&self.children, self.direction, dimensions, mode)?;

        Ok(match self.direction {
            Direction::Horizontal => Lines::join_horizontally(outputs),
            Direction::Vertical => outputs.into_iter().flatten().collect(),
        })
    }
}

#[cfg(test)]
mod tests {
    use std::iter;

    use derive_more::AsRef;

    use super::Split;
    use super::SplitKind;
    use crate::Component;
    use crate::Direction;
    use crate::DrawMode;
    use crate::Line;
    use crate::Lines;

    #[derive(AsRef, Debug)]
    struct Echo1(Lines);
    #[derive(AsRef, Debug)]
    struct Echo2(Lines);

    #[derive(AsRef, Debug)]
    struct Echo3(Lines);

    mod horizontal {
        use super::*;
        use crate::components::echo::Echo;
        use crate::Dimensions;
        #[test]
        fn test_adaptive() {
            let kind = SplitKind::Adaptive;
            let dimension = Direction::Horizontal;

            // left rows all the same length
            let mut left_msg = Lines(vec![
                vec!["test"].try_into().unwrap(),
                vec!["ok", "so"].try_into().unwrap(),
                vec!["sync"].try_into().unwrap(),
            ]);
            let right_msg = Lines(vec![
                vec!["xx"].try_into().unwrap(),
                vec!["yyy"].try_into().unwrap(),
                vec!["z"].try_into().unwrap(),
            ]);
            let splitter = Split::new(
                vec![Echo(left_msg.clone()), Echo(right_msg.clone())],
                dimension,
                kind.clone(),
            );
            let horizontal = Lines(vec![
                vec!["test", "xx", " "].try_into().unwrap(),
                vec!["ok", "so", "yyy"].try_into().unwrap(),
                vec!["sync", "z", "  "].try_into().unwrap(),
            ]);

            let output = splitter
                .draw(Dimensions::new(10, 10), DrawMode::Normal)
                .unwrap();
            assert_eq!(output, horizontal);

            // uneven left row
            left_msg.0[0] = vec!["hello world"].try_into().unwrap();
            let horizontal = Lines(vec![
                vec!["hello world", "xx", " "].try_into().unwrap(),
                vec!["ok", "so", &" ".repeat(7), "yyy"].try_into().unwrap(),
                vec!["sync", &" ".repeat(7), "z", "  "].try_into().unwrap(),
            ]);

            let splitter = Split::new(vec![Echo(left_msg), Echo(right_msg)], dimension, kind);

            let output = splitter
                .draw(Dimensions::new(15, 15), DrawMode::Normal)
                .unwrap();
            assert_eq!(output, horizontal);
        }

        #[test]
        fn test_equal() {
            let kind = SplitKind::Equal;
            let dimension = Direction::Horizontal;

            // left rows all the same length
            let mut left_msg = Lines(vec![
                vec!["test"].try_into().unwrap(),
                vec!["ok", "so"].try_into().unwrap(),
                vec!["sync"].try_into().unwrap(),
            ]);
            let right_msg = Lines(vec![
                vec!["xx"].try_into().unwrap(),
                vec!["yyy"].try_into().unwrap(),
                vec!["z"].try_into().unwrap(),
            ]);
            let splitter = Split::new(
                vec![Echo(left_msg.clone()), Echo(right_msg.clone())],
                dimension,
                kind.clone(),
            );
            let horizontal = Lines(vec![
                vec!["test", &" ".repeat(6), "xx", &" ".repeat(8)]
                    .try_into()
                    .unwrap(),
                vec!["ok", "so", &" ".repeat(6), "yyy", &" ".repeat(7)]
                    .try_into()
                    .unwrap(),
                vec!["sync", &" ".repeat(6), "z", &" ".repeat(9)]
                    .try_into()
                    .unwrap(),
            ]);

            let output = splitter
                .draw(Dimensions::new(20, 20), DrawMode::Normal)
                .unwrap();
            assert_eq!(output, horizontal);

            // uneven left row
            left_msg.0[0] = vec!["hello worl"].try_into().unwrap();
            let horizontal = Lines(vec![
                vec!["hello worl", "xx", &" ".repeat(8)].try_into().unwrap(),
                vec!["ok", "so", &" ".repeat(6), "yyy", &" ".repeat(7)]
                    .try_into()
                    .unwrap(),
                vec!["sync", &" ".repeat(6), "z", &" ".repeat(9)]
                    .try_into()
                    .unwrap(),
            ]);

            let splitter = Split::new(vec![Echo(left_msg), Echo(right_msg)], dimension, kind);

            let output = splitter
                .draw(Dimensions::new(20, 20), DrawMode::Normal)
                .unwrap();
            assert_eq!(output, horizontal);
        }

        #[test]
        fn test_many_sized() {
            let msg1 = Lines(vec![
                vec!["test", "ok"].try_into().unwrap(),
                vec!["also"].try_into().unwrap(),
            ]);
            let msg2 = Lines(vec![vec!["hola"].try_into().unwrap()]);
            let msg3 = Lines(vec![vec!["way way way way too long"].try_into().unwrap()]);
            let splitter = Split::new(
                vec![Echo(msg1), Echo(msg2), Echo(msg3)],
                Direction::Horizontal,
                SplitKind::Sized(vec![0.25, 0.5, 0.25]),
            );

            let output = splitter
                .draw(Dimensions::new(20, 20), DrawMode::Normal)
                .unwrap();

            let expected = Lines(vec![
                vec!["test", "o", "hola", &" ".repeat(6), "way w"]
                    .try_into()
                    .unwrap(),
                vec!["also", " ", &" ".repeat(10), &" ".repeat(5)]
                    .try_into()
                    .unwrap(),
            ]);

            assert_eq!(output, expected);
        }
    }

    mod vertical {
        use super::*;
        use crate::components::echo::Echo;
        use crate::Dimensions;

        #[test]
        fn test_equal() {
            let kind = SplitKind::Equal;
            let dimension = Direction::Vertical;

            let top = Lines(vec![
                vec!["Line 1"].try_into().unwrap(),
                vec!["Line 2222"].try_into().unwrap(),
            ]);
            let mut bottom = Lines(vec![
                vec!["Line 11"].try_into().unwrap(),
                vec!["Line 12"].try_into().unwrap(),
                vec!["Last line just kiddi"].try_into().unwrap(),
            ]);
            let splitter = Split::new(
                vec![Echo(top.clone()), Echo(bottom.clone())],
                dimension,
                kind,
            );

            let mut output = top;
            output.0.extend(iter::repeat(Line::default()).take(8));
            output.0.append(&mut bottom.0);
            output.0.extend(iter::repeat(Line::default()).take(7));

            let drawn = splitter
                .draw(Dimensions::new(20, 20), DrawMode::Normal)
                .unwrap();

            assert_eq!(drawn, output);
        }

        #[test]
        fn test_adaptive() {
            let kind = SplitKind::Adaptive;
            let dimension = Direction::Vertical;

            let top = Lines(vec![
                vec!["Line 1"].try_into().unwrap(),
                vec!["Line 2222"].try_into().unwrap(),
            ]);
            let mut bottom = Lines(vec![
                vec!["Line 11"].try_into().unwrap(),
                vec!["Line 12"].try_into().unwrap(),
                vec!["Last line just kiddi"].try_into().unwrap(),
            ]);
            let splitter = Split::new(
                vec![Echo(top.clone()), Echo(bottom.clone())],
                dimension,
                kind,
            );

            let mut output = top;
            output.0.append(&mut bottom.0);

            let drawn = splitter
                .draw(Dimensions::new(20, 20), DrawMode::Normal)
                .unwrap();
            assert_eq!(drawn, output);
        }

        #[test]
        fn test_many_sized() {
            let msg1 = Lines(vec![
                vec!["line1"].try_into().unwrap(),
                vec!["line2"].try_into().unwrap(),
                vec!["line3"].try_into().unwrap(),
                vec!["line4"].try_into().unwrap(),
                vec!["line5"].try_into().unwrap(),
                vec!["line6"].try_into().unwrap(),
            ]);
            let msg2 = Lines(vec![vec!["line7"].try_into().unwrap()]);
            let msg3 = Lines(vec![vec!["line8"].try_into().unwrap()]);
            let splitter = Split::new(
                vec![Echo(msg1), Echo(msg2), Echo(msg3)],
                Direction::Vertical,
                SplitKind::Sized(vec![0.25, 0.5, 0.25]),
            );

            let output = splitter
                .draw(Dimensions::new(20, 20), DrawMode::Normal)
                .unwrap();

            let expected = Lines(vec![
                vec!["line1"].try_into().unwrap(),
                vec!["line2"].try_into().unwrap(),
                vec!["line3"].try_into().unwrap(),
                vec!["line4"].try_into().unwrap(),
                vec!["line5"].try_into().unwrap(),
                vec!["line7"].try_into().unwrap(),
                Line::default(),
                Line::default(),
                Line::default(),
                Line::default(),
                Line::default(),
                Line::default(),
                Line::default(),
                Line::default(),
                Line::default(),
                vec!["line8"].try_into().unwrap(),
                Line::default(),
                Line::default(),
                Line::default(),
                Line::default(),
            ]);

            assert_eq!(output, expected);
        }
    }

    mod panics {
        use super::*;
        use crate::components::Blank;
        use crate::Dimensions;

        #[test]
        fn test_no_children() {
            let lines = Split::<Blank>::new(vec![], Direction::Horizontal, SplitKind::Equal)
                .draw(Dimensions::new(20, 20), DrawMode::Normal)
                .unwrap();
            assert!(lines.is_empty());
        }

        #[test]
        #[should_panic(expected = "There must be an equal number of ratios and children.")]
        fn test_different_ratio_count() {
            Split::new(
                vec![Box::new(Blank), Box::new(Blank), Box::new(Blank)],
                Direction::Vertical,
                SplitKind::Sized(vec![0.4, 0.4]),
            );
        }
    }
}


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

use std::cmp::Ordering;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write as _;
use std::mem;
use std::slice;
use std::vec;

use crossterm::cursor::MoveToColumn;
use crossterm::terminal::Clear;
use crossterm::terminal::ClearType;
use crossterm::Command;
use unicode_segmentation::UnicodeSegmentation;

use crate::vec_as_fmt_write::VecAsFmtWrite;
use crate::Span;

/// A `Line` is an abstraction for a collection of stylized or unstylized strings.
/// Since each `Span` denotes a portion of a single line, an ordered collection represents a single line of text.
#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct Line(
    /// Sequence is normalized.
    /// * All spans are non-empty.
    /// * Adjacent spans have different styles.
    Vec,
);

impl Line {
    pub fn unstyled(text: &str) -> anyhow::Result<Line> {
        Ok(Line::from_iter([Span::new_unstyled(text)?]))
    }

    pub fn sanitized(text: &str) -> Line {
        Line::from_iter([Span::sanitized(text)])
    }

    /// Return the length of the all words in the line added together.
    pub fn len(&self) -> usize {
        self.0.iter().map(Span::len).sum()
    }

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

    /// Adds padding to the right side of the line.
    /// This adds a new unstyled word consisting entirely of the appropriate number of spaces.
    /// If no padding is requested, then no word is added.
    pub fn pad_right(&mut self, amount: usize) {
        self.push(Span::padding(amount));
    }

    /// Same behavior as pad right, but the pad is on the left
    pub fn pad_left(&mut self, amount: usize) {
        self.push_front(Span::padding(amount));
    }

    /// Truncates the right side of the line until it is no longer than `max_width`.
    /// This will delete words entirely if they cannot fit.
    /// If the line is padded to 0, then it will become an empty line.
    pub fn truncate_line(&mut self, max_width: usize) {
        let mut cur_width = 0;

        for (index, span) in self.0.iter_mut().enumerate() {
            if cur_width >= max_width {
                self.0.truncate(index);
                break;
            }
            let word = span.content.graphemes(true);
            let word_len = word.clone().count();
            // if the line is going to overflow
            if word_len + cur_width > max_width {
                let word = word
                    // cut off the extra graphemes
                    .take(max_width.saturating_sub(cur_width))
                    .collect();

                // overwrite the current word
                // unfortunately, there is no way to mutably update the word, seemingly.
                span.content = word;

                // drop the remaining words
                self.0.truncate(index + 1);

                break;
            }
            cur_width += word_len;
        }
    }

    /// Slices out some middle subline of the Line. Removes the first `start` characters and
    /// keeps `width` characters after that.
    pub fn trim_ends(&mut self, mut start: usize, mut width: usize) {
        let mut owned = Vec::new();
        std::mem::swap(&mut owned, &mut self.0);

        for mut span in owned.into_iter() {
            // TODO(cjhopman): Other code here uses a mix of graphemes count and span.len() for computing the length of
            // a span, but these are totally different approaches. span.len() supposedly uses a more accurate approach,
            // so we should consider switching to that throughout. But, to keep this code self-consistent it uses only
            // the graphemes approach.
            let chars = span.content.graphemes(true);
            let len = chars.clone().count();

            if start > 0 && len < start {
                start -= len;
                continue;
            }

            let end = std::cmp::min(len, start + width);

            if start != 0 || end != len {
                span.content = chars.skip(start).take(end - start).collect();
            }
            self.push(span);

            width -= end - start;
            start = 0;

            if width == 0 {
                break;
            }
        }
    }

    /// Either calls [`pad_right`](Line::pad_right) or [`truncate_line`](Line::truncate_line) until the line is the exact width specified.
    /// This call acts on the right side of the `Line`.
    pub fn to_exact_width(&mut self, exact_width: usize) {
        let len = self.len();
        match len.cmp(&exact_width) {
            Ordering::Less => {
                self.pad_right(exact_width - len);
            }
            Ordering::Equal => {}
            Ordering::Greater => {
                self.truncate_line(exact_width);
            }
        }
    }

    /// Renders the formatted content of the line to `stdout`.
    /// The buffer must be flushed to produce output.
    pub(crate) fn render_with_clear_and_nl(&self, writer: &mut Vec<u8>) -> anyhow::Result<()> {
        let mut writer = VecAsFmtWrite(writer);

        for word in &self.0 {
            word.render(&mut writer)?;
        }

        Clear(ClearType::UntilNewLine).write_ansi(&mut writer)?;
        writeln!(writer)?;
        MoveToColumn(0).write_ansi(&mut writer)?;

        Ok(())
    }

    /// Render the line as a string with ANSI escape codes.
    ///
    /// Without trailing newline or an escape sequence to clear the line.
    pub fn render(&self) -> String {
        let mut result = String::new();
        for word in &self.0 {
            word.render(&mut result)
                .expect("writing to a string should not fail");
        }
        result
    }

    /// Iterate over the spans in the line.
    pub fn iter(&self) -> slice::Iter {
        self.0.iter()
    }

    /// Concatenate spans, discarding any styling.
    pub fn to_unstyled(&self) -> String {
        self.0.iter().map(|span| span.content.as_ref()).collect()
    }

    /// Append a span to the line.
    pub fn push(&mut self, span: Span) {
        if span.is_empty() {
            return;
        }
        if let Some(last) = self.0.last_mut() {
            if last.style == span.style {
                last.content.to_mut().push_str(&span.content);
                return;
            }
        }
        self.0.push(span);
    }

    /// Prepend a span to the line.
    pub fn push_front(&mut self, span: Span) {
        let this = mem::take(self);
        self.push(span);
        self.extend(this);
    }

    pub fn fmt_for_test(&self) -> impl Display + '_ {
        struct Impl<'a>(&'a Line);
        impl<'a> Display for Impl<'a> {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                for span in &self.0.0 {
                    write!(f, "{}", span.fmt_for_test())?;
                }
                Ok(())
            }
        }
        Impl(self)
    }
}

/// Iterate spans in a line.
impl IntoIterator for Line {
    type Item = Span;
    type IntoIter = vec::IntoIter<Self::Item>;

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

impl FromIterator for Line {
    fn from_iter<T: IntoIterator<Item = Span>>(iter: T) -> Self {
        let mut line = Line::default();
        line.extend(iter);
        line
    }
}

impl TryFrom<Vec<String>> for Line {
    type Error = anyhow::Error;

    fn try_from(other: Vec<String>) -> Result<Self, Self::Error> {
        other
            .into_iter()
            .map(Span::new_unstyled)
            .collect::<anyhow::Result<Line>>()
    }
}

impl TryFrom<Vec<&str>> for Line {
    type Error = anyhow::Error;

    fn try_from(other: Vec<&str>) -> Result<Self, Self::Error> {
        other
            .into_iter()
            .map(Span::new_unstyled)
            .collect::<anyhow::Result<Line>>()
    }
}

impl Extend for Line {
    fn extend<T: IntoIterator<Item = Span>>(&mut self, iter: T) {
        for span in iter {
            self.push(span);
        }
    }
}

#[cfg(test)]
mod tests {
    use crossterm::style::Color;

    use super::*;

    #[test]
    fn test_words_len() {
        let normal = Line::from_iter([
            Span::new_colored("test", Color::Black).unwrap(),
            Span::new_colored("hello", Color::Blue).unwrap(),
            Span::new_colored("world", Color::Black).unwrap(),
        ]);

        assert_eq!(normal.len(), 14);

        assert_eq!(Line::default().len(), 0);
    }

    #[test]
    fn test_pad_line_right() {
        let mut test = Line::from_iter([
            Span::new_colored("test", Color::DarkBlue).unwrap(),
            Span::new_colored("ok", Color::DarkCyan).unwrap(),
        ]);
        let mut new_test: Line = test.clone();
        test.push(" ".repeat(4).try_into().unwrap());
        new_test.pad_right(4);
        assert_eq!(test, new_test);

        new_test.pad_right(6);
        test.push(" ".repeat(6).try_into().unwrap());
        assert_eq!(test, new_test);

        new_test.pad_right(10);
        test.push(" ".repeat(10).try_into().unwrap());
        assert_eq!(test, new_test);
    }

    #[test]
    fn test_pad_line_left() -> anyhow::Result<()> {
        let mut test: Line = Line::from_iter([
            Span::new_colored("test", Color::DarkCyan).unwrap(),
            Span::new_colored("ok", Color::Cyan).unwrap(),
        ]);
        let mut new_test: Line = test.clone();
        test.push_front(" ".repeat(4).try_into()?);
        new_test.pad_left(4);
        assert_eq!(test, new_test);

        new_test.pad_left(6);
        test.push_front(" ".repeat(6).try_into()?);
        assert_eq!(test, new_test);

        new_test.pad_left(10);
        test.push_front(" ".repeat(10).try_into()?);
        assert_eq!(test, new_test);

        Ok(())
    }

    #[test]
    fn test_truncate_line() -> anyhow::Result<()> {
        let mut test: Line = Line::from_iter([
            Span::new_colored("test", Color::Blue).unwrap(),
            Span::new_colored("ok", Color::Red).unwrap(),
        ]);
        let mut new_test: Line = test.clone();
        test.truncate_line(10);
        assert_eq!(test, new_test);

        new_test.truncate_line(6);
        assert_eq!(test, new_test);

        new_test.truncate_line(5);
        test.0.pop().unwrap();
        test.push(Span::new_colored("o", Color::Red).unwrap());
        assert_eq!(test, new_test);

        new_test.truncate_line(4);
        test.0.pop().unwrap();
        assert_eq!(test, new_test);

        new_test.truncate_line(0);
        assert_eq!(new_test, Line::default());

        Ok(())
    }

    #[test]
    fn test_trim_ends() -> anyhow::Result<()> {
        let line = |spans: &[&str]| -> anyhow::Result<Line> { spans.to_vec().try_into() };
        let mut test = line(&["hello", "cat", "world"])?;
        test.trim_ends(0, 15);
        assert_eq!(test, line(&["hello", "cat", "world"])?);

        test.trim_ends(0, 13);
        assert_eq!(test, line(&["hello", "cat", "world"])?);

        let mut test = line(&["hello", "cat", "world"])?;
        test.trim_ends(2, 10);
        assert_eq!(test, line(&["llo", "cat", "worl"])?);

        let mut test = line(&["hello", "cat", "world"])?;
        test.trim_ends(6, 2);
        assert_eq!(test, line(&["at"])?);

        let mut test = line(&["hello", "cat", "world"])?;
        test.trim_ends(9, 2);
        assert_eq!(test, line(&["or"])?);

        Ok(())
    }

    #[test]
    fn test_push_collapses() {
        let mut line = Line::default();
        line.push(Span::new_colored("ab", Color::Cyan).unwrap());
        line.push(Span::new_colored("c", Color::Cyan).unwrap());
        line.push(Span::new_colored("d", Color::Red).unwrap());

        let expected = Line::from_iter([
            Span::new_colored("abc", Color::Cyan).unwrap(),
            Span::new_colored("d", Color::Red).unwrap(),
        ]);

        assert_eq!(expected, line);
    }

    #[test]
    fn test_push_front() {
        let mut line = Line::default();
        line.push_front(Span::new_colored("d", Color::Cyan).unwrap());
        line.push_front(Span::new_colored("c", Color::Cyan).unwrap());
        line.push_front(Span::new_colored("ab", Color::Red).unwrap());

        let expected = Line::from_iter([
            Span::new_colored("ab", Color::Red).unwrap(),
            Span::new_colored("cd", Color::Cyan).unwrap(),
        ]);

        assert_eq!(expected, line);
    }

    #[test]
    fn test_fmt_for_test() {
        let line = Line::from_iter([
            Span::new_colored("abra", Color::Blue).unwrap(),
            Span::new_colored("cadabra", Color::Red).unwrap(),
        ]);

        assert_eq!(
            "abracadabra",
            format!("{}", line.fmt_for_test())
        );
    }
}


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

use std::cmp;
use std::cmp::Ordering;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::iter;
use std::mem;

use crossterm::style::Attribute;
use crossterm::style::Attributes;
use crossterm::style::Color;
use itertools::Itertools;
use termwiz::cell::Intensity;
use termwiz::color::ColorSpec;
use termwiz::color::RgbColor;
use termwiz::escape::csi::Sgr;
use termwiz::escape::csi::CSI;
use termwiz::escape::Action;

use crate::style::ContentStyle;
use crate::style::StyledContent;
use crate::Dimensions;
use crate::Line;
use crate::Span;

#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct Lines(pub Vec<Line>);

/// State container that's used to parse strings with ANSI color codes in them.
#[derive(Default)]
struct ColoredStringParser {
    foreground_color: Option<Color>,
    background_color: Option<Color>,
    attributes: Attributes,
    line_buffer: String,
    spans: Vec<Span>,
}

impl ColoredStringParser {
    fn push_current(&mut self) {
        let sc = StyledContent::new(
            ContentStyle {
                foreground_color: self.foreground_color,
                background_color: self.background_color,
                attributes: self.attributes,
            },
            std::mem::take(&mut self.line_buffer),
        );
        self.spans.push(Span::new_styled_lossy(sc));
    }

    fn spec_to_color(spec: ColorSpec) -> Option<Color> {
        match spec {
            ColorSpec::Default => None,
            ColorSpec::PaletteIndex(idx) => Some(Color::AnsiValue(idx)),
            ColorSpec::TrueColor(srgba) => Some(RgbColor::from(srgba).to_tuple_rgb8().into()),
        }
    }

    /// Given a line w/ some ANSI encoded color characters, turn it into a list of spans.
    fn parse_line(&mut self, parser: &mut termwiz::escape::parser::Parser, s: &str) -> Line {
        // Because we only stick "printable" characters onto the buffer, and skip any other
        // class of characters that we don't recognize, this should be a
        // roughly sanitized string. We do one more pass when we create the Span, just to
        // make sure, but that is a pretty fast check.
        parser.parse(s.as_bytes(), |a| match a {
            Action::Print(c) => {
                self.line_buffer.push(c);
            }
            Action::CSI(CSI::Sgr(Sgr::Reset)) => {
                self.push_current();
                self.foreground_color = None;
                self.background_color = None;
                self.attributes = Attributes::default();
            }
            Action::CSI(CSI::Sgr(Sgr::Intensity(intensity))) => {
                self.push_current();
                self.attributes = match intensity {
                    Intensity::Normal => Attributes::default(),
                    Intensity::Bold => Attributes::from(Attribute::Bold),
                    Intensity::Half => Attributes::from(Attribute::Dim),
                };
            }
            Action::CSI(CSI::Sgr(Sgr::Foreground(spec))) => {
                self.push_current();
                self.foreground_color = Self::spec_to_color(spec);
            }

            Action::CSI(CSI::Sgr(Sgr::Background(spec))) => {
                self.push_current();
                self.background_color = Self::spec_to_color(spec);
            }
            _ => {}
        });
        self.push_current();
        Line::from_iter(mem::take(&mut self.spans))
    }
}

/// Set of helper methods for `Vec<Line>`, that manipulate on each line individually.
impl Lines {
    /// Empty lines block.
    pub fn new() -> Lines {
        Lines(Vec::new())
    }

    /// Creates an instance of [`Lines`] with a style applied from a single multiline string.
    pub fn from_multiline_string(multiline_string: &str, style: ContentStyle) -> Lines {
        multiline_string
            .lines()
            .map(|line| {
                let styled = StyledContent::new(style, line.to_owned());
                Line::from_iter([Span::new_styled_lossy(styled)])
            })
            .collect()
    }

    /// Takes a multiline string that might contain ANSI color codes, and returns a set of lines
    /// that include spans representing those color codes.
    ///
    /// Note that any other types of control characters are omitted, and certain whitespace
    /// characters are also disallowed / replaced.
    pub fn from_colored_multiline_string(multiline_string: &str) -> Lines {
        let mut parser = termwiz::escape::parser::Parser::new();
        let mut color_parser = ColoredStringParser::default();
        multiline_string
            .lines()
            .map(|s| color_parser.parse_line(&mut parser, s))
            .collect()
    }

    /// Number of lines.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// No lines.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub fn push(&mut self, line: Line) {
        self.0.push(line);
    }

    pub fn iter(&self) -> impl ExactSizeIterator<Item = &Line> {
        self.0.iter()
    }

    pub fn iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut Line> {
        self.0.iter_mut()
    }

    /// truncates all lines to the same max width.
    pub fn truncate_lines(&mut self, max_width: usize) {
        self.iter_mut()
            .for_each(|line| line.truncate_line(max_width));
    }

    /// Returns the max column width of any line
    pub fn max_line_length(&self) -> usize {
        // for each line in the LHS
        self.iter()
            // add the length of each word in the line
            .map(|line| line.len())
            // pick the maximum line length
            .max()
            // or set it to 0 if there are no lines on the left side
            .unwrap_or_default()
    }

    /// Given a set of lines, inserts the specified amount of padding to the longest line.
    /// However, the entire block of lines will remain a rectangle afterwards.
    /// This means that shorter lines will receive extra paddding such that it has the same length as the longest line
    pub fn pad_lines_right(&mut self, amount: usize) {
        if amount == 0 {
            return;
        }

        let longest_len = self.max_line_length();
        for line in self.iter_mut() {
            let len = line.len();
            line.pad_right(amount + (longest_len - len));
        }
    }

    /// Prepends a fixed `amount` of padding to each row.
    /// Unlike `pad_lines_right`, this method does not add extra padding to shorter lines.
    /// The right end of the padding remains ragged.
    pub fn pad_lines_left(&mut self, amount: usize) {
        if amount == 0 {
            return;
        }

        self.iter_mut().for_each(|line| {
            line.pad_left(amount);
        });
    }

    /// Adds padding to each line dependent on its length so that each line is the same length.
    /// The line is left justified always.  This is because right or center aligning lines inherently justifies them.
    /// Therefore, there is no explicit method for justifying text any other way.
    pub fn justify(&mut self) {
        let longest_len = self.max_line_length();
        for line in self.iter_mut() {
            let len = line.len();
            line.pad_right(longest_len - len);
        }
    }

    /// Given a set of lines, set them all to the exact width
    pub fn set_lines_to_exact_width(&mut self, exact_width: usize) {
        self.iter_mut()
            .for_each(|line| line.to_exact_width(exact_width));
    }

    /// Extends the Lines list by the given length, adding empty lines at the bottom
    pub fn pad_lines_bottom(&mut self, amount: usize) {
        let mut extender = iter::repeat(Line::default()).take(amount);
        self.0.extend(&mut extender);
    }

    /// Same functionality as `pad_lines_bottom` but on the top.
    pub fn pad_lines_top(&mut self, amount: usize) {
        let extender = iter::repeat(Line::default()).take(amount);

        self.0.splice(0..0, extender);
    }

    /// Truncates the line list to the given length, removing entries at the end.
    pub fn truncate_lines_bottom(&mut self, desired_length: usize) {
        self.0.truncate(desired_length);
    }

    /// Sets the line list to the given length, padding or truncating from the bottom.
    pub fn set_lines_to_exact_length(&mut self, desired_length: usize) {
        match self.len().cmp(&desired_length) {
            Ordering::Less => {
                self.pad_lines_bottom(desired_length - self.len());
            }
            Ordering::Equal => {}
            Ordering::Greater => {
                self.truncate_lines_bottom(desired_length);
            }
        }
    }

    /// Truncates columns and rows that do not fit within a bounding box
    pub fn shrink_lines_to_dimensions(&mut self, dimensions: Dimensions) {
        self.iter_mut()
            .for_each(|line| line.truncate_line(dimensions.width));
        self.truncate_lines_bottom(dimensions.height);
    }

    /// Formats and renders all lines to `stdout`.
    /// Notably, this *queues* the lines for rendering.  You must flush the buffer.
    /// If a limit is specified, no more than that amount will be drained.
    /// The limit is on the number of *lines*, **NOT** the number of *bytes*.
    /// Care should be taken with calling a limit of 0 - this will cause no lines to render and the buffer to never be drained.
    pub(crate) fn render(
        &mut self,
        writer: &mut Vec<u8>,
        limit: Option<usize>,
    ) -> anyhow::Result<()> {
        let limit = limit.unwrap_or(self.len());
        let amt = cmp::min(limit, self.len());
        for line in self.0.drain(..amt) {
            line.render_with_clear_and_nl(writer)?;
        }

        Ok(())
    }

    /// Returns the maximum line width and the number of lines.
    /// This corresponds to how much space a justified version of the output would take.
    pub fn dimensions(&self) -> anyhow::Result<Dimensions> {
        let x = self.max_line_length();
        let y = self.len();

        Ok((x, y).into())
    }

    /// Sets the lines to the exact dimensions specified below, truncating or padding as necessary.
    pub fn set_lines_to_exact_dimensions(&mut self, Dimensions { width, height }: Dimensions) {
        self.set_lines_to_exact_length(height);
        self.set_lines_to_exact_width(width);
    }

    /// Join blocks horizontally, i.e. side by side.
    pub fn join_horizontally(blocks: Vec<Lines>) -> Lines {
        if blocks.is_empty() {
            return Lines::new();
        }

        // unwrap ok because guaranteed > 0 children from constructor
        let longest = blocks.iter().map(|output| output.len()).max().unwrap();

        // pad all other outputs to be the same length.  Then, justify them to be uniform blocks.
        let padded = blocks.into_iter().update(|output| {
            output.set_lines_to_exact_length(longest);
            output.justify();
        });

        // can't do arbitrary zip, so this'll have to do
        padded
            .reduce(|mut all, output| {
                for (all_line, output_line) in all.iter_mut().zip(output.into_iter()) {
                    all_line.extend(output_line);
                }

                all
            })
            // safe to unwrap because at least one child component required
            .unwrap()
    }

    pub fn fmt_for_test(&self) -> impl Display + '_ {
        struct Impl<'a>(&'a Lines);
        impl<'a> Display for Impl<'a> {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                for line in self.0.iter() {
                    writeln!(f, "{}", line.fmt_for_test())?;
                }
                Ok(())
            }
        }
        Impl(self)
    }
}

impl FromIterator<Line> for Lines {
    fn from_iter<I: IntoIterator<Item = Line>>(iter: I) -> Self {
        Self(iter.into_iter().collect())
    }
}

impl IntoIterator for Lines {
    type Item = Line;
    type IntoIter = <Vec<Line> as IntoIterator>::IntoIter;

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

#[cfg(test)]
mod tests {
    use crossterm::style::Attribute;
    use crossterm::style::Color;

    use super::*;

    #[test]
    fn truncate_lines() -> anyhow::Result<()> {
        let mut test: Lines = Lines(vec![
            vec!["test", "line"].try_into()?,
            vec!["another one"].try_into()?,
        ]);

        let mut new_test = test.clone();
        new_test.truncate_lines(5);
        test.0[0] = vec!["test", "l"].try_into()?;
        test.0[1] = vec!["anoth"].try_into()?;
        assert_eq!(test, new_test);

        let mut empty = Lines::new();
        empty.truncate_lines(5);
        assert_eq!(empty, Lines::new());

        Ok(())
    }

    #[test]
    fn test_max_line_length() -> anyhow::Result<()> {
        let test = Lines(vec![
            Line::default(),
            vec!["test", "line"].try_into()?,
            vec!["another one"].try_into()?,
        ]);

        assert_eq!(test.max_line_length(), 11);
        assert_eq!(Lines::new().max_line_length(), 0);

        Ok(())
    }

    #[test]
    fn test_pad_lines_right() -> anyhow::Result<()> {
        let mut test = Lines(vec![
            vec!["test", "line"].try_into()?, // 8 chars
            vec!["another one"].try_into()?,  // 11 chars
            Line::default(),                  // 0 chars
        ]);
        let result = Lines(vec![
            vec!["test", "line", &" ".repeat(11 + 3)].try_into()?,
            vec!["another one", &" ".repeat(11)].try_into()?,
            vec![" ".repeat(11 + 11)].try_into()?,
        ]);
        test.pad_lines_right(11);
        assert_eq!(test, result);

        Ok(())
    }

    #[test]
    fn test_pad_lines_left() -> anyhow::Result<()> {
        let mut test = Lines(vec![
            vec!["test", "line"].try_into()?, // 8 chars
            vec!["another one"].try_into()?,  // 11 chars
            Line::default(),                  // 0 chars
        ]);
        let result = Lines(vec![
            vec![" ".repeat(11).as_ref(), "test", "line"].try_into()?,
            vec![" ".repeat(11).as_ref(), "another one"].try_into()?,
            vec![" ".repeat(11)].try_into()?,
        ]);
        test.pad_lines_left(11);
        assert_eq!(test, result);

        Ok(())
    }

    #[test]
    fn test_pad_lines_bottom() -> anyhow::Result<()> {
        let mut test = Lines(vec![vec!["test"].try_into()?, vec!["another"].try_into()?]);
        test.pad_lines_bottom(3);
        let result = Lines(vec![
            vec!["test"].try_into()?,
            vec!["another"].try_into()?,
            Line::default(),
            Line::default(),
            Line::default(),
        ]);

        assert_eq!(test, result);

        Ok(())
    }

    #[test]
    fn test_pad_lines_top() -> anyhow::Result<()> {
        let mut test = Lines(vec![vec!["test"].try_into()?, vec!["another"].try_into()?]);
        test.pad_lines_top(3);
        let result = Lines(vec![
            Line::default(),
            Line::default(),
            Line::default(),
            vec!["test"].try_into()?,
            vec!["another"].try_into()?,
        ]);

        assert_eq!(test, result);

        Ok(())
    }

    #[test]
    fn test_truncate_lines_bottom() -> anyhow::Result<()> {
        let mut test = Lines(vec![
            vec!["test"].try_into()?,
            vec!["another"].try_into()?,
            vec!["one more"].try_into()?,
        ]);
        test.truncate_lines_bottom(1);
        let output = Lines(vec![vec!["test"].try_into()?]);
        assert_eq!(test, output);

        Ok(())
    }

    #[test]
    fn test_justify() -> anyhow::Result<()> {
        let mut test = Lines(vec![
            vec!["test"].try_into()?,
            Line::default(),
            vec!["ok"].try_into()?,
        ]);

        test.justify();
        let expected = Lines(vec![
            vec!["test"].try_into()?,
            vec![" ".repeat(4)].try_into()?,
            vec!["ok", "  "].try_into()?,
        ]);

        assert_eq!(test, expected);

        Ok(())
    }

    #[test]
    fn test_from_multiline_string() {
        let content = "foo bar\n\nbaz\nsome other line";
        let style = ContentStyle {
            foreground_color: Some(Color::Red),
            background_color: None,
            attributes: Default::default(),
        };
        let test = Lines::from_multiline_string(content, style);
        let expected = Lines(vec![
            Line::from_iter([Span::new_styled_lossy(StyledContent::new(
                style,
                "foo bar".to_owned(),
            ))]),
            Line::from_iter([Span::new_styled_lossy(StyledContent::new(
                style,
                "".to_owned(),
            ))]),
            Line::from_iter([Span::new_styled_lossy(StyledContent::new(
                style,
                "baz".to_owned(),
            ))]),
            Line::from_iter([Span::new_styled_lossy(StyledContent::new(
                style,
                "some other line".to_owned(),
            ))]),
        ]);

        assert_eq!(test, expected);
    }

    #[allow(clippy::from_iter_instead_of_collect)] // More readable this way.
    #[test]
    fn test_colored_from_multiline_string() {
        // Lots of little things we check in here, including that we persist state
        // across lines, that we only change the state that is requested to be changed,
        // that we control fg/bg properly, that we parse different ansi formats, etc.
        let test_string = format!(
            "This is a string
That has both {blue}8 bit blue {blue2}(in both formats)
in it,{reset} as well as {high_blue}256 color blue,
{reset}and {rgb_blue}RGB blue as well.{reset} It resets to the
console default at the end.
It can do {rgb_blue}{bg_blue}background colors, {blue}foreground colors,
{bold}colored, and {reset}normal {bold}bold,{remove_bold} and it
strips out {bs}invalid control sequences",
            blue = "\x1b[34m",
            blue2 = "\x1b[38;5;4m",
            high_blue = "\x1b[38;5;20m",
            rgb_blue = "\x1b[38;2;0;0;238m",
            bg_blue = "\x1b[44m",
            reset = "\x1b[0m",
            bold = "\x1b[1m",
            remove_bold = "\x1b[22m",
            bs = "\x1b[D"
        );

        let default = ContentStyle::default();
        // Both 34m and 38;5;4m parse to the same thing, so no need for blue2
        let blue = ContentStyle {
            foreground_color: Some(Color::AnsiValue(4)),
            ..Default::default()
        };
        let high_blue = ContentStyle {
            foreground_color: Some(Color::AnsiValue(20)),
            ..Default::default()
        };
        let rgb_blue = ContentStyle {
            foreground_color: Some(Color::from((0, 0, 238))),
            ..Default::default()
        };
        let bg_blue = ContentStyle {
            background_color: Some(Color::AnsiValue(4)),
            ..Default::default()
        };
        let rgb_blue_and_bg_blue = ContentStyle {
            foreground_color: rgb_blue.foreground_color,
            background_color: bg_blue.background_color,
            ..Default::default()
        };
        let blue_and_bg_blue = ContentStyle {
            foreground_color: blue.foreground_color,
            background_color: bg_blue.background_color,
            ..Default::default()
        };
        let blue_and_bg_blue_bold = ContentStyle {
            foreground_color: blue.foreground_color,
            background_color: bg_blue.background_color,
            attributes: Attributes::from(Attribute::Bold),
        };
        let bold = ContentStyle {
            attributes: Attributes::from(Attribute::Bold),
            ..Default::default()
        };

        let expected = vec![
            vec![StyledContent::new(default, "This is a string")],
            vec![
                StyledContent::new(default, "That has both "),
                StyledContent::new(blue, "8 bit blue "),
                StyledContent::new(blue, "(in both formats)"),
            ],
            vec![
                StyledContent::new(blue, "in it,"),
                StyledContent::new(default, " as well as "),
                StyledContent::new(high_blue, "256 color blue,"),
            ],
            vec![
                StyledContent::new(default, "and "),
                StyledContent::new(rgb_blue, "RGB blue as well."),
                StyledContent::new(default, " It resets to the"),
            ],
            vec![StyledContent::new(default, "console default at the end.")],
            vec![
                StyledContent::new(default, "It can do "),
                StyledContent::new(rgb_blue_and_bg_blue, "background colors, "),
                StyledContent::new(blue_and_bg_blue, "foreground colors,"),
            ],
            vec![
                StyledContent::new(blue_and_bg_blue_bold, "colored, and "),
                StyledContent::new(default, "normal "),
                StyledContent::new(bold, "bold,"),
                StyledContent::new(default, " and it"),
            ],
            vec![StyledContent::new(
                default,
                "strips out invalid control sequences",
            )],
        ];
        let expected: Lines = expected
            .into_iter()
            .map(|spans| {
                Line::from_iter(spans.iter().map(|sc| {
                    Span::new_styled_lossy(StyledContent::new(
                        *sc.style(),
                        (*sc.content()).to_owned(),
                    ))
                }))
            })
            .collect();

        let lines = Lines::from_colored_multiline_string(&test_string);

        assert_eq!(expected, lines);
    }

    #[test]
    fn test_fmt_for_test() {
        let lines = Lines::from_iter([
            Line::unstyled("orange").unwrap(),
            Line::from_iter([Span::new_colored("pineapple", Color::Yellow).unwrap()]),
        ]);
        assert_eq!(
            "orange\n<span fg=yellow>pineapple</span>\n",
            format!("{}", lines.fmt_for_test())
        );
    }
}


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

use std::borrow::Cow;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;

use crossterm::style::Attribute;
use crossterm::style::Color;
use crossterm::style::ContentStyle;
use crossterm::style::ResetColor;
use crossterm::style::SetAttributes;
use crossterm::style::SetBackgroundColor;
use crossterm::style::SetForegroundColor;
use crossterm::style::StyledContent;
use crossterm::Command;
use termwiz::cell;
use unicode_segmentation::Graphemes;
use unicode_segmentation::UnicodeSegmentation;

#[derive(Debug, thiserror::Error)]
enum SpanError {
    #[error("Word {0} contains non-space whitespace")]
    InvalidWhitespace(String),
}

/// A `Span` is a segment of text that may or may not have [`style`](crate::style) applied to it.
/// One may think of this as a unit of text.  It must be [`valid`](Span::valid) to be constructed.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct Span {
    pub(crate) content: Cow<'static, str>,
    pub style: ContentStyle,
}

/// Test whether a char is permissable to be inside a Span.
/// Whitespace is not allowed, except for spaces.
pub(crate) fn char_valid(c: char) -> bool {
    c == ' ' || !c.is_whitespace()
}

/// Strip invalid characters from the string.
pub(crate) fn sanitize<S: std::fmt::Display>(stringlike: S) -> String {
    let mut content = stringlike.to_string();
    content.retain(char_valid);
    content
}

impl Span {
    #[inline]
    // This could be const fn, but `crossterm::Attributes` constructor is not const.
    pub fn dash() -> Span {
        Span {
            content: Cow::Borrowed("-"),
            style: ContentStyle::default(),
        }
    }

    /// `Spans` are only valid if they do not contain any non whitespace characters other than a space.
    /// This is because the `superconsole` expects content to be mono-spaced and newlines, tabs, etc., violate this.
    pub fn valid(stringlike: &str) -> bool {
        !stringlike.contains(|c: char| !char_valid(c))
    }

    pub fn sanitized<S: std::fmt::Display>(string: S) -> Self {
        let content = sanitize(string);
        Span {
            content: Cow::Owned(content),
            style: ContentStyle::default(),
        }
    }

    pub fn content(&self) -> &str {
        &self.content
    }

    /// Create an unstyled span with the specified amount of whitespace padding.
    pub fn padding(amount: usize) -> Self {
        Self {
            content: Cow::Owned(format!("{:<width$}", "", width = amount)),
            style: ContentStyle::default(),
        }
    }

    /// Attempt to create a new, unstyled span equivalent to the underlying stringlike.
    /// This will fail if the input string is not [`valid`](Span::valid).
    pub fn new_unstyled<S: std::fmt::Display>(stringlike: S) -> anyhow::Result<Self> {
        let owned = stringlike.to_string();
        if Self::valid(&owned) {
            Ok(Self {
                content: Cow::Owned(owned),
                style: ContentStyle::default(),
            })
        } else {
            Err(SpanError::InvalidWhitespace(owned).into())
        }
    }

    pub fn new_unstyled_lossy<S: std::fmt::Display>(stringlike: S) -> Self {
        let content = sanitize(stringlike);
        Self {
            content: Cow::Owned(content),
            style: ContentStyle::default(),
        }
    }

    /// Equivalent to [`new_unstyled`](Span::new_unstyled), except with styling.
    // TODO(brasselsprouts): Does this have to be a `String`? probably not.
    pub fn new_styled(content: StyledContent<String>) -> anyhow::Result<Self> {
        if Self::valid(content.content()) {
            Ok(Self {
                content: Cow::Owned(content.content().clone()),
                style: *content.style(),
            })
        } else {
            Err(SpanError::InvalidWhitespace(content.content().to_owned()).into())
        }
    }

    /// Equivalent to [`Self::new_styled`] except it will sanitize the content.
    pub fn new_styled_lossy(span: StyledContent<String>) -> Self {
        let content = sanitize(span.content());
        Self {
            content: Cow::Owned(content),
            style: *span.style(),
        }
    }

    pub fn new_colored(text: &str, color: Color) -> anyhow::Result<Self> {
        Self::new_styled(StyledContent::new(
            ContentStyle {
                foreground_color: Some(color),
                ..ContentStyle::default()
            },
            text.to_owned(),
        ))
    }

    pub fn new_colored_lossy(text: &str, color: Color) -> Self {
        Self::new_styled_lossy(StyledContent::new(
            ContentStyle {
                foreground_color: Some(color),
                ..ContentStyle::default()
            },
            text.to_owned(),
        ))
    }

    /// Returns the number of graphemes in the span.
    pub fn len(&self) -> usize {
        // Pulled this dep from another FB employee's project - better unicode support for terminal column widths.
        cell::unicode_column_width(&self.content, None)
    }

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

    /// Iterates over each [`Grapheme`](Graphemes) in the [`Span`].
    /// Applies the stylization of the `Span` to each `Grapheme`.
    /// Because a `Grapheme` is represented as another string, the sub-`Span` is represented as a `Span`.
    /// This `panics` if it encounters unicode that it doesn't know how to deal with.
    pub fn iter(&self) -> impl Iterator<Item = Span> + '_ {
        SpanIterator(&self.style, self.content.graphemes(true))
    }

    pub(crate) fn render(&self, f: &mut impl fmt::Write) -> fmt::Result {
        if self.is_empty() {
            return Ok(());
        }

        let mut reset_background = false;
        let mut reset_foreground = false;
        let mut reset = false;

        if let Some(bg) = self.style.background_color {
            SetBackgroundColor(bg).write_ansi(f)?;
            reset_background = true;
        }
        if let Some(fg) = self.style.foreground_color {
            SetForegroundColor(fg).write_ansi(f)?;
            reset_foreground = true;
        }
        if !self.style.attributes.is_empty() {
            SetAttributes(self.style.attributes).write_ansi(f)?;
            reset = true;
        }

        write!(f, "{}", self.content)?;

        if reset {
            ResetColor.write_ansi(f)?;
        } else {
            if reset_background {
                SetBackgroundColor(Color::Reset).write_ansi(f)?;
            }
            if reset_foreground {
                SetForegroundColor(Color::Reset).write_ansi(f)?;
            }
        }

        Ok(())
    }

    pub fn fmt_for_test(&self) -> impl Display + '_ {
        fn to_snake_case(s: &str) -> String {
            let mut result = String::new();
            for c in s.chars() {
                if c.is_uppercase() {
                    if !result.is_empty() {
                        result.push('_');
                    }
                    result.push(c.to_ascii_lowercase());
                } else {
                    result.push(c);
                }
            }
            result
        }

        fn fmt_color(color: Color) -> impl Display {
            struct Impl(Color);
            impl Display for Impl {
                fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                    match self.0 {
                        Color::Reset
                        | Color::Black
                        | Color::Red
                        | Color::Green
                        | Color::Yellow
                        | Color::Blue
                        | Color::Magenta
                        | Color::Cyan
                        | Color::White
                        | Color::Grey
                        | Color::DarkGrey
                        | Color::DarkRed
                        | Color::DarkGreen
                        | Color::DarkYellow
                        | Color::DarkBlue
                        | Color::DarkMagenta
                        | Color::DarkCyan => {
                            write!(f, "{}", to_snake_case(&format!("{:?}", self.0)))
                        }
                        Color::Rgb { r, g, b } => write!(f, "rgb({}, {}, {})", r, g, b),
                        Color::AnsiValue(v) => write!(f, "ansi({})", v),
                    }
                }
            }
            Impl(color)
        }

        struct Impl<'a>(&'a Span);
        impl<'a> Display for Impl<'a> {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                let style_is_default = self.0.style.foreground_color.is_none()
                    && self.0.style.background_color.is_none()
                    && self.0.style.attributes.is_empty();
                if style_is_default {
                    write!(f, "{}", self.0.content)
                } else {
                    write!(f, "<span")?;
                    if let Some(fg) = self.0.style.foreground_color {
                        write!(f, " fg={}", fmt_color(fg))?;
                    }
                    if let Some(bg) = self.0.style.background_color {
                        write!(f, " bg={}", fmt_color(bg))?;
                    }
                    if !self.0.style.attributes.is_empty() {
                        let mut a = self.0.style.attributes;
                        for known in Attribute::iterator() {
                            if a.has(known) {
                                write!(f, " {}", to_snake_case(&format!("{:?}", known)))?;
                                a.unset(known);
                            }
                        }
                        if !a.is_empty() {
                            write!(f, " unknown_attributes={:?}", a)?;
                        }
                    }
                    write!(f, ">")?;
                    write!(f, "{}", self.0.content)?;
                    write!(f, "")?;
                    Ok(())
                }
            }
        }

        Impl(self)
    }
}

impl TryFrom<String> for Span {
    type Error = anyhow::Error;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::new_unstyled(value)
    }
}

impl TryFrom<&str> for Span {
    type Error = anyhow::Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Self::new_unstyled(value)
    }
}

impl TryFrom


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Need to replace this with a CMake build, using https://github.com/XiangpengHao/cxx-cmake-example as inspiration

fn main() {
    cxx_build::bridge("src/lib.rs") // returns a cc::Build
        .file("src/cachelib.cpp")
        .flag_if_supported("-std=c++11")
        .compile("cachelib");

    println!("cargo:rerun-if-changed=src/lib.rs");
    println!("cargo:rerun-if-changed=src/cachelib.cpp");
    println!("cargo:rerun-if-changed=src/cachelib.h");
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::time::Duration;

use anyhow::Error;
use anyhow::Result;
use bytes::BufMut;

use crate::lrucache::VolatileLruCachePool;

/// Utility function for in-memory caching via Abomonation and a volatile pool
/// Will attempt to fetch and decode a cached item
pub fn get_cached<T>(cache_pool: &VolatileLruCachePool, cache_key: &str) -> Result<Option<T>>
where
    T: abomonation::Abomonation + Clone + Send + 'static,
{
    let cache_handle = cache_pool.get_handle(&cache_key)?;
    let cache_data: Option<Vec<u8>> = match cache_handle {
        None => None,
        Some(cache_handle) => {
            let mut bytes = Vec::new();
            bytes.put(cache_handle.get_reader()?);
            Some(bytes)
        }
    };

    Ok(cache_data.map(|mut vec| {
        let (obj, tail) = unsafe { abomonation::decode::<T>(&mut vec) }.expect("Failed to decode");
        assert!(
            tail.is_empty(),
            "Decode did not consume all data, left {}",
            tail.len()
        );
        obj.clone()
    }))
}

/// Encodes an item via Abomonation, and then inserts it into a volatile pool
///
/// Returns `false` if the entry could not be inserted (e.g. another entry with the same
/// key was inserted first)
pub fn set_cached<T>(
    cache_pool: &VolatileLruCachePool,
    cache_key: &str,
    entry: &T,
    ttl: Option<Duration>,
) -> Result<bool>
where
    T: abomonation::Abomonation + Clone + Send + 'static,
{
    let handle = cache_pool.allocate_with_ttl(cache_key, abomonation::measure(entry), ttl)?;
    let mut handle = handle.ok_or_else(|| Error::msg("can not allocate cachelib handle"))?;

    handle
        .get_writer()
        .and_then(|mut writer| unsafe {
            abomonation::encode(entry, &mut writer).map_err(|e| e.into())
        })
        .and_then(|_| cache_pool.insert_handle(handle))
}

#[cfg(test)]
mod test {
    use fbinit::FacebookInit;

    use super::*;
    use crate::lrucache::*;

    fn create_cache(fb: FacebookInit) {
        let config = LruCacheConfig::new(16 * 1024 * 1024);

        if let Err(e) = init_cache(fb, config) {
            panic!("{}", e);
        }
    }

    #[fbinit::test]
    fn get_repeatedly(fb: FacebookInit) {
        create_cache(fb);

        let pool = get_or_create_volatile_pool("get_twice", 4 * 1024 * 1024).unwrap();

        assert!(set_cached(&pool, &"key".to_string(), &"hello_world".to_string(), None).unwrap());
        assert_eq!(
            get_cached::<String>(&pool, &"key".to_string())
                .unwrap()
                .unwrap(),
            "hello_world"
        );

        assert!(
            !set_cached(
                &pool,
                &"key".to_string(),
                &"goodbye world".to_string(),
                Some(Duration::from_secs(100))
            )
            .unwrap()
        );
        assert_eq!(
            get_cached::<String>(&pool, &"key".to_string())
                .unwrap()
                .unwrap(),
            "hello_world"
        );

        assert!(
            set_cached(
                &pool,
                &"key2".to_string(),
                &"hello_world2".to_string(),
                None
            )
            .unwrap()
        );
        assert_eq!(
            get_cached::<String>(&pool, &"key2".to_string())
                .unwrap()
                .unwrap(),
            "hello_world2"
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use thiserror::Error;

#[derive(Debug, Error)]
pub enum ErrorKind {
    #[error("Not in a supported container type, but container config requested")]
    NotInContainer,
    #[error(
        "You must call cachelib::init_cache or init_cache_with_cacheadmin before using the cache"
    )]
    CacheNotInitialized,
    #[error("Get available space failed: {0}")]
    AvailableSpaceError(String),
    #[error("Cannot access cache memory: {0}")]
    CannotGetMemory(String),
    #[error("Handle insertion failed: {0}")]
    HandleInsertFail(String),
    #[error("Cannot allocate handle")]
    HandleAllocateFail,
    #[error("Cannot get handle: {0}")]
    HandleGetFail(String),
    #[error("Cannot get handle as offset: {0}")]
    HandleGetOffsetFail(String),
    #[error("Failed to get pointer from offset: {0}")]
    PointerFromOffsetFail(String),
    #[error("Pool resize failure: {0}")]
    PoolResizeFailure(String),
    #[error("CacheAdmin failure: {0}")]
    CacheAdminFailure(String),
    #[error("Bad access config: {0}")]
    BadAccessConfig(String),
    #[error("Bad persistence config: {0}")]
    BadPersistenceConfig(String),
    #[error("Requested a volatile cache pool from a persistent cache.")]
    VolatileCachePoolError,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

mod abomonation_cache;
mod errors;
mod lrucache;

// export Abomonation so that users of this crate don't need to add abomination as dependency
pub use abomonation::Abomonation;

pub use crate::abomonation_cache::*;
pub use crate::errors::*;
pub use crate::lrucache::*;

#[cxx::bridge(namespace = "facebook::rust::cachelib")]
mod ffi {
    #[namespace = "std::chrono"]
    extern "C++" {
        type milliseconds = stdchrono::milliseconds;
    }

    #[namespace = "folly"]
    extern "C++" {
        type StringPiece<'a> = folly::StringPiece<'a>;
    }

    unsafe extern "C++" {
        include!("cachelib/rust/src/cachelib.h");

        #[namespace = "facebook::cachelib"]
        type CacheAdmin;
        fn make_cacheadmin(
            cache: Pin<&mut LruAllocator>,
            oncall: &CxxString,
        ) -> Result<UniquePtr<CacheAdmin>>;

        type LruAllocator;
        fn make_lru_allocator(
            config: UniquePtr<LruAllocatorConfig>,
        ) -> Result<UniquePtr<LruAllocator>>;
        fn make_shm_lru_allocator(
            config: UniquePtr<LruAllocatorConfig>,
        ) -> Result<UniquePtr<LruAllocator>>;

        type LruAllocatorConfig;
        fn make_lru_allocator_config() -> Result<UniquePtr<LruAllocatorConfig>>;
        fn setCacheSize(
            self: Pin<&mut LruAllocatorConfig>,
            size: usize,
        ) -> Pin<&mut LruAllocatorConfig>;
        fn enable_container_memory_monitor(config: Pin<&mut LruAllocatorConfig>) -> Result<bool>;

        #[namespace = "facebook::cachelib"]
        type RebalanceStrategy;
        fn make_hits_per_slab_rebalancer(
            diff_ratio: f64,
            min_retained_slabs: u32,
            min_tail_age: u32,
        ) -> Result<SharedPtr<RebalanceStrategy>>;
        fn make_lru_tail_age_rebalancer(
            age_difference_ratio: f64,
            min_retained_slabs: u32,
        ) -> Result<SharedPtr<RebalanceStrategy>>;

        fn enable_free_memory_monitor(
            config: Pin<&mut LruAllocatorConfig>,
            interval: milliseconds,
            advisePercentPerIteration: u32,
            maxAdvisePercentage: u32,
            lowerLimit: u32,
            upperLimit: u32,
            adviseStrategy: SharedPtr<RebalanceStrategy>,
        ) -> Result<()>;
        fn enable_resident_memory_monitor(
            config: Pin<&mut LruAllocatorConfig>,
            interval: milliseconds,
            advisePercentPerIteration: u32,
            maxAdvisePercentage: u32,
            lowerLimit: u32,
            upperLimit: u32,
            adviseStrategy: SharedPtr<RebalanceStrategy>,
        ) -> Result<()>;

        fn enable_pool_rebalancing(
            config: Pin<&mut LruAllocatorConfig>,
            strategy: SharedPtr<RebalanceStrategy>,
            interval: milliseconds,
        ) -> Result<()>;
        fn enable_pool_resizing(
            config: Pin<&mut LruAllocatorConfig>,
            strategy: SharedPtr<RebalanceStrategy>,
            interval: milliseconds,
            slabs_per_iteration: u32,
        ) -> Result<()>;

        fn set_access_config(
            config: Pin<&mut LruAllocatorConfig>,
            bucketsPower: u32,
            locksPower: u32,
        ) -> Result<()>;

        fn set_base_address(config: Pin<&mut LruAllocatorConfig>, addr: usize) -> Result<()>;

        fn setCacheName<'a>(
            self: Pin<&'a mut LruAllocatorConfig>,
            name: &CxxString,
        ) -> Pin<&'a mut LruAllocatorConfig>;
        fn enable_cache_persistence(
            config: Pin<&mut LruAllocatorConfig>,
            directory: Pin<&mut CxxString>,
        );

        fn add_pool(cache: &LruAllocator, name: StringPiece<'_>, size: usize) -> Result<i8>;
        fn get_unreserved_size(cache: &LruAllocator) -> usize;

        type LruItemHandle;
        fn get_size(handle: &LruItemHandle) -> usize;
        fn get_memory(handle: &LruItemHandle) -> *const u8;
        fn get_writable_memory(handle: Pin<&mut LruItemHandle>) -> Result<*mut u8>;
        unsafe fn get_item_ptr_as_offset(cache: &LruAllocator, ptr: *const u8) -> Result<usize>;

        fn allocate_item(
            cache: &LruAllocator,
            id: i8,
            key: StringPiece<'_>,
            size: u32,
            ttl_secs: u32,
        ) -> Result<UniquePtr<LruItemHandle>>;

        fn insert_handle(cache: &LruAllocator, handle: Pin<&mut LruItemHandle>) -> Result<bool>;
        fn insert_or_replace_handle(
            cache: &LruAllocator,
            handle: Pin<&mut LruItemHandle>,
        ) -> Result<()>;

        fn remove_item(cache: &LruAllocator, key: StringPiece<'_>) -> Result<()>;
        fn find_item(
            cache: &LruAllocator,
            key: StringPiece<'_>,
        ) -> Result<UniquePtr<LruItemHandle>>;

        fn get_pool_size(cache: &LruAllocator, pool: i8) -> Result<usize>;
        fn grow_pool(cache: &LruAllocator, pool: i8, size: usize) -> Result<bool>;
        fn shrink_pool(cache: &LruAllocator, pool: i8, size: usize) -> Result<bool>;
        fn resize_pools(cache: &LruAllocator, src: i8, dst: i8, size: usize) -> Result<bool>;
    }
}

/// The C++ implementation of LruAllocator is thread safe.
unsafe impl Send for ffi::LruAllocator {}
unsafe impl Sync for ffi::LruAllocator {}

/// The C++ implementation of CacheAdmin is thread safe.
unsafe impl Send for ffi::CacheAdmin {}
unsafe impl Sync for ffi::CacheAdmin {}