1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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 {
            // no diff
            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
}

/// Prints a batch as a diff.
///
/// Converts a batch to a diff string, adhering to the given width and align (in `settings`).
///
/// # Arguments
///
/// * `batch`: The batch to print.
/// * `settings`: Settings object containing a width and align rule.
///
/// # Returns
///
/// A diff string representing the given batch.
pub fn print_batch(batch: Batch, settings: &Settings) -> String {
    print_lines(&batch.lines, settings, batch.left_diff, batch.right_diff)
}