#[test]
fn rotacf_plan_constant_orientation() {
    let mut system = build_plane_system();
    let sel = system.select("resid 1").unwrap();
    let orient = crate::plans::analysis::rotacf::OrientationSpec::PlaneIndices([1, 2, 3]);
    let mut plan = RotAcfPlan::new(sel, GroupBy::Resid, orient);
    let frames = vec![system.positions0.clone().unwrap(), 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::TimeSeries { data, cols, .. } => {
            assert_eq!(cols, 4);
            assert!((data[0] - 1.0).abs() < 1e-6);
            assert!((data[1] - 1.0).abs() < 1e-6);
            assert!((data[2] - 1.0).abs() < 1e-6);
            assert!((data[3] - 1.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn conductivity_plan_basic() {
    let mut system = build_system();
    let sel = system.select("resid 1").unwrap();
    let charges = vec![1.0f64, 1.0f64];
    let mut plan = ConductivityPlan::new(sel, GroupBy::Resid, charges, 1.0)
        .with_lag_mode(LagMode::MultiTau);
    let frames = vec![
        vec![[0.0, 0.0, 0.0, 1.0], [1.0, 0.0, 0.0, 1.0]],
        vec![[1.0, 0.0, 0.0, 1.0], [2.0, 0.0, 0.0, 1.0]],
    ];
    let box_ = Box3::Orthorhombic {
        lx: 10.0,
        ly: 10.0,
        lz: 10.0,
    };
    let mut traj = InMemoryTrajWithBox::new(frames, box_);
    let mut exec = Executor::new(system);
    let out = exec.run_plan(&mut plan, &mut traj).unwrap();
    match out {
        PlanOutput::TimeSeries { cols, data, time, .. } => {
            assert_eq!(cols, 1);
            let qelec = 1.60217656535e-19_f64;
            let kb = 1.380648813e-23_f64;
            let vol_nm3 = 10.0 * 10.0 * 10.0;
            let vol = vol_nm3 * 1.0e-27;
            let multi = qelec * qelec / (6.0 * kb * 1.0 * vol);
            let expected = 4.0 * multi;
            let (idx, _) = time
                .iter()
                .enumerate()
                .min_by(|a, b| {
                    let da = (a.1 - 1.0).abs();
                    let db = (b.1 - 1.0).abs();
                    da.partial_cmp(&db).unwrap()
                })
                .unwrap();
            let got = data[idx] as f64;
            let rel = (got - expected).abs() / expected.abs().max(1.0);
            assert!(rel < 1.0e-3, "got {got} expected {expected}");
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn ion_pair_corr_constant_pairs() {
    let mut system = build_two_resid_system();
    let sel = system.select("resid 1:2").unwrap();
    let group_types = vec![0usize, 1usize];
    let mut plan = IonPairCorrelationPlan::new(sel, GroupBy::Resid, 2.0, 2.0)
        .with_group_types(group_types);
    let frames = vec![system.positions0.clone().unwrap(), 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::TimeSeries { data, cols, rows, .. } => {
            assert_eq!(cols, 6);
            assert_eq!(rows, 2);
            assert!((data[0] - 1.0).abs() < 1e-6);
            assert!((data[3] - 1.0).abs() < 1e-6);
            assert!((data[6] - 1.0).abs() < 1e-6);
            assert!((data[9] - 1.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn structure_factor_shapes() {
    let mut system = build_system();
    let sel = system.select("name CA").unwrap();
    let mut plan = StructureFactorPlan::new(sel, 5, 5.0, 4, 2.0, PbcMode::None);
    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::StructureFactor(output) => {
            assert_eq!(output.r.len(), 5);
            assert_eq!(output.g_r.len(), 5);
            assert_eq!(output.q.len(), 4);
            assert_eq!(output.s_q.len(), 4);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn water_count_basic() {
    let mut system = build_two_resid_system();
    let water = system.select("resid 1").unwrap();
    let center = system.select("resid 1").unwrap();
    let mut plan = WaterCountPlan::new(water, center, [0.5, 0.5, 0.5], [1.0, 1.0, 1.0]);
    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::Grid(output) => {
            assert_eq!(output.dims, [3, 3, 3]);
            assert!((output.mean[0] - 1.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn hbond_distance_only() {
    let mut system = build_two_resid_system();
    let donors = system.select("resid 1").unwrap();
    let acceptors = system.select("resid 2").unwrap();
    let mut plan = HbondPlan::new(donors, acceptors, 2.0);
    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::TimeSeries { data, cols, rows, .. } => {
            assert_eq!(cols, 1);
            assert_eq!(rows, 1);
            assert!((data[0] - 1.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn rdf_plan_counts() {
    let mut system = build_system();
    let sel = system.select("name CA").unwrap();
    let mut plan = RdfPlan::new(sel.clone(), sel, 5, 5.0, PbcMode::None);
    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::Rdf(rdf) => {
            let total: u64 = rdf.counts.iter().sum();
            assert!(total > 0);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn polymer_end_to_end_basic() {
    let mut system = build_polymer_system(2, 3);
    let sel = system.select("name C").unwrap();
    let mut plan = EndToEndPlan::new(sel);
    let frames = vec![linear_frame(2, 3, 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::Matrix { data, rows, cols } => {
            assert_eq!(rows, 1);
            assert_eq!(cols, 2);
            assert_eq!(data.len(), 2);
            assert!((data[0] - 2.0).abs() < 1e-6);
            assert!((data[1] - 2.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn polymer_contour_length_basic() {
    let mut system = build_polymer_system(2, 3);
    let sel = system.select("name C").unwrap();
    let mut plan = ContourLengthPlan::new(sel);
    let frames = vec![linear_frame(2, 3, 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::Matrix { data, rows, cols } => {
            assert_eq!(rows, 1);
            assert_eq!(cols, 2);
            assert_eq!(data.len(), 2);
            assert!((data[0] - 2.0).abs() < 1e-6);
            assert!((data[1] - 2.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn polymer_chain_rg_basic() {
    let mut system = build_polymer_system(2, 3);
    let sel = system.select("name C").unwrap();
    let mut plan = ChainRgPlan::new(sel);
    let frames = vec![linear_frame(2, 3, 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::Matrix { data, rows, cols } => {
            let expected = (2.0f32 / 3.0f32).sqrt();
            assert_eq!(rows, 1);
            assert_eq!(cols, 2);
            assert!((data[0] - expected).abs() < 1e-6);
            assert!((data[1] - expected).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn polymer_bond_length_histogram() {
    let mut system = build_polymer_system(2, 3);
    let sel = system.select("name C").unwrap();
    let mut plan = BondLengthDistributionPlan::new(sel, 2, 2.0);
    let frames = vec![linear_frame(2, 3, 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::Histogram { centers: _, counts } => {
            assert_eq!(counts.len(), 2);
            assert_eq!(counts[0], 0);
            assert_eq!(counts[1], 4);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn polymer_bond_angle_histogram() {
    let mut system = build_polymer_system(2, 3);
    let sel = system.select("name C").unwrap();
    let mut plan = BondAngleDistributionPlan::new(sel, 3, true);
    let frames = vec![right_angle_frame(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::Histogram { centers: _, counts } => {
            assert_eq!(counts.len(), 3);
            assert_eq!(counts[1], 2);
        }
        _ => panic!("unexpected output"),
    }
}


#[test]
fn polymer_persistence_basic() {
    let mut system = build_polymer_system(1, 3);
    let sel = system.select("name C").unwrap();
    let mut plan = PersistenceLengthPlan::new(sel);
    let frames = vec![linear_frame(1, 3, 1.0, 0.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::Persistence(p) => {
            assert_eq!(p.bond_autocorrelation.len(), 2);
            assert!((p.bond_autocorrelation[0] - 1.0).abs() < 1e-6);
            assert!((p.lb - 1.0).abs() < 1e-6);
        }
        _ => panic!("unexpected output"),
    }
}
