#[test]
fn torsion_diffusion_plan_basic() {
    let mut system = build_four_resid_system();
    let sel = system.select("resid 1 or resid 2 or resid 3 or resid 4").unwrap();
    let mut plan = TorsionDiffusionPlan::new(sel);
    let frames = vec![vec![
        [1.0, 0.0, 0.0, 1.0],
        [0.0, 0.0, 0.0, 1.0],
        [0.0, 1.0, 0.0, 1.0],
        [1.0, 1.0, 0.0, 1.0],
    ]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Matrix { data, rows, cols } => {
            assert_eq!(rows, 1);
            assert_eq!(cols, 4);
            let sum: f32 = data.iter().sum();
            assert!((sum - 1.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn toroidal_diffusion_plan_basic() {
    let mut system = build_four_resid_system();
    let sel = system.select("resid 1 or resid 2 or resid 3 or resid 4").unwrap();
    let mut plan = ToroidalDiffusionPlan::new(sel);
    let frames = vec![vec![
        [1.0, 0.0, 0.0, 1.0],
        [0.0, 0.0, 0.0, 1.0],
        [0.0, 1.0, 0.0, 1.0],
        [1.0, 1.0, 0.0, 1.0],
    ]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Matrix { data, rows, cols } => {
            assert_eq!(rows, 1);
            assert_eq!(cols, 4);
            let sum: f32 = data.iter().sum();
            assert!((sum - 1.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn multipucker_plan_basic() {
    let mut system = build_plane_system();
    let sel = system.select("name CA").unwrap();
    let mut plan = MultiPuckerPlan::new(sel, 4);
    let frames = vec![vec![
        [0.0, 0.0, 0.0, 1.0],
        [1.0, 0.0, 0.0, 1.0],
        [0.0, 1.0, 0.0, 1.0],
    ]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Matrix { data, rows, cols } => {
            assert_eq!(rows, 1);
            assert_eq!(cols, 4);
            let sum: f32 = data.iter().sum();
            assert!((sum - 0.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn pucker_plan_basic() {
    let mut system = build_plane_system();
    let sel = system.select("name CA").unwrap();
    let mut plan = PuckerPlan::new(sel);
    let frames = vec![vec![
        [0.0, 0.0, 0.0, 1.0],
        [1.0, 0.0, 0.0, 1.0],
        [0.0, 1.0, 0.0, 1.0],
    ]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 1);
            assert!(vals[0] > 0.0);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn randomize_ions_plan_basic() {
    let mut interner = StringInterner::new();
    let ion = interner.intern_upper("NA");
    let ow = interner.intern_upper("OW");
    let hw = interner.intern_upper("HW");
    let ca = interner.intern_upper("CA");
    let res_ion = interner.intern_upper("ION");
    let res_wat = interner.intern_upper("WAT");
    let res_sol = interner.intern_upper("SOL");
    let atoms = AtomTable {
        name_id: vec![ion, ow, hw, ca],
        resname_id: vec![res_ion, res_wat, res_wat, res_sol],
        resid: vec![1, 2, 2, 3],
        chain_id: vec![0, 0, 0, 0],
        element_id: vec![0, 0, 0, 0],
        mass: vec![1.0, 1.0, 1.0, 1.0],
    };
    let positions0 = Some(vec![
        [0.0, 0.0, 0.0, 1.0],
        [1.0, 0.0, 0.0, 1.0],
        [1.0, 1.0, 0.0, 1.0],
        [5.0, 5.0, 5.0, 1.0],
    ]);
    let mut system = System::with_atoms(atoms, interner, positions0);
    let sel = system.select("name NA").unwrap();
    let around = system.select("resid 3").unwrap();
    let mut plan = RandomizeIonsPlan::new(sel, 123).with_around(Some(around), 0.5, 0.0, false);
    let frames = vec![vec![
        [0.0, 0.0, 0.0, 1.0],
        [1.0, 0.0, 0.0, 1.0],
        [1.0, 1.0, 0.0, 1.0],
        [5.0, 5.0, 5.0, 1.0],
    ]];
    let mut traj = InMemoryTrajWithBox::new(
        frames,
        Box3::Orthorhombic {
            lx: 10.0,
            ly: 10.0,
            lz: 10.0,
        },
    );
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 12);
            let coords: Vec<[f32; 3]> = vals
                .chunks(3)
                .map(|c| [c[0], c[1], c[2]])
                .collect();
            assert_eq!(coords.len(), 4);
            assert!((coords[0][0] - 1.0).abs() < 1e-6);
            assert!((coords[0][1]).abs() < 1e-6);
            assert!((coords[1][0]).abs() < 1e-6);
            assert!((coords[2][0]).abs() < 1e-6);
            assert!((coords[2][1] - 1.0).abs() < 1e-6);
            assert!((coords[3][0] - 5.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn randomize_ions_plan_triclinic() {
    let mut interner = StringInterner::new();
    let ion = interner.intern_upper("NA");
    let ow = interner.intern_upper("OW");
    let hw = interner.intern_upper("HW");
    let res_ion = interner.intern_upper("ION");
    let res_wat = interner.intern_upper("WAT");
    let atoms = AtomTable {
        name_id: vec![ion, ow, hw],
        resname_id: vec![res_ion, res_wat, res_wat],
        resid: vec![1, 2, 2],
        chain_id: vec![0, 0, 0],
        element_id: vec![0, 0, 0],
        mass: vec![1.0, 1.0, 1.0],
    };
    let positions0 = Some(vec![
        [0.0, 0.0, 0.0, 1.0],
        [1.0, 0.0, 0.0, 1.0],
        [1.0, 1.0, 0.0, 1.0],
    ]);
    let mut system = System::with_atoms(atoms, interner, positions0);
    let sel = system.select("name NA").unwrap();
    let mut plan = RandomizeIonsPlan::new(sel, 7);
    let frames = vec![vec![
        [0.0, 0.0, 0.0, 1.0],
        [1.0, 0.0, 0.0, 1.0],
        [1.0, 1.0, 0.0, 1.0],
    ]];
    let mut traj = InMemoryTrajWithBox::new(
        frames,
        Box3::Triclinic {
            m: [2.0, 0.0, 0.0, 0.5, 3.0, 0.0, 0.0, 0.0, 4.0],
        },
    );
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 9);
            let coords: Vec<[f32; 3]> = vals
                .chunks(3)
                .map(|c| [c[0], c[1], c[2]])
                .collect();
            assert_eq!(coords.len(), 3);
            assert!((coords[0][0] - 1.0).abs() < 1e-6);
            assert!((coords[1][0]).abs() < 1e-6);
            assert!((coords[2][1] - 1.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn image_plan_triclinic_selection() {
    let mut system = build_two_resid_system();
    let sel = system.select("resid 1").unwrap();
    let mut plan = ImagePlan::new(sel);
    let frames = vec![vec![[2.9, 1.5, 1.0, 1.0], [5.0, 0.0, 0.0, 1.0]]];
    let mut traj = InMemoryTrajWithBox::new(
        frames,
        Box3::Triclinic {
            m: [2.0, 0.0, 0.0, 1.0, 3.0, 0.0, 0.0, 0.0, 4.0],
        },
    );
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 6);
            assert!((vals[0] - 0.9).abs() < 1e-6);
            assert!((vals[1] - 1.5).abs() < 1e-6);
            assert!((vals[2] - 1.0).abs() < 1e-6);
            assert!((vals[3] - 5.0).abs() < 1e-6);
            assert!((vals[4]).abs() < 1e-6);
            assert!((vals[5]).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn fiximagedbonds_plan_basic() {
    let mut system = build_system();
    let sel = system.select("name CA").unwrap();
    let mut plan = FixImageBondsPlan::new(sel);
    let frames = vec![vec![[2.5, -1.0, 0.0, 1.0], [3.0, 0.0, 0.0, 1.0]]];
    let mut traj = InMemoryTrajWithBox::new(
        frames,
        Box3::Orthorhombic {
            lx: 3.0,
            ly: 3.0,
            lz: 3.0,
        },
    );
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 6);
        }
        _ => panic!("unexpected output"),
    }
}

#[test]
fn count_in_voxel_basic() {
    let mut system = build_system();
    let sel = system.select("name CA").unwrap();
    let mut plan = CountInVoxelPlan::new(
        sel.clone(),
        sel,
        [1.0, 1.0, 1.0],
        [1.0, 1.0, 1.0],
    );
    let frames = vec![vec![[0.2, 0.2, 0.2, 1.0], [0.2, 0.2, 0.2, 1.0]]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Grid(grid) => {
            assert_eq!(grid.dims, [2, 2, 2]);
            assert_eq!(grid.mean.len(), 8);
            assert!((grid.mean[0] - 2.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn distance_to_point_plan_basic() {
    let mut system = build_system();
    let sel = system.select("name CA").unwrap();
    let mut plan = DistanceToPointPlan::new(sel, [0.0, 0.0, 0.0], PbcMode::None);
    let frames = vec![vec![[0.0, 0.0, 0.0, 1.0], [3.0, 4.0, 0.0, 1.0]]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Matrix { data, rows, cols } => {
            assert_eq!(rows, 1);
            assert_eq!(cols, 2);
            assert!((data[0]).abs() < 1e-6);
            assert!((data[1] - 5.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn align_plan_translation_only() {
    let mut system = build_system();
    let sel = system.select("name CA").unwrap();
    let mut plan = AlignPlan::new(sel, ReferenceMode::Topology, false, true);
    let frames = vec![vec![[10.0, 0.0, 0.0, 1.0], [11.0, 0.0, 0.0, 1.0]]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Matrix { data, rows, cols } => {
            assert_eq!(rows, 1);
            assert_eq!(cols, 12);
            assert!((data[0] - 1.0).abs() < 1e-6);
            assert!((data[4] - 1.0).abs() < 1e-6);
            assert!((data[8] - 1.0).abs() < 1e-6);
            let tx = data[9];
            assert!((tx + 10.0).abs() < 1e-5);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn angle_plan_basic() {
    let mut system = build_four_resid_system();
    let sel_a = system.select("resid 1").unwrap();
    let sel_b = system.select("resid 2").unwrap();
    let sel_c = system.select("resid 3").unwrap();
    let mut plan = AnglePlan::new(sel_a, sel_b, sel_c, false, PbcMode::None, true);
    let frames = vec![system.positions0.clone().unwrap()];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 1);
            assert!((vals[0] - 90.0).abs() < 1e-4);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn dihedral_plan_basic() {
    let mut system = build_four_resid_system();
    let sel_a = system.select("resid 1").unwrap();
    let sel_b = system.select("resid 2").unwrap();
    let sel_c = system.select("resid 3").unwrap();
    let sel_d = system.select("resid 4").unwrap();
    let mut plan = DihedralPlan::new(sel_a, sel_b, sel_c, sel_d, false, PbcMode::None, true, false);
    let frames = vec![system.positions0.clone().unwrap()];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 1);
            assert!((vals[0] + 90.0).abs() < 1e-4);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn mindist_plan_basic() {
    let mut system = build_system();
    let sel_a = system.select("resid 1").unwrap();
    let sel_b = system.select("resid 1").unwrap();
    let mut plan = MindistPlan::new(sel_a, sel_b, PbcMode::None);
    let frames = vec![vec![[0.0, 0.0, 0.0, 1.0], [3.0, 4.0, 0.0, 1.0]]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 1);
            assert!((vals[0]).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn closest_atom_plan_basic() {
    let mut system = build_system();
    let sel = system.select("resid 1").unwrap();
    let mut plan = ClosestAtomPlan::new(sel, [0.1, 0.0, 0.0], PbcMode::None);
    let frames = vec![vec![[0.0, 0.0, 0.0, 1.0], [3.0, 4.0, 0.0, 1.0]]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 1);
            assert!((vals[0] - 0.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn search_neighbors_plan_basic() {
    let mut system = build_system();
    let target = system.select("resid 1").unwrap();
    let probe = system.select("resid 1").unwrap();
    let mut plan = SearchNeighborsPlan::new(target, probe, 0.5, PbcMode::None);
    let frames = vec![vec![[0.0, 0.0, 0.0, 1.0], [3.0, 4.0, 0.0, 1.0]]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 1);
            assert!((vals[0] - 2.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn native_contacts_plan_basic() {
    let mut system = build_two_resid_system();
    let sel = system.select("resid 1:2").unwrap();
    let mut plan = NativeContactsPlan::new(
        sel.clone(),
        sel,
        ReferenceMode::Frame0,
        1.5,
        PbcMode::None,
    );
    let frames = vec![
        vec![[0.0, 0.0, 0.0, 1.0], [1.0, 0.0, 0.0, 1.0]],
        vec![[0.0, 0.0, 0.0, 1.0], [3.0, 0.0, 0.0, 1.0]],
    ];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 2);
            assert!((vals[0] - 1.0).abs() < 1e-6);
            assert!((vals[1]).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn strip_plan_basic() {
    let mut system = build_two_resid_system();
    let sel = system.select("resid 1").unwrap();
    let mut plan = StripPlan::new(sel);
    let frames = vec![vec![[1.0, 2.0, 3.0, 1.0], [9.0, 9.0, 9.0, 1.0]]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 3);
            assert!((vals[0] - 1.0).abs() < 1e-6);
            assert!((vals[1] - 2.0).abs() < 1e-6);
            assert!((vals[2] - 3.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn scale_plan_basic() {
    let system = build_system();
    let mut plan = ScalePlan::new(2.0);
    let frames = vec![vec![[1.0, -2.0, 3.0, 1.0], [0.5, 0.0, -1.0, 1.0]]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 6);
            assert!((vals[0] - 2.0).abs() < 1e-6);
            assert!((vals[1] + 4.0).abs() < 1e-6);
            assert!((vals[2] - 6.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn rotate_plan_basic() {
    let system = build_system();
    let rot = [
        0.0, -1.0, 0.0,
        1.0,  0.0, 0.0,
        0.0,  0.0, 1.0,
    ];
    let mut plan = RotatePlan::new(rot);
    let frames = vec![vec![[0.0, 0.0, 0.0, 1.0], [1.0, 0.0, 0.0, 1.0]]];
    let mut traj = InMemoryTraj::new(frames);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::Series(vals) => {
            assert_eq!(vals.len(), 6);
            assert!((vals[3]).abs() < 1e-6);
            assert!((vals[4] - 1.0).abs() < 1e-6);
            assert!((vals[5]).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}
