use crate::input::Settings;
use crate::{Line, Side, Batch};
fn side_char(side: &Side) -> char {
match side {
Side::Both => ' ',
Side::Left => '-',
Side::Right => '+'
}
}
fn print_byte(b: &&u8) -> char {
if 32 <= **b && **b < 127 {
match char::from_u32((**b).into()) {
None => '.',
Some(c) => c
}
} else {
'.'
}
}
#[test]
fn print_byte_returns_char_for_printable() {
assert_eq!(print_byte(&&69), 'E');
assert_eq!(print_byte(&&32), ' ');
assert_eq!(print_byte(&&126), '~');
}
#[test]
fn print_byte_returns_dot_for_not_printable() {
assert_eq!(print_byte(&&10), '.');
}
fn line_header(side: &Side, left_offs: usize, right_offs: usize) -> String {
match side {
Side::Both => format!("{}{:#08x}, {:#08x}:", side_char(side), left_offs, right_offs),
Side::Left => format!("{}{:#08x}, :", side_char(side), left_offs),
Side::Right => format!("{} , {:#08x}:", side_char(side), right_offs),
}
}
fn pad_to_width(bytes: usize, left: bool) -> String {
" ".repeat(bytes * 2 + ((bytes + if left { 2 } else { 1 }) / 2))
}
fn cluster_hex(bytes: &Vec<&u8>, offs: usize) -> String {
bytes.iter().enumerate().map(|(i, b)| if i % 2 == offs % 2 { format!("{:02x}", b) } else { format!("{:02x} ", b) }).collect::<Vec<String>>().join("")
}
fn print_single_line(side: &Side, start_offs: usize, width: usize, bytes: Vec<&u8>, left_offs: isize, right_offs: isize) -> String {
if start_offs + bytes.len() > width {
format!(
"{}\n{}",
print_single_line(side, start_offs, width, bytes[..width-start_offs].to_vec(), left_offs, right_offs),
print_single_line(side, 0, width, bytes[width-start_offs..].to_vec(), left_offs+width as isize, right_offs+width as isize),
)
} else {
format!("{}{}{}{}| {}{}", line_header(side, (left_offs+start_offs as isize) as usize, (right_offs+start_offs as isize) as usize), pad_to_width(start_offs, true), cluster_hex(&bytes, start_offs), pad_to_width(width-bytes.len()-start_offs, width % 2 == 1), " ".repeat(start_offs), bytes.iter().map(print_byte).collect::<String>())
}
}
fn print_line(line: &Line, width: usize, align: fn(usize, usize) -> usize) -> String {
if line.content.is_empty() { "".into() } else {
let mut left_end = None;
let mut right_end = None;
let mut total_range = None;
let mut li = 0;
let mut ri = 0;
for (i, r) in line.content.iter().enumerate() {
match r.1 {
Side::Left => {
li += 1;
left_end = Some(li);
if total_range == None { total_range = Some((i, i, 0)); }
total_range = total_range.map(|mut r| { r.1 = i+1; r.2 = align(li, ri); r });
},
Side::Right => {
ri += 1;
right_end = Some(ri);
if total_range == None { total_range = Some((i, i, 0)); }
total_range = total_range.map(|mut r| { r.1 = i+1; r.2 = align(li, ri); r });
},
Side::Both => {
li += 1;
ri += 1;
}
}
}
let mut lines = vec![];
if total_range.is_some() {
let tr = total_range.unwrap();
let hd: Vec<&u8> = line.content[..tr.0].iter().map(|e| e.0).collect();
let tl: Vec<&u8> = line.content[tr.1..].iter().map(|e| e.0).collect();
if !hd.is_empty() {
lines.push(print_single_line(&Side::Both, 0, width, hd, line.left_offs as isize, line.right_offs as isize));
}
if left_end.is_some() {
let lf = line.content[tr.0..tr.1].iter().filter(|r| r.1 != Side::Right).map(|r| r.0).collect();
lines.push(print_single_line(&Side::Left, tr.0, width, lf, line.left_offs as isize, line.right_offs as isize));
}
let mut consumed_both = 0;
if left_end.is_none() != right_end.is_none() {
let both: Vec<(usize, &(&u8, Side))> = line.content[tr.0..tr.1].iter().enumerate().filter(|r| r.1.1 == Side::Both).collect();
if !both.is_empty() {
consumed_both = both.len();
let offs: isize = (both[0].0 + tr.0) as isize;
lines.push(print_single_line(if left_end.is_some() { &Side::Right } else { &Side::Left }, if left_end.is_some() { tr.0 + (align)(offs as usize, 0) } else { tr.0 + (align)(0, offs as usize) }, width, both.iter().map(|r| r.1.0).collect(), line.left_offs as isize, line.right_offs as isize));
}
}
if right_end.is_some() {
let rt = line.content[tr.0..tr.1].iter().filter(|r| r.1 != Side::Left).map(|r| r.0).collect();
lines.push(print_single_line(&Side::Right, tr.0, width, rt, line.left_offs as isize, line.right_offs as isize));
}
if !tl.is_empty() {
let le = left_end.unwrap_or(tr.0 + consumed_both);
let re = right_end.unwrap_or(tr.0 + consumed_both);
lines.push(print_single_line(&Side::Both, tr.2, width, tl, line.left_offs as isize + le as isize - tr.2 as isize, line.right_offs as isize + re as isize - tr.2 as isize));
}
} else {
let bytes: Vec<&u8> = line.content.iter().map(|r| r.0).collect();
lines.push(print_single_line(&Side::Both, 0, width, bytes, line.left_offs as isize, line.right_offs as isize));
}
lines.join("\n")
}
}
fn print_lines(lines: &Vec<Line>, settings: &Settings, left_diff: usize, right_diff: usize) -> String {
let mut out = format!("\n@@ -{:#x},{} +{:#x},{} @@ {:#08x}, {:#08x}", lines[0].left_offs, left_diff, lines[0].right_offs, right_diff, lines[0].left_offs, lines[0].right_offs);
for line in lines {
out.push('\n');
out.push_str(&print_line(&line, settings.width, settings.align));
}
out
}
pub fn print_batch(batch: Batch, settings: &Settings) -> String {
print_lines(&batch.lines, settings, batch.left_diff, batch.right_diff)
}