use std::cmp::Reverse;
use std::collections::BinaryHeap;
use crate::types::{Command, CyclePolicy, PlacedCommand};
const COLOR_UNVISITED: u8 = 0;
const COLOR_ON_PATH: u8 = 1;
const COLOR_DONE: u8 = 2;
const NO_SCC: usize = usize::MAX;
#[derive(Clone, Copy)]
struct CopyInfo {
src: usize,
dst: usize,
length: usize,
}
struct SccList {
sccs: Vec<Vec<usize>>, active: Vec<usize>, id: Vec<usize>, }
#[derive(Debug, Default)]
pub struct InplaceStats {
pub num_copies: usize,
pub num_adds: usize,
pub edges: usize,
pub cycles_broken: usize,
pub copies_converted: usize,
pub bytes_converted: usize,
}
fn tarjan_scc(adj: &[Vec<usize>], n: usize) -> Vec<Vec<usize>> {
let mut index_counter = 0usize;
let mut index = vec![usize::MAX; n]; let mut lowlink = vec![0usize; n];
let mut on_stack = vec![false; n];
let mut tarjan_stack: Vec<usize> = Vec::new();
let mut sccs: Vec<Vec<usize>> = Vec::new();
let mut call_stack: Vec<(usize, usize)> = Vec::new();
for start in 0..n {
if index[start] != usize::MAX {
continue;
}
index[start] = index_counter;
lowlink[start] = index_counter;
index_counter += 1;
on_stack[start] = true;
tarjan_stack.push(start);
call_stack.push((start, 0));
while let Some(&(v, ni)) = call_stack.last() {
if ni < adj[v].len() {
let w = adj[v][ni];
call_stack.last_mut().unwrap().1 += 1;
if index[w] == usize::MAX {
index[w] = index_counter;
lowlink[w] = index_counter;
index_counter += 1;
on_stack[w] = true;
tarjan_stack.push(w);
call_stack.push((w, 0));
} else if on_stack[w] {
if index[w] < lowlink[v] {
lowlink[v] = index[w];
}
}
} else {
call_stack.pop();
if let Some(&(parent, _)) = call_stack.last() {
if lowlink[v] < lowlink[parent] {
lowlink[parent] = lowlink[v];
}
}
if lowlink[v] == index[v] {
let mut scc = Vec::new();
loop {
let w = tarjan_stack.pop().unwrap();
on_stack[w] = false;
scc.push(w);
if w == v {
break;
}
}
sccs.push(scc);
}
}
}
}
sccs }
fn find_cycle_in_scc(
adj: &[Vec<usize>],
scc: &[usize],
sid: usize,
scc_id: &[usize],
removed: &[bool],
color: &mut [u8],
scan_start: &mut usize,
) -> Option<Vec<usize>> {
let mut path: Vec<usize> = Vec::new();
while *scan_start < scc.len() {
let start = scc[*scan_start];
if removed[start] || color[start] != COLOR_UNVISITED {
*scan_start += 1;
continue;
}
color[start] = COLOR_ON_PATH;
path.push(start);
let mut stack: Vec<(usize, usize)> = vec![(start, 0)];
while !stack.is_empty() {
let (v, ni) = *stack.last().unwrap();
let mut next_ni = ni;
let mut advanced = false;
while next_ni < adj[v].len() {
let w = adj[v][next_ni];
next_ni += 1;
if scc_id[w] != sid || removed[w] {
continue;
}
if color[w] == COLOR_ON_PATH {
let pos = path.iter().position(|&x| x == w).unwrap();
let cycle = path[pos..].to_vec();
for &u in &path {
color[u] = COLOR_UNVISITED;
}
return Some(cycle);
}
if color[w] == COLOR_UNVISITED {
stack.last_mut().unwrap().1 = next_ni;
color[w] = COLOR_ON_PATH;
path.push(w);
stack.push((w, 0));
advanced = true;
break;
}
}
if !advanced {
stack.pop();
color[v] = COLOR_DONE; path.pop();
}
}
*scan_start += 1;
}
None
}
fn build_crwi_digraph(
copy_info: &[CopyInfo],
n: usize,
stats: &mut InplaceStats,
) -> (Vec<Vec<usize>>, Vec<usize>) {
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
let mut in_deg: Vec<usize> = vec![0; n];
let mut write_sorted: Vec<usize> = (0..n).collect();
write_sorted.sort_unstable_by_key(|&j| copy_info[j].dst);
let write_starts: Vec<usize> = write_sorted.iter().map(|&j| copy_info[j].dst).collect();
for i in 0..n {
let src = copy_info[i].src;
let length = copy_info[i].length;
let read_end = src + length;
let lo = write_starts.partition_point(|&ws| ws < src);
let hi = write_starts.partition_point(|&ws| ws < read_end);
if lo > 0 {
let j = write_sorted[lo - 1];
if j != i {
let dj = copy_info[j].dst;
let lj = copy_info[j].length;
if dj + lj > src {
adj[i].push(j);
in_deg[j] += 1;
stats.edges += 1;
}
}
}
for k in lo..hi {
let j = write_sorted[k];
if j != i {
adj[i].push(j);
in_deg[j] += 1;
stats.edges += 1;
}
}
}
(adj, in_deg)
}
fn build_scc_list(adj: &[Vec<usize>], n: usize) -> SccList {
let sccs_raw = tarjan_scc(adj, n);
let mut id = vec![NO_SCC; n];
let mut sccs: Vec<Vec<usize>> = Vec::new();
let mut active: Vec<usize> = Vec::new();
for scc in &sccs_raw {
if scc.len() > 1 {
let sid = sccs.len();
for &v in scc {
id[v] = sid;
}
active.push(scc.len());
sccs.push(scc.clone());
}
}
SccList { sccs, active, id }
}
fn pick_victim(
copy_info: &[CopyInfo],
adj: &[Vec<usize>],
scc_list: &mut SccList,
removed: &[bool],
color: &mut [u8],
scc_ptr: &mut usize,
scan_pos: &mut usize,
policy: CyclePolicy,
n: usize,
) -> usize {
match policy {
CyclePolicy::Constant => (0..n).find(|&i| !removed[i]).unwrap(),
CyclePolicy::Localmin => loop {
while *scc_ptr < scc_list.sccs.len() && scc_list.active[*scc_ptr] == 0 {
*scc_ptr += 1;
*scan_pos = 0;
}
if *scc_ptr >= scc_list.sccs.len() {
break (0..n).find(|&i| !removed[i]).unwrap();
}
let result = find_cycle_in_scc(
adj,
&scc_list.sccs[*scc_ptr],
*scc_ptr,
&scc_list.id,
removed,
color,
scan_pos,
);
match result {
Some(cycle) => {
break *cycle.iter().min_by_key(|&&i| (copy_info[i].length, i)).unwrap();
}
None => {
*scc_ptr += 1;
*scan_pos = 0;
}
}
},
}
}
fn run_kahn(
copy_info: &[CopyInfo],
adj: &[Vec<usize>],
scc_list: &mut SccList,
in_deg: &mut [usize],
r: &[u8],
add_info: &mut Vec<(usize, Vec<u8>)>,
policy: CyclePolicy,
n: usize,
stats: &mut InplaceStats,
) -> Vec<usize> {
let mut removed = vec![false; n];
let mut topo_order = Vec::with_capacity(n);
let mut color = vec![COLOR_UNVISITED; n];
let mut scc_ptr = 0usize;
let mut scan_pos = 0usize;
let mut heap: BinaryHeap<Reverse<(usize, usize)>> = BinaryHeap::new();
for i in 0..n {
if in_deg[i] == 0 {
heap.push(Reverse((copy_info[i].length, i)));
}
}
let mut processed = 0;
while processed < n {
while let Some(Reverse((_, v))) = heap.pop() {
if removed[v] {
continue;
}
removed[v] = true;
topo_order.push(v);
processed += 1;
if scc_list.id[v] != NO_SCC {
scc_list.active[scc_list.id[v]] -= 1;
}
for &w in &adj[v] {
if !removed[w] {
in_deg[w] -= 1;
if in_deg[w] == 0 {
heap.push(Reverse((copy_info[w].length, w)));
}
}
}
}
if processed >= n {
break;
}
let victim = pick_victim(
copy_info, adj, scc_list, &removed, &mut color,
&mut scc_ptr, &mut scan_pos, policy, n,
);
let ci = copy_info[victim];
add_info.push((ci.dst, r[ci.src..ci.src + ci.length].to_vec()));
stats.cycles_broken += 1;
stats.copies_converted += 1;
stats.bytes_converted += ci.length;
removed[victim] = true;
processed += 1;
if scc_list.id[victim] != NO_SCC {
scc_list.active[scc_list.id[victim]] -= 1;
}
for &w in &adj[victim] {
if !removed[w] {
in_deg[w] -= 1;
if in_deg[w] == 0 {
heap.push(Reverse((copy_info[w].length, w)));
}
}
}
}
topo_order
}
pub fn make_inplace(
r: &[u8],
commands: &[Command],
policy: CyclePolicy,
) -> (Vec<PlacedCommand>, InplaceStats) {
let mut stats = InplaceStats::default();
if commands.is_empty() {
return (Vec::new(), stats);
}
let mut copy_info: Vec<CopyInfo> = Vec::new();
let mut add_info: Vec<(usize, Vec<u8>)> = Vec::new();
let mut write_pos = 0usize;
for cmd in commands {
match cmd {
Command::Copy { offset, length } => {
copy_info.push(CopyInfo { src: *offset, dst: write_pos, length: *length });
write_pos += length;
}
Command::Add { data } => {
add_info.push((write_pos, data.clone()));
write_pos += data.len();
}
}
}
let n = copy_info.len();
if n == 0 {
stats.num_adds = add_info.len();
return (
add_info
.into_iter()
.map(|(dst, data)| PlacedCommand::Add { dst, data })
.collect(),
stats,
);
}
let (adj, mut in_deg) = build_crwi_digraph(©_info, n, &mut stats);
let mut scc_list = build_scc_list(&adj, n);
let topo_order = run_kahn(
©_info, &adj, &mut scc_list, &mut in_deg,
r, &mut add_info, policy, n, &mut stats,
);
stats.num_copies = topo_order.len();
let mut result: Vec<PlacedCommand> = Vec::new();
for &i in &topo_order {
let ci = copy_info[i];
result.push(PlacedCommand::Copy { src: ci.src, dst: ci.dst, length: ci.length });
}
for (dst, data) in add_info {
result.push(PlacedCommand::Add { dst, data });
}
stats.num_adds = result.len() - stats.num_copies;
(result, stats)
}