nonogrid 0.7.3

Efficient nonogram solver
Documentation
use std::fmt::{self, Display};

#[cfg(feature = "colored")]
use colored::{self, ColoredString, Colorize};
use hashbrown::HashMap;

use crate::{
    block::{base::color::ColorDesc, binary::BinaryColor, Block, Color, Description},
    board::{Board, LineDirection},
    utils::{
        pad, pad_with,
        rc::{MutRc, ReadRc, ReadRef},
        transpose,
    },
};

pub trait Renderer<B>
where
    B: Block,
{
    fn with_board(board: MutRc<Board<B>>) -> Self;
    fn render(&self) -> String;
    fn render_simple(&self) -> String;

    fn concat(rows: impl Iterator<Item = Vec<String>>) -> String {
        let rows: Vec<_> = rows.map(|line| line.concat()).collect();
        rows.join("\n")
    }
}

#[derive(Debug)]
pub struct ShellRenderer<B>
where
    B: Block,
{
    board: MutRc<Board<B>>,
}

impl<B> Renderer<B> for ShellRenderer<B>
where
    B: Block + Display,
    B::Color: Display,
{
    fn with_board(board: MutRc<Board<B>>) -> Self {
        Self { board }
    }

    fn render(&self) -> String {
        let full_width = self.side_width() + self.board().width();

        let mut header = self.header_lines();
        for row in &mut header {
            pad_with(row, "#".to_string(), full_width, false);
        }

        let header = header.into_iter().map(|row| {
            row.into_iter()
                .map(|s| ColoredString::from(s.as_str()))
                .collect()
        });

        let side = self.side_lines();
        let side = side
            .into_iter()
            .map(|row| row.into_iter().map(|s| ColoredString::from(s.as_str())));

        let grid = self.grid_lines();
        let grid = side.zip(grid).map(|(s, g)| s.chain(g).collect());

        Self::concat(header.chain(grid).map(|line: Vec<ColoredString>| {
            line.iter().map(|symbol| pad(symbol, 2, true)).collect()
        }))
    }

    fn render_simple(&self) -> String {
        Self::concat(
            self.grid_lines()
                .into_iter()
                .map(|row| row.iter().map(ToString::to_string).collect()),
        )
    }
}

impl<B> ShellRenderer<B>
where
    B: Block,
{
    fn board(&self) -> ReadRef<'_, Board<B>> {
        self.board.read()
    }

    fn side_width(&self) -> usize {
        Self::descriptions_width(self.board().descriptions(LineDirection::Row))
    }

    fn descriptions_width(descriptions: &[ReadRc<Description<B>>]) -> usize {
        descriptions
            .iter()
            .map(|desc| desc.vec.len())
            .max()
            .unwrap_or(0)
    }
}

impl<B> ShellRenderer<B>
where
    B: Block + Display,
{
    fn desc_to_string(desc: &ReadRc<Description<B>>) -> Vec<String> {
        desc.vec.iter().map(ToString::to_string).collect()
    }

    fn descriptions_to_matrix(descriptions: &[ReadRc<Description<B>>]) -> Vec<Vec<String>> {
        let mut rows: Vec<_> = descriptions.iter().map(Self::desc_to_string).collect();

        let width = Self::descriptions_width(descriptions);

        for row in &mut rows {
            pad_with(row, " ".to_string(), width, false);
        }
        rows
    }

    fn side_lines(&self) -> Vec<Vec<String>> {
        Self::descriptions_to_matrix(self.board().descriptions(LineDirection::Row))
    }

    fn header_lines(&self) -> Vec<Vec<String>> {
        transpose(&Self::descriptions_to_matrix(
            self.board().descriptions(LineDirection::Column),
        ))
        .unwrap()
    }
}

#[cfg(not(feature = "colored"))]
type ColoredString = String;

impl From<ColorDesc> for ColoredString {
    #[cfg(feature = "colored")]
    fn from(color_desc: ColorDesc) -> Self {
        let color_res: Result<colored::Color, _> = color_desc.name().parse();
        if let Ok(color) = color_res {
            " ".on_color(color)
        } else {
            let symbol = color_desc.symbol();
            symbol.as_str().into()
        }
    }

    #[cfg(not(feature = "colored"))]
    fn from(color_desc: ColorDesc) -> Self {
        color_desc.symbol()
    }
}

impl<B> ShellRenderer<B>
where
    B: Block,
    B::Color: Display,
{
    fn cell_symbol(&self, cell: &B::Color) -> ColoredString {
        let id = cell.as_color_id();

        id.and_then(|color_id| self.board().desc_by_id(color_id).map(From::from))
            .unwrap_or_else(|| cell.to_string().as_str().into())
    }

    fn grid_lines(&self) -> Vec<Vec<ColoredString>> {
        let mut color_cache = HashMap::new();
        self.board()
            .iter_rows()
            .map(|row| {
                row.iter()
                    .map(|cell| {
                        color_cache
                            .entry(cell)
                            .or_insert_with(|| self.cell_symbol(cell))
                            .clone()
                    })
                    .collect()
            })
            .collect()
    }
}

impl Display for BinaryColor {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use BinaryColor::{Black, BlackOrWhite, Undefined, White};

        let symbol = match self {
            White => '.',
            Black => '\u{25A0}',
            Undefined | BlackOrWhite => '?',
        };
        write!(f, "{}", symbol)
    }
}