use super::*;

#[test]
fn toggle_overall() {
    let mut state = State::default();
    assert_eq!(state.overall, PickSet::Include);

    let changed_files = repo::ChangedFiles::default();

    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Exclude);

    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Include);
}

// TODO add test in which files (and/or changes) are added and rm'd

#[test]
fn toggle_file() {
    let mut state = State::default();
    assert_eq!(state.overall, PickSet::Include);

    let file_a = "A";
    let file_b = "B";
    let file_c = "C";

    let to_path = |raw: &str| file::Path {
        raw: raw.to_string(),
        is_dir: false,
    };

    let changed_files = repo::ChangedFiles::from_iter([
        (to_path(file_a), repo::ChangedFile::default()),
        (to_path(file_b), repo::ChangedFile::default()),
        (to_path(file_c), repo::ChangedFile::default()),
    ]);

    let toggle_file = |state: &mut State, file: &str| {
        update(
            state,
            Msg::ToggleFile {
                path: to_path(file),
            },
            &changed_files,
        )
    };

    let file =
        |state: &State, file: &str| *state.files.get(&to_path(file)).unwrap();

    // Exclude A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(file(&state, file_c), PickSet::Include);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude all
    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Exclude);

    // Include all
    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(file(&state, file_c), PickSet::Include);
    assert_eq!(state.overall, PickSet::Partial);

    // Include A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(file(&state, file_c), PickSet::Include);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(file(&state, file_c), PickSet::Include);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude B
    toggle_file(&mut state, file_b);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Exclude);
    assert_eq!(file(&state, file_c), PickSet::Include);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude C
    toggle_file(&mut state, file_c);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Exclude);
    assert_eq!(file(&state, file_c), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Exclude);

    // Include A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(file(&state, file_b), PickSet::Exclude);
    assert_eq!(file(&state, file_c), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Partial);

    // Include B
    toggle_file(&mut state, file_b);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(file(&state, file_c), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude all
    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Exclude);

    // Include all
    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude C
    toggle_file(&mut state, file_c);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(file(&state, file_c), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(file(&state, file_c), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude all
    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Exclude);

    // Include B
    toggle_file(&mut state, file_b);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(file(&state, file_c), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Partial);
}

#[test]
fn toggle_change() {
    let mut state = State::default();
    assert_eq!(state.overall, PickSet::Include);

    let file_a = "A";
    let file_b = "B";
    let file_c = "C";

    let to_path = |raw: &str| file::Path {
        raw: raw.to_string(),
        is_dir: false,
    };

    let change_a_1 = repo::ChangedFileDiff::SolveNameConflict;

    let change_b_1 = repo::ChangedFileDiff::SolveNameConflict;
    let change_b_2 = repo::ChangedFileDiff::Undel;

    let change_c_1 = repo::ChangedFileDiff::Add { contents: None };
    let change_c_2 = repo::ChangedFileDiff::DelRoot;
    let change_c_3 = repo::ChangedFileDiff::AddRoot;

    let changed_files = repo::ChangedFiles::from_iter([
        (
            to_path(file_a),
            repo::ChangedFile::from_iter([change_a_1.clone()]),
        ),
        (
            to_path(file_b),
            repo::ChangedFile::from_iter([
                change_b_1.clone(),
                change_b_2.clone(),
            ]),
        ),
        (
            to_path(file_c),
            repo::ChangedFile::from_iter([
                change_c_1.clone(),
                change_c_2.clone(),
                change_c_3.clone(),
            ]),
        ),
    ]);

    let toggle_change =
        |state: &mut State, file: &str, change: &repo::ChangedFileDiff| {
            update(
                state,
                Msg::ToggleChange {
                    path: to_path(file),
                    diff_id: diff::id_parts_hash(change),
                },
                &changed_files,
            )
        };

    let file =
        |state: &State, file: &str| *state.files.get(&to_path(file)).unwrap();
    let change = |state: &State, file: &str, change: &repo::ChangedFileDiff| {
        let diff_id = diff::id_parts_hash(change);
        *state
            .changes
            .get(&to_path(file))
            .unwrap()
            .changes
            .get(&diff_id)
            .unwrap()
    };

    // Exclude A.1
    toggle_change(&mut state, file_a, &change_a_1);
    assert_eq!(change(&state, file_a, &change_a_1), Pick::Exclude);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Partial);

    // Include A.1
    toggle_change(&mut state, file_a, &change_a_1);
    assert_eq!(change(&state, file_a, &change_a_1), Pick::Include);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude B.1
    toggle_change(&mut state, file_b, &change_b_1);
    assert_eq!(change(&state, file_b, &change_b_1), Pick::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Partial);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude B.2
    toggle_change(&mut state, file_b, &change_b_2);
    assert_eq!(change(&state, file_b, &change_b_2), Pick::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Partial);

    // Include B.1
    toggle_change(&mut state, file_b, &change_b_1);
    assert_eq!(change(&state, file_b, &change_b_1), Pick::Include);
    assert_eq!(file(&state, file_b), PickSet::Partial);
    assert_eq!(state.overall, PickSet::Partial);

    // Include B.2
    toggle_change(&mut state, file_b, &change_b_2);
    assert_eq!(change(&state, file_b, &change_b_2), Pick::Include);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude C.1
    toggle_change(&mut state, file_c, &change_c_1);
    assert_eq!(change(&state, file_c, &change_c_1), Pick::Exclude);
    assert_eq!(file(&state, file_c), PickSet::Partial);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude C.2
    toggle_change(&mut state, file_c, &change_c_2);
    assert_eq!(change(&state, file_c, &change_c_2), Pick::Exclude);
    assert_eq!(file(&state, file_c), PickSet::Partial);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude C.3
    toggle_change(&mut state, file_c, &change_c_3);
    assert_eq!(change(&state, file_c, &change_c_3), Pick::Exclude);
    assert_eq!(file(&state, file_c), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Partial);

    // Include C.1
    toggle_change(&mut state, file_c, &change_c_1);
    assert_eq!(change(&state, file_c, &change_c_1), Pick::Include);
    assert_eq!(file(&state, file_c), PickSet::Partial);
    assert_eq!(state.overall, PickSet::Partial);

    // Include C.2
    toggle_change(&mut state, file_c, &change_c_2);
    assert_eq!(change(&state, file_c, &change_c_2), Pick::Include);
    assert_eq!(file(&state, file_c), PickSet::Partial);
    assert_eq!(state.overall, PickSet::Partial);

    // Include C.3
    toggle_change(&mut state, file_c, &change_c_3);
    assert_eq!(change(&state, file_c, &change_c_3), Pick::Include);
    assert_eq!(file(&state, file_c), PickSet::Include);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude C.1
    toggle_change(&mut state, file_c, &change_c_1);
    assert_eq!(change(&state, file_c, &change_c_1), Pick::Exclude);
    assert_eq!(file(&state, file_c), PickSet::Partial);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude B.1
    toggle_change(&mut state, file_b, &change_b_1);
    assert_eq!(change(&state, file_b, &change_b_1), Pick::Exclude);
    assert_eq!(file(&state, file_b), PickSet::Partial);
    assert_eq!(state.overall, PickSet::Partial);

    // Include C.1
    toggle_change(&mut state, file_c, &change_c_1);
    assert_eq!(change(&state, file_c, &change_c_1), Pick::Include);
    assert_eq!(file(&state, file_c), PickSet::Include);
    assert_eq!(state.overall, PickSet::Partial);
}

#[test]
fn toggle_file_and_change() {
    let mut state = State::default();
    assert_eq!(state.overall, PickSet::Include);

    let file_a = "A";

    let to_path = |raw: &str| file::Path {
        raw: raw.to_string(),
        is_dir: false,
    };

    let change_a_1 = repo::ChangedFileDiff::SolveNameConflict;

    let changed_files = repo::ChangedFiles::from_iter([(
        to_path(file_a),
        repo::ChangedFile::from_iter([change_a_1.clone()]),
    )]);

    let toggle_file = |state: &mut State, file: &str| {
        update(
            state,
            Msg::ToggleFile {
                path: to_path(file),
            },
            &changed_files,
        )
    };
    let toggle_change =
        |state: &mut State, file: &str, change: &repo::ChangedFileDiff| {
            update(
                state,
                Msg::ToggleChange {
                    path: to_path(file),
                    diff_id: diff::id_parts_hash(change),
                },
                &changed_files,
            )
        };

    let file =
        |state: &State, file: &str| *state.files.get(&to_path(file)).unwrap();
    let change = |state: &State, file: &str, change: &repo::ChangedFileDiff| {
        let diff_id = diff::id_parts_hash(change);
        *state
            .changes
            .get(&to_path(file))
            .unwrap()
            .changes
            .get(&diff_id)
            .unwrap()
    };

    // Exclude A.1
    toggle_change(&mut state, file_a, &change_a_1);
    assert_eq!(change(&state, file_a, &change_a_1), Pick::Exclude);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Exclude);

    // Include A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude A.1
    toggle_change(&mut state, file_a, &change_a_1);
    assert_eq!(change(&state, file_a, &change_a_1), Pick::Exclude);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Exclude);

    // Include A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Exclude);

    // Include A.1
    toggle_change(&mut state, file_a, &change_a_1);
    assert_eq!(change(&state, file_a, &change_a_1), Pick::Include);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(state.overall, PickSet::Include);
}

#[test]
fn toggle_overall_and_file() {
    let mut state = State::default();
    assert_eq!(state.overall, PickSet::Include);

    let file_a = "A";
    let file_b = "B";

    let to_path = |raw: &str| file::Path {
        raw: raw.to_string(),
        is_dir: false,
    };

    let change_a_1 = repo::ChangedFileDiff::SolveNameConflict;
    let change_b_1 = repo::ChangedFileDiff::SolveNameConflict;

    let changed_files = repo::ChangedFiles::from_iter([
        (
            to_path(file_a),
            repo::ChangedFile::from_iter([change_a_1.clone()]),
        ),
        (
            to_path(file_b),
            repo::ChangedFile::from_iter([change_b_1.clone()]),
        ),
    ]);

    let toggle_file = |state: &mut State, file: &str| {
        update(
            state,
            Msg::ToggleFile {
                path: to_path(file),
            },
            &changed_files,
        )
    };

    let file =
        |state: &State, file: &str| *state.files.get(&to_path(file)).unwrap();

    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Exclude);

    // Include A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(state.overall, PickSet::Partial);

    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Exclude);

    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Exclude);

    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Include);

    // Exclude B
    toggle_file(&mut state, file_b);
    assert_eq!(file(&state, file_b), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Exclude);

    // Include B
    toggle_file(&mut state, file_b);
    assert_eq!(file(&state, file_b), PickSet::Include);
    assert_eq!(state.overall, PickSet::Partial);
}

#[test]
fn toggle_overall_partial_with_partial_file() {
    let mut state = State::default();
    assert_eq!(state.overall, PickSet::Include);

    let file_a = "A";
    let file_b = "B";

    let to_path = |raw: &str| file::Path {
        raw: raw.to_string(),
        is_dir: false,
    };

    let change_a_1 = repo::ChangedFileDiff::SolveNameConflict;
    let change_a_2 = repo::ChangedFileDiff::Undel;
    let change_b_1 = repo::ChangedFileDiff::SolveNameConflict;
    let change_b_2 = repo::ChangedFileDiff::Add { contents: None };

    let changed_files = repo::ChangedFiles::from_iter([
        (
            to_path(file_a),
            repo::ChangedFile::from_iter([
                change_a_1.clone(),
                change_a_2.clone(),
            ]),
        ),
        (
            to_path(file_b),
            repo::ChangedFile::from_iter([
                change_b_1.clone(),
                change_b_2.clone(),
            ]),
        ),
    ]);

    let toggle_file = |state: &mut State, file: &str| {
        update(
            state,
            Msg::ToggleFile {
                path: to_path(file),
            },
            &changed_files,
        )
    };

    let toggle_change =
        |state: &mut State, file: &str, change: &repo::ChangedFileDiff| {
            update(
                state,
                Msg::ToggleChange {
                    path: to_path(file),
                    diff_id: diff::id_parts_hash(change),
                },
                &changed_files,
            )
        };

    let file =
        |state: &State, file: &str| *state.files.get(&to_path(file)).unwrap();
    let change = |state: &State, file: &str, change: &repo::ChangedFileDiff| {
        let diff_id = diff::id_parts_hash(change);
        *state
            .changes
            .get(&to_path(file))
            .unwrap()
            .changes
            .get(&diff_id)
            .unwrap()
    };

    // Exclude A.1
    toggle_change(&mut state, file_a, &change_a_1);
    assert_eq!(change(&state, file_a, &change_a_1), Pick::Exclude);
    assert_eq!(file(&state, file_a), PickSet::Partial);
    assert_eq!(state.overall, PickSet::Partial);

    // Exclude A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Exclude);
    assert_eq!(state.overall, PickSet::Partial);

    // Include A
    toggle_file(&mut state, file_a);
    assert_eq!(file(&state, file_a), PickSet::Include);
    assert_eq!(state.overall, PickSet::Include);

    // Switching to partial should set the file A's to partial too
    update(&mut state, Msg::ToggleOverall, &changed_files);
    assert_eq!(state.overall, PickSet::Partial);
    assert_eq!(file(&state, file_a), PickSet::Partial);
    assert_eq!(change(&state, file_a, &change_a_1), Pick::Exclude);
}